Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚧 Port all changes from upstream #35

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ local.properties
.idea/caches
.idea

# Visual Studio
.vs/

# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild

Expand Down
6 changes: 6 additions & 0 deletions bridge/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ else ()
add_definitions(-DENABLE_PROFILE=0)
endif ()

if (${ENABLE_LOG})
add_definitions(-DENABLE_LOG=1)
else()
add_definitions(-DENABLE_LOG=0)
endif()

if(NOT MSVC)
if (${CMAKE_BUILD_TYPE} STREQUAL "Release" OR ${CMAKE_BUILD_TYPE} STREQUAL "RelWithDebInfo")
include(CheckIPOSupported)
Expand Down
47 changes: 43 additions & 4 deletions bridge/bindings/qjs/converter_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ struct Converter<IDLOptional<IDLDOMString>> : public ConverterBase<IDLDOMString>
}
static JSValue ToValue(JSContext* ctx, const std::string& str) { return Converter<IDLDOMString>::ToValue(ctx, str); }
static JSValue ToValue(JSContext* ctx, typename Converter<IDLDOMString>::ImplType value) {
if (value == AtomicString::Null()) {
return JS_UNDEFINED;
}

return Converter<IDLDOMString>::ToValue(ctx, std::move(value));
}
};
Expand All @@ -243,7 +247,12 @@ struct Converter<IDLNullable<IDLDOMString>> : public ConverterBase<IDLDOMString>
}

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); }
static JSValue ToValue(JSContext* ctx, const AtomicString& value) {
if (value == AtomicString::Null()) {
return JS_NULL;
}
return value.ToQuickJS(ctx);
}
};

template <>
Expand Down Expand Up @@ -340,6 +349,8 @@ struct Converter<IDLOptional<IDLSequence<T>>> : public ConverterBase<IDLSequence

return Converter<IDLSequence<T>>::FromValue(ctx, value, exception_state);
}

static JSValue ToValue(JSContext* ctx, ImplType value) { return Converter<IDLSequence<T>>::ToValue(ctx, value); }
};

template <typename T>
Expand Down Expand Up @@ -395,6 +406,17 @@ template <>
struct Converter<JSEventListener> : public ConverterBase<JSEventListener> {
static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) {
assert(!JS_IsException(value));
if (JS_IsObject(value) && !JS_IsFunction(ctx, value)) {
JSValue handleEventMethod = JS_GetPropertyStr(ctx, value, "handleEvent");

if (JS_IsFunction(ctx, handleEventMethod)) {
auto result = JSEventListener::CreateOrNull(QJSFunction::Create(ctx, handleEventMethod, value));
JS_FreeValue(ctx, handleEventMethod);
return result;
}

return JSEventListener::CreateOrNull(nullptr);
}
return JSEventListener::CreateOrNull(QJSFunction::Create(ctx, value));
}
};
Expand Down Expand Up @@ -445,6 +467,10 @@ struct Converter<IDLNullable<JSEventListener>> : public ConverterBase<JSEventLis
return nullptr;
}

if (!JS_IsFunction(ctx, value) && !JS_IsObject(value)) {
return nullptr;
}

assert(!JS_IsException(value));
return Converter<JSEventListener>::FromValue(ctx, value, exception_state);
}
Expand All @@ -458,7 +484,12 @@ struct Converter<T, typename std::enable_if_t<std::is_base_of<DictionaryBase, T>
return T::Create(ctx, value, exception_state);
}

static JSValue ToValue(JSContext* ctx, typename T::ImplType value) { return value->toQuickJS(ctx); }
static JSValue ToValue(JSContext* ctx, typename T::ImplType value) {
if (value == nullptr)
return JS_NULL;

return value->toQuickJS(ctx);
}
};

