Commit e68e62c8 authored by yangguo@chromium.org's avatar yangguo@chromium.org

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
parent 552a9c24
......@@ -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.
......
......@@ -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,
......
......@@ -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)
......
This diff is collapsed.
......@@ -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);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment