| // 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; |
| } |
| status_text_ = request->response_headers()->GetStatusText(); |
| response_code_ = request->response_headers()->response_code(); |
| size_t iter = 0; |
| std::string name; |
| std::string value; |
| while ( |
| request->response_headers()->EnumerateHeaderLines(&iter, &name, &value)) { |
| base::ListValue header; |
| header.GetList().emplace_back(name); |
| header.GetList().emplace_back(value); |
| headers_.GetList().push_back(std::move(header)); |
| } |
| 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); |
| auto metadata = |
| cache::Cache::GetInstance()->Metadata(kResourceType, key); |
| if (!cached || !metadata || !metadata->FindKey("options")) { |
| promise_reference->value().Resolve( |
| cache_utils::FromV8Value(isolate, v8::Undefined(isolate))); |
| return; |
| } |
| script::v8c::EntryScope entry_scope(isolate); |
| auto response = cache_utils::CreateResponse( |
| isolate, *cached, *(metadata->FindKey("options"))); |
| if (!response) { |
| promise_reference->value().Reject(); |
| } else { |
| promise_reference->value().Resolve( |
| cache_utils::FromV8Value(isolate, response.value())); |
| } |
| }, |
| environment_settings, |
| cache_utils::GetKey(environment_settings->base_url(), 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->base_url(), |
| 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->base_url(), |
| 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* global_environment = get_global_environment(environment_settings); |
| auto* isolate = global_environment->isolate(); |
| auto context = isolate->GetCurrentContext(); |
| script::v8c::EntryScope entry_scope(isolate); |
| auto v8_response = |
| GetV8Value(response_reference->referenced_value()).As<v8::Object>(); |
| auto body_used = cache_utils::Get(v8_response, "bodyUsed"); |
| if (!body_used || body_used->As<v8::Boolean>()->Value()) { |
| promise_reference->value().Reject(script::kTypeError); |
| return promise; |
| } |
| auto array_buffer_promise = cache_utils::Call(v8_response, "arrayBuffer"); |
| if (!array_buffer_promise) { |
| promise_reference->value().Reject(); |
| return promise; |
| } |
| cache_utils::Then( |
| array_buffer_promise.value(), |
| 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, |
| v8::Local<v8::Promise> array_buffer_promise) |
| -> base::Optional<v8::Local<v8::Promise>> { |
| uint32_t key = |
| cache_utils::GetKey(environment_settings->base_url(), |
| request_reference->referenced_value()); |
| std::string url = |
| cache_utils::GetUrl(environment_settings->base_url(), |
| request_reference->referenced_value()); |
| auto options = cache_utils::ExtractResponseOptions( |
| cache_utils::ToV8Value(response_reference->referenced_value())); |
| if (!options) { |
| promise_reference->value().Reject(); |
| return base::nullopt; |
| } |
| base::DictionaryValue metadata; |
| metadata.SetKey("url", base::Value(url)); |
| metadata.SetKey("options", std::move(options.value())); |
| cache::Cache::GetInstance()->Store( |
| kResourceType, key, |
| cache_utils::ToUint8Vector(array_buffer_promise->Result()), |
| std::move(metadata)); |
| promise_reference->value().Resolve(); |
| return base::nullopt; |
| }, |
| 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->base_url(), |
| 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); |
| std::vector<v8::Local<v8::Value>> requests; |
| auto keys = |
| cache::Cache::GetInstance()->KeysWithMetadata(kResourceType); |
| for (uint32_t key : keys) { |
| auto metadata = |
| cache::Cache::GetInstance()->Metadata(kResourceType, key); |
| if (!metadata) { |
| continue; |
| } |
| auto url = metadata->FindKey("url"); |
| if (!url) { |
| continue; |
| } |
| base::Optional<v8::Local<v8::Value>> request = |
| cache_utils::CreateRequest(isolate, url->GetString()); |
| if (request) { |
| requests.push_back(std::move(request.value())); |
| } |
| } |
| 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; |
| } |
| { |
| base::DictionaryValue metadata; |
| metadata.SetKey("url", base::Value(fetcher->url().spec())); |
| base::DictionaryValue options; |
| options.SetKey("status", base::Value(fetcher->response_code())); |
| options.SetKey("statusText", base::Value(fetcher->status_text())); |
| options.SetKey("headers", std::move(fetcher->headers())); |
| metadata.SetKey("options", std::move(options)); |
| |
| cache::Cache::GetInstance()->Store( |
| kResourceType, key, fetcher->BufferToVector(), std::move(metadata)); |
| 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 |