| // Copyright 2015 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. |
| |
| #include "third_party/blink/renderer/core/streams/readable_stream_operations.h" |
| |
| #include <utility> |
| |
| #include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_message_port.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_script_runner.h" |
| #include "third_party/blink/renderer/core/messaging/message_port.h" |
| #include "third_party/blink/renderer/core/streams/underlying_source_base.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/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/wtf/assertions.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| base::Optional<bool> BooleanOperationWithRethrow( |
| ScriptState* script_state, |
| ScriptValue value, |
| const char* operation, |
| ExceptionState& exception_state) { |
| DCHECK(!value.IsEmpty()); |
| |
| if (!value.IsObject()) |
| return false; |
| |
| v8::TryCatch block(script_state->GetIsolate()); |
| v8::Local<v8::Value> args[] = {value.V8Value()}; |
| v8::Local<v8::Value> local_value; |
| bool result_bool = false; |
| |
| if (!V8ScriptRunner::CallExtra(script_state, operation, args) |
| .ToLocal(&local_value) || |
| !local_value->BooleanValue(script_state->GetContext()).To(&result_bool)) { |
| DCHECK(block.HasCaught() || |
| script_state->GetIsolate()->IsExecutionTerminating()); |
| exception_state.RethrowV8Exception(block.Exception()); |
| return base::nullopt; |
| } |
| |
| DCHECK(!block.HasCaught()); |
| return result_bool; |
| } |
| |
| // Performs |operation| on |value|, catching any exceptions. This is for use in |
| // DCHECK(). It is unsafe for general use because it ignores errors. Returns |
| // |fallback_value|, which must be chosen so that the DCHECK() passed if an |
| // exception was thrown, so that the behaviour matches a release build. |
| bool BooleanOperationForDCheck(ScriptState* script_state, |
| ScriptValue value, |
| const char* operation, |
| bool fallback_value) { |
| v8::Local<v8::Value> args[] = {value.V8Value()}; |
| v8::Local<v8::Value> result_value; |
| bool result_bool = false; |
| v8::TryCatch block(script_state->GetIsolate()); |
| if (V8ScriptRunner::CallExtra(script_state, operation, args) |
| .ToLocal(&result_value) && |
| result_value->BooleanValue(script_state->GetContext()).To(&result_bool)) { |
| DCHECK(!block.HasCaught()); |
| return result_bool; |
| } |
| DCHECK(block.HasCaught() || |
| script_state->GetIsolate()->IsExecutionTerminating()); |
| return fallback_value; |
| } |
| |
| // Performs IsReadableStreamDefaultReader(value), catching exceptions. Should |
| // only be used in DCHECK(). Returns true on exception. |
| bool IsDefaultReaderForDCheck(ScriptState* script_state, ScriptValue value) { |
| return BooleanOperationForDCheck(script_state, value, |
| "IsReadableStreamDefaultReader", true); |
| } |
| |
| } // namespace |
| |
| ScriptValue ReadableStreamOperations::CreateReadableStream( |
| ScriptState* script_state, |
| UnderlyingSourceBase* underlying_source, |
| ScriptValue strategy) { |
| ScriptState::Scope scope(script_state); |
| |
| v8::Local<v8::Value> js_underlying_source = |
| ToV8(underlying_source, script_state); |
| v8::Local<v8::Value> js_strategy = strategy.V8Value(); |
| v8::Local<v8::Value> args[] = {js_underlying_source, js_strategy}; |
| return ScriptValue( |
| script_state, |
| V8ScriptRunner::CallExtra( |
| script_state, "createReadableStreamWithExternalController", args)); |
| } |
| |
| ScriptValue ReadableStreamOperations::CreateReadableStream( |
| ScriptState* script_state, |
| ScriptValue underlying_source, |
| ScriptValue strategy, |
| ExceptionState& exception_state) { |
| ScriptState::Scope scope(script_state); |
| |
| v8::TryCatch block(script_state->GetIsolate()); |
| v8::Local<v8::Value> args[] = {underlying_source.V8Value(), |
| strategy.V8Value()}; |
| v8::Local<v8::Value> result; |
| |
| if (!V8ScriptRunner::CallExtra(script_state, "createReadableStream", args) |
| .ToLocal(&result)) { |
| DCHECK(block.HasCaught() || |
| script_state->GetIsolate()->IsExecutionTerminating()); |
| exception_state.RethrowV8Exception(block.Exception()); |
| return ScriptValue(); |
| } |
| return ScriptValue(script_state, result); |
| } |
| |
| ScriptValue ReadableStreamOperations::CreateCountQueuingStrategy( |
| ScriptState* script_state, |
| size_t high_water_mark) { |
| ScriptState::Scope scope(script_state); |
| |
| v8::Local<v8::Value> args[] = { |
| v8::Number::New(script_state->GetIsolate(), high_water_mark)}; |
| return ScriptValue( |
| script_state, |
| V8ScriptRunner::CallExtra(script_state, |
| "createBuiltInCountQueuingStrategy", args)); |
| } |
| |
| ScriptValue ReadableStreamOperations::GetReader( |
| ScriptState* script_state, |
| ScriptValue stream, |
| ExceptionState& exception_state) { |
| DCHECK(IsReadableStreamForDCheck(script_state, stream)); |
| |
| v8::TryCatch block(script_state->GetIsolate()); |
| v8::Local<v8::Value> args[] = {stream.V8Value()}; |
| ScriptValue result( |
| script_state, |
| V8ScriptRunner::CallExtra(script_state, |
| "AcquireReadableStreamDefaultReader", args)); |
| if (block.HasCaught()) { |
| exception_state.RethrowV8Exception(block.Exception()); |
| return ScriptValue(); |
| } |
| DCHECK(!result.IsEmpty() || |
| script_state->GetIsolate()->IsExecutionTerminating()); |
| return result; |
| } |
| |
| base::Optional<bool> ReadableStreamOperations::IsReadableStream( |
| ScriptState* script_state, |
| ScriptValue value, |
| ExceptionState& exception_state) { |
| return BooleanOperationWithRethrow(script_state, value, "IsReadableStream", |
| exception_state); |
| } |
| |
| bool ReadableStreamOperations::IsReadableStreamForDCheck( |
| ScriptState* script_state, |
| ScriptValue value) { |
| return BooleanOperationForDCheck(script_state, value, "IsReadableStream", |
| true); |
| } |
| |
| base::Optional<bool> ReadableStreamOperations::IsDisturbed( |
| ScriptState* script_state, |
| ScriptValue stream, |
| ExceptionState& exception_state) { |
| DCHECK(IsReadableStreamForDCheck(script_state, stream)); |
| return BooleanOperationWithRethrow( |
| script_state, stream, "IsReadableStreamDisturbed", exception_state); |
| } |
| |
| bool ReadableStreamOperations::IsDisturbedForDCheck(ScriptState* script_state, |
| ScriptValue stream) { |
| DCHECK(IsReadableStreamForDCheck(script_state, stream)); |
| return BooleanOperationForDCheck(script_state, stream, |
| "IsReadableStreamDisturbed", false); |
| } |
| |
| base::Optional<bool> ReadableStreamOperations::IsLocked( |
| ScriptState* script_state, |
| ScriptValue stream, |
| ExceptionState& exception_state) { |
| DCHECK(IsReadableStreamForDCheck(script_state, stream)); |
| return BooleanOperationWithRethrow(script_state, stream, |
| "IsReadableStreamLocked", exception_state); |
| } |
| |
| bool ReadableStreamOperations::IsLockedForDCheck(ScriptState* script_state, |
| ScriptValue stream) { |
| DCHECK(IsReadableStreamForDCheck(script_state, stream)); |
| return BooleanOperationForDCheck(script_state, stream, |
| "IsReadableStreamLocked", false); |
| } |
| |
| base::Optional<bool> ReadableStreamOperations::IsReadable( |
| ScriptState* script_state, |
| ScriptValue stream, |
| ExceptionState& exception_state) { |
| DCHECK(IsReadableStreamForDCheck(script_state, stream)); |
| return BooleanOperationWithRethrow( |
| script_state, stream, "IsReadableStreamReadable", exception_state); |
| } |
| |
| base::Optional<bool> ReadableStreamOperations::IsClosed( |
| ScriptState* script_state, |
| ScriptValue stream, |
| ExceptionState& exception_state) { |
| DCHECK(IsReadableStreamForDCheck(script_state, stream)); |
| return BooleanOperationWithRethrow(script_state, stream, |
| "IsReadableStreamClosed", exception_state); |
| } |
| |
| base::Optional<bool> ReadableStreamOperations::IsErrored( |
| ScriptState* script_state, |
| ScriptValue stream, |
| ExceptionState& exception_state) { |
| DCHECK(IsReadableStreamForDCheck(script_state, stream)); |
| return BooleanOperationWithRethrow( |
| script_state, stream, "IsReadableStreamErrored", exception_state); |
| } |
| |
| base::Optional<bool> ReadableStreamOperations::IsReadableStreamDefaultReader( |
| ScriptState* script_state, |
| ScriptValue value, |
| ExceptionState& exception_state) { |
| return BooleanOperationWithRethrow( |
| script_state, value, "IsReadableStreamDefaultReader", exception_state); |
| } |
| |
| ScriptPromise ReadableStreamOperations::DefaultReaderRead( |
| ScriptState* script_state, |
| ScriptValue reader) { |
| DCHECK(IsDefaultReaderForDCheck(script_state, reader)); |
| |
| v8::TryCatch block(script_state->GetIsolate()); |
| v8::Local<v8::Value> args[] = {reader.V8Value()}; |
| v8::MaybeLocal<v8::Value> maybe_result = V8ScriptRunner::CallExtra( |
| script_state, "ReadableStreamDefaultReaderRead", args); |
| if (maybe_result.IsEmpty()) { |
| DCHECK(block.HasCaught() || |
| script_state->GetIsolate()->IsExecutionTerminating()); |
| return ScriptPromise::Reject(script_state, block.Exception()); |
| } |
| return ScriptPromise::Cast(script_state, maybe_result.ToLocalChecked()); |
| } |
| |
| ScriptValue ReadableStreamOperations::Tee(ScriptState* script_state, |
| ScriptValue stream, |
| ExceptionState& exception_state) { |
| DCHECK(IsReadableStreamForDCheck(script_state, stream)); |
| DCHECK(!IsLockedForDCheck(script_state, stream)); |
| |
| v8::TryCatch block(script_state->GetIsolate()); |
| v8::Local<v8::Value> args[] = {stream.V8Value()}; |
| v8::Local<v8::Value> result; |
| if (!V8ScriptRunner::CallExtra(script_state, "ReadableStreamTee", args) |
| .ToLocal(&result)) { |
| exception_state.RethrowV8Exception(block.Exception()); |
| return ScriptValue(); |
| } |
| return ScriptValue(script_state, result); |
| } |
| |
| void ReadableStreamOperations::Serialize(ScriptState* script_state, |
| ScriptValue stream, |
| MessagePort* port, |
| ExceptionState& exception_state) { |
| DCHECK(port); |
| DCHECK(IsReadableStreamForDCheck(script_state, stream)); |
| DCHECK(RuntimeEnabledFeatures::TransferableStreamsEnabled()); |
| v8::TryCatch block(script_state->GetIsolate()); |
| v8::Local<v8::Value> port_v8_value = ToV8(port, script_state); |
| DCHECK(!port_v8_value.IsEmpty()); |
| v8::Local<v8::Value> args[] = {stream.V8Value(), port_v8_value}; |
| ScriptValue result( |
| script_state, |
| V8ScriptRunner::CallExtra(script_state, "ReadableStreamSerialize", args)); |
| if (block.HasCaught()) { |
| exception_state.RethrowV8Exception(block.Exception()); |
| return; |
| } |
| return; |
| } |
| |
| ScriptValue ReadableStreamOperations::Deserialize( |
| ScriptState* script_state, |
| MessagePort* port, |
| ExceptionState& exception_state) { |
| DCHECK(port); |
| DCHECK(RuntimeEnabledFeatures::TransferableStreamsEnabled()); |
| auto* isolate = script_state->GetIsolate(); |
| v8::TryCatch block(isolate); |
| v8::Local<v8::Value> port_v8 = ToV8(port, script_state); |
| DCHECK(!port_v8.IsEmpty()); |
| v8::Local<v8::Value> args[] = {port_v8}; |
| ScriptValue result(script_state, |
| V8ScriptRunner::CallExtra( |
| script_state, "ReadableStreamDeserialize", args)); |
| if (block.HasCaught()) { |
| exception_state.RethrowV8Exception(block.Exception()); |
| return ScriptValue(); |
| } |
| if (result.IsEmpty()) { |
| DCHECK(isolate->IsExecutionTerminating()); |
| return ScriptValue(); |
| } |
| DCHECK(IsReadableStreamForDCheck(script_state, result)); |
| return result; |
| } |
| |
| ScriptPromise ReadableStreamOperations::Cancel( |
| ScriptState* script_state, |
| ScriptValue stream, |
| ScriptValue reason, |
| ExceptionState& exception_state) { |
| v8::Local<v8::Value> args[] = {stream.V8Value(), reason.V8Value()}; |
| v8::TryCatch block(script_state->GetIsolate()); |
| v8::Local<v8::Value> result; |
| |
| if (!V8ScriptRunner::CallExtra(script_state, "ReadableStreamCancel", args) |
| .ToLocal(&result)) { |
| DCHECK(block.HasCaught() || |
| script_state->GetIsolate()->IsExecutionTerminating()); |
| exception_state.RethrowV8Exception(block.Exception()); |
| return ScriptPromise(); |
| } |
| return ScriptPromise(script_state, result); |
| } |
| |
| ScriptPromise ReadableStreamOperations::PipeTo( |
| ScriptState* script_state, |
| ScriptValue stream, |
| ScriptValue destination, |
| ScriptValue options, |
| ExceptionState& exception_state) { |
| v8::TryCatch block(script_state->GetIsolate()); |
| v8::Local<v8::Value> result; |
| v8::Isolate* isolate = script_state->GetIsolate(); |
| v8::Local<v8::Boolean> prevent_close; |
| v8::Local<v8::Boolean> prevent_abort; |
| v8::Local<v8::Boolean> prevent_cancel; |
| |
| if (options.IsUndefined()) { |
| // All values default to false. |
| prevent_close = v8::Boolean::New(isolate, false); |
| prevent_abort = v8::Boolean::New(isolate, false); |
| prevent_cancel = v8::Boolean::New(isolate, false); |
| } else { |
| v8::Local<v8::Context> context = script_state->GetContext(); |
| v8::Local<v8::Object> v8_options; |
| if (!options.V8Value()->ToObject(context).ToLocal(&v8_options)) { |
| exception_state.RethrowV8Exception(block.Exception()); |
| return ScriptPromise(); |
| } |
| v8::Local<v8::Value> prevent_close_v, prevent_abort_v, prevent_cancel_v; |
| if (!v8_options->Get(context, V8String(isolate, "preventClose")) |
| .ToLocal(&prevent_close_v)) { |
| exception_state.RethrowV8Exception(block.Exception()); |
| return ScriptPromise(); |
| } |
| if (!v8_options->Get(context, V8String(isolate, "preventAbort")) |
| .ToLocal(&prevent_abort_v)) { |
| exception_state.RethrowV8Exception(block.Exception()); |
| return ScriptPromise(); |
| } |
| if (!v8_options->Get(context, V8String(isolate, "preventCancel")) |
| .ToLocal(&prevent_cancel_v)) { |
| exception_state.RethrowV8Exception(block.Exception()); |
| return ScriptPromise(); |
| } |
| prevent_close = prevent_close_v->ToBoolean(isolate); |
| prevent_abort = prevent_abort_v->ToBoolean(isolate); |
| prevent_cancel = prevent_cancel_v->ToBoolean(isolate); |
| } |
| |
| v8::Local<v8::Value> args[] = {stream.V8Value(), destination.V8Value(), |
| prevent_close, prevent_abort, prevent_cancel}; |
| if (!V8ScriptRunner::CallExtra(script_state, "ReadableStreamPipeTo", args) |
| .ToLocal(&result)) { |
| DCHECK(block.HasCaught() || |
| script_state->GetIsolate()->IsExecutionTerminating()); |
| exception_state.RethrowV8Exception(block.Exception()); |
| return ScriptPromise(); |
| } |
| |
| return ScriptPromise(script_state, result); |
| } |
| |
| } // namespace blink |