blob: 5a1cc40c23539cebd35ea4ef5d46369f8233551a [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/fileapi/file_reader_loader.h"
#include <limits>
#include <memory>
#include <utility>
#include "base/memory/scoped_refptr.h"
#include "mojo/public/cpp/system/wait.h"
#include "third_party/blink/public/common/blob/blob_utils.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/fileapi/blob.h"
#include "third_party/blink/renderer/core/fileapi/file_reader_loader_client.h"
#include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h"
#include "third_party/blink/renderer/core/loader/threadable_loader.h"
#include "third_party/blink/renderer/core/loader/threadable_loader_client.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/platform/blob/blob_registry.h"
#include "third_party/blink/renderer/platform/blob/blob_url.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_initiator_type_names.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
#include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/base64.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include "v8/include/v8.h"
namespace blink {
// static
std::unique_ptr<FileReaderLoader> FileReaderLoader::Create(
ReadType read_type,
FileReaderLoaderClient* client) {
return std::make_unique<FileReaderLoader>(read_type, client);
}
FileReaderLoader::FileReaderLoader(ReadType read_type,
FileReaderLoaderClient* client)
: read_type_(read_type),
client_(client),
// TODO(hajimehoshi): Pass an appropriate task runner to SimpleWatcher
// constructor.
handle_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC),
binding_(this),
weak_factory_(this) {}
FileReaderLoader::~FileReaderLoader() {
Cleanup();
UnadjustReportedMemoryUsageToV8();
}
void FileReaderLoader::Start(scoped_refptr<BlobDataHandle> blob_data) {
#if DCHECK_IS_ON()
DCHECK(!started_loading_) << "FileReaderLoader can only be used once";
started_loading_ = true;
#endif // DCHECK_IS_ON()
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes =
blink::BlobUtils::GetDataPipeCapacity(blob_data->size());
mojo::ScopedDataPipeProducerHandle producer_handle;
MojoResult rv = CreateDataPipe(&options, &producer_handle, &consumer_handle_);
if (rv != MOJO_RESULT_OK) {
Failed(FileErrorCode::kNotReadableErr, FailureType::kMojoPipeCreation);
return;
}
mojom::blink::BlobReaderClientPtr client_ptr;
binding_.Bind(MakeRequest(&client_ptr));
blob_data->ReadAll(std::move(producer_handle), std::move(client_ptr));
if (IsSyncLoad()) {
// Wait for OnCalculatedSize, which will also synchronously drain the data
// pipe.
binding_.WaitForIncomingMethodCall();
if (received_on_complete_)
return;
if (!received_all_data_) {
Failed(FileErrorCode::kNotReadableErr,
FailureType::kSyncDataNotAllLoaded);
return;
}
// Wait for OnComplete
binding_.WaitForIncomingMethodCall();
if (!received_on_complete_) {
Failed(FileErrorCode::kNotReadableErr,
FailureType::kSyncOnCompleteNotReceived);
}
}
}
void FileReaderLoader::Cancel() {
error_code_ = FileErrorCode::kAbortErr;
Cleanup();
}
DOMArrayBuffer* FileReaderLoader::ArrayBufferResult() {
DCHECK_EQ(read_type_, kReadAsArrayBuffer);
if (array_buffer_result_)
return array_buffer_result_;
// If the loading is not started or an error occurs, return an empty result.
if (!raw_data_ || error_code_ != FileErrorCode::kOK)
return nullptr;
DOMArrayBuffer* result = DOMArrayBuffer::Create(raw_data_->ToArrayBuffer());
if (finished_loading_) {
array_buffer_result_ = result;
AdjustReportedMemoryUsageToV8(
-1 * static_cast<int64_t>(raw_data_->ByteLength()));
raw_data_.reset();
}
return result;
}
String FileReaderLoader::StringResult() {
DCHECK_NE(read_type_, kReadAsArrayBuffer);
DCHECK_NE(read_type_, kReadByClient);
if (!raw_data_ || (error_code_ != FileErrorCode::kOK) ||
is_raw_data_converted_) {
return string_result_;
}
switch (read_type_) {
case kReadAsArrayBuffer:
// No conversion is needed.
return string_result_;
case kReadAsBinaryString:
SetStringResult(raw_data_->ToString());
break;
case kReadAsText:
SetStringResult(ConvertToText());
break;
case kReadAsDataURL:
// Partial data is not supported when reading as data URL.
if (finished_loading_)
SetStringResult(ConvertToDataURL());
break;
default:
NOTREACHED();
}
if (finished_loading_) {
DCHECK(is_raw_data_converted_);
AdjustReportedMemoryUsageToV8(
-1 * static_cast<int64_t>(raw_data_->ByteLength()));
raw_data_.reset();
}
return string_result_;
}
void FileReaderLoader::SetEncoding(const String& encoding) {
if (!encoding.IsEmpty())
encoding_ = WTF::TextEncoding(encoding);
}
void FileReaderLoader::Cleanup() {
handle_watcher_.Cancel();
consumer_handle_.reset();
// If we get any error, we do not need to keep a buffer around.
if (error_code_ != FileErrorCode::kOK) {
raw_data_.reset();
string_result_ = "";
is_raw_data_converted_ = true;
decoder_.reset();
array_buffer_result_ = nullptr;
UnadjustReportedMemoryUsageToV8();
}
}
void FileReaderLoader::Failed(FileErrorCode error_code, FailureType type) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(EnumerationHistogram, failure_histogram,
("Storage.Blob.FileReaderLoader.FailureType",
static_cast<int>(FailureType::kCount)));
// If an error was already reported, don't report this error again.
if (error_code_ != FileErrorCode::kOK)
return;
error_code_ = error_code;
failure_histogram.Count(static_cast<int>(type));
Cleanup();
if (client_)
client_->DidFail(error_code_);
}
void FileReaderLoader::OnStartLoading(uint64_t total_bytes) {
total_bytes_ = total_bytes;
DCHECK(!raw_data_);
if (read_type_ != kReadByClient) {
// Check that we can cast to unsigned since we have to do
// so to call ArrayBuffer's create function.
// FIXME: Support reading more than the current size limit of ArrayBuffer.
if (total_bytes > std::numeric_limits<unsigned>::max()) {
Failed(FileErrorCode::kNotReadableErr, FailureType::kTotalBytesTooLarge);
return;
}
raw_data_ = std::make_unique<ArrayBufferBuilder>(total_bytes);
if (!raw_data_->IsValid()) {
Failed(FileErrorCode::kNotReadableErr,
FailureType::kArrayBufferBuilderCreation);
return;
}
raw_data_->SetVariableCapacity(false);
}
if (client_)
client_->DidStartLoading();
}
void FileReaderLoader::OnReceivedData(const char* data, unsigned data_length) {
DCHECK(data);
// Bail out if we already encountered an error.
if (error_code_ != FileErrorCode::kOK)
return;
if (read_type_ == kReadByClient) {
bytes_loaded_ += data_length;
if (client_)
client_->DidReceiveDataForClient(data, data_length);
return;
}
unsigned bytes_appended = raw_data_->Append(data, data_length);
if (!bytes_appended) {
raw_data_.reset();
bytes_loaded_ = 0;
Failed(FileErrorCode::kNotReadableErr,
FailureType::kArrayBufferBuilderAppend);
return;
}
bytes_loaded_ += bytes_appended;
is_raw_data_converted_ = false;
AdjustReportedMemoryUsageToV8(bytes_appended);
if (client_)
client_->DidReceiveData();
}
void FileReaderLoader::OnFinishLoading() {
if (read_type_ != kReadByClient && raw_data_) {
raw_data_->ShrinkToFit();
is_raw_data_converted_ = false;
}
finished_loading_ = true;
Cleanup();
if (client_)
client_->DidFinishLoading();
}
void FileReaderLoader::OnCalculatedSize(uint64_t total_size,
uint64_t expected_content_size) {
auto weak_this = weak_factory_.GetWeakPtr();
OnStartLoading(expected_content_size);
// OnStartLoading calls out to our client, which could delete |this|, so bail
// out if that happened.
if (!weak_this)
return;
if (expected_content_size == 0) {
received_all_data_ = true;
return;
}
if (IsSyncLoad()) {
OnDataPipeReadable(MOJO_RESULT_OK);
} else {
handle_watcher_.Watch(
consumer_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE,
WTF::BindRepeating(&FileReaderLoader::OnDataPipeReadable,
WTF::Unretained(this)));
}
}
void FileReaderLoader::OnComplete(int32_t status, uint64_t data_length) {
DEFINE_THREAD_SAFE_STATIC_LOCAL(SparseHistogram,
file_reader_loader_read_errors_histogram,
("Storage.Blob.FileReaderLoader.ReadError"));
if (status != net::OK) {
net_error_ = status;
file_reader_loader_read_errors_histogram.Sample(std::max(0, -net_error_));
Failed(status == net::ERR_FILE_NOT_FOUND ? FileErrorCode::kNotFoundErr
: FileErrorCode::kNotReadableErr,
FailureType::kBackendReadError);
return;
}
if (data_length != total_bytes_) {
Failed(FileErrorCode::kNotReadableErr, FailureType::kReadSizesIncorrect);
return;
}
received_on_complete_ = true;
if (received_all_data_)
OnFinishLoading();
}
void FileReaderLoader::OnDataPipeReadable(MojoResult result) {
if (result != MOJO_RESULT_OK) {
if (!received_all_data_) {
Failed(FileErrorCode::kNotReadableErr,
FailureType::kDataPipeNotReadableWithBytesLeft);
}
return;
}
while (true) {
uint32_t num_bytes;
const void* buffer;
MojoResult result = consumer_handle_->BeginReadData(
&buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
if (!IsSyncLoad())
return;
result = mojo::Wait(consumer_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE);
if (result == MOJO_RESULT_OK)
continue;
}
if (result == MOJO_RESULT_FAILED_PRECONDITION) {
// Pipe closed.
if (!received_all_data_) {
Failed(FileErrorCode::kNotReadableErr,
FailureType::kMojoPipeClosedEarly);
}
return;
}
if (result != MOJO_RESULT_OK) {
Failed(FileErrorCode::kNotReadableErr,
FailureType::kMojoPipeUnexpectedReadError);
return;
}
auto weak_this = weak_factory_.GetWeakPtr();
OnReceivedData(static_cast<const char*>(buffer), num_bytes);
// OnReceivedData calls out to our client, which could delete |this|, so
// bail out if that happened.
if (!weak_this)
return;
consumer_handle_->EndReadData(num_bytes);
if (BytesLoaded() >= total_bytes_) {
received_all_data_ = true;
if (received_on_complete_)
OnFinishLoading();
return;
}
}
}
void FileReaderLoader::AdjustReportedMemoryUsageToV8(int64_t usage) {
if (!usage)
return;
memory_usage_reported_to_v8_ += usage;
v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(usage);
DCHECK_GE(memory_usage_reported_to_v8_, 0);
}
void FileReaderLoader::UnadjustReportedMemoryUsageToV8() {
if (!memory_usage_reported_to_v8_)
return;
v8::Isolate::GetCurrent()->AdjustAmountOfExternalAllocatedMemory(
-memory_usage_reported_to_v8_);
memory_usage_reported_to_v8_ = 0;
}
String FileReaderLoader::ConvertToText() {
if (!bytes_loaded_)
return "";
// Decode the data.
// The File API spec says that we should use the supplied encoding if it is
// valid. However, we choose to ignore this requirement in order to be
// consistent with how WebKit decodes the web content: always has the BOM
// override the provided encoding.
// FIXME: consider supporting incremental decoding to improve the perf.
StringBuilder builder;
if (!decoder_) {
decoder_ = TextResourceDecoder::Create(TextResourceDecoderOptions(
TextResourceDecoderOptions::kPlainTextContent,
encoding_.IsValid() ? encoding_ : UTF8Encoding()));
}
builder.Append(decoder_->Decode(static_cast<const char*>(raw_data_->Data()),
raw_data_->ByteLength()));
if (finished_loading_)
builder.Append(decoder_->Flush());
return builder.ToString();
}
String FileReaderLoader::ConvertToDataURL() {
StringBuilder builder;
builder.Append("data:");
if (!bytes_loaded_)
return builder.ToString();
if (data_type_.IsEmpty()) {
// Match Firefox in defaulting to application/octet-stream when the MIME
// type is unknown. See https://crbug.com/48368.
builder.Append("application/octet-stream");
} else {
builder.Append(data_type_);
}
builder.Append(";base64,");
Vector<char> out;
Base64Encode(static_cast<const char*>(raw_data_->Data()),
raw_data_->ByteLength(), out);
out.push_back('\0');
builder.Append(out.data());
return builder.ToString();
}
void FileReaderLoader::SetStringResult(const String& result) {
AdjustReportedMemoryUsageToV8(
-1 * static_cast<int64_t>(string_result_.CharactersSizeInBytes()));
is_raw_data_converted_ = true;
string_result_ = result;
AdjustReportedMemoryUsageToV8(string_result_.CharactersSizeInBytes());
}
} // namespace blink