Revision: 24335
Author:   yang...@chromium.org
Date:     Tue Sep 30 15:29:08 2014 UTC
Log:      Introduce PromiseRejectCallback.

R=aand...@chromium.org, yu...@chromium.org, rossb...@chromium.org
API=v8::Isolate::SetPromiseRejectCallback, v8::Promise::HasHandler
LOG=Y
BUG=v8:3093

Review URL: https://codereview.chromium.org/600723005
https://code.google.com/p/v8/source/detail?r=24335

Modified:
 /branches/bleeding_edge/include/v8.h
 /branches/bleeding_edge/src/api.cc
 /branches/bleeding_edge/src/api.h
 /branches/bleeding_edge/src/bootstrapper.cc
 /branches/bleeding_edge/src/compiler/linkage.cc
 /branches/bleeding_edge/src/contexts.h
 /branches/bleeding_edge/src/debug.cc
 /branches/bleeding_edge/src/debug.h
 /branches/bleeding_edge/src/heap/heap.cc
 /branches/bleeding_edge/src/heap/heap.h
 /branches/bleeding_edge/src/isolate.cc
 /branches/bleeding_edge/src/isolate.h
 /branches/bleeding_edge/src/promise.js
 /branches/bleeding_edge/src/runtime/runtime.cc
 /branches/bleeding_edge/src/runtime/runtime.h
 /branches/bleeding_edge/test/cctest/test-api.cc
 /branches/bleeding_edge/test/cctest/test-debug.cc

=======================================
--- /branches/bleeding_edge/include/v8.h        Mon Sep 29 12:59:54 2014 UTC
+++ /branches/bleeding_edge/include/v8.h        Tue Sep 30 15:29:08 2014 UTC
@@ -85,6 +85,7 @@
 class ObjectTemplate;
 class Platform;
 class Primitive;
+class Promise;
 class RawOperationDescriptor;
 class Script;
 class Signature;
@@ -2851,6 +2852,12 @@
   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 @@
 // --- 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 @@
    */
   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 @@
   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.
=======================================
--- /branches/bleeding_edge/src/api.cc  Mon Sep 29 12:59:54 2014 UTC
+++ /branches/bleeding_edge/src/api.cc  Tue Sep 30 15:29:08 2014 UTC
@@ -5983,6 +5983,16 @@
   EXCEPTION_BAILOUT_CHECK(isolate, Local<Promise>());
   return Local<Promise>::Cast(Utils::ToLocal(result));
 }
+
+
+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 {
@@ -6730,6 +6740,13 @@
   i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
   isolate->RemoveCallCompletedCallback(callback);
 }
+
+
+void Isolate::SetPromiseRejectCallback(PromiseRejectCallback callback) {
+  if (callback == NULL) return;
+  i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
+  isolate->SetPromiseRejectCallback(callback);
+}


 void Isolate::RunMicrotasks() {
=======================================
--- /branches/bleeding_edge/src/api.h   Wed Aug 20 15:25:13 2014 UTC
+++ /branches/bleeding_edge/src/api.h   Tue Sep 30 15:29:08 2014 UTC
@@ -232,6 +232,8 @@

   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(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)
=======================================
--- /branches/bleeding_edge/src/bootstrapper.cc Fri Sep 19 14:59:14 2014 UTC
+++ /branches/bleeding_edge/src/bootstrapper.cc Tue Sep 30 15:29:08 2014 UTC
@@ -1538,6 +1538,8 @@
   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);
=======================================
--- /branches/bleeding_edge/src/compiler/linkage.cc Tue Sep 30 10:29:32 2014 UTC +++ /branches/bleeding_edge/src/compiler/linkage.cc Tue Sep 30 15:29:08 2014 UTC
@@ -145,7 +145,6 @@
     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 @@
     case Runtime::kParseJson:
     case Runtime::kPrepareStep:
     case Runtime::kPreventExtensions:
+    case Runtime::kPromiseRejectEvent:
+    case Runtime::kPromiseRevokeReject:
     case Runtime::kRegExpCompile:
     case Runtime::kRegExpExecMultiple:
     case Runtime::kResolvePossiblyDirectEval:
=======================================
--- /branches/bleeding_edge/src/contexts.h      Fri Sep 12 15:07:43 2014 UTC
+++ /branches/bleeding_edge/src/contexts.h      Tue Sep 30 15:29:08 2014 UTC
@@ -180,6 +180,7 @@
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 @@
     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,