template <typename T>
Expand Down Expand Up @@ -532,8 +563,16 @@ struct Converter<T, typename std::enable_if_t<std::is_base_of<ScriptWrappable, T
ExceptionMessage::ArgumentNotOfType(argv_index, wrapper_type_info->className));
return nullptr;
}
static JSValue ToValue(JSContext* ctx, T* value) { return value->ToQuickJS(); }
static JSValue ToValue(JSContext* ctx, const T* value) { return value->ToQuickJS(); }
static JSValue ToValue(JSContext* ctx, T* value) {
if (value == nullptr)
return JS_NULL;
return value->ToQuickJS();
}
static JSValue ToValue(JSContext* ctx, const T* value) {
if (value == nullptr)
return JS_NULL;
return value->ToQuickJS();
}
};

template <typename T>
Expand Down
28 changes: 16 additions & 12 deletions bridge/bindings/qjs/cppgc/member.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,21 @@ class ScriptWrappable;
template <typename T, typename = std::is_base_of<ScriptWrappable, T>>
class Member {
public:
struct KeyHasher {
std::size_t operator()(const Member& k) const { return reinterpret_cast<std::size_t>(k.raw_); }
};

Member() = default;
Member(T* ptr) { SetRaw(ptr); }
Member(const Member<T>& other) {
raw_ = other.raw_;
runtime_ = other.runtime_;
js_object_ptr_ = other.js_object_ptr_;
((JSRefCountHeader*)other.js_object_ptr_)->ref_count++;
}
~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 || phase == JS_GC_PHASE_REMOVE_CYCLES) {
JS_FreeValueRT(runtime_, JS_MKPTR(JS_TAG_OBJECT, js_object_ptr_));
}
JS_FreeValueRT(runtime_, JS_MKPTR(JS_TAG_OBJECT, js_object_ptr_));
}
};

Expand All @@ -57,9 +54,9 @@ class Member {
JS_FreeValueRT(runtime_, JS_MKPTR(JS_TAG_OBJECT, js_object_ptr_));
} else {
auto* wrappable = To<ScriptWrappable>(raw_);
assert(wrappable->GetExecutingContext()->HasMutationScope());
assert(wrappable->executingContext()->HasMutationScope());
// Record the free operation to avoid JSObject had been freed immediately.
wrappable->GetExecutingContext()->mutationScope()->RecordFree(wrappable);
wrappable->executingContext()->mutationScope()->RecordFree(wrappable);
}
raw_ = nullptr;
js_object_ptr_ = nullptr;
Expand All @@ -70,6 +67,7 @@ class Member {
raw_ = other.raw_;
runtime_ = other.runtime_;
js_object_ptr_ = other.js_object_ptr_;
((JSRefCountHeader*)other.js_object_ptr_)->ref_count++;
return *this;
}
// Move assignment.
Expand All @@ -94,11 +92,17 @@ class Member {
T* operator->() const { return Get(); }
T& operator*() const { return *Get(); }

T* Release() {
T* result = Get();
Clear();
return result;
}

private:
void SetRaw(T* p) {
if (p != nullptr) {
auto* wrappable = To<ScriptWrappable>(p);
assert_m(wrappable->GetExecutingContext()->HasMutationScope(),
assert_m(wrappable->executingContext()->HasMutationScope(),
"Member must be used after MemberMutationScope allcated.");
runtime_ = wrappable->runtime();
js_object_ptr_ = JS_VALUE_GET_PTR(wrappable->ToQuickJSUnsafe());
Expand Down
4 changes: 4 additions & 0 deletions bridge/bindings/qjs/exception_state.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,8 @@ JSValue ExceptionState::ToQuickJS() {
return exception_;
}

JSValue ExceptionState::CurrentException(JSContext* ctx) {
return JS_GetException(ctx);
}

} // namespace mercury
2 changes: 2 additions & 0 deletions bridge/bindings/qjs/exception_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class ExceptionState {

JSValue ToQuickJS();

static JSValue CurrentException(JSContext* ctx);

private:
JSValue exception_{JS_NULL};
};
Expand Down
12 changes: 10 additions & 2 deletions bridge/bindings/qjs/heap_vector.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,27 @@ class HeapVector final {
public:
HeapVector() = default;

void Trace(GCVisitor* visitor) const;
void TraceValue(GCVisitor* visitor) const;
void TraceMember(GCVisitor* visitor) const;

private:
std::vector<V> entries_;
};

template <typename V>
void HeapVector<V>::Trace(GCVisitor* visitor) const {
void HeapVector<V>::TraceValue(GCVisitor* visitor) const {
for (auto& item : entries_) {
visitor->TraceValue(item);
}
}

template <typename V>
void HeapVector<V>::TraceMember(GCVisitor* visitor) const {
for (auto& item : entries_) {
visitor->TraceMember(item);
}
}

} // namespace mercury

#endif // BRIDGE_BINDINGS_QJS_HEAP_VECTOR_H_
2 changes: 1 addition & 1 deletion bridge/bindings/qjs/idl_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct IDLInt64 final : public IDLTypeBaseHelper<int32_t> {};
struct IDLUint32 final : public IDLTypeBaseHelper<uint32_t> {};
struct IDLDouble final : public IDLTypeBaseHelper<double> {};

class SharedNativeString;
struct SharedNativeString;
// DOMString is UTF-16 strings.
// https://stackoverflow.com/questions/35123890/what-is-a-domstring-really
struct IDLDOMString final : public IDLTypeBaseHelper<AtomicString> {};
Expand Down
10 changes: 7 additions & 3 deletions bridge/bindings/qjs/qjs_function.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,14 @@ ScriptValue QJSFunction::Invoke(JSContext* ctx, const ScriptValue& this_val, int
for (int i = 0; i < argc; i++) {
argv[0 + i] = arguments[i].QJSValue();
}
ExecutingContext* context = ExecutingContext::From(ctx);
// prof: context->dartIsolateContext()->profiler()->StartTrackSteps("JS_Call");

JSValue returnValue = JS_Call(ctx, function_, this_val.QJSValue(), argc, argv);
JSValue returnValue = JS_Call(ctx, function_, JS_IsNull(this_val_) ? this_val.QJSValue() : this_val_, argc, argv);

ExecutingContext* context = ExecutingContext::From(ctx);
context->DrainPendingPromiseJobs();
// prof: context->dartIsolateContext()->profiler()->FinishTrackSteps();

context->DrainMicrotasks();

// Free the previous duplicated function.
JS_FreeValue(ctx, function_);
Expand All @@ -72,6 +75,7 @@ ScriptValue QJSFunction::Invoke(JSContext* ctx, const ScriptValue& this_val, int

void QJSFunction::Trace(GCVisitor* visitor) const {
visitor->TraceValue(function_);
visitor->TraceValue(this_val_);
}

} // namespace mercury
14 changes: 13 additions & 1 deletion bridge/bindings/qjs/qjs_function.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,21 @@ class QJSFunction {
void* private_data) {
return std::make_shared<QJSFunction>(ctx, qjs_function_callback, length, private_data);
}
static std::shared_ptr<QJSFunction> Create(JSContext* ctx, JSValue function, JSValue this_val) {
return std::make_shared<QJSFunction>(ctx, function, this_val);
};
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_); }
explicit QJSFunction(JSContext* ctx, JSValue function, JSValue this_val)
: ctx_(ctx),
runtime_(JS_GetRuntime(ctx)),
function_(JS_DupValue(ctx, function)),
this_val_(JS_DupValue(ctx, this_val)) {}
~QJSFunction() {
JS_FreeValueRT(runtime_, function_);
JS_FreeValueRT(runtime_, this_val_);
}

bool IsFunction(JSContext* ctx);

Expand All @@ -52,6 +63,7 @@ class QJSFunction {
JSContext* ctx_{nullptr};
JSRuntime* runtime_{nullptr};
JSValue function_{JS_NULL};
JSValue this_val_{JS_NULL};
};

} // namespace mercury
Expand Down
4 changes: 3 additions & 1 deletion bridge/bindings/qjs/script_promise_resolver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ ScriptPromise ScriptPromiseResolver::Promise() {
}

void ScriptPromiseResolver::ResolveOrRejectImmediately(JSValue value) {
// prof: context_->dartIsolateContext()->profiler()->StartTrackAsyncEvaluation();
{
if (state_ == kResolving) {
JSValue arguments[] = {value};
Expand All @@ -54,7 +55,8 @@ void ScriptPromiseResolver::ResolveOrRejectImmediately(JSValue value) {
JS_FreeValue(context_->ctx(), return_value);
}
}
context_->DrainPendingPromiseJobs();
context_->DrainMicrotasks();
// prof: context_->dartIsolateContext()->profiler()->FinishTrackAsyncEvaluation();
}

} // namespace mercury
14 changes: 11 additions & 3 deletions bridge/bindings/qjs/script_wrappable.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ ScriptValue ScriptWrappable::ToValue() {
return ScriptValue(ctx_, jsObject_);
}

multi_threading::Dispatcher* ScriptWrappable::GetDispatcher() const {
return context_->dartIsolateContext()->dispatcher().get();
}

/// This callback will be called when QuickJS GC is running at marking stage.
/// Users of this class should override `void TraceMember(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func)` to
/// tell GC which member of their class should be collected by GC.
Expand All @@ -47,8 +51,8 @@ static void HandleJSObjectFinalized(JSRuntime* rt, JSValue val) {
// When a JSObject got finalized by QuickJS GC, we can not guarantee the ExecutingContext are still alive and
// accessible.
if (isContextValid(object->contextId())) {
ExecutingContext* context = object->GetExecutingContext();
MemberMutationScope scope{object->GetExecutingContext()};
ExecutingContext* context = object->executingContext();
MemberMutationScope scope{object->executingContext()};
delete object;
} else {
delete object;
Expand Down Expand Up @@ -240,6 +244,8 @@ void ScriptWrappable::InitializeQuickJSObject() {
desc->value = return_value;
desc->getter = JS_NULL;
desc->setter = JS_NULL;
} else {
JS_FreeValue(ctx, return_value);
}
return true;
}
Expand All @@ -254,6 +260,8 @@ void ScriptWrappable::InitializeQuickJSObject() {
desc->value = return_value;
desc->getter = JS_NULL;
desc->setter = JS_NULL;
} else {
JS_FreeValue(ctx, return_value);
}
return true;
}
Expand Down Expand Up @@ -285,7 +293,7 @@ void ScriptWrappable::InitializeQuickJSObject() {
JS_SetOpaque(jsObject_, this);

// Let our instance into inherit prototype methods.
JSValue prototype = GetExecutingContext()->contextData()->prototypeForType(wrapper_type_info);
JSValue prototype = executingContext()->contextData()->prototypeForType(wrapper_type_info);
JS_SetPrototype(ctx_, jsObject_, prototype);
}

Expand Down
8 changes: 5 additions & 3 deletions bridge/bindings/qjs/script_wrappable.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <quickjs/quickjs.h>
#include "bindings/qjs/cppgc/garbage_collected.h"
#include "foundation/macros.h"
#include "multiple_threading/dispatcher.h"
#include "wrapper_type_info.h"

namespace mercury {
Expand Down Expand Up @@ -51,7 +52,8 @@ class ScriptWrappable : public GarbageCollected<ScriptWrappable> {
JSValue ToQuickJSUnsafe() const;

ScriptValue ToValue();
FORCE_INLINE ExecutingContext* GetExecutingContext() const { return context_; };
FORCE_INLINE ExecutingContext* executingContext() const { return context_; };
multi_threading::Dispatcher* GetDispatcher() const;
FORCE_INLINE JSContext* ctx() const { return ctx_; }
FORCE_INLINE JSRuntime* runtime() const { return runtime_; }
FORCE_INLINE int64_t contextId() const { return context_id_; }
Expand Down Expand Up @@ -91,8 +93,8 @@ Local<T>::~Local<T>() {
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);
if (LIKELY(wrappable->executingContext()->HasMutationScope())) {
wrappable->executingContext()->mutationScope()->RecordFree(wrappable);
} else {
assert_m(false, "LocalHandle must be used after MemberMutationScope allcated.");
}
Expand Down
Loading