Introduce PromiseRejectCallback.
R=aandrey@chromium.org, yurys@chromium.org, rossberg@chromium.org API=v8::Isolate::SetPromiseRejectCallback, v8::Promise::HasHandler LOG=Y BUG=v8:3093 Review URL: https://codereview.chromium.org/600723005 git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@24335 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
552a9c2447
commit
e68e62c891
26
include/v8.h
26
include/v8.h
@ -85,6 +85,7 @@ class ObjectOperationDescriptor;
|
||||
class ObjectTemplate;
|
||||
class Platform;
|
||||
class Primitive;
|
||||
class Promise;
|
||||
class RawOperationDescriptor;
|
||||
class Script;
|
||||
class Signature;
|
||||
@ -2851,6 +2852,12 @@ class V8_EXPORT Promise : public Object {
|
||||
Local<Promise> Catch(Handle<Function> handler);
|
||||
Local<Promise> Then(Handle<Function> handler);
|
||||
|
||||
/**
|
||||
* Returns true if the promise has at least one derived promise, and
|
||||
* therefore resolve/reject handlers (including default handler).
|
||||
*/
|
||||
bool HasHandler();
|
||||
|
||||
V8_INLINE static Promise* Cast(Value* obj);
|
||||
|
||||
private:
|
||||
@ -4196,6 +4203,16 @@ typedef void (*MemoryAllocationCallback)(ObjectSpace space,
|
||||
// --- Leave Script Callback ---
|
||||
typedef void (*CallCompletedCallback)();
|
||||
|
||||
// --- Promise Reject Callback ---
|
||||
enum PromiseRejectEvent {
|
||||
kPromiseRejectWithNoHandler = 0,
|
||||
kPromiseHandlerAddedAfterReject = 1
|
||||
};
|
||||
|
||||
typedef void (*PromiseRejectCallback)(Handle<Promise> promise,
|
||||
Handle<Value> value,
|
||||
PromiseRejectEvent event);
|
||||
|
||||
// --- Microtask Callback ---
|
||||
typedef void (*MicrotaskCallback)(void* data);
|
||||
|
||||
@ -4771,6 +4788,13 @@ class V8_EXPORT Isolate {
|
||||
*/
|
||||
void RemoveCallCompletedCallback(CallCompletedCallback callback);
|
||||
|
||||
|
||||
/**
|
||||
* Set callback to notify about promise reject with no handler, or
|
||||
* revocation of such a previous notification once the handler is added.
|
||||
*/
|
||||
void SetPromiseRejectCallback(PromiseRejectCallback callback);
|
||||
|
||||
/**
|
||||
* Experimental: Runs the Microtask Work Queue until empty
|
||||
* Any exceptions thrown by microtask callbacks are swallowed.
|
||||
@ -5880,7 +5904,7 @@ class Internals {
|
||||
static const int kNullValueRootIndex = 7;
|
||||
static const int kTrueValueRootIndex = 8;
|
||||
static const int kFalseValueRootIndex = 9;
|
||||
static const int kEmptyStringRootIndex = 164;
|
||||
static const int kEmptyStringRootIndex = 166;
|
||||
|
||||
// The external allocation limit should be below 256 MB on all architectures
|
||||
// to avoid that resource-constrained embedders run low on memory.
|
||||
|
17
src/api.cc
17
src/api.cc
@ -5985,6 +5985,16 @@ Local<Promise> Promise::Then(Handle<Function> handler) {
|
||||
}
|
||||
|
||||
|
||||
bool Promise::HasHandler() {
|
||||
i::Handle<i::JSObject> promise = Utils::OpenHandle(this);
|
||||
i::Isolate* isolate = promise->GetIsolate();
|
||||
LOG_API(isolate, "Promise::HasRejectHandler");
|
||||
ENTER_V8(isolate);
|
||||
i::Handle<i::Symbol> key = isolate->factory()->promise_has_handler_symbol();
|
||||
return i::JSObject::GetDataProperty(promise, key)->IsTrue();
|
||||
}
|
||||
|
||||
|
||||
bool v8::ArrayBuffer::IsExternal() const {
|
||||
return Utils::OpenHandle(this)->is_external();
|
||||
}
|
||||
@ -6732,6 +6742,13 @@ void Isolate::RemoveCallCompletedCallback(CallCompletedCallback callback) {
|
||||
}
|
||||
|
||||
|
||||
void Isolate::SetPromiseRejectCallback(PromiseRejectCallback callback) {
|
||||
if (callback == NULL) return;
|
||||
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
|
||||
isolate->SetPromiseRejectCallback(callback);
|
||||
}
|
||||
|
||||
|
||||
void Isolate::RunMicrotasks() {
|
||||
reinterpret_cast<i::Isolate*>(this)->RunMicrotasks();
|
||||
}
|
||||
|
@ -232,6 +232,8 @@ class Utils {
|
||||
|
||||
static inline Local<Message> MessageToLocal(
|
||||
v8::internal::Handle<v8::internal::Object> obj);
|
||||
static inline Local<Promise> PromiseToLocal(
|
||||
v8::internal::Handle<v8::internal::JSObject> obj);
|
||||
static inline Local<StackTrace> StackTraceToLocal(
|
||||
v8::internal::Handle<v8::internal::JSArray> obj);
|
||||
static inline Local<StackFrame> StackFrameToLocal(
|
||||
@ -355,6 +357,7 @@ MAKE_TO_LOCAL(ToLocal, SignatureInfo, Signature)
|
||||
MAKE_TO_LOCAL(AccessorSignatureToLocal, FunctionTemplateInfo, AccessorSignature)
|
||||
MAKE_TO_LOCAL(ToLocal, TypeSwitchInfo, TypeSwitch)
|
||||
MAKE_TO_LOCAL(MessageToLocal, Object, Message)
|
||||
MAKE_TO_LOCAL(PromiseToLocal, JSObject, Promise)
|
||||
MAKE_TO_LOCAL(StackTraceToLocal, JSArray, StackTrace)
|
||||
MAKE_TO_LOCAL(StackFrameToLocal, JSObject, StackFrame)
|
||||
MAKE_TO_LOCAL(NumberToLocal, Object, Number)
|
||||
|
@ -1538,6 +1538,8 @@ void Genesis::InstallNativeFunctions() {
|
||||
INSTALL_NATIVE(JSFunction, "PromiseChain", promise_chain);
|
||||
INSTALL_NATIVE(JSFunction, "PromiseCatch", promise_catch);
|
||||
INSTALL_NATIVE(JSFunction, "PromiseThen", promise_then);
|
||||
INSTALL_NATIVE(JSFunction, "PromiseHasRejectHandler",
|
||||
promise_has_reject_handler);
|
||||
|
||||
INSTALL_NATIVE(JSFunction, "NotifyChange", observers_notify_change);
|
||||
INSTALL_NATIVE(JSFunction, "EnqueueSpliceRecord", observers_enqueue_splice);
|
||||
|
@ -145,7 +145,6 @@ bool Linkage::NeedsFrameState(Runtime::FunctionId function) {
|
||||
case Runtime::kDebugEvaluate:
|
||||
case Runtime::kDebugGetLoadedScripts:
|
||||
case Runtime::kDebugGetPropertyDetails:
|
||||
case Runtime::kDebugPromiseRejectEvent:
|
||||
case Runtime::kDebugPromiseEvent:
|
||||
case Runtime::kDeleteProperty:
|
||||
case Runtime::kDeoptimizeFunction:
|
||||
@ -164,6 +163,8 @@ bool Linkage::NeedsFrameState(Runtime::FunctionId function) {
|
||||
case Runtime::kParseJson:
|
||||
case Runtime::kPrepareStep:
|
||||
case Runtime::kPreventExtensions:
|
||||
case Runtime::kPromiseRejectEvent:
|
||||
case Runtime::kPromiseRevokeReject:
|
||||
case Runtime::kRegExpCompile:
|
||||
case Runtime::kRegExpExecMultiple:
|
||||
case Runtime::kResolvePossiblyDirectEval:
|
||||
|
@ -180,6 +180,7 @@ enum BindingFlags {
|
||||
V(PROMISE_CHAIN_INDEX, JSFunction, promise_chain) \
|
||||
V(PROMISE_CATCH_INDEX, JSFunction, promise_catch) \
|
||||
V(PROMISE_THEN_INDEX, JSFunction, promise_then) \
|
||||
V(PROMISE_HAS_REJECT_HANDLER_INDEX, JSFunction, promise_has_reject_handler) \
|
||||
V(TO_COMPLETE_PROPERTY_DESCRIPTOR_INDEX, JSFunction, \
|
||||
to_complete_property_descriptor) \
|
||||
V(DERIVED_HAS_TRAP_INDEX, JSFunction, derived_has_trap) \
|
||||
@ -377,6 +378,7 @@ class Context: public FixedArray {
|
||||
PROMISE_CHAIN_INDEX,
|
||||
PROMISE_CATCH_INDEX,
|
||||
PROMISE_THEN_INDEX,
|
||||
PROMISE_HAS_REJECT_HANDLER_INDEX,
|
||||
TO_COMPLETE_PROPERTY_DESCRIPTOR_INDEX,
|
||||
DERIVED_HAS_TRAP_INDEX,
|
||||
DERIVED_GET_TRAP_INDEX,
|
||||
|
40
src/debug.cc
40
src/debug.cc
@ -1262,17 +1262,6 @@ bool Debug::IsBreakOnException(ExceptionBreakType type) {
|
||||
}
|
||||
|
||||
|
||||
bool Debug::PromiseHasRejectHandler(Handle<JSObject> promise) {
|
||||
Handle<JSFunction> fun = Handle<JSFunction>::cast(
|
||||
JSObject::GetDataProperty(isolate_->js_builtins_object(),
|
||||
isolate_->factory()->NewStringFromStaticChars(
|
||||
"PromiseHasRejectHandler")));
|
||||
Handle<Object> result =
|
||||
Execution::Call(isolate_, fun, promise, 0, NULL).ToHandleChecked();
|
||||
return result->IsTrue();
|
||||
}
|
||||
|
||||
|
||||
void Debug::PrepareStep(StepAction step_action,
|
||||
int step_count,
|
||||
StackFrame::Id frame_id) {
|
||||
@ -2521,14 +2510,37 @@ void Debug::OnThrow(Handle<Object> exception, bool uncaught) {
|
||||
void Debug::OnPromiseReject(Handle<JSObject> promise, Handle<Object> value) {
|
||||
if (in_debug_scope() || ignore_events()) return;
|
||||
HandleScope scope(isolate_);
|
||||
OnException(value, false, promise);
|
||||
// Check whether the promise has been marked as having triggered a message.
|
||||
Handle<Symbol> key = isolate_->factory()->promise_debug_marker_symbol();
|
||||
if (JSObject::GetDataProperty(promise, key)->IsUndefined()) {
|
||||
OnException(value, false, promise);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
MaybeHandle<Object> Debug::PromiseHasUserDefinedRejectHandler(
|
||||
Handle<JSObject> promise) {
|
||||
Handle<JSFunction> fun = Handle<JSFunction>::cast(
|
||||
JSObject::GetDataProperty(isolate_->js_builtins_object(),
|
||||
isolate_->factory()->NewStringFromStaticChars(
|
||||
"PromiseHasUserDefinedRejectHandler")));
|
||||
return Execution::Call(isolate_, fun, promise, 0, NULL);
|
||||
}
|
||||
|
||||
|
||||
void Debug::OnException(Handle<Object> exception, bool uncaught,
|
||||
Handle<Object> promise) {
|
||||
if (promise->IsJSObject()) {
|
||||
uncaught |= !PromiseHasRejectHandler(Handle<JSObject>::cast(promise));
|
||||
if (!uncaught && promise->IsJSObject()) {
|
||||
Handle<JSObject> jspromise = Handle<JSObject>::cast(promise);
|
||||
// Mark the promise as already having triggered a message.
|
||||
Handle<Symbol> key = isolate_->factory()->promise_debug_marker_symbol();
|
||||
JSObject::SetProperty(jspromise, key, key, STRICT).Assert();
|
||||
// Check whether the promise reject is considered an uncaught exception.
|
||||
Handle<Object> has_reject_handler;
|
||||
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
|
||||
isolate_, has_reject_handler,
|
||||
PromiseHasUserDefinedRejectHandler(jspromise), /* void */);
|
||||
uncaught = has_reject_handler->IsFalse();
|
||||
}
|
||||
// Bail out if exception breaks are not active
|
||||
if (uncaught) {
|
||||
|
@ -533,8 +533,8 @@ class Debug {
|
||||
// Mirror cache handling.
|
||||
void ClearMirrorCache();
|
||||
|
||||
// Returns a promise if the pushed try-catch handler matches the current one.
|
||||
bool PromiseHasRejectHandler(Handle<JSObject> promise);
|
||||
MaybeHandle<Object> PromiseHasUserDefinedRejectHandler(
|
||||
Handle<JSObject> promise);
|
||||
|
||||
void CallEventCallback(v8::DebugEvent event,
|
||||
Handle<Object> exec_state,
|
||||
|
@ -2880,6 +2880,8 @@ void Heap::CreateInitialObjects() {
|
||||
set_stack_trace_symbol(*factory->NewPrivateOwnSymbol());
|
||||
set_uninitialized_symbol(*factory->NewPrivateOwnSymbol());
|
||||
set_home_object_symbol(*factory->NewPrivateOwnSymbol());
|
||||
set_promise_debug_marker_symbol(*factory->NewPrivateOwnSymbol());
|
||||
set_promise_has_handler_symbol(*factory->NewPrivateOwnSymbol());
|
||||
|
||||
Handle<SeededNumberDictionary> slow_element_dictionary =
|
||||
SeededNumberDictionary::New(isolate(), 0, TENURED);
|
||||
|
@ -189,6 +189,8 @@ namespace internal {
|
||||
V(Symbol, detailed_stack_trace_symbol, DetailedStackTraceSymbol) \
|
||||
V(Symbol, normal_ic_symbol, NormalICSymbol) \
|
||||
V(Symbol, home_object_symbol, HomeObjectSymbol) \
|
||||
V(Symbol, promise_debug_marker_symbol, PromiseDebugMarkerSymbol) \
|
||||
V(Symbol, promise_has_handler_symbol, PromiseHasHandlerSymbol) \
|
||||
V(FixedArray, materialized_objects, MaterializedObjects) \
|
||||
V(FixedArray, allocation_sites_scratchpad, AllocationSitesScratchpad) \
|
||||
V(FixedArray, microtask_queue, MicrotaskQueue)
|
||||
|
208
src/isolate.cc
208
src/isolate.cc
@ -978,6 +978,8 @@ bool Isolate::ShouldReportException(bool* can_be_caught_externally,
|
||||
}
|
||||
|
||||
|
||||
// Traverse prototype chain to find out whether the object is derived from
|
||||
// the Error object.
|
||||
bool Isolate::IsErrorObject(Handle<Object> obj) {
|
||||
if (!obj->IsJSObject()) return false;
|
||||
|
||||
@ -1000,6 +1002,96 @@ bool Isolate::IsErrorObject(Handle<Object> obj) {
|
||||
|
||||
static int fatal_exception_depth = 0;
|
||||
|
||||
|
||||
Handle<JSMessageObject> Isolate::CreateMessage(Handle<Object> exception,
|
||||
MessageLocation* location) {
|
||||
Handle<JSArray> stack_trace_object;
|
||||
if (capture_stack_trace_for_uncaught_exceptions_) {
|
||||
if (IsErrorObject(exception)) {
|
||||
// We fetch the stack trace that corresponds to this error object.
|
||||
Handle<Name> key = factory()->detailed_stack_trace_symbol();
|
||||
// Look up as own property. If the lookup fails, the exception is
|
||||
// probably not a valid Error object. In that case, we fall through
|
||||
// and capture the stack trace at this throw site.
|
||||
LookupIterator lookup(exception, key,
|
||||
LookupIterator::OWN_SKIP_INTERCEPTOR);
|
||||
Handle<Object> stack_trace_property;
|
||||
if (Object::GetProperty(&lookup).ToHandle(&stack_trace_property) &&
|
||||
stack_trace_property->IsJSArray()) {
|
||||
stack_trace_object = Handle<JSArray>::cast(stack_trace_property);
|
||||
}
|
||||
}
|
||||
if (stack_trace_object.is_null()) {
|
||||
// Not an error object, we capture at throw site.
|
||||
stack_trace_object = CaptureCurrentStackTrace(
|
||||
stack_trace_for_uncaught_exceptions_frame_limit_,
|
||||
stack_trace_for_uncaught_exceptions_options_);
|
||||
}
|
||||
}
|
||||
|
||||
// If the exception argument is a custom object, turn it into a string
|
||||
// before throwing as uncaught exception. Note that the pending
|
||||
// exception object to be set later must not be turned into a string.
|
||||
if (exception->IsJSObject() && !IsErrorObject(exception)) {
|
||||
MaybeHandle<Object> maybe_exception =
|
||||
Execution::ToDetailString(this, exception);
|
||||
if (!maybe_exception.ToHandle(&exception)) {
|
||||
exception =
|
||||
factory()->InternalizeOneByteString(STATIC_CHAR_VECTOR("exception"));
|
||||
}
|
||||
}
|
||||
return MessageHandler::MakeMessageObject(this, "uncaught_exception", location,
|
||||
HandleVector<Object>(&exception, 1),
|
||||
stack_trace_object);
|
||||
}
|
||||
|
||||
|
||||
void ReportBootstrappingException(Handle<Object> exception,
|
||||
MessageLocation* location) {
|
||||
base::OS::PrintError("Exception thrown during bootstrapping\n");
|
||||
if (location == NULL || location->script().is_null()) return;
|
||||
// We are bootstrapping and caught an error where the location is set
|
||||
// and we have a script for the location.
|
||||
// In this case we could have an extension (or an internal error
|
||||
// somewhere) and we print out the line number at which the error occured
|
||||
// to the console for easier debugging.
|
||||
int line_number =
|
||||
location->script()->GetLineNumber(location->start_pos()) + 1;
|
||||
if (exception->IsString() && location->script()->name()->IsString()) {
|
||||
base::OS::PrintError(
|
||||
"Extension or internal compilation error: %s in %s at line %d.\n",
|
||||
String::cast(*exception)->ToCString().get(),
|
||||
String::cast(location->script()->name())->ToCString().get(),
|
||||
line_number);
|
||||
} else if (location->script()->name()->IsString()) {
|
||||
base::OS::PrintError(
|
||||
"Extension or internal compilation error in %s at line %d.\n",
|
||||
String::cast(location->script()->name())->ToCString().get(),
|
||||
line_number);
|
||||
} else {
|
||||
base::OS::PrintError("Extension or internal compilation error.\n");
|
||||
}
|
||||
#ifdef OBJECT_PRINT
|
||||
// Since comments and empty lines have been stripped from the source of
|
||||
// builtins, print the actual source here so that line numbers match.
|
||||
if (location->script()->source()->IsString()) {
|
||||
Handle<String> src(String::cast(location->script()->source()));
|
||||
PrintF("Failing script:\n");
|
||||
int len = src->length();
|
||||
int line_number = 1;
|
||||
PrintF("%5d: ", line_number);
|
||||
for (int i = 0; i < len; i++) {
|
||||
uint16_t character = src->Get(i);
|
||||
PrintF("%c", character);
|
||||
if (character == '\n' && i < len - 2) {
|
||||
PrintF("%5d: ", ++line_number);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void Isolate::DoThrow(Object* exception, MessageLocation* location) {
|
||||
DCHECK(!has_pending_exception());
|
||||
|
||||
@ -1014,7 +1106,6 @@ void Isolate::DoThrow(Object* exception, MessageLocation* location) {
|
||||
bool report_exception = catchable_by_javascript && should_report_exception;
|
||||
bool try_catch_needs_message =
|
||||
can_be_caught_externally && try_catch_handler()->capture_message_;
|
||||
bool bootstrapping = bootstrapper()->IsActive();
|
||||
bool rethrowing_message = thread_local_top()->rethrowing_message_;
|
||||
|
||||
thread_local_top()->rethrowing_message_ = false;
|
||||
@ -1032,52 +1123,15 @@ void Isolate::DoThrow(Object* exception, MessageLocation* location) {
|
||||
ComputeLocation(&potential_computed_location);
|
||||
location = &potential_computed_location;
|
||||
}
|
||||
// It's not safe to try to make message objects or collect stack traces
|
||||
// while the bootstrapper is active since the infrastructure may not have
|
||||
// been properly initialized.
|
||||
if (!bootstrapping) {
|
||||
Handle<JSArray> stack_trace_object;
|
||||
if (capture_stack_trace_for_uncaught_exceptions_) {
|
||||
if (IsErrorObject(exception_handle)) {
|
||||
// We fetch the stack trace that corresponds to this error object.
|
||||
Handle<Name> key = factory()->detailed_stack_trace_symbol();
|
||||
// Look up as own property. If the lookup fails, the exception is
|
||||
// probably not a valid Error object. In that case, we fall through
|
||||
// and capture the stack trace at this throw site.
|
||||
LookupIterator lookup(exception_handle, key,
|
||||
LookupIterator::OWN_SKIP_INTERCEPTOR);
|
||||
Handle<Object> stack_trace_property;
|
||||
if (Object::GetProperty(&lookup).ToHandle(&stack_trace_property) &&
|
||||
stack_trace_property->IsJSArray()) {
|
||||
stack_trace_object = Handle<JSArray>::cast(stack_trace_property);
|
||||
}
|
||||
}
|
||||
if (stack_trace_object.is_null()) {
|
||||
// Not an error object, we capture at throw site.
|
||||
stack_trace_object = CaptureCurrentStackTrace(
|
||||
stack_trace_for_uncaught_exceptions_frame_limit_,
|
||||
stack_trace_for_uncaught_exceptions_options_);
|
||||
}
|
||||
}
|
||||
|
||||
Handle<Object> exception_arg = exception_handle;
|
||||
// If the exception argument is a custom object, turn it into a string
|
||||
// before throwing as uncaught exception. Note that the pending
|
||||
// exception object to be set later must not be turned into a string.
|
||||
if (exception_arg->IsJSObject() && !IsErrorObject(exception_arg)) {
|
||||
MaybeHandle<Object> maybe_exception =
|
||||
Execution::ToDetailString(this, exception_arg);
|
||||
if (!maybe_exception.ToHandle(&exception_arg)) {
|
||||
exception_arg = factory()->InternalizeOneByteString(
|
||||
STATIC_CHAR_VECTOR("exception"));
|
||||
}
|
||||
}
|
||||
Handle<Object> message_obj = MessageHandler::MakeMessageObject(
|
||||
this,
|
||||
"uncaught_exception",
|
||||
location,
|
||||
HandleVector<Object>(&exception_arg, 1),
|
||||
stack_trace_object);
|
||||
if (bootstrapper()->IsActive()) {
|
||||
// It's not safe to try to make message objects or collect stack traces
|
||||
// while the bootstrapper is active since the infrastructure may not have
|
||||
// been properly initialized.
|
||||
ReportBootstrappingException(exception_handle, location);
|
||||
} else {
|
||||
Handle<Object> message_obj = CreateMessage(exception_handle, location);
|
||||
|
||||
thread_local_top()->pending_message_obj_ = *message_obj;
|
||||
if (location != NULL) {
|
||||
thread_local_top()->pending_message_script_ = *location->script();
|
||||
@ -1089,56 +1143,14 @@ void Isolate::DoThrow(Object* exception, MessageLocation* location) {
|
||||
// exception not caught by JavaScript, even when an external handler is
|
||||
// present. This flag is intended for use by JavaScript developers, so
|
||||
// print a user-friendly stack trace (not an internal one).
|
||||
if (fatal_exception_depth == 0 &&
|
||||
FLAG_abort_on_uncaught_exception &&
|
||||
if (fatal_exception_depth == 0 && FLAG_abort_on_uncaught_exception &&
|
||||
(report_exception || can_be_caught_externally)) {
|
||||
fatal_exception_depth++;
|
||||
PrintF(stderr,
|
||||
"%s\n\nFROM\n",
|
||||
PrintF(stderr, "%s\n\nFROM\n",
|
||||
MessageHandler::GetLocalizedMessage(this, message_obj).get());
|
||||
PrintCurrentStackTrace(stderr);
|
||||
base::OS::Abort();
|
||||
}
|
||||
} else if (location != NULL && !location->script().is_null()) {
|
||||
// We are bootstrapping and caught an error where the location is set
|
||||
// and we have a script for the location.
|
||||
// In this case we could have an extension (or an internal error
|
||||
// somewhere) and we print out the line number at which the error occured
|
||||
// to the console for easier debugging.
|
||||
int line_number =
|
||||
location->script()->GetLineNumber(location->start_pos()) + 1;
|
||||
if (exception->IsString() && location->script()->name()->IsString()) {
|
||||
base::OS::PrintError(
|
||||
"Extension or internal compilation error: %s in %s at line %d.\n",
|
||||
String::cast(exception)->ToCString().get(),
|
||||
String::cast(location->script()->name())->ToCString().get(),
|
||||
line_number);
|
||||
} else if (location->script()->name()->IsString()) {
|
||||
base::OS::PrintError(
|
||||
"Extension or internal compilation error in %s at line %d.\n",
|
||||
String::cast(location->script()->name())->ToCString().get(),
|
||||
line_number);
|
||||
} else {
|
||||
base::OS::PrintError("Extension or internal compilation error.\n");
|
||||
}
|
||||
#ifdef OBJECT_PRINT
|
||||
// Since comments and empty lines have been stripped from the source of
|
||||
// builtins, print the actual source here so that line numbers match.
|
||||
if (location->script()->source()->IsString()) {
|
||||
Handle<String> src(String::cast(location->script()->source()));
|
||||
PrintF("Failing script:\n");
|
||||
int len = src->length();
|
||||
int line_number = 1;
|
||||
PrintF("%5d: ", line_number);
|
||||
for (int i = 0; i < len; i++) {
|
||||
uint16_t character = src->Get(i);
|
||||
PrintF("%c", character);
|
||||
if (character == '\n' && i < len - 2) {
|
||||
PrintF("%5d: ", ++line_number);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -1314,8 +1326,6 @@ Handle<Object> Isolate::GetPromiseOnStackOnThrow() {
|
||||
StackHandler* handler = StackHandler::FromAddress(Isolate::handler(tltop));
|
||||
do {
|
||||
if (handler == promise_try) {
|
||||
// Mark the pushed try-catch handler to prevent a later duplicate event
|
||||
// triggered with the following reject.
|
||||
return tltop->promise_on_stack_->promise();
|
||||
}
|
||||
handler = handler->next();
|
||||
@ -2278,6 +2288,20 @@ void Isolate::FireCallCompletedCallback() {
|
||||
}
|
||||
|
||||
|
||||
void Isolate::SetPromiseRejectCallback(PromiseRejectCallback callback) {
|
||||
promise_reject_callback_ = callback;
|
||||
}
|
||||
|
||||
|
||||
void Isolate::ReportPromiseReject(Handle<JSObject> promise,
|
||||
Handle<Object> value,
|
||||
v8::PromiseRejectEvent event) {
|
||||
if (promise_reject_callback_ == NULL) return;
|
||||
promise_reject_callback_(v8::Utils::PromiseToLocal(promise),
|
||||
v8::Utils::ToLocal(value), event);
|
||||
}
|
||||
|
||||
|
||||
void Isolate::EnqueueMicrotask(Handle<Object> microtask) {
|
||||
DCHECK(microtask->IsJSFunction() || microtask->IsCallHandlerInfo());
|
||||
Handle<FixedArray> queue(heap()->microtask_queue(), this);
|
||||
|
@ -385,6 +385,7 @@ typedef List<HeapObject*> DebugObjectCache;
|
||||
V(uint32_t, per_isolate_assert_data, 0xFFFFFFFFu) \
|
||||
V(InterruptCallback, api_interrupt_callback, NULL) \
|
||||
V(void*, api_interrupt_callback_data, NULL) \
|
||||
V(PromiseRejectCallback, promise_reject_callback, NULL) \
|
||||
ISOLATE_INIT_SIMULATOR_LIST(V)
|
||||
|
||||
#define THREAD_LOCAL_TOP_ACCESSOR(type, name) \
|
||||
@ -1102,6 +1103,10 @@ class Isolate {
|
||||
void RemoveCallCompletedCallback(CallCompletedCallback callback);
|
||||
void FireCallCompletedCallback();
|
||||
|
||||
void SetPromiseRejectCallback(PromiseRejectCallback callback);
|
||||
void ReportPromiseReject(Handle<JSObject> promise, Handle<Object> value,
|
||||
v8::PromiseRejectEvent event);
|
||||
|
||||
void EnqueueMicrotask(Handle<Object> microtask);
|
||||
void RunMicrotasks();
|
||||
|
||||
@ -1216,6 +1221,9 @@ class Isolate {
|
||||
// then return true.
|
||||
bool PropagatePendingExceptionToExternalTryCatch();
|
||||
|
||||
Handle<JSMessageObject> CreateMessage(Handle<Object> exception,
|
||||
MessageLocation* location);
|
||||
|
||||
// Traverse prototype chain to find out whether the object is derived from
|
||||
// the Error object.
|
||||
bool IsErrorObject(Handle<Object> obj);
|
||||
|
@ -19,6 +19,7 @@ var PromiseChain;
|
||||
var PromiseCatch;
|
||||
var PromiseThen;
|
||||
var PromiseHasRejectHandler;
|
||||
var PromiseHasUserDefinedRejectHandler;
|
||||
|
||||
// mirror-debugger.js currently uses builtins.promiseStatus. It would be nice
|
||||
// if we could move these property names into the closure below.
|
||||
@ -30,9 +31,10 @@ var promiseValue = GLOBAL_PRIVATE("Promise#value");
|
||||
var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve");
|
||||
var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject");
|
||||
var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
|
||||
var promiseDebug = GLOBAL_PRIVATE("Promise#debug");
|
||||
var promiseHasHandler = %PromiseHasHandlerSymbol();
|
||||
var lastMicrotaskId = 0;
|
||||
|
||||
|
||||
(function() {
|
||||
|
||||
var $Promise = function Promise(resolver) {
|
||||
@ -159,11 +161,12 @@ var lastMicrotaskId = 0;
|
||||
|
||||
PromiseReject = function PromiseReject(promise, r) {
|
||||
// Check promise status to confirm that this reject has an effect.
|
||||
// Check promiseDebug property to avoid duplicate event.
|
||||
if (DEBUG_IS_ACTIVE &&
|
||||
GET_PRIVATE(promise, promiseStatus) == 0 &&
|
||||
!HAS_DEFINED_PRIVATE(promise, promiseDebug)) {
|
||||
%DebugPromiseRejectEvent(promise, r);
|
||||
// Call runtime for callbacks to the debugger or for unhandled reject.
|
||||
if (GET_PRIVATE(promise, promiseStatus) == 0) {
|
||||
var debug_is_active = DEBUG_IS_ACTIVE;
|
||||
if (debug_is_active || !HAS_DEFINED_PRIVATE(promise, promiseHasHandler)) {
|
||||
%PromiseRejectEvent(promise, r, debug_is_active);
|
||||
}
|
||||
}
|
||||
PromiseDone(promise, -1, r, promiseOnReject)
|
||||
}
|
||||
@ -199,12 +202,17 @@ var lastMicrotaskId = 0;
|
||||
}
|
||||
|
||||
function PromiseRejected(r) {
|
||||
var promise;
|
||||
if (this === $Promise) {
|
||||
// Optimized case, avoid extra closure.
|
||||
return PromiseSet(new $Promise(promiseRaw), -1, r);
|
||||
promise = PromiseSet(new $Promise(promiseRaw), -1, r);
|
||||
// The debug event for this would always be an uncaught promise reject,
|
||||
// which is usually simply noise. Do not trigger that debug event.
|
||||
%PromiseRejectEvent(promise, r, false);
|
||||
} else {
|
||||
return new this(function(resolve, reject) { reject(r) });
|
||||
promise = new this(function(resolve, reject) { reject(r) });
|
||||
}
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Simple chaining.
|
||||
@ -227,11 +235,18 @@ var lastMicrotaskId = 0;
|
||||
+1);
|
||||
break;
|
||||
case -1: // Rejected
|
||||
if (!HAS_DEFINED_PRIVATE(this, promiseHasHandler)) {
|
||||
// Promise has already been rejected, but had no handler.
|
||||
// Revoke previously triggered reject event.
|
||||
%PromiseRevokeReject(this);
|
||||
}
|
||||
PromiseEnqueue(GET_PRIVATE(this, promiseValue),
|
||||
[onReject, deferred],
|
||||
-1);
|
||||
break;
|
||||
}
|
||||
// Mark this promise as having handler.
|
||||
SET_PRIVATE(this, promiseHasHandler, true);
|
||||
if (DEBUG_IS_ACTIVE) {
|
||||
%DebugPromiseEvent({ promise: deferred.promise, parentPromise: this });
|
||||
}
|
||||
@ -325,22 +340,24 @@ var lastMicrotaskId = 0;
|
||||
|
||||
// Utility for debugger
|
||||
|
||||
function PromiseHasRejectHandlerRecursive(promise) {
|
||||
function PromiseHasUserDefinedRejectHandlerRecursive(promise) {
|
||||
var queue = GET_PRIVATE(promise, promiseOnReject);
|
||||
if (IS_UNDEFINED(queue)) return false;
|
||||
// Do a depth first search for a reject handler that's not
|
||||
// the default PromiseIdRejectHandler.
|
||||
for (var i = 0; i < queue.length; i += 2) {
|
||||
if (queue[i] != PromiseIdRejectHandler) return true;
|
||||
if (PromiseHasRejectHandlerRecursive(queue[i + 1].promise)) return true;
|
||||
if (PromiseHasUserDefinedRejectHandlerRecursive(queue[i + 1].promise)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
PromiseHasRejectHandler = function PromiseHasRejectHandler() {
|
||||
// Mark promise as already having triggered a reject event.
|
||||
SET_PRIVATE(this, promiseDebug, true);
|
||||
return PromiseHasRejectHandlerRecursive(this);
|
||||
// Return whether the promise will be handled by a user-defined reject
|
||||
// handler somewhere down the promise chain. For this, we do a depth-first
|
||||
// search for a reject handler that's not the default PromiseIdRejectHandler.
|
||||
PromiseHasUserDefinedRejectHandler =
|
||||
function PromiseHasUserDefinedRejectHandler() {
|
||||
return PromiseHasUserDefinedRejectHandlerRecursive(this);
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
@ -1183,16 +1183,6 @@ RUNTIME_FUNCTION(Runtime_TransitionElementsKind) {
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_DebugPromiseRejectEvent) {
|
||||
DCHECK(args.length() == 2);
|
||||
HandleScope scope(isolate);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(Object, value, 1);
|
||||
isolate->debug()->OnPromiseReject(promise, value);
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_DeleteProperty) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK(args.length() == 3);
|
||||
@ -1872,6 +1862,42 @@ RUNTIME_FUNCTION(Runtime_ThrowUnsupportedSuperError) {
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_PromiseRejectEvent) {
|
||||
DCHECK(args.length() == 3);
|
||||
HandleScope scope(isolate);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0);
|
||||
CONVERT_ARG_HANDLE_CHECKED(Object, value, 1);
|
||||
CONVERT_BOOLEAN_ARG_CHECKED(debug_event, 2);
|
||||
if (debug_event) isolate->debug()->OnPromiseReject(promise, value);
|
||||
Handle<Symbol> key = isolate->factory()->promise_has_handler_symbol();
|
||||
// Do not report if we actually have a handler.
|
||||
if (JSObject::GetDataProperty(promise, key)->IsUndefined()) {
|
||||
isolate->ReportPromiseReject(promise, value,
|
||||
v8::kPromiseRejectWithNoHandler);
|
||||
}
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_PromiseRevokeReject) {
|
||||
DCHECK(args.length() == 1);
|
||||
HandleScope scope(isolate);
|
||||
CONVERT_ARG_HANDLE_CHECKED(JSObject, promise, 0);
|
||||
Handle<Symbol> key = isolate->factory()->promise_has_handler_symbol();
|
||||
// At this point, no revocation has been issued before
|
||||
RUNTIME_ASSERT(JSObject::GetDataProperty(promise, key)->IsUndefined());
|
||||
isolate->ReportPromiseReject(promise, Handle<Object>(),
|
||||
v8::kPromiseHandlerAddedAfterReject);
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_PromiseHasHandlerSymbol) {
|
||||
DCHECK(args.length() == 0);
|
||||
return isolate->heap()->promise_has_handler_symbol();
|
||||
}
|
||||
|
||||
|
||||
RUNTIME_FUNCTION(Runtime_StackGuard) {
|
||||
SealHandleScope shs(isolate);
|
||||
DCHECK(args.length() == 0);
|
||||
|
@ -80,8 +80,10 @@ namespace internal {
|
||||
F(DebugPushPromise, 1, 1) \
|
||||
F(DebugPopPromise, 0, 1) \
|
||||
F(DebugPromiseEvent, 1, 1) \
|
||||
F(DebugPromiseRejectEvent, 2, 1) \
|
||||
F(DebugAsyncTaskEvent, 1, 1) \
|
||||
F(PromiseRejectEvent, 3, 1) \
|
||||
F(PromiseRevokeReject, 1, 1) \
|
||||
F(PromiseHasHandlerSymbol, 0, 1) \
|
||||
F(FlattenString, 1, 1) \
|
||||
F(LoadMutableDouble, 2, 1) \
|
||||
F(TryMigrateInstance, 1, 1) \
|
||||
|
@ -17618,6 +17618,249 @@ TEST(RethrowBogusErrorStackTrace) {
|
||||
}
|
||||
|
||||
|
||||
v8::PromiseRejectEvent reject_event = v8::kPromiseRejectWithNoHandler;
|
||||
int promise_reject_counter = 0;
|
||||
int promise_revoke_counter = 0;
|
||||
|
||||
void PromiseRejectCallback(v8::Handle<v8::Promise> promise,
|
||||
v8::Handle<v8::Value> value,
|
||||
v8::PromiseRejectEvent event) {
|
||||
if (event == v8::kPromiseRejectWithNoHandler) {
|
||||
promise_reject_counter++;
|
||||
CcTest::global()->Set(v8_str("rejected"), promise);
|
||||
CcTest::global()->Set(v8_str("value"), value);
|
||||
} else {
|
||||
promise_revoke_counter++;
|
||||
CcTest::global()->Set(v8_str("revoked"), promise);
|
||||
CHECK(value.IsEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
v8::Handle<v8::Promise> GetPromise(const char* name) {
|
||||
return v8::Handle<v8::Promise>::Cast(CcTest::global()->Get(v8_str(name)));
|
||||
}
|
||||
|
||||
|
||||
v8::Handle<v8::Value> RejectValue() {
|
||||
return CcTest::global()->Get(v8_str("value"));
|
||||
}
|
||||
|
||||
|
||||
void ResetPromiseStates() {
|
||||
promise_reject_counter = 0;
|
||||
promise_revoke_counter = 0;
|
||||
CcTest::global()->Set(v8_str("rejected"), v8_str(""));
|
||||
CcTest::global()->Set(v8_str("value"), v8_str(""));
|
||||
CcTest::global()->Set(v8_str("revoked"), v8_str(""));
|
||||
}
|
||||
|
||||
|
||||
TEST(PromiseRejectCallback) {
|
||||
LocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
|
||||
isolate->SetPromiseRejectCallback(PromiseRejectCallback);
|
||||
|
||||
ResetPromiseStates();
|
||||
|
||||
// Create promise p0.
|
||||
CompileRun(
|
||||
"var reject; \n"
|
||||
"var p0 = new Promise( \n"
|
||||
" function(res, rej) { \n"
|
||||
" reject = rej; \n"
|
||||
" } \n"
|
||||
"); \n");
|
||||
CHECK(!GetPromise("p0")->HasHandler());
|
||||
CHECK_EQ(0, promise_reject_counter);
|
||||
CHECK_EQ(0, promise_revoke_counter);
|
||||
|
||||
// Add resolve handler (and default reject handler) to p0.
|
||||
CompileRun("var p1 = p0.then(function(){});");
|
||||
CHECK(GetPromise("p0")->HasHandler());
|
||||
CHECK(!GetPromise("p1")->HasHandler());
|
||||
CHECK_EQ(0, promise_reject_counter);
|
||||
CHECK_EQ(0, promise_revoke_counter);
|
||||
|
||||
// Reject p0.
|
||||
CompileRun("reject('ppp');");
|
||||
CHECK(GetPromise("p0")->HasHandler());
|
||||
CHECK(!GetPromise("p1")->HasHandler());
|
||||
CHECK_EQ(1, promise_reject_counter);
|
||||
CHECK_EQ(0, promise_revoke_counter);
|
||||
CHECK_EQ(v8::kPromiseRejectWithNoHandler, reject_event);
|
||||
CHECK(GetPromise("rejected")->Equals(GetPromise("p1")));
|
||||
CHECK(RejectValue()->Equals(v8_str("ppp")));
|
||||
|
||||
// Reject p0 again. Callback is not triggered again.
|
||||
CompileRun("reject();");
|
||||
CHECK(GetPromise("p0")->HasHandler());
|
||||
CHECK(!GetPromise("p1")->HasHandler());
|
||||
CHECK_EQ(1, promise_reject_counter);
|
||||
CHECK_EQ(0, promise_revoke_counter);
|
||||
|
||||
// Add resolve handler to p1.
|
||||
CompileRun("var p2 = p1.then(function(){});");
|
||||
CHECK(GetPromise("p0")->HasHandler());
|
||||
CHECK(GetPromise("p1")->HasHandler());
|
||||
CHECK(!GetPromise("p2")->HasHandler());
|
||||
CHECK_EQ(2, promise_reject_counter);
|
||||
CHECK_EQ(1, promise_revoke_counter);
|
||||
CHECK(GetPromise("rejected")->Equals(GetPromise("p2")));
|
||||
CHECK(RejectValue()->Equals(v8_str("ppp")));
|
||||
CHECK(GetPromise("revoked")->Equals(GetPromise("p1")));
|
||||
|
||||
ResetPromiseStates();
|
||||
|
||||
// Create promise q0.
|
||||
CompileRun(
|
||||
"var q0 = new Promise( \n"
|
||||
" function(res, rej) { \n"
|
||||
" reject = rej; \n"
|
||||
" } \n"
|
||||
"); \n");
|
||||
CHECK(!GetPromise("q0")->HasHandler());
|
||||
CHECK_EQ(0, promise_reject_counter);
|
||||
CHECK_EQ(0, promise_revoke_counter);
|
||||
|
||||
// Add reject handler to q0.
|
||||
CompileRun("var q1 = q0.catch(function() {});");
|
||||
CHECK(GetPromise("q0")->HasHandler());
|
||||
CHECK(!GetPromise("q1")->HasHandler());
|
||||
CHECK_EQ(0, promise_reject_counter);
|
||||
CHECK_EQ(0, promise_revoke_counter);
|
||||
|
||||
// Reject q0.
|
||||
CompileRun("reject('qq')");
|
||||
CHECK(GetPromise("q0")->HasHandler());
|
||||
CHECK(!GetPromise("q1")->HasHandler());
|
||||
CHECK_EQ(0, promise_reject_counter);
|
||||
CHECK_EQ(0, promise_revoke_counter);
|
||||
|
||||
// Add a new reject handler, which rejects by returning Promise.reject().
|
||||
// The returned promise q_ triggers a reject callback at first, only to
|
||||
// revoke it when returning it causes q2 to be rejected.
|
||||
CompileRun(
|
||||
"var q_;"
|
||||
"var q2 = q0.catch( \n"
|
||||
" function() { \n"
|
||||
" q_ = Promise.reject('qqq'); \n"
|
||||
" return q_; \n"
|
||||
" } \n"
|
||||
"); \n");
|
||||
CHECK(GetPromise("q0")->HasHandler());
|
||||
CHECK(!GetPromise("q1")->HasHandler());
|
||||
CHECK(!GetPromise("q2")->HasHandler());
|
||||
CHECK(GetPromise("q_")->HasHandler());
|
||||
CHECK_EQ(2, promise_reject_counter);
|
||||
CHECK_EQ(1, promise_revoke_counter);
|
||||
CHECK(GetPromise("rejected")->Equals(GetPromise("q2")));
|
||||
CHECK(GetPromise("revoked")->Equals(GetPromise("q_")));
|
||||
CHECK(RejectValue()->Equals(v8_str("qqq")));
|
||||
|
||||
// Add a reject handler to the resolved q1, which rejects by throwing.
|
||||
CompileRun(
|
||||
"var q3 = q1.then( \n"
|
||||
" function() { \n"
|
||||
" throw 'qqqq'; \n"
|
||||
" } \n"
|
||||
"); \n");
|
||||
CHECK(GetPromise("q0")->HasHandler());
|
||||
CHECK(GetPromise("q1")->HasHandler());
|
||||
CHECK(!GetPromise("q2")->HasHandler());
|
||||
CHECK(!GetPromise("q3")->HasHandler());
|
||||
CHECK_EQ(3, promise_reject_counter);
|
||||
CHECK_EQ(1, promise_revoke_counter);
|
||||
CHECK(GetPromise("rejected")->Equals(GetPromise("q3")));
|
||||
CHECK(RejectValue()->Equals(v8_str("qqqq")));
|
||||
|
||||
ResetPromiseStates();
|
||||
|
||||
// Create promise r0, which has three handlers, two of which handle rejects.
|
||||
CompileRun(
|
||||
"var r0 = new Promise( \n"
|
||||
" function(res, rej) { \n"
|
||||
" reject = rej; \n"
|
||||
" } \n"
|
||||
"); \n"
|
||||
"var r1 = r0.catch(function() {}); \n"
|
||||
"var r2 = r0.then(function() {}); \n"
|
||||
"var r3 = r0.then(function() {}, \n"
|
||||
" function() {}); \n");
|
||||
CHECK(GetPromise("r0")->HasHandler());
|
||||
CHECK(!GetPromise("r1")->HasHandler());
|
||||
CHECK(!GetPromise("r2")->HasHandler());
|
||||
CHECK(!GetPromise("r3")->HasHandler());
|
||||
CHECK_EQ(0, promise_reject_counter);
|
||||
CHECK_EQ(0, promise_revoke_counter);
|
||||
|
||||
// Reject r0.
|
||||
CompileRun("reject('rrr')");
|
||||
CHECK(GetPromise("r0")->HasHandler());
|
||||
CHECK(!GetPromise("r1")->HasHandler());
|
||||
CHECK(!GetPromise("r2")->HasHandler());
|
||||
CHECK(!GetPromise("r3")->HasHandler());
|
||||
CHECK_EQ(1, promise_reject_counter);
|
||||
CHECK_EQ(0, promise_revoke_counter);
|
||||
CHECK(GetPromise("rejected")->Equals(GetPromise("r2")));
|
||||
CHECK(RejectValue()->Equals(v8_str("rrr")));
|
||||
|
||||
// Add reject handler to r2.
|
||||
CompileRun("var r4 = r2.catch(function() {});");
|
||||
CHECK(GetPromise("r0")->HasHandler());
|
||||
CHECK(!GetPromise("r1")->HasHandler());
|
||||
CHECK(GetPromise("r2")->HasHandler());
|
||||
CHECK(!GetPromise("r3")->HasHandler());
|
||||
CHECK(!GetPromise("r4")->HasHandler());
|
||||
CHECK_EQ(1, promise_reject_counter);
|
||||
CHECK_EQ(1, promise_revoke_counter);
|
||||
CHECK(GetPromise("revoked")->Equals(GetPromise("r2")));
|
||||
CHECK(RejectValue()->Equals(v8_str("rrr")));
|
||||
|
||||
// Add reject handlers to r4.
|
||||
CompileRun("var r5 = r4.then(function() {}, function() {});");
|
||||
CHECK(GetPromise("r0")->HasHandler());
|
||||
CHECK(!GetPromise("r1")->HasHandler());
|
||||
CHECK(GetPromise("r2")->HasHandler());
|
||||
CHECK(!GetPromise("r3")->HasHandler());
|
||||
CHECK(GetPromise("r4")->HasHandler());
|
||||
CHECK(!GetPromise("r5")->HasHandler());
|
||||
CHECK_EQ(1, promise_reject_counter);
|
||||
CHECK_EQ(1, promise_revoke_counter);
|
||||
|
||||
ResetPromiseStates();
|
||||
|
||||
// Create promise s0, which has three handlers, none of which handle rejects.
|
||||
CompileRun(
|
||||
"var s0 = new Promise( \n"
|
||||
" function(res, rej) { \n"
|
||||
" reject = rej; \n"
|
||||
" } \n"
|
||||
"); \n"
|
||||
"var s1 = s0.then(function() {}); \n"
|
||||
"var s2 = s0.then(function() {}); \n"
|
||||
"var s3 = s0.then(function() {}); \n");
|
||||
CHECK(GetPromise("s0")->HasHandler());
|
||||
CHECK(!GetPromise("s1")->HasHandler());
|
||||
CHECK(!GetPromise("s2")->HasHandler());
|
||||
CHECK(!GetPromise("s3")->HasHandler());
|
||||
CHECK_EQ(0, promise_reject_counter);
|
||||
CHECK_EQ(0, promise_revoke_counter);
|
||||
|
||||
// Reject s0.
|
||||
CompileRun("reject('sss')");
|
||||
CHECK(GetPromise("s0")->HasHandler());
|
||||
CHECK(!GetPromise("s1")->HasHandler());
|
||||
CHECK(!GetPromise("s2")->HasHandler());
|
||||
CHECK(!GetPromise("s3")->HasHandler());
|
||||
CHECK_EQ(3, promise_reject_counter);
|
||||
CHECK_EQ(0, promise_revoke_counter);
|
||||
CHECK(RejectValue()->Equals(v8_str("sss")));
|
||||
}
|
||||
|
||||
|
||||
void AnalyzeStackOfEvalWithSourceURL(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
|
@ -7518,3 +7518,75 @@ TEST(DebugBreakOffThreadTerminate) {
|
||||
CompileRun("while (true);");
|
||||
CHECK(try_catch.HasTerminated());
|
||||
}
|
||||
|
||||
|
||||
static void DebugEventExpectNoException(
|
||||
const v8::Debug::EventDetails& event_details) {
|
||||
v8::DebugEvent event = event_details.GetEvent();
|
||||
CHECK_NE(v8::Exception, event);
|
||||
}
|
||||
|
||||
|
||||
static void TryCatchWrappedThrowCallback(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::TryCatch try_catch;
|
||||
CompileRun("throw 'rejection';");
|
||||
CHECK(try_catch.HasCaught());
|
||||
}
|
||||
|
||||
|
||||
TEST(DebugPromiseInterceptedByTryCatch) {
|
||||
DebugLocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
v8::Debug::SetDebugEventListener(&DebugEventExpectNoException);
|
||||
ChangeBreakOnException(false, true);
|
||||
|
||||
v8::Handle<v8::FunctionTemplate> fun =
|
||||
v8::FunctionTemplate::New(isolate, TryCatchWrappedThrowCallback);
|
||||
env->Global()->Set(v8_str("fun"), fun->GetFunction());
|
||||
|
||||
CompileRun("var p = new Promise(function(res, rej) { fun(); res(); });");
|
||||
CompileRun(
|
||||
"var r;"
|
||||
"p.chain(function() { r = 'resolved'; },"
|
||||
" function() { r = 'rejected'; });");
|
||||
CHECK(CompileRun("r")->Equals(v8_str("resolved")));
|
||||
}
|
||||
|
||||
|
||||
static int exception_event_counter = 0;
|
||||
|
||||
|
||||
static void DebugEventCountException(
|
||||
const v8::Debug::EventDetails& event_details) {
|
||||
v8::DebugEvent event = event_details.GetEvent();
|
||||
if (event == v8::Exception) exception_event_counter++;
|
||||
}
|
||||
|
||||
|
||||
static void ThrowCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
CompileRun("throw 'rejection';");
|
||||
}
|
||||
|
||||
|
||||
TEST(DebugPromiseRejectedByCallback) {
|
||||
DebugLocalContext env;
|
||||
v8::Isolate* isolate = env->GetIsolate();
|
||||
v8::HandleScope scope(isolate);
|
||||
v8::Debug::SetDebugEventListener(&DebugEventCountException);
|
||||
ChangeBreakOnException(false, true);
|
||||
exception_event_counter = 0;
|
||||
|
||||
v8::Handle<v8::FunctionTemplate> fun =
|
||||
v8::FunctionTemplate::New(isolate, ThrowCallback);
|
||||
env->Global()->Set(v8_str("fun"), fun->GetFunction());
|
||||
|
||||
CompileRun("var p = new Promise(function(res, rej) { fun(); res(); });");
|
||||
CompileRun(
|
||||
"var r;"
|
||||
"p.chain(function() { r = 'resolved'; },"
|
||||
" function(e) { r = 'rejected' + e; });");
|
||||
CHECK(CompileRun("r")->Equals(v8_str("rejectedrejection")));
|
||||
CHECK_EQ(1, exception_event_counter);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user