Introduce PromiseRejectCallback.
authoryangguo@chromium.org <yangguo@chromium.org>
Tue, 30 Sep 2014 15:29:08 +0000 (15:29 +0000)
committeryangguo@chromium.org <yangguo@chromium.org>
Tue, 30 Sep 2014 15:29:08 +0000 (15:29 +0000)
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

17 files changed:
include/v8.h
src/api.cc
src/api.h
src/bootstrapper.cc
src/compiler/linkage.cc
src/contexts.h
src/debug.cc
src/debug.h
src/heap/heap.cc
src/heap/heap.h
src/isolate.cc
src/isolate.h
src/promise.js
src/runtime/runtime.cc
src/runtime/runtime.h
test/cctest/test-api.cc
test/cctest/test-debug.cc

index 4d5d7138043954bb598c7ad4b5319c6d27c38a15..c9b3862be5027216830f95536cfade85d77b1f7e 100644 (file)
@@ -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.
index 63ea457cec3c1e75db81b4922f685073f1f66055..f70d0e0e3dc418fc97a886ee9f8a59001cee7de2 100644 (file)
@@ -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();
 }
index 9aed5dd4e971e859790fcb02e9d1117baa5bda17..1dc894701031db688538b1df73161c894c9aee21 100644 (file)
--- a/src/api.h
+++ b/src/api.h
@@ -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)
index 250562a2aa17a0a8c47dfc8567b449b5ccade0f0..16f030803f5a80f4058b00081bb8ce381f4154da 100644 (file)
@@ -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);
index 82dd440cd6708f6c8707870a3825cc1a000e5df3..cd597999b013a0e1880f50110120b625b7dccb6e 100644 (file)
@@ -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:
index ac25e48853080f384600f0417e9073aba50daa69..38bdf84e6222bdde039f04aaf5f22b42924ffa8e 100644 (file)
@@ -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,
index f0e77968eb48fb6135ff9a8636a4dac6adbd18ad..cacc8e274a9d7b5f567b6c78a243ec8bec03e850 100644 (file)
@@ -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) {
index 7e792b69cf7aed9845f0d25a1feeec37facb6f5a..9b14afcb34914b9c0d0c8741c965ab69b1c846be 100644 (file)
@@ -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,
index 78c45b0e2210b30fdb9fdf0485c48f63204d66a3..bc200451be092805dfe910ac0001794910823c1c 100644 (file)
@@ -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);
index 87b939ad6c219986c192e33304fab1f76ff788d1..88e534fe3c4bc46e5f4cf8aab2de1effb89205d1 100644 (file)
@@ -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)
index 7d1f835aa10c4844f2342a4a5cc0cf37951dc604..4ccd8ca5578e40a760fc7b0a703f0c3b8e14e0ad 100644 (file)
@@ -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);
index 9ea30743bdf56db47473f3197976c778ffac698f..7892ff6d1824eba67b0661b69dc44c1d0c8b21ee 100644 (file)
@@ -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);
index 37c10ec08eb298d5543738531168fe0315ca7854..e402a18532647b96b6b07268858f95c4aa627064 100644 (file)
@@ -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);
   };
 
   // -------------------------------------------------------------------
index dbb51ec926f7e1b29b66d7e275216c4149f172c7..815bc4592a65600086955620e69c10557621eb25 100644 (file)
@@ -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);
index eafb19f354a28f7f2329f2dc64cf9dfd01701921..8b25e517340ecf4bdfa4c96231a26a1232e54d83 100644 (file)
@@ -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)                              \
index bf2ef23ffaea84e18e353369ed031017bcd31092..b4af849328659373fda081c836f6e6ffae65e4fb 100644 (file)
@@ -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());
index 2f0674a34d98b94c6183ad10ed7ed4347c4c02ff..adf9ba99b2db880d32d7104c081ca580dbc35abd 100644 (file)
@@ -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);
+}