| // 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. |
| |
| #include "third_party/blink/renderer/modules/file_system_access/file_system_underlying_sink.h" |
| |
| #include "third_party/blink/renderer/bindings/core/v8/array_buffer_or_array_buffer_view_or_blob_or_usv_string.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_value.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_throw_dom_exception.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_write_params.h" |
| #include "third_party/blink/renderer/core/dom/dom_exception.h" |
| #include "third_party/blink/renderer/core/fileapi/blob.h" |
| #include "third_party/blink/renderer/modules/file_system_access/file_system_access_error.h" |
| #include "third_party/blink/renderer/modules/file_system_access/file_system_writable_file_stream.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/blob/blob_data.h" |
| |
| namespace blink { |
| |
| FileSystemUnderlyingSink::FileSystemUnderlyingSink( |
| ExecutionContext* context, |
| mojo::PendingRemote<mojom::blink::FileSystemAccessFileWriter> writer_remote) |
| : writer_remote_(context) { |
| writer_remote_.Bind(std::move(writer_remote), |
| context->GetTaskRunner(TaskType::kMiscPlatformAPI)); |
| DCHECK(writer_remote_.is_bound()); |
| } |
| |
| ScriptPromise FileSystemUnderlyingSink::start( |
| ScriptState* script_state, |
| WritableStreamDefaultController* controller, |
| ExceptionState& exception_state) { |
| return ScriptPromise::CastUndefined(script_state); |
| } |
| |
| ScriptPromise FileSystemUnderlyingSink::write( |
| ScriptState* script_state, |
| ScriptValue chunk, |
| WritableStreamDefaultController* controller, |
| ExceptionState& exception_state) { |
| v8::Local<v8::Value> value = chunk.V8Value(); |
| |
| ArrayBufferOrArrayBufferViewOrBlobOrUSVStringOrWriteParams input; |
| V8ArrayBufferOrArrayBufferViewOrBlobOrUSVStringOrWriteParams::ToImpl( |
| script_state->GetIsolate(), value, input, |
| UnionTypeConversionMode::kNotNullable, exception_state); |
| if (exception_state.HadException()) |
| return ScriptPromise(); |
| |
| if (input.IsNull()) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "Cannot provide null object"); |
| return ScriptPromise(); |
| } |
| |
| if (input.IsWriteParams()) { |
| return HandleParams(script_state, std::move(*input.GetAsWriteParams()), |
| exception_state); |
| } |
| |
| ArrayBufferOrArrayBufferViewOrBlobOrUSVString write_data; |
| V8ArrayBufferOrArrayBufferViewOrBlobOrUSVString::ToImpl( |
| script_state->GetIsolate(), value, write_data, |
| UnionTypeConversionMode::kNotNullable, exception_state); |
| if (exception_state.HadException()) |
| return ScriptPromise(); |
| return WriteData(script_state, offset_, std::move(write_data), |
| exception_state); |
| } |
| |
| ScriptPromise FileSystemUnderlyingSink::close(ScriptState* script_state, |
| ExceptionState& exception_state) { |
| if (!writer_remote_.is_bound() || pending_operation_) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "Object reached an invalid state"); |
| return ScriptPromise(); |
| } |
| pending_operation_ = |
| MakeGarbageCollected<ScriptPromiseResolver>(script_state); |
| ScriptPromise result = pending_operation_->Promise(); |
| writer_remote_->Close(WTF::Bind(&FileSystemUnderlyingSink::CloseComplete, |
| WrapPersistent(this))); |
| |
| return result; |
| } |
| |
| ScriptPromise FileSystemUnderlyingSink::abort(ScriptState* script_state, |
| ScriptValue reason, |
| ExceptionState& exception_state) { |
| // The specification guarantees that this will only be called after all |
| // pending writes have been aborted. Terminating the remote connection |
| // will ensure that the writes are not closed successfully. |
| if (writer_remote_.is_bound()) |
| writer_remote_.reset(); |
| return ScriptPromise::CastUndefined(script_state); |
| } |
| |
| ScriptPromise FileSystemUnderlyingSink::HandleParams( |
| ScriptState* script_state, |
| const WriteParams& params, |
| ExceptionState& exception_state) { |
| if (params.type() == "truncate") { |
| if (!params.hasSizeNonNull()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kSyntaxError, |
| "Invalid params passed. truncate requires a size argument"); |
| return ScriptPromise(); |
| } |
| return Truncate(script_state, params.sizeNonNull(), exception_state); |
| } |
| |
| if (params.type() == "seek") { |
| if (!params.hasPositionNonNull()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kSyntaxError, |
| "Invalid params passed. seek requires a position argument"); |
| return ScriptPromise(); |
| } |
| return Seek(script_state, params.positionNonNull(), exception_state); |
| } |
| |
| if (params.type() == "write") { |
| uint64_t position = |
| params.hasPositionNonNull() ? params.positionNonNull() : offset_; |
| if (!params.hasData()) { |
| exception_state.ThrowDOMException( |
| DOMExceptionCode::kSyntaxError, |
| "Invalid params passed. write requires a data argument"); |
| return ScriptPromise(); |
| } |
| return WriteData(script_state, position, params.data(), exception_state); |
| } |
| |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "Object reached an invalid state"); |
| return ScriptPromise(); |
| } |
| |
| ScriptPromise FileSystemUnderlyingSink::WriteData( |
| ScriptState* script_state, |
| uint64_t position, |
| const ArrayBufferOrArrayBufferViewOrBlobOrUSVString& data, |
| ExceptionState& exception_state) { |
| DCHECK(!data.IsNull()); |
| |
| auto blob_data = std::make_unique<BlobData>(); |
| Blob* blob = nullptr; |
| if (data.IsArrayBuffer()) { |
| DOMArrayBuffer* array_buffer = data.GetAsArrayBuffer(); |
| blob_data->AppendBytes(array_buffer->Data(), array_buffer->ByteLength()); |
| } else if (data.IsArrayBufferView()) { |
| DOMArrayBufferView* array_buffer_view = data.GetAsArrayBufferView().View(); |
| blob_data->AppendBytes(array_buffer_view->BaseAddress(), |
| array_buffer_view->byteLength()); |
| } else if (data.IsBlob()) { |
| blob = data.GetAsBlob(); |
| } else if (data.IsUSVString()) { |
| // Let the developer be explicit about line endings. |
| blob_data->AppendText(data.GetAsUSVString(), |
| /*normalize_line_endings_to_native=*/false); |
| } |
| |
| if (!blob) { |
| uint64_t size = blob_data->length(); |
| blob = MakeGarbageCollected<Blob>( |
| BlobDataHandle::Create(std::move(blob_data), size)); |
| } |
| |
| return WriteBlob(script_state, position, blob, exception_state); |
| } |
| |
| ScriptPromise FileSystemUnderlyingSink::WriteBlob( |
| ScriptState* script_state, |
| uint64_t position, |
| Blob* blob, |
| ExceptionState& exception_state) { |
| if (!writer_remote_.is_bound() || pending_operation_) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "Object reached an invalid state"); |
| return ScriptPromise(); |
| } |
| pending_operation_ = |
| MakeGarbageCollected<ScriptPromiseResolver>(script_state); |
| ScriptPromise result = pending_operation_->Promise(); |
| writer_remote_->Write(position, blob->AsMojoBlob(), |
| WTF::Bind(&FileSystemUnderlyingSink::WriteComplete, |
| WrapPersistent(this))); |
| return result; |
| } |
| |
| ScriptPromise FileSystemUnderlyingSink::Truncate( |
| ScriptState* script_state, |
| uint64_t size, |
| ExceptionState& exception_state) { |
| if (!writer_remote_.is_bound() || pending_operation_) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "Object reached an invalid state"); |
| return ScriptPromise(); |
| } |
| pending_operation_ = |
| MakeGarbageCollected<ScriptPromiseResolver>(script_state); |
| ScriptPromise result = pending_operation_->Promise(); |
| writer_remote_->Truncate( |
| size, WTF::Bind(&FileSystemUnderlyingSink::TruncateComplete, |
| WrapPersistent(this), size)); |
| return result; |
| } |
| |
| ScriptPromise FileSystemUnderlyingSink::Seek(ScriptState* script_state, |
| uint64_t offset, |
| ExceptionState& exception_state) { |
| if (!writer_remote_.is_bound() || pending_operation_) { |
| exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, |
| "Object reached an invalid state"); |
| return ScriptPromise(); |
| } |
| offset_ = offset; |
| return ScriptPromise::CastUndefined(script_state); |
| } |
| |
| void FileSystemUnderlyingSink::WriteComplete( |
| mojom::blink::FileSystemAccessErrorPtr result, |
| uint64_t bytes_written) { |
| DCHECK(pending_operation_); |
| file_system_access_error::ResolveOrReject(pending_operation_, *result); |
| pending_operation_ = nullptr; |
| |
| if (result->status == mojom::blink::FileSystemAccessStatus::kOk) { |
| // Advance offset. |
| offset_ += bytes_written; |
| } |
| } |
| |
| void FileSystemUnderlyingSink::TruncateComplete( |
| uint64_t to_size, |
| mojom::blink::FileSystemAccessErrorPtr result) { |
| DCHECK(pending_operation_); |
| file_system_access_error::ResolveOrReject(pending_operation_, *result); |
| pending_operation_ = nullptr; |
| |
| if (result->status == mojom::blink::FileSystemAccessStatus::kOk) { |
| // Set offset to smallest last set size so that a subsequent write is not |
| // out of bounds. |
| offset_ = to_size < offset_ ? to_size : offset_; |
| } |
| } |
| |
| void FileSystemUnderlyingSink::CloseComplete( |
| mojom::blink::FileSystemAccessErrorPtr result) { |
| DCHECK(pending_operation_); |
| file_system_access_error::ResolveOrReject(pending_operation_, *result); |
| pending_operation_ = nullptr; |
| // We close the mojo pipe because we intend this writable file stream to be |
| // discarded after close. Subsequent operations will fail. |
| writer_remote_.reset(); |
| } |
| |
| void FileSystemUnderlyingSink::Trace(Visitor* visitor) const { |
| ScriptWrappable::Trace(visitor); |
| UnderlyingSinkBase::Trace(visitor); |
| visitor->Trace(writer_remote_); |
| visitor->Trace(pending_operation_); |
| } |
| |
| } // namespace blink |