Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into refactor/remove_host_…
Browse files Browse the repository at this point in the history
…class
  • Loading branch information
andycall committed Feb 7, 2022
2 parents 4350338 + 457fc82 commit 9fd04e1
Show file tree
Hide file tree
Showing 34 changed files with 583 additions and 253 deletions.
67 changes: 67 additions & 0 deletions bridge/bindings/qjs/rejected_promises.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2021 Alibaba Inc. All rights reserved.
* Author: Kraken Team.
*/

#include "rejected_promises.h"
#include "executing_context.h"

namespace kraken::binding::qjs {

RejectedPromises::Message::Message(ExecutionContext* context, JSValue promise, JSValue reason)
: m_runtime(context->runtime()), m_promise(JS_DupValue(context->ctx(), promise)), m_reason(JS_DupValue(context->ctx(), reason)) {}

RejectedPromises::Message::~Message() {
JS_FreeValueRT(m_runtime, m_promise);
JS_FreeValueRT(m_runtime, m_reason);
}

void RejectedPromises::trackUnhandledPromiseRejection(ExecutionContext* context, JSValue promise, JSValue reason) {
void* ptr = JS_VALUE_GET_PTR(promise);
if (m_unhandledRejections.count(ptr) == 0) {
m_unhandledRejections[ptr] = std::make_unique<Message>(context, promise, reason);
}
// One promise will never have more than one unhandled rejection.
}

void RejectedPromises::trackHandledPromiseRejection(ExecutionContext* 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);
} 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<Message>(context, promise, reason));
}
}

void RejectedPromises::process(ExecutionContext* context) {
// Copy m_unhandledRejections to avoid endless recursion call.
std::unordered_map<void*, std::unique_ptr<Message>> unhandledRejections;
for (auto& entry : m_unhandledRejections) {
unhandledRejections[entry.first] = std::unique_ptr<Message>(m_unhandledRejections[entry.first].release());
}
m_unhandledRejections.clear();

// Copy m_reportHandledRejection to avoid endless recursion call.
std::vector<std::unique_ptr<Message>> reportHandledRejection;
reportHandledRejection.reserve(reportHandledRejection.size());
for (auto& entry : m_reportHandledRejection) {
reportHandledRejection.push_back(std::unique_ptr<Message>(entry.release()));
}
m_reportHandledRejection.clear();

// Dispatch unhandled rejectionEvents.
for (auto& entry : unhandledRejections) {
context->reportError(entry.second->m_reason);
context->dispatchGlobalUnhandledRejectionEvent(context, entry.second->m_promise, entry.second->m_reason);
}

// Dispatch handledRejection events.
for (auto& entry : reportHandledRejection) {
context->dispatchGlobalRejectionHandledEvent(context, entry->m_promise, entry->m_reason);
}
}

} // namespace kraken::binding::qjs
44 changes: 44 additions & 0 deletions bridge/bindings/qjs/rejected_promises.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2021 Alibaba Inc. All rights reserved.
* Author: Kraken Team.
*/

#ifndef KRAKENBRIDGE_BINDINGS_QJS_REJECTED_PROMISES_H_
#define KRAKENBRIDGE_BINDINGS_QJS_REJECTED_PROMISES_H_

#include <quickjs/quickjs.h>
#include <memory>
#include <unordered_map>
#include <vector>

namespace kraken::binding::qjs {

class ExecutionContext;

class RejectedPromises {
public:
class Message {
public:
Message(ExecutionContext* context, JSValue promise, JSValue reason);
~Message();

JSRuntime* m_runtime{nullptr};
JSValue m_promise{JS_NULL};
JSValue m_reason{JS_NULL};
};

// Keeping track unhandled promise rejection in current context, and throw unhandledRejection error
void trackUnhandledPromiseRejection(ExecutionContext* 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);
// Trigger events after promise executed.
void process(ExecutionContext* context);

private:
std::unordered_map<void*, std::unique_ptr<Message>> m_unhandledRejections;
std::vector<std::unique_ptr<Message>> m_reportHandledRejection;
};

} // namespace kraken::binding::qjs