=======================================
--- /branches/bleeding_edge/src/debug.cc        Wed Sep 17 15:29:42 2014 UTC
+++ /branches/bleeding_edge/src/debug.cc        Tue Sep 30 15:29:08 2014 UTC
@@ -1260,17 +1260,6 @@
     return break_on_exception_;
   }
 }
-
-
-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,
@@ -2521,14 +2510,37 @@
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) {
=======================================
--- /branches/bleeding_edge/src/debug.h Tue Sep 30 08:23:02 2014 UTC
+++ /branches/bleeding_edge/src/debug.h Tue Sep 30 15:29:08 2014 UTC
@@ -533,8 +533,8 @@
   // 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,
=======================================
--- /branches/bleeding_edge/src/heap/heap.cc    Thu Sep 25 07:32:13 2014 UTC
+++ /branches/bleeding_edge/src/heap/heap.cc    Tue Sep 30 15:29:08 2014 UTC
@@ -2880,6 +2880,8 @@
   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);
=======================================
--- /branches/bleeding_edge/src/heap/heap.h     Thu Sep 25 07:32:13 2014 UTC
+++ /branches/bleeding_edge/src/heap/heap.h     Tue Sep 30 15:29:08 2014 UTC
@@ -189,6 +189,8 @@
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)
=======================================
--- /branches/bleeding_edge/src/isolate.cc      Mon Sep 29 09:39:22 2014 UTC
+++ /branches/bleeding_edge/src/isolate.cc      Tue Sep 30 15:29:08 2014 UTC
@@ -978,6 +978,8 @@
 }


+// 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 @@

 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 @@
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 @@
       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 @@
// 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 @@
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();
@@ -2276,6 +2286,20 @@
     call_completed_callbacks_.at(i)();
   }
 }
+
+
+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) {
=======================================
--- /branches/bleeding_edge/src/isolate.h       Mon Sep 29 07:29:14 2014 UTC
+++ /branches/bleeding_edge/src/isolate.h       Tue Sep 30 15:29:08 2014 UTC
@@ -385,6 +385,7 @@
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 @@
   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 @@
   // 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);
=======================================
--- /branches/bleeding_edge/src/promise.js      Wed Aug 20 19:21:56 2014 UTC
+++ /branches/bleeding_edge/src/promise.js      Tue Sep 30 15:29:08 2014 UTC
@@ -19,6 +19,7 @@
 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 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 @@

   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 @@
   }

   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 @@
                        +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 @@

   // 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);
   };

   // -------------------------------------------------------------------
=======================================
--- /branches/bleeding_edge/src/runtime/runtime.cc Tue Sep 30 10:46:04 2014 UTC +++ /branches/bleeding_edge/src/runtime/runtime.cc Tue Sep 30 15:29:08 2014 UTC
@@ -1181,16 +1181,6 @@
   JSObject::TransitionElementsKind(array, map->elements_kind());
   return *array;
 }
-
-
-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) {
@@ -1870,6 +1860,42 @@
       isolate,
NewReferenceError("unsupported_super", HandleVector<Object>(NULL, 0)));
 }
+
+
+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) {
=======================================
--- /branches/bleeding_edge/src/runtime/runtime.h Tue Sep 30 10:42:44 2014 UTC +++ /branches/bleeding_edge/src/runtime/runtime.h Tue Sep 30 15:29:08 2014 UTC
@@ -80,8 +80,10 @@
   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)                              \
=======================================
--- /branches/bleeding_edge/test/cctest/test-api.cc Mon Sep 29 12:59:54 2014 UTC +++ /branches/bleeding_edge/test/cctest/test-api.cc Tue Sep 30 15:29:08 2014 UTC
@@ -17616,6 +17616,249 @@
   v8::V8::SetCaptureStackTraceForUncaughtExceptions(false);
   v8::V8::RemoveMessageListeners(RethrowBogusErrorStackTraceHandler);
 }
+
+
+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(
=======================================
--- /branches/bleeding_edge/test/cctest/test-debug.cc Mon Sep 22 12:45:47 2014 UTC +++ /branches/bleeding_edge/test/cctest/test-debug.cc Tue Sep 30 15:29:08 2014 UTC
@@ -7518,3 +7518,75 @@
   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);
+}

--
--
v8-dev mailing list
v8-dev@googlegroups.com
http://groups.google.com/group/v8-dev
--- You received this message because you are subscribed to the Google Groups "v8-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to v8-dev+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to