| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Implementation of functions that are shared between ReadableStream and |
| // WritableStream. |
| |
| #include "third_party/blink/renderer/core/streams/miscellaneous_operations.h" |
| |
| #include <math.h> |
| |
| #include "base/optional.h" |
| #include "third_party/blink/renderer/core/streams/stream_algorithms.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/bindings/script_state.h" |
| #include "third_party/blink/renderer/platform/bindings/trace_wrapper_v8_reference.h" |
| #include "third_party/blink/renderer/platform/bindings/v8_binding.h" |
| #include "third_party/blink/renderer/platform/heap/visitor.h" |
| #include "third_party/blink/renderer/platform/wtf/assertions.h" |
| #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| // ResolveMethod implements part of CreateAlgorithmFromUnderlyingMethod and |
| // CallOrNoop1. |
| v8::MaybeLocal<v8::Value> ResolveMethod(ScriptState* script_state, |
| v8::Local<v8::Object> object, |
| const char* method_name, |
| const char* name_for_error, |
| ExceptionState& exception_state) { |
| auto* isolate = script_state->GetIsolate(); |
| v8::TryCatch try_catch(isolate); |
| |
| // Algorithm steps from CreateAlgorithmFromUnderlyingMethod in the standard. |
| // https://streams.spec.whatwg.org/#create-algorithm-from-underlying-method |
| // 5. Let method be ? GetV(underlyingObject, methodName). |
| auto method_maybe = object->Get(script_state->GetContext(), |
| V8AtomicString(isolate, method_name)); |
| v8::Local<v8::Value> method; |
| if (!method_maybe.ToLocal(&method)) { |
| exception_state.RethrowV8Exception(try_catch.Exception()); |
| return v8::MaybeLocal<v8::Value>(); |
| } |
| |
| // 6. If method is not undefined, |
| // a. If ! IsCallable(method) is false, throw a TypeError exception. |
| if (!method->IsFunction() && !method->IsUndefined()) { |
| exception_state.ThrowTypeError(String(name_for_error) + |
| " must be a function or undefined"); |
| return v8::MaybeLocal<v8::Value>(); |
| } |
| |
| return method; |
| } |
| |
| // PromiseRejectInternal() implements Promise.reject(_r_) from the ECMASCRIPT |
| // standard, https://tc39.github.io/ecma262/#sec-promise.reject. |
| // The |recursion_depth| argument is used to prevent infinite recursion in the |
| // case that we can't create a promise. |
| v8::Local<v8::Promise> PromiseRejectInternal(ScriptState* script_state, |
| v8::Local<v8::Value> value, |
| int recursion_depth) { |
| auto context = script_state->GetContext(); |
| v8::TryCatch trycatch(script_state->GetIsolate()); |
| // TODO(ricea): Can this fail for reasons other than memory exhaustion? Can we |
| // recover if it does? |
| auto resolver = v8::Promise::Resolver::New(context).ToLocalChecked(); |
| if (resolver->Reject(context, value).IsNothing()) { |
| // Assume that the exception can be successfully used to create a Promise. |
| // TODO(ricea): Can the body of this if statement actually be reached? |
| if (recursion_depth >= 2) { |
| LOG(FATAL) << "Recursion depth exceeded in PromiseRejectInternal"; |
| } |
| return PromiseRejectInternal(script_state, trycatch.Exception(), |
| recursion_depth + 1); |
| } |
| return resolver->GetPromise(); |
| } |
| |
| class DefaultSizeAlgorithm final : public StrategySizeAlgorithm { |
| public: |
| base::Optional<double> Run(ScriptState*, |
| v8::Local<v8::Value>, |
| ExceptionState&) override { |
| return 1; |
| } |
| }; |
| |
| class JavaScriptSizeAlgorithm final : public StrategySizeAlgorithm { |
| public: |
| JavaScriptSizeAlgorithm(v8::Isolate* isolate, v8::Local<v8::Function> size) |
| : function_(isolate, size) {} |
| |
| base::Optional<double> Run(ScriptState* script_state, |
| v8::Local<v8::Value> chunk, |
| ExceptionState& exception_state) override { |
| auto* isolate = script_state->GetIsolate(); |
| auto context = script_state->GetContext(); |
| v8::TryCatch trycatch(isolate); |
| v8::Local<v8::Value> argv[] = {chunk}; |
| |
| // https://streams.spec.whatwg.org/#make-size-algorithm-from-size-function |
| // 3.a. Return ? Call(size, undefined, « chunk »). |
| v8::MaybeLocal<v8::Value> result_maybe = function_.NewLocal(isolate)->Call( |
| context, v8::Undefined(isolate), 1, argv); |
| v8::Local<v8::Value> result; |
| if (!result_maybe.ToLocal(&result)) { |
| exception_state.RethrowV8Exception(trycatch.Exception()); |
| return base::nullopt; |
| } |
| |
| // This conversion to double comes from the EnqueueValueWithSize |
| // operation: https://streams.spec.whatwg.org/#enqueue-value-with-size |
| // 2. Let size be ? ToNumber(size). |
| v8::MaybeLocal<v8::Number> number_maybe = result->ToNumber(context); |
| v8::Local<v8::Number> number; |
| if (!number_maybe.ToLocal(&number)) { |
| exception_state.RethrowV8Exception(trycatch.Exception()); |
| return base::nullopt; |
| } |
| return number->Value(); |
| } |
| |
| void Trace(Visitor* visitor) override { |
| visitor->Trace(function_); |
| StrategySizeAlgorithm::Trace(visitor); |
| } |
| |
| private: |
| TraceWrapperV8Reference<v8::Function> function_; |
| }; |
| |
| class TrivialStreamAlgorithm final : public StreamAlgorithm { |
| public: |
| v8::Local<v8::Promise> Run(ScriptState* script_state, |
| int argc, |
| v8::Local<v8::Value> argv[]) override { |
| return PromiseResolveWithUndefined(script_state); |
| } |
| }; |
| |
| class JavaScriptStreamAlgorithmWithoutExtraArg final : public StreamAlgorithm { |
| public: |
| JavaScriptStreamAlgorithmWithoutExtraArg(v8::Isolate* isolate, |
| v8::Local<v8::Function> method, |
| v8::Local<v8::Object> recv) |
| : recv_(isolate, recv), method_(isolate, method) {} |
| |
| // |argc| is equivalent to the "algoArgCount" argument to |
| // CreateAlgorithmFromUnderlyingMethod() in the standard, but it is |
| // determined when the algorithm is called rather than when the algorithm is |
| // created. |
| v8::Local<v8::Promise> Run(ScriptState* script_state, |
| int argc, |
| v8::Local<v8::Value> argv[]) override { |
| // This method technically supports any number of arguments, but we only |
| // call it with 0 or 1 in practice. |
| DCHECK_GE(argc, 0); |
| auto* isolate = script_state->GetIsolate(); |
| // https://streams.spec.whatwg.org/#create-algorithm-from-underlying-method |
| // 6.b.i. Return ! PromiseCall(method, underlyingObject, extraArgs). |
| // In this class extraArgs is always empty, but there may be other arguments |
| // supplied to the method. |
| return PromiseCall(script_state, method_.NewLocal(isolate), |
| recv_.NewLocal(isolate), argc, argv); |
| } |
| |
| void Trace(Visitor* visitor) override { |
| visitor->Trace(recv_); |
| visitor->Trace(method_); |
| StreamAlgorithm::Trace(visitor); |
| } |
| |
| private: |
| TraceWrapperV8Reference<v8::Object> recv_; |
| TraceWrapperV8Reference<v8::Function> method_; |
| }; |
| |
| class JavaScriptStreamAlgorithmWithExtraArg final : public StreamAlgorithm { |
| public: |
| JavaScriptStreamAlgorithmWithExtraArg(v8::Isolate* isolate, |
| v8::Local<v8::Function> method, |
| v8::Local<v8::Value> extra_arg, |
| v8::Local<v8::Object> recv) |
| : recv_(isolate, recv), |
| method_(isolate, method), |
| extra_arg_(isolate, extra_arg) {} |
| |
| // |argc| is equivalent to the "algoArgCount" argument to |
| // CreateAlgorithmFromUnderlyingMethod() in the standard, |
| v8::Local<v8::Promise> Run(ScriptState* script_state, |
| int argc, |
| v8::Local<v8::Value> argv[]) override { |
| DCHECK_GE(argc, 0); |
| DCHECK_LE(argc, 1); |
| auto* isolate = script_state->GetIsolate(); |
| // https://streams.spec.whatwg.org/#create-algorithm-from-underlying-method |
| // 6.c. |
| // i. Let fullArgs be a List consisting of arg followed by the |
| // elements of extraArgs in order. |
| v8::Local<v8::Value> full_argv[2]; |
| if (argc != 0) { |
| full_argv[0] = argv[0]; |
| } |
| full_argv[argc] = extra_arg_.NewLocal(isolate); |
| int full_argc = argc + 1; |
| |
| // ii. Return ! PromiseCall(method, underlyingObject, fullArgs). |
| return PromiseCall(script_state, method_.NewLocal(isolate), |
| recv_.NewLocal(isolate), full_argc, full_argv); |
| } |
| |
| void Trace(Visitor* visitor) override { |
| visitor->Trace(recv_); |
| visitor->Trace(method_); |
| visitor->Trace(extra_arg_); |
| StreamAlgorithm::Trace(visitor); |
| } |
| |
| private: |
| TraceWrapperV8Reference<v8::Object> recv_; |
| TraceWrapperV8Reference<v8::Function> method_; |
| TraceWrapperV8Reference<v8::Value> extra_arg_; |
| }; |
| |
| class JavaScriptStreamStartAlgorithm : public StreamStartAlgorithm { |
| public: |
| JavaScriptStreamStartAlgorithm(v8::Isolate* isolate, |
| v8::Local<v8::Object> recv, |
| const char* method_name_for_error, |
| v8::Local<v8::Value> controller) |
| : recv_(isolate, recv), |
| method_name_for_error_(method_name_for_error), |
| controller_(isolate, controller) {} |
| |
| v8::MaybeLocal<v8::Promise> Run(ScriptState* script_state, |
| ExceptionState& exception_state) override { |
| auto* isolate = script_state->GetIsolate(); |
| // https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller-from-underlying-sink |
| // 3. Let startAlgorithm be the following steps: |
| // a. Return ? InvokeOrNoop(underlyingSink, "start", « controller »). |
| auto value_maybe = CallOrNoop1( |
| script_state, recv_.NewLocal(isolate), "start", method_name_for_error_, |
| controller_.NewLocal(isolate), exception_state); |
| if (exception_state.HadException()) { |
| return v8::MaybeLocal<v8::Promise>(); |
| } |
| v8::Local<v8::Value> value; |
| if (!value_maybe.ToLocal(&value)) { |
| exception_state.ThrowTypeError("internal error"); |
| return v8::MaybeLocal<v8::Promise>(); |
| } |
| return PromiseResolve(script_state, value); |
| } |
| |
| void Trace(Visitor* visitor) override { |
| visitor->Trace(recv_); |
| visitor->Trace(controller_); |
| StreamStartAlgorithm::Trace(visitor); |
| } |
| |
| private: |
| TraceWrapperV8Reference<v8::Object> recv_; |
| const char* const method_name_for_error_; |
| TraceWrapperV8Reference<v8::Value> controller_; |
| }; |
| |
| } // namespace |
| |
| // TODO(ricea): For optimal performance, method_name should be cached as an |
| // atomic v8::String. It's not clear who should own the cache. |
| CORE_EXPORT StreamAlgorithm* CreateAlgorithmFromUnderlyingMethod( |
| ScriptState* script_state, |
| v8::Local<v8::Object> underlying_object, |
| const char* method_name, |
| const char* method_name_for_error, |
| v8::MaybeLocal<v8::Value> extra_arg, |
| ExceptionState& exception_state) { |
| // https://streams.spec.whatwg.org/#create-algorithm-from-underlying-method |
| // 5. Let method be ? GetV(underlyingObject, methodName). |
| // 6. If method is not undefined, |
| // a. If ! IsCallable(method) is false, throw a TypeError exception. |
| v8::MaybeLocal<v8::Value> method_maybe = |
| ResolveMethod(script_state, underlying_object, method_name, |
| method_name_for_error, exception_state); |
| v8::Local<v8::Value> method; |
| if (!method_maybe.ToLocal(&method)) { |
| DCHECK(exception_state.HadException()); |
| return nullptr; |
| } |
| |
| if (method->IsUndefined()) { |
| // 7. Return an algorithm which returns a promise resolved with undefined. |
| return MakeGarbageCollected<TrivialStreamAlgorithm>(); |
| } |
| DCHECK(method->IsFunction()); |
| |
| auto* isolate = script_state->GetIsolate(); |
| |
| // The standard switches on the number of arguments to be passed to the |
| // algorithm, but this implementation doesn't care about that. Instead we |
| // switch on whether or not there is an extraArg, as that decides whether or |
| // not we need to reconstruct the argument list at runtime. |
| v8::Local<v8::Value> extra_arg_local; |
| if (!extra_arg.ToLocal(&extra_arg_local)) { |
| return MakeGarbageCollected<JavaScriptStreamAlgorithmWithoutExtraArg>( |
| isolate, method.As<v8::Function>(), underlying_object); |
| } |
| |
| return MakeGarbageCollected<JavaScriptStreamAlgorithmWithExtraArg>( |
| isolate, method.As<v8::Function>(), extra_arg_local, underlying_object); |
| } |
| |
| CORE_EXPORT StreamStartAlgorithm* CreateStartAlgorithm( |
| ScriptState* script_state, |
| v8::Local<v8::Object> underlying_object, |
| const char* method_name_for_error, |
| v8::Local<v8::Value> controller) { |
| return MakeGarbageCollected<JavaScriptStreamStartAlgorithm>( |
| script_state->GetIsolate(), underlying_object, method_name_for_error, |
| controller); |
| } |
| |
| CORE_EXPORT v8::MaybeLocal<v8::Value> CallOrNoop1( |
| ScriptState* script_state, |
| v8::Local<v8::Object> object, |
| const char* method_name, |
| const char* name_for_error, |
| v8::Local<v8::Value> arg0, |
| ExceptionState& exception_state) { |
| // https://streams.spec.whatwg.org/#invoke-or-noop |
| // 4. Let method be ? GetV(O, P). |
| v8::MaybeLocal<v8::Value> method_maybe = ResolveMethod( |
| script_state, object, method_name, name_for_error, exception_state); |
| v8::Local<v8::Value> method; |
| if (!method_maybe.ToLocal(&method)) { |
| DCHECK(exception_state.HadException()); |
| return v8::MaybeLocal<v8::Value>(); |
| } |
| |
| // 5. If method is undefined, return undefined. |
| if (method->IsUndefined()) { |
| return v8::Undefined(script_state->GetIsolate()); |
| } |
| DCHECK(method->IsFunction()); |
| |
| // 6. Return ? Call(method, O, args). |
| return method.As<v8::Function>()->Call(script_state->GetContext(), object, 1, |
| &arg0); |
| } |
| |
| CORE_EXPORT v8::Local<v8::Promise> PromiseCall(ScriptState* script_state, |
| v8::Local<v8::Function> method, |
| v8::Local<v8::Object> recv, |
| int argc, |
| v8::Local<v8::Value> argv[]) { |
| DCHECK_GE(argc, 0); |
| v8::TryCatch trycatch(script_state->GetIsolate()); |
| |
| // https://streams.spec.whatwg.org/#promise-call |
| // 4. Let returnValue be Call(F, V, args). |
| v8::MaybeLocal<v8::Value> result_maybe = |
| method->Call(script_state->GetContext(), recv, argc, argv); |
| |
| v8::Local<v8::Value> result; |
| // 5. If returnValue is an abrupt completion, return a promise rejected with |
| // returnValue.[[Value]]. |
| if (!result_maybe.ToLocal(&result)) { |
| return PromiseReject(script_state, trycatch.Exception()); |
| } |
| |
| // 6. Otherwise, return a promise resolved with returnValue.[[Value]]. |
| return PromiseResolve(script_state, result); |
| } |
| |
| CORE_EXPORT double ValidateAndNormalizeHighWaterMark( |
| double high_water_mark, |
| ExceptionState& exception_state) { |
| // https://streams.spec.whatwg.org/#validate-and-normalize-high-water-mark |
| // 2. If highWaterMark is NaN or highWaterMark < 0, throw a RangeError |
| // exception. |
| if (isnan(high_water_mark) || high_water_mark < 0) { |
| exception_state.ThrowRangeError( |
| "A queuing strategy's highWaterMark property must be a nonnegative, " |
| "non-NaN number"); |
| return 0; |
| } |
| |
| // 3. Return highWaterMark. |
| return high_water_mark; |
| } |
| |
| CORE_EXPORT StrategySizeAlgorithm* MakeSizeAlgorithmFromSizeFunction( |
| ScriptState* script_state, |
| v8::Local<v8::Value> size, |
| ExceptionState& exception_state) { |
| // 1. If size is undefined, return an algorithm that returns 1. |
| if (size->IsUndefined()) { |
| return MakeGarbageCollected<DefaultSizeAlgorithm>(); |
| } |
| |
| // 2. If ! IsCallable(size) is false, throw a TypeError exception. |
| if (!size->IsFunction()) { |
| exception_state.ThrowTypeError( |
| "A queuing strategy's size property must be a function"); |
| return nullptr; |
| } |
| |
| // 3. Return an algorithm that performs the following steps, taking a chunk |
| // argument: |
| // a. Return ? Call(size, undefined, « chunk »). |
| return MakeGarbageCollected<JavaScriptSizeAlgorithm>( |
| script_state->GetIsolate(), size.As<v8::Function>()); |
| } |
| |
| // PromiseResolve implements Promise.resolve(_x_) from the ECMASCRIPT standard, |
| // https://tc39.github.io/ecma262/#sec-promise.resolve, except that the |
| // Get(_x_, "constructor") step is skipped. |
| CORE_EXPORT v8::Local<v8::Promise> PromiseResolve(ScriptState* script_state, |
| v8::Local<v8::Value> value) { |
| if (value->IsPromise()) { |
| return value.As<v8::Promise>(); |
| } |
| auto context = script_state->GetContext(); |
| v8::TryCatch trycatch(script_state->GetIsolate()); |
| // TODO(ricea): Can this fail for reasons other than memory exhaustion? Can we |
| // recover if it does? |
| auto resolver = v8::Promise::Resolver::New(context).ToLocalChecked(); |
| if (resolver->Resolve(context, value).IsNothing()) { |
| // TODO(ricea): Is this actually reachable? |
| return PromiseReject(script_state, trycatch.Exception()); |
| } |
| return resolver->GetPromise(); |
| } |
| |
| CORE_EXPORT v8::Local<v8::Promise> PromiseResolveWithUndefined( |
| ScriptState* script_state) { |
| return PromiseResolve(script_state, |
| v8::Undefined(script_state->GetIsolate())); |
| } |
| |
| CORE_EXPORT v8::Local<v8::Promise> PromiseReject(ScriptState* script_state, |
| v8::Local<v8::Value> value) { |
| return PromiseRejectInternal(script_state, value, 0); |
| } |
| |
| } // namespace blink |