blob: bbef89f19239f60b10ebd7ab1acff221ac2f8636 [file] [log] [blame]
// Copyright 2022 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/web/cache.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/json/string_escape.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "cobalt/base/source_location.h"
#include "cobalt/cache/cache.h"
#include "cobalt/script/global_environment.h"
#include "cobalt/script/source_code.h"
#include "cobalt/script/v8c/conversion_helpers.h"
#include "cobalt/script/v8c/v8c_user_object_holder.h"
#include "cobalt/script/v8c/v8c_value_handle.h"
#include "cobalt/web/cache_utils.h"
#include "cobalt/web/context.h"
#include "cobalt/web/environment_settings_helper.h"
#include "net/url_request/url_request_context.h"
#include "v8/include/v8.h"
namespace cobalt {
namespace web {
namespace {
const disk_cache::ResourceType kResourceType =
disk_cache::ResourceType::kCacheApi;
} // namespace
Cache::Fetcher::Fetcher(network::NetworkModule* network_module, const GURL& url,
base::OnceCallback<void(bool)> callback)
: network_module_(network_module),
url_(url),
callback_(std::move(callback)),
buffer_(new net::GrowableIOBuffer()) {
network_module_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&Cache::Fetcher::Start, base::Unretained(this)));
}
std::vector<uint8_t> Cache::Fetcher::BufferToVector() const {
auto* buffer_begin = reinterpret_cast<const uint8_t*>(buffer_->data());
return std::vector<uint8_t>(buffer_begin, buffer_begin + buffer_size_);
}
std::string Cache::Fetcher::BufferToString() const {
return std::string(buffer_->data(), buffer_size_);
}
void Cache::Fetcher::Start() {
request_ = network_module_->url_request_context()->CreateRequest(
url_, net::RequestPriority::DEFAULT_PRIORITY, this);
request_->Start();
}
void Cache::Fetcher::Notify(bool success) {
// Need to delete |URLRequest| instance on network thread.
request_.reset();
std::move(callback_).Run(success);
}
void Cache::Fetcher::OnDone(bool success) {
buffer_size_ = buffer_->offset();
buffer_->set_offset(0);
network_module_->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&Cache::Fetcher::Notify, base::Unretained(this), success));
}
void Cache::Fetcher::ReadResponse(net::URLRequest* request) {
int bytes_read = request->Read(buffer_, buffer_->RemainingCapacity());
bool read_running_async = bytes_read == net::ERR_IO_PENDING;
bool response_complete = !read_running_async && bytes_read <= 0;
// If read is still running, the buffer will be written to and then
// |OnReadCompleted()| will be called.
if (read_running_async) {
return;
}
if (response_complete) {
OnDone(/*success=*/bytes_read == net::OK);
return;
}
// Read completed synchronously. Call |OnReadCompleted()| asynchronously to
// avoid blocking IO.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&Cache::Fetcher::OnReadCompleted,
base::Unretained(this), request, bytes_read));
}
void Cache::Fetcher::OnResponseStarted(net::URLRequest* request,
int net_error) {
DCHECK_NE(net::ERR_IO_PENDING, net_error);
request->GetMimeType(&mime_type_);
if (net_error != net::OK) {
OnDone(/*success=*/false);
return;
}
int initial_capacity = request->response_headers()->HasHeader(
net::HttpRequestHeaders::kContentLength)
? request->response_headers()->GetContentLength()
: 64 * 1024;
buffer_->SetCapacity(initial_capacity);
ReadResponse(request);
}
void Cache::Fetcher::OnReadCompleted(net::URLRequest* request, int bytes_read) {
DCHECK_NE(net::ERR_IO_PENDING, bytes_read);
if (bytes_read <= 0) {
OnDone(/*success=*/bytes_read == net::OK);
return;
}
// The offset is how much of the buffer is used.
buffer_->set_offset(buffer_->offset() + bytes_read);
// Grow the buffer, if needed.
if (buffer_->RemainingCapacity() == 0) {
buffer_->SetCapacity(buffer_->capacity() + 16 * 1024);
}
ReadResponse(request);
}
script::HandlePromiseAny Cache::Match(
script::EnvironmentSettings* environment_settings,
const script::ValueHandleHolder& request) {
script::HandlePromiseAny promise =
get_script_value_factory(environment_settings)
->CreateBasicPromise<script::Any>();
auto promise_reference =
std::make_unique<script::ValuePromiseAny::Reference>(this, promise);
auto context = get_context(environment_settings);
context->message_loop()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
[](script::EnvironmentSettings* environment_settings, uint32_t key,
std::unique_ptr<script::ValuePromiseAny::Reference>
promise_reference) {
auto global_environment =
get_global_environment(environment_settings);
auto* isolate = global_environment->isolate();
auto cached =
cache::Cache::GetInstance()->Retrieve(kResourceType, key);
if (!cached) {
promise_reference->value().Resolve(
cache_utils::GetUndefined(environment_settings));
return;
}
script::v8c::EntryScope entry_scope(isolate);
auto response = cache_utils::CreateResponse(environment_settings,
std::move(cached));
if (!response) {
promise_reference->value().Reject();
} else {
promise_reference->value().Resolve(script::Any(response.value()));
}
},
environment_settings,
cache_utils::GetKey(environment_settings, request),
std::move(promise_reference)));
return promise;
}
void Cache::PerformAdd(
script::EnvironmentSettings* environment_settings,
std::unique_ptr<script::ValueHandleHolder::Reference> request_reference,
std::unique_ptr<script::ValuePromiseVoid::Reference> promise_reference) {
auto* global_environment = get_global_environment(environment_settings);
auto* isolate = global_environment->isolate();
script::v8c::EntryScope entry_scope(isolate);
uint32_t key = cache_utils::GetKey(environment_settings,
request_reference->referenced_value());
if (fetchers_.find(key) != fetchers_.end()) {
base::AutoLock auto_lock(*(fetchers_[key]->lock()));
auto* promises = &(fetch_contexts_[key].first);
promises->push_back(std::move(promise_reference));
return;
}
auto promises =
std::vector<std::unique_ptr<script::ValuePromiseVoid::Reference>>();
promises.push_back(std::move(promise_reference));
fetch_contexts_[key] =
std::make_pair(std::move(promises), environment_settings);
auto* context = get_context(environment_settings);
fetchers_[key] = std::make_unique<Cache::Fetcher>(
context->network_module(),
GURL(cache_utils::GetUrl(environment_settings,
request_reference->referenced_value())),
base::BindOnce(&Cache::OnFetchCompleted, base::Unretained(this), key));
}
script::HandlePromiseVoid Cache::Add(
script::EnvironmentSettings* environment_settings,
const script::ValueHandleHolder& request) {
auto request_reference =
std::make_unique<script::ValueHandleHolder::Reference>(this, request);
script::HandlePromiseVoid promise =
get_script_value_factory(environment_settings)
->CreateBasicPromise<void>();
auto promise_reference =
std::make_unique<script::ValuePromiseVoid::Reference>(this, promise);
auto context = get_context(environment_settings);
context->message_loop()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&Cache::PerformAdd, base::Unretained(this),
environment_settings, std::move(request_reference),
std::move(promise_reference)));
return promise;
}
script::HandlePromiseVoid Cache::Put(
script::EnvironmentSettings* environment_settings,
const script::ValueHandleHolder& request,
const script::ValueHandleHolder& response) {
auto request_reference =
std::make_unique<script::ValueHandleHolder::Reference>(this, request);
auto response_reference =
std::make_unique<script::ValueHandleHolder::Reference>(this, response);
script::HandlePromiseVoid promise =
get_script_value_factory(environment_settings)
->CreateBasicPromise<void>();
auto promise_reference =
std::make_unique<script::ValuePromiseVoid::Reference>(this, promise);
auto context = get_context(environment_settings);
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
[](script::EnvironmentSettings* environment_settings,
std::unique_ptr<script::ValueHandleHolder::Reference>
request_reference,
std::unique_ptr<script::ValueHandleHolder::Reference>
response_reference,
std::unique_ptr<script::ValuePromiseVoid::Reference>
promise_reference) {
auto* global_environment =
get_global_environment(environment_settings);
auto* isolate = global_environment->isolate();
script::v8c::EntryScope entry_scope(isolate);
auto context = global_environment->context();
auto maybe_body_used = cache_utils::TryGet(
context, GetV8Value(response_reference->referenced_value()),
"bodyUsed");
if (maybe_body_used.IsEmpty() ||
maybe_body_used.ToLocalChecked().As<v8::Boolean>()->Value()) {
promise_reference->value().Reject(script::kTypeError);
return;
}
auto maybe_text_function = cache_utils::TryGet(
context, GetV8Value(response_reference->referenced_value()),
"text");
if (maybe_text_function.IsEmpty()) {
promise_reference->value().Reject();
return;
}
auto text_function = maybe_text_function.ToLocalChecked();
v8::Local<v8::Value> text_result;
auto response_context =
script::GetIsolate(response_reference->referenced_value())
->GetCurrentContext();
if (text_function.IsEmpty() || !text_function->IsFunction() ||
!(text_function.As<v8::Function>()
->Call(response_context,
GetV8Value(response_reference->referenced_value()),
/*argc=*/0,
/*argv=*/nullptr)
.ToLocal(&text_result))) {
promise_reference->value().Reject();
return;
}
std::string url = cache_utils::GetUrl(
environment_settings, request_reference->referenced_value());
auto data = v8::Object::New(isolate);
cache_utils::Set(context, data, "environment_settings",
v8::External::New(isolate, environment_settings));
cache_utils::Set(
context, data, "promise_reference",
v8::External::New(isolate, promise_reference.release()));
cache_utils::Set(
context, data, "request_reference",
v8::External::New(isolate, request_reference.release()));
auto then_callback =
v8::Function::New(
context,
[](const v8::FunctionCallbackInfo<v8::Value>& info) {
auto* isolate = info.GetIsolate();
auto context = info.GetIsolate()->GetCurrentContext();
auto* environment_settings =
static_cast<script::EnvironmentSettings*>(
cache_utils::Get(context, info.Data(),
"environment_settings")
.As<v8::External>()
->Value());
std::unique_ptr<script::ValueHandleHolder::Reference>
request_reference(
static_cast<script::ValueHandleHolder::Reference*>(
cache_utils::Get(context, info.Data(),
"request_reference")
.As<v8::External>()
->Value()));
std::unique_ptr<script::ValuePromiseVoid::Reference>
promise_reference(
static_cast<script::ValuePromiseVoid::Reference*>(
cache_utils::Get(context, info.Data(),
"promise_reference")
.As<v8::External>()
->Value()));
uint32_t key = cache_utils::GetKey(
environment_settings,
request_reference->referenced_value());
std::string url = cache_utils::GetUrl(
environment_settings,
request_reference->referenced_value());
std::string body;
FromJSValue(info.GetIsolate(), info[0],
script::v8c::kNoConversionFlags, nullptr,
&body);
auto* begin =
reinterpret_cast<const uint8_t*>(body.data());
auto data = std::make_unique<std::vector<uint8_t>>(
begin, begin + body.size());
cache::Cache::GetInstance()->Store(
kResourceType, key, *data, base::Value(url));
promise_reference->value().Resolve();
},
data)
.ToLocalChecked();
if (text_result.As<v8::Promise>()
->Then(context, then_callback)
.IsEmpty()) {
promise_reference->value().Reject();
return;
}
auto catch_callback =
v8::Function::New(
context,
[](const v8::FunctionCallbackInfo<v8::Value>& info) {
auto* isolate = info.GetIsolate();
auto context = info.GetIsolate()->GetCurrentContext();
std::unique_ptr<script::ValuePromiseVoid::Reference>
promise_reference(
static_cast<script::ValuePromiseVoid::Reference*>(
cache_utils::Get(context, info.Data(),
"promise_reference")
.As<v8::External>()
->Value()));
promise_reference->value().Reject();
},
data)
.ToLocalChecked();
if (text_result.As<v8::Promise>()
->Catch(context, catch_callback)
.IsEmpty()) {
promise_reference->value().Reject();
return;
}
// Run |response.text()| promise.
isolate->PerformMicrotaskCheckpoint();
},
environment_settings, std::move(request_reference),
std::move(response_reference), std::move(promise_reference)));
return promise;
}
script::HandlePromiseBool Cache::Delete(
script::EnvironmentSettings* environment_settings,
const script::ValueHandleHolder& request) {
script::HandlePromiseBool promise =
get_script_value_factory(environment_settings)
->CreateBasicPromise<bool>();
auto request_reference =
std::make_unique<script::ValueHandleHolder::Reference>(this, request);
auto promise_reference =
std::make_unique<script::ValuePromiseBool::Reference>(this, promise);
auto context = get_context(environment_settings);
context->message_loop()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
[](script::EnvironmentSettings* environment_settings,
std::unique_ptr<script::ValueHandleHolder::Reference>
request_reference,
std::unique_ptr<script::ValuePromiseBool::Reference>
promise_reference) {
auto* global_environment =
get_global_environment(environment_settings);
auto* isolate = global_environment->isolate();
script::v8c::EntryScope entry_scope(isolate);
promise_reference->value().Resolve(
cache::Cache::GetInstance()->Delete(
kResourceType, cache_utils::GetKey(
environment_settings,
request_reference->referenced_value())));
},
environment_settings, std::move(request_reference),
std::move(promise_reference)));
return promise;
}
script::HandlePromiseAny Cache::Keys(
script::EnvironmentSettings* environment_settings) {
script::HandlePromiseAny promise =
get_script_value_factory(environment_settings)
->CreateBasicPromise<script::Any>();
auto promise_reference =
std::make_unique<script::ValuePromiseAny::Reference>(this, promise);
auto context = get_context(environment_settings);
context->message_loop()->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
[](script::EnvironmentSettings* environment_settings,
std::unique_ptr<script::ValuePromiseAny::Reference>
promise_reference) {
auto* global_environment =
get_global_environment(environment_settings);
auto* isolate = global_environment->isolate();
script::v8c::EntryScope entry_scope(isolate);
auto keys =
cache::Cache::GetInstance()->KeysWithMetadata(kResourceType);
std::vector<v8::Local<v8::Value>> requests;
for (uint8_t key :
cache::Cache::GetInstance()->KeysWithMetadata(kResourceType)) {
std::unique_ptr<base::Value> url =
cache::Cache::GetInstance()->Metadata(kResourceType, key);
if (url && url->is_string()) {
base::Optional<script::Any> request =
cache_utils::CreateRequest(environment_settings,
url->GetString());
if (request) {
requests.push_back(GetV8Value(*(request->GetScriptValue())));
}
}
}
promise_reference->value().Resolve(
script::Any(new script::v8c::V8cValueHandleHolder(
isolate, v8::Array::New(isolate, requests.data(),
requests.size()))));
},
environment_settings, std::move(promise_reference)));
return promise;
}
void Cache::OnFetchCompleted(uint32_t key, bool success) {
auto* environment_settings = fetch_contexts_[key].second;
auto* context = get_context(environment_settings);
context->message_loop()->task_runner()->PostTask(
FROM_HERE, base::BindOnce(&Cache::OnFetchCompletedMainThread,
base::Unretained(this), key, success));
}
void Cache::OnFetchCompletedMainThread(uint32_t key, bool success) {
auto* fetcher = fetchers_[key].get();
auto* promises = &(fetch_contexts_[key].first);
if (!success) {
{
base::AutoLock auto_lock(*fetcher->lock());
while (promises->size() > 0) {
promises->back()->value().Reject();
promises->pop_back();
}
}
fetchers_.erase(key);
fetch_contexts_.erase(key);
return;
}
{
cache::Cache::GetInstance()->Store(kResourceType, key,
fetcher->BufferToVector(),
base::Value(fetcher->url().spec()));
if (fetcher->mime_type() == "text/javascript") {
auto* environment_settings = fetch_contexts_[key].second;
auto* global_environment = get_global_environment(environment_settings);
auto* isolate = global_environment->isolate();
script::v8c::EntryScope entry_scope(isolate);
global_environment->Compile(script::SourceCode::CreateSourceCode(
fetcher->BufferToString(), base::SourceLocation(__FILE__, 1, 1)));
}
base::AutoLock auto_lock(*fetcher->lock());
while (promises->size() > 0) {
promises->back()->value().Resolve();
promises->pop_back();
}
}
fetchers_.erase(key);
fetch_contexts_.erase(key);
}
} // namespace web
} // namespace cobalt