Add functionality to StreamThenPromise()

ReadableStreamNative needs some extra functionality from
StreamThenPromise():

* Support the single argument form of Then(). The |on_rejected| argument
  is now optional.
* Behave as Catch() by setting the |on_fulfilled| argument to nullptr.
* Return the return value from Then() instead of void. Only a Promise
  resolving to undefined can be created due to the StreamScriptFunction
  API having no provision for a return value, but this is sufficient to
  implement ReadableStream.

BUG=902633

Change-Id: I44f0ba0109568c8fd09882e2f1f05ba50816364a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1523214
Reviewed-by: Yutaka Hirano <yhirano@chromium.org>
Commit-Queue: Adam Rice <ricea@chromium.org>
Cr-Commit-Position: refs/heads/master@{#641440}
diff --git a/third_party/blink/renderer/core/streams/stream_script_function.cc b/third_party/blink/renderer/core/streams/stream_script_function.cc
index 2bd72878..8204397 100644
--- a/third_party/blink/renderer/core/streams/stream_script_function.cc
+++ b/third_party/blink/renderer/core/streams/stream_script_function.cc
@@ -17,19 +17,32 @@
   CallWithLocal(args[0]);
 }
 
-void StreamThenPromise(v8::Local<v8::Context> context,
-                       v8::Local<v8::Promise> promise,
-                       StreamScriptFunction* on_fulfilled,
-                       StreamScriptFunction* on_rejected) {
-  DCHECK(on_fulfilled);
-  DCHECK(on_rejected);
-  auto result = promise->Then(context, on_fulfilled->BindToV8Function(),
-                              on_rejected->BindToV8Function());
-  if (result.IsEmpty()) {
+v8::Local<v8::Promise> StreamThenPromise(v8::Local<v8::Context> context,
+                                         v8::Local<v8::Promise> promise,
+                                         StreamScriptFunction* on_fulfilled,
+                                         StreamScriptFunction* on_rejected) {
+  v8::MaybeLocal<v8::Promise> result_maybe;
+  if (!on_fulfilled) {
+    DCHECK(on_rejected);
+    result_maybe = promise->Catch(context, on_rejected->BindToV8Function());
+  } else if (on_rejected) {
+    result_maybe = promise->Then(context, on_fulfilled->BindToV8Function(),
+                                 on_rejected->BindToV8Function());
+  } else {
+    result_maybe = promise->Then(context, on_fulfilled->BindToV8Function());
+  }
+
+  v8::Local<v8::Promise> result;
+  if (!result_maybe.ToLocal(&result)) {
     DVLOG(3)
         << "assuming that failure of promise->Then() is caused by shutdown and"
            "ignoring it";
+    // Try to create a dummy promise so that the calling code can continue. If
+    // we can't create one, then we can't return to the calling context so we
+    // have to crash. This shouldn't happen except on OOM.
+    result = v8::Promise::Resolver::New(context).ToLocalChecked()->GetPromise();
   }
+  return result;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/streams/stream_script_function.h b/third_party/blink/renderer/core/streams/stream_script_function.h
index 9ed75c0..599e26f 100644
--- a/third_party/blink/renderer/core/streams/stream_script_function.h
+++ b/third_party/blink/renderer/core/streams/stream_script_function.h
@@ -29,11 +29,13 @@
 
 // A convenient wrapper for promise->Then() for when both paths are
 // StreamScriptFunctions. It avoids having to call BindToV8Function()
-// explicitly. Both |on_fulfilled| and |on_rejected| must be non-null.
-void StreamThenPromise(v8::Local<v8::Context>,
-                       v8::Local<v8::Promise>,
-                       StreamScriptFunction* on_fulfilled,
-                       StreamScriptFunction* on_rejected);
+// explicitly. If |on_rejected| is null then behaves like single-argument
+// Then(). If |on_fulfilled| is null then it calls Catch().
+v8::Local<v8::Promise> StreamThenPromise(
+    v8::Local<v8::Context>,
+    v8::Local<v8::Promise>,
+    StreamScriptFunction* on_fulfilled,
+    StreamScriptFunction* on_rejected = nullptr);
 
 }  // namespace blink