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