blob: 1375c01e3dee1f7ae285e194840a33f2058b0786 [file] [log] [blame]
// Copyright 2018 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/native_file_system/native_file_system_writer.h"
#include <memory>
#include <utility>
#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/v8_blob.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_readable_stream.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/fetch/fetch_data_loader.h"
#include "third_party/blink/renderer/core/fetch/readable_stream_bytes_consumer.h"
#include "third_party/blink/renderer/core/fileapi/blob.h"
#include "third_party/blink/renderer/core/fileapi/file_error.h"
#include "third_party/blink/renderer/core/streams/readable_stream.h"
#include "third_party/blink/renderer/modules/native_file_system/native_file_system_error.h"
#include "third_party/blink/renderer/modules/native_file_system/native_file_system_file_handle.h"
#include "third_party/blink/renderer/platform/blob/blob_data.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
NativeFileSystemWriter::NativeFileSystemWriter(
ExecutionContext* context,
mojo::PendingRemote<mojom::blink::NativeFileSystemFileWriter>
writer_pending_remote)
: ContextLifecycleObserver(context),
writer_remote_(std::move(writer_pending_remote)) {
DCHECK(writer_remote_);
}
ScriptPromise NativeFileSystemWriter::write(
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 = Blob::Create(BlobDataHandle::Create(std::move(blob_data), size));
}
return WriteBlob(script_state, position, blob);
}
ScriptPromise NativeFileSystemWriter::WriteBlob(ScriptState* script_state,
uint64_t position,
Blob* blob) {
if (!writer_remote_ || pending_operation_) {
return ScriptPromise::RejectWithDOMException(
script_state, MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError));
}
pending_operation_ =
MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise result = pending_operation_->Promise();
writer_remote_->Write(
position, blob->AsMojoBlob(),
WTF::Bind(&NativeFileSystemWriter::WriteComplete, WrapPersistent(this)));
return result;
}
class NativeFileSystemWriter::StreamWriterClient
: public GarbageCollected<StreamWriterClient>,
public FetchDataLoader::Client {
USING_GARBAGE_COLLECTED_MIXIN(StreamWriterClient);
public:
explicit StreamWriterClient(NativeFileSystemWriter* writer)
: writer_(writer) {}
void DidFetchDataStartedDataPipe(
mojo::ScopedDataPipeConsumerHandle data_pipe) override {
data_pipe_ = std::move(data_pipe);
}
mojo::ScopedDataPipeConsumerHandle TakeDataPipe() {
DCHECK(data_pipe_);
return std::move(data_pipe_);
}
void DidFetchDataLoadedDataPipe() override {
// WriteComplete could have been called with an error before we reach this
// point, in that case just return.
if (did_complete_)
return;
DCHECK(!did_finish_writing_to_pipe_);
DCHECK(writer_->pending_operation_);
did_finish_writing_to_pipe_ = true;
}
void DidFetchDataLoadFailed() override {
// WriteComplete could have been called with an error before we reach this
// point, in that case just return.
if (did_complete_)
return;
DCHECK(writer_->pending_operation_);
did_complete_ = true;
writer_->pending_operation_->Reject(
file_error::CreateDOMException(base::File::FILE_ERROR_FAILED));
Reset();
}
void Abort() override {
// WriteComplete could have been called with an error before we reach this
// point, in that case just return.
if (did_complete_)
return;
DCHECK(writer_->pending_operation_);
did_complete_ = true;
writer_->pending_operation_->Reject(
file_error::CreateDOMException(base::File::FILE_ERROR_ABORT));
Reset();
}
void WriteComplete(mojom::blink::NativeFileSystemErrorPtr result,
uint64_t bytes_written) {
// Early return if we already completed (with an error) before.
if (did_complete_)
return;
DCHECK(writer_->pending_operation_);
did_complete_ = true;
if (result->status != mojom::blink::NativeFileSystemStatus::kOk) {
native_file_system_error::Reject(writer_->pending_operation_, *result);
} else {
DCHECK(did_finish_writing_to_pipe_);
writer_->pending_operation_->Resolve();
}
Reset();
}
void Trace(Visitor* visitor) override {
Client::Trace(visitor);
visitor->Trace(writer_);
}
private:
void Reset() {
writer_->pending_operation_ = nullptr;
writer_->stream_loader_ = nullptr;
}
Member<NativeFileSystemWriter> writer_;
mojo::ScopedDataPipeConsumerHandle data_pipe_;
bool did_finish_writing_to_pipe_ = false;
bool did_complete_ = false;
};
ScriptPromise NativeFileSystemWriter::WriteStream(
ScriptState* script_state,
uint64_t position,
ReadableStream* stream,
ExceptionState& exception_state) {
if (!writer_remote_ || pending_operation_) {
return ScriptPromise::RejectWithDOMException(
script_state, MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError));
}
DCHECK(!stream_loader_);
auto* consumer = MakeGarbageCollected<ReadableStreamBytesConsumer>(
script_state, stream, exception_state);
if (exception_state.HadException())
return ScriptPromise();
stream_loader_ = FetchDataLoader::CreateLoaderAsDataPipe(
ExecutionContext::From(script_state)
->GetTaskRunner(TaskType::kInternalDefault));
pending_operation_ =
MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise result = pending_operation_->Promise();
auto* client = MakeGarbageCollected<StreamWriterClient>(this);
stream_loader_->Start(consumer, client);
writer_remote_->WriteStream(
position, client->TakeDataPipe(),
WTF::Bind(&StreamWriterClient::WriteComplete, WrapPersistent(client)));
return result;
}
ScriptPromise NativeFileSystemWriter::truncate(ScriptState* script_state,
uint64_t size) {
if (!writer_remote_ || pending_operation_) {
return ScriptPromise::RejectWithDOMException(
script_state, MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError));
}
pending_operation_ =
MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise result = pending_operation_->Promise();
writer_remote_->Truncate(size,
WTF::Bind(&NativeFileSystemWriter::TruncateComplete,
WrapPersistent(this)));
return result;
}
ScriptPromise NativeFileSystemWriter::close(ScriptState* script_state) {
if (!writer_remote_ || pending_operation_) {
return ScriptPromise::RejectWithDOMException(
script_state, MakeGarbageCollected<DOMException>(
DOMExceptionCode::kInvalidStateError));
}
pending_operation_ =
MakeGarbageCollected<ScriptPromiseResolver>(script_state);
ScriptPromise result = pending_operation_->Promise();
writer_remote_->Close(
WTF::Bind(&NativeFileSystemWriter::CloseComplete, WrapPersistent(this)));
return result;
}
void NativeFileSystemWriter::Trace(Visitor* visitor) {
ScriptWrappable::Trace(visitor);
ContextLifecycleObserver::Trace(visitor);
visitor->Trace(file_);
visitor->Trace(pending_operation_);
visitor->Trace(stream_loader_);
}
void NativeFileSystemWriter::WriteComplete(
mojom::blink::NativeFileSystemErrorPtr result,
uint64_t bytes_written) {
DCHECK(pending_operation_);
native_file_system_error::ResolveOrReject(pending_operation_, *result);
pending_operation_ = nullptr;
}
void NativeFileSystemWriter::TruncateComplete(
mojom::blink::NativeFileSystemErrorPtr result) {
DCHECK(pending_operation_);
native_file_system_error::ResolveOrReject(pending_operation_, *result);
pending_operation_ = nullptr;
}
void NativeFileSystemWriter::CloseComplete(
mojom::blink::NativeFileSystemErrorPtr result) {
DCHECK(pending_operation_);
native_file_system_error::ResolveOrReject(pending_operation_, *result);
file_ = nullptr;
pending_operation_ = nullptr;
// We close the mojo pipe because we intend this writer to be discarded after
// close. Subsequent operations will fail.
writer_remote_.reset();
}
void NativeFileSystemWriter::ContextDestroyed(ExecutionContext*) {
writer_remote_.reset();
}
} // namespace blink