#endif // KRAKENBRIDGE_BINDINGS_QJS_REJECTED_PROMISES_H_
2 changes: 1 addition & 1 deletion bridge/core/dart_methods.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

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);
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);
typedef void (*RequestBatchUpdate)(int32_t contextId);
Expand Down
5 changes: 2 additions & 3 deletions bridge/core/dom/element.cc
Original file line number Diff line number Diff line change
Expand Up @@ -386,9 +386,8 @@ IMPL_FUNCTION(Element, toBlob)(JSContext* ctx, JSValue this_val, int argc, JSVal
JS_FreeValue(ctx, argumentsArray);
JS_FreeValue(ctx, arrayBuffer);
} else {
JSValue errorObject = JS_NewError(ctx);
JSValue errorMessage = JS_NewString(ctx, error);
JS_SetPropertyStr(ctx, errorObject, "message", errorMessage);
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();
Expand Down
103 changes: 82 additions & 21 deletions bridge/core/executing_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ bool ExecutionContext::handleException(JSValue* exception) {
if (JS_IsException(*exception)) {
JSValue error = JS_GetException(m_ctx);
reportError(error);
dispatchGlobalErrorEvent(error);
dispatchGlobalErrorEvent(this, error);
JS_FreeValue(m_ctx, error);
return false;
}
Expand Down Expand Up @@ -257,6 +257,9 @@ void ExecutionContext::drainPendingPromiseJobs() {
break;
}
}

// Throw error when promise are not handled.
m_rejectedPromise.process(this);
}

void ExecutionContext::defineGlobalProperty(const char* prop, JSValue value) {
Expand All @@ -279,33 +282,91 @@ uint8_t* ExecutionContext::dumpByteCode(const char* code, uint32_t codeLength, c
return bytes;
}

void ExecutionContext::dispatchGlobalErrorEvent(JSValueConst error) {
JSValue errorHandler = JS_GetPropertyStr(m_ctx, globalObject, "__global_onerror_handler__");
JSValue returnValue = JS_Call(m_ctx, errorHandler, globalObject, 1, &error);
drainPendingPromiseJobs();
if (JS_IsException(returnValue)) {
JSValue error = JS_GetException(m_ctx);
reportError(error);
JS_FreeValue(m_ctx, error);
void ExecutionContext::dispatchGlobalErrorEvent(ExecutionContext* context, JSValueConst error) {
JSContext* ctx = context->ctx();
auto* window = static_cast<WindowInstance*>(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();
}
JS_FreeValue(m_ctx, returnValue);
JS_FreeValue(m_ctx, errorHandler);
}

void ExecutionContext::dispatchGlobalPromiseRejectionEvent(JSValueConst promise, JSValueConst error) {
JSValue errorHandler = JS_GetPropertyStr(m_ctx, globalObject, "__global_unhandled_promise_handler__");
JSValue arguments[] = {promise, error};
JSValue returnValue = JS_Call(m_ctx, errorHandler, globalObject, 2, arguments);
drainPendingPromiseJobs();
handleException(&returnValue);
JS_FreeValue(m_ctx, returnValue);
JS_FreeValue(m_ctx, errorHandler);
static void dispatchPromiseRejectionEvent(const char* eventType, ExecutionContext* context, JSValueConst promise, JSValueConst error) {
JSContext* ctx = context->ctx();
auto* window = static_cast<WindowInstance*>(JS_GetOpaque(context->global(), Window::classId()));

// Trigger PromiseRejectionEvent(unhandledrejection) event.
{
JSValue PromiseRejectionEventValue = JS_GetPropertyStr(ctx, context->global(), "PromiseRejectionEvent");
JSValue errorType = JS_NewString(ctx, eventType);
JSValue errorInit = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, errorInit, "promise", JS_DupValue(ctx, promise));
JS_SetPropertyStr(ctx, errorInit, "reason", JS_DupValue(ctx, error));
JSValue arguments[] = {errorType, errorInit};
JSValue rejectEventValue = JS_CallConstructor(context->ctx(), PromiseRejectionEventValue, 2, arguments);
if (JS_IsException(rejectEventValue)) {
context->handleException(&rejectEventValue);
return;
}

auto* rejectEvent = static_cast<EventInstance*>(JS_GetOpaque(rejectEventValue, Event::kEventClassID));
window->dispatchEvent(rejectEvent);

JS_FreeValue(ctx, errorType);
JS_FreeValue(ctx, errorInit);
JS_FreeValue(ctx, rejectEventValue);
JS_FreeValue(ctx, PromiseRejectionEventValue);

context->drainPendingPromiseJobs();
}
}

void ExecutionContext::dispatchGlobalUnhandledRejectionEvent(ExecutionContext* context, JSValueConst promise, JSValueConst error) {
// Trigger onerror event.
dispatchGlobalErrorEvent(context, error);

// Trigger unhandledRejection event.
dispatchPromiseRejectionEvent("unhandledrejection", context, promise, error);
}

void ExecutionContext::dispatchGlobalRejectionHandledEvent(ExecutionContext* context, JSValue promise, JSValue error) {
// Trigger rejectionhandled event.
dispatchPromiseRejectionEvent("rejectionhandled", context, promise, error);
}

void ExecutionContext::promiseRejectTracker(JSContext* ctx, JSValue promise, JSValue reason, int is_handled, void* opaque) {
auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(ctx));
context->reportError(reason);
context->dispatchGlobalPromiseRejectionEvent(promise, reason);
// The unhandledrejection event is the promise-equivalent of the global error event, which is fired for uncaught exceptions.
// Because a rejected promise could be handled after the fact, by attaching catch(onRejected) or then(onFulfilled, onRejected) to it,
// the additional rejectionhandled event is needed to indicate that a promise which was previously rejected should no longer be considered unhandled.
if (is_handled) {
context->m_rejectedPromise.trackHandledPromiseRejection(context, promise, reason);
} else {
context->m_rejectedPromise.trackUnhandledPromiseRejection(context, promise, reason);
}
}

void installFunctionProperty(ExecutionContext* context, JSValue thisObject, const char* functionName, JSCFunction function, int argc) {
Expand Down
9 changes: 6 additions & 3 deletions bridge/core/executing_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "dart_methods.h"
#include "executing_context_data.h"
#include "frame/dom_timer_coordinator.h"
#include "rejected_promises.h"

using JSExceptionHandler = std::function<void(int32_t contextId, const char* message)>;

Expand Down Expand Up @@ -108,11 +109,12 @@ class ExecutionContext {
struct list_head promise_job_list;
struct list_head native_function_job_list;

static void dispatchGlobalUnhandledRejectionEvent(ExecutionContext* context, JSValueConst promise, JSValueConst error);
static void dispatchGlobalRejectionHandledEvent(ExecutionContext* context, JSValueConst promise, JSValueConst error);
static void dispatchGlobalErrorEvent(ExecutionContext* context, JSValueConst error);

private:
static void promiseRejectTracker(JSContext* ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void* opaque);
void dispatchGlobalErrorEvent(JSValueConst error);
void dispatchGlobalPromiseRejectionEvent(JSValueConst promise, JSValueConst error);
void reportError(JSValueConst error);

int32_t contextId;
JSExceptionHandler _handler;
Expand All @@ -126,6 +128,7 @@ class ExecutionContext {
ExecutionContextData m_data{this};
UICommandBuffer m_commandBuffer{this};
std::unique_ptr<DartMethodPointer> m_dartMethodPtr = std::make_unique<DartMethodPointer>();
RejectedPromises m_rejectedPromise;
};

// The read object's method or properties via Proxy, we should redirect this_val from Proxy into target property of
Expand Down
Loading

0 comments on commit 9fd04e1

Please sign in to comment.