blob: d3ce8cc7482a9634c9576df359a9d4758fb2c49e [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_utils.h"
#include <algorithm>
#include <utility>
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "cobalt/script/v8c/conversion_helpers.h"
#include "cobalt/script/v8c/native_promise.h"
#include "cobalt/web/environment_settings_helper.h"
#include "starboard/common/murmurhash2.h"
namespace cobalt {
namespace web {
namespace cache_utils {
v8::Local<v8::String> V8String(v8::Isolate* isolate, const std::string& s) {
return v8::String::NewFromUtf8(isolate, s.c_str())
.FromMaybe(v8::String::Empty(isolate));
}
std::string FromV8String(v8::Isolate* isolate, v8::Local<v8::Value> value) {
if (!value->IsString()) {
return "";
}
auto v8_string = value.As<v8::String>();
std::string result;
FromJSValue(isolate, v8_string, script::v8c::kNoConversionFlags, nullptr,
&result);
return result;
}
base::Optional<v8::Local<v8::Value>> Parse(v8::Isolate* isolate,
const std::string& json) {
return Evaluate(isolate, "{ const obj = " + json + "; obj; }");
}
base::Optional<v8::Local<v8::Value>> BaseToV8(v8::Isolate* isolate,
const base::Value& value) {
auto json = std::make_unique<std::string>();
base::JSONWriter::WriteWithOptions(
value, base::JSONWriter::OPTIONS_OMIT_BINARY_VALUES, json.get());
return Parse(isolate, *json);
}
base::Optional<v8::Local<v8::Promise>> OptionalPromise(
base::Optional<v8::Local<v8::Value>> value) {
if (!value || !(*value)->IsPromise()) {
return base::nullopt;
}
return value->As<v8::Promise>();
}
std::string Stringify(v8::Isolate* isolate, v8::Local<v8::Value> value) {
auto global = isolate->GetCurrentContext()->Global();
Set(global, "___tempObject", value);
auto result = Evaluate(isolate, "JSON.stringify(___tempObject);");
Delete(global, "___tempObject");
if (!result) {
return "";
}
return FromV8String(isolate, result.value());
}
base::Optional<base::Value> Deserialize(const std::string& json) {
if (json.empty()) {
return base::nullopt;
}
return base::Value::FromUniquePtrValue(base::JSONReader::Read(json));
}
base::Optional<base::Value> V8ToBase(v8::Isolate* isolate,
v8::Local<v8::Value> value) {
return Deserialize(Stringify(isolate, value));
}
template <typename T>
base::Optional<v8::Local<T>> ToOptional(v8::MaybeLocal<T> value) {
if (value.IsEmpty()) {
return base::nullopt;
}
return value.ToLocalChecked();
}
v8::Isolate* GetIsolate(v8::Local<v8::Value> object) {
if (!object->IsObject()) {
return nullptr;
}
return object.As<v8::Object>()->GetIsolate();
}
base::Optional<v8::Local<v8::Value>> GetInternal(v8::Local<v8::Value> object,
const std::string& path,
bool parent) {
auto* isolate = GetIsolate(object);
if (!isolate) {
return base::nullopt;
}
base::Optional<v8::Local<v8::Value>> curr = object;
auto context = isolate->GetCurrentContext();
auto parts = base::SplitString(path, ".", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
int offset = parent ? -1 : 0;
for (int i = 0; i < parts.size() + offset; i++) {
if (!curr || !curr.value()->IsObject()) {
return base::nullopt;
}
std::string part = parts[i];
if (base::ContainsOnlyChars(part, "0123456789")) {
uint32_t index;
if (!base::StringToUint32(part, &index)) {
return base::nullopt;
}
curr = ToOptional(curr->As<v8::Object>()->Get(context, index));
} else {
curr = ToOptional(
curr->As<v8::Object>()->Get(context, V8String(isolate, part)));
}
}
return curr;
}
base::Optional<v8::Local<v8::Value>> Get(v8::Local<v8::Value> object,
const std::string& path) {
return GetInternal(object, path, /*parent=*/false);
}
const base::Value* Get(const base::Value& value, const std::string& path,
bool parent) {
if (!value.is_dict() && !value.is_list()) {
return nullptr;
}
const base::Value* curr = &value;
auto parts = base::SplitString(path, ".", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
int offset = parent ? -1 : 0;
for (int i = 0; i < parts.size() + offset; i++) {
std::string part = parts[i];
if (curr->is_list()) {
uint32_t index;
if (!base::StringToUint32(part, &index)) {
return nullptr;
}
if (index > curr->GetList().size() - 1) {
return nullptr;
}
curr = &curr->GetList()[index];
} else if (curr->is_dict()) {
curr = curr->FindKey(part);
} else {
return nullptr;
}
}
return curr;
}
template <typename T>
using V8Transform =
std::function<base::Optional<T>(v8::Isolate*, v8::Local<v8::Value>)>;
struct V8Transforms {
static base::Optional<double> ToDouble(v8::Isolate* isolate,
v8::Local<v8::Value> value) {
if (!value->IsNumber()) {
return base::nullopt;
}
return value.As<v8::Number>()->Value();
}
static base::Optional<std::string> ToString(v8::Isolate* isolate,
v8::Local<v8::Value> value) {
if (!value->IsString()) {
return base::nullopt;
}
auto v8_string = value.As<v8::String>();
std::string result;
FromJSValue(isolate, v8_string, script::v8c::kNoConversionFlags, nullptr,
&result);
return std::move(result);
}
}; // V8Transforms
template <typename T>
base::Optional<T> Get(v8::Local<v8::Value> object, const std::string& path,
V8Transform<T> transform) {
auto value = GetInternal(object, path, /*parent=*/false);
if (!value) {
return base::nullopt;
}
return transform(GetIsolate(object), value.value());
}
base::Optional<std::string> GetString(v8::Local<v8::Value> object,
const std::string& path) {
return Get<std::string>(object, path, V8Transforms::ToString);
}
base::Optional<double> GetNumber(v8::Local<v8::Value> object,
const std::string& path) {
return Get<double>(object, path, V8Transforms::ToDouble);
}
bool Set(v8::Local<v8::Object> object, const std::string& key,
v8::Local<v8::Value> value) {
auto* isolate = object->GetIsolate();
auto context = isolate->GetCurrentContext();
auto result = object->Set(context, V8String(isolate, key), value);
return result.FromMaybe(false);
}
bool Delete(v8::Local<v8::Object> object, const std::string& key) {
auto* isolate = object->GetIsolate();
auto context = isolate->GetCurrentContext();
auto result = object->Delete(context, V8String(isolate, key));
return result.FromMaybe(false);
}
double FromNumber(v8::Local<v8::Value> value) {
return value.As<v8::Number>()->Value();
}
v8::Local<v8::Number> ToNumber(v8::Isolate* isolate, double d) {
return v8::Number::New(isolate, d);
}
std::vector<uint8_t> ToUint8Vector(v8::Local<v8::Value> buffer) {
if (!buffer->IsArrayBuffer()) {
return std::vector<uint8_t>();
}
auto array_buffer = buffer.As<v8::ArrayBuffer>();
auto byte_length = array_buffer->ByteLength();
auto uint8_array =
v8::Uint8Array::New(array_buffer, /*byte_offset=*/0, byte_length);
auto vector = std::vector<uint8_t>(byte_length);
uint8_array->CopyContents(vector.data(), byte_length);
return std::move(vector);
}
base::Optional<v8::Local<v8::Value>> Call(
v8::Local<v8::Value> object, const std::string& path,
std::initializer_list<v8::Local<v8::Value>> args) {
if (!object->IsObject()) {
return base::nullopt;
}
v8::Local<v8::Value> result;
auto optional_function = cache_utils::Get(object, path);
if (!optional_function || !optional_function.value()->IsFunction()) {
return base::nullopt;
}
auto context_object =
cache_utils::GetInternal(object, path, /*parent=*/true).value();
auto context =
context_object.As<v8::Object>()->GetIsolate()->GetCurrentContext();
const size_t argc = args.size();
std::vector<v8::Local<v8::Value>> argv = args;
return ToOptional(optional_function->As<v8::Function>()->Call(
context, context_object, argv.size(), argv.data()));
}
base::Optional<v8::Local<v8::Value>> Then(v8::Local<v8::Value> value,
OnFullfilled on_fullfilled) {
if (!value->IsPromise()) {
on_fullfilled.Reset();
return base::nullopt;
}
auto promise = value.As<v8::Promise>();
auto* isolate = promise->GetIsolate();
auto context = isolate->GetCurrentContext();
auto data = v8::Object::New(isolate);
Set(data, "promise", promise);
auto* on_fullfilled_ptr = new OnFullfilled(std::move(on_fullfilled));
Set(data, "onFullfilled", v8::External::New(isolate, on_fullfilled_ptr));
auto resulting_promise = promise->Then(
context,
v8::Function::New(
context,
[](const v8::FunctionCallbackInfo<v8::Value>& info) {
auto promise = Get(info.Data(), "promise")->As<v8::Promise>();
auto on_fullfilled = std::unique_ptr<OnFullfilled>(
static_cast<OnFullfilled*>(Get(info.Data(), "onFullfilled")
->As<v8::External>()
->Value()));
auto optional_resulting_promise =
std::move(*on_fullfilled).Run(promise);
if (!optional_resulting_promise) {
return;
}
info.GetReturnValue().Set(optional_resulting_promise.value());
},
data)
.ToLocalChecked(),
v8::Function::New(
context,
[](const v8::FunctionCallbackInfo<v8::Value>& info) {
auto on_fullfilled = std::unique_ptr<OnFullfilled>(
static_cast<OnFullfilled*>(Get(info.Data(), "onFullfilled")
->As<v8::External>()
->Value()));
on_fullfilled->Reset();
info.GetIsolate()->ThrowException(info[0]);
},
data)
.ToLocalChecked());
if (resulting_promise.IsEmpty()) {
delete on_fullfilled_ptr;
return base::nullopt;
}
return resulting_promise.ToLocalChecked();
}
void Resolve(v8::Local<v8::Promise::Resolver> resolver,
v8::Local<v8::Value> value) {
auto* isolate = resolver->GetIsolate();
script::v8c::EntryScope entry_scope(isolate);
auto context = isolate->GetCurrentContext();
if (value.IsEmpty()) {
value = v8::Undefined(isolate);
}
auto result = resolver->Resolve(context, value);
DCHECK(result.FromJust());
}
void Reject(v8::Local<v8::Promise::Resolver> resolver) {
auto* isolate = resolver->GetIsolate();
script::v8c::EntryScope entry_scope(isolate);
auto context = isolate->GetCurrentContext();
auto result = resolver->Reject(context, v8::Undefined(isolate));
DCHECK(result.FromJust());
}
script::HandlePromiseAny FromResolver(
v8::Local<v8::Promise::Resolver> resolver) {
auto* isolate = resolver->GetIsolate();
return script::HandlePromiseAny(
new script::v8c::V8cUserObjectHolder<
script::v8c::NativePromise<script::Any>>(isolate, resolver));
}
void Trace(v8::Isolate* isolate,
std::initializer_list<v8::Local<v8::Value>> values,
std::vector<v8::TracedGlobal<v8::Value>*>& traced_globals_out,
base::OnceClosure& cleanup_traced) {
auto heap_tracer =
script::v8c::V8cEngine::GetFromIsolate(isolate)->heap_tracer();
std::vector<std::unique_ptr<v8::TracedGlobal<v8::Value>>> traced_globals;
for (auto value : values) {
auto traced_global =
std::make_unique<v8::TracedGlobal<v8::Value>>(isolate, value);
heap_tracer->AddRoot(traced_global.get());
traced_globals_out.push_back(traced_global.get());
traced_globals.push_back(std::move(traced_global));
}
cleanup_traced = base::BindOnce(
[](script::v8c::V8cHeapTracer* heap_tracer,
std::vector<std::unique_ptr<v8::TracedGlobal<v8::Value>>>
traced_globals) {
for (int i = 0; i < traced_globals.size(); i++) {
heap_tracer->RemoveRoot(traced_globals[i].get());
}
},
heap_tracer, std::move(traced_globals));
}
script::Any FromV8Value(v8::Isolate* isolate, v8::Local<v8::Value> value) {
return script::Any(new script::v8c::V8cValueHandleHolder(isolate, value));
}
v8::Local<v8::Value> ToV8Value(const script::Any& any) {
return script::GetV8Value(*any.GetScriptValue());
}
base::Optional<v8::Local<v8::Value>> Evaluate(v8::Isolate* isolate,
const std::string& js_code) {
auto context = isolate->GetCurrentContext();
auto script = v8::Script::Compile(context, V8String(isolate, js_code));
if (script.IsEmpty()) {
return base::nullopt;
}
return ToOptional(script.ToLocalChecked()->Run(context));
}
base::Optional<v8::Local<v8::Value>> CreateInstance(
v8::Isolate* isolate, const std::string& class_name,
std::initializer_list<v8::Local<v8::Value>> args) {
auto constructor = Evaluate(isolate, class_name);
if (!constructor) {
return base::nullopt;
}
auto context = isolate->GetCurrentContext();
std::vector<v8::Local<v8::Value>> argv = args;
return ToOptional(constructor.value().As<v8::Function>()->NewInstance(
context, argv.size(), argv.data()));
}
base::Optional<v8::Local<v8::Value>> CreateRequest(v8::Isolate* isolate,
const std::string& url,
const base::Value& options) {
auto v8_options = v8::Object::New(isolate);
auto mode = options.FindKey("mode");
if (mode) {
Set(v8_options, "mode", V8String(isolate, mode->GetString()));
}
auto headers = options.FindKey("headers");
if (headers) {
auto v8_headers = v8::Object::New(isolate);
for (const auto& header : headers->GetList()) {
const auto& pair = header.GetList();
DCHECK(pair.size() == 2);
auto name = pair[0].GetString();
auto value = pair[1].GetString();
Set(v8_headers, name, V8String(isolate, value));
}
Set(v8_options, "headers", v8_headers);
}
return CreateInstance(isolate, "Request",
{V8String(isolate, url), v8_options});
}
base::Optional<v8::Local<v8::Value>> CreateResponse(
v8::Isolate* isolate, const std::vector<uint8_t>& body,
const base::Value& options) {
auto status = options.FindKey("status");
auto status_text = options.FindKey("statusText");
auto headers = options.FindKey("headers");
if (body.size() == 0 || !status || !status_text || !headers) {
return base::nullopt;
}
auto v8_body = v8::ArrayBuffer::New(isolate, body.size());
memcpy(v8_body->GetBackingStore()->Data(), body.data(), body.size());
auto v8_options = v8::Object::New(isolate);
Set(v8_options, "status", ToNumber(isolate, status->GetDouble()));
Set(v8_options, "statusText", V8String(isolate, status_text->GetString()));
auto v8_headers = v8::Object::New(isolate);
for (const auto& header : headers->GetList()) {
const auto& pair = header.GetList();
DCHECK(pair.size() == 2);
auto name = pair[0].GetString();
auto value = pair[1].GetString();
Set(v8_headers, name, V8String(isolate, value));
}
Set(v8_options, "headers", v8_headers);
return CreateInstance(isolate, "Response", {v8_body, v8_options});
}
base::Optional<base::Value> ExtractResponseOptions(
v8::Local<v8::Value> response) {
if (!response->IsObject()) {
return base::nullopt;
}
auto response_object = response.As<v8::Object>();
auto* isolate = response_object->GetIsolate();
auto context = isolate->GetCurrentContext();
auto global = context->Global();
Set(global, "___tempResponseObject", response_object);
auto result = Evaluate(isolate,
"(() =>"
"___tempResponseObject instanceof Response && {"
"status: ___tempResponseObject.status,"
"statusText: ___tempResponseObject.statusText,"
"headers: Array.from(___tempResponseObject.headers),"
"}"
")()");
Delete(global, "___tempResponseObject");
if (!result) {
return base::nullopt;
}
return V8ToBase(isolate, result.value());
}
uint32_t GetKey(const std::string& s) {
return starboard::MurmurHash2_32(s.c_str(), s.size());
}
uint32_t GetKey(const GURL& base_url,
const script::ValueHandleHolder& request_info) {
return GetKey(GetUrl(base_url, request_info));
}
std::string GetUrl(const GURL& base_url,
const script::ValueHandleHolder& request_info) {
auto v8_value = GetV8Value(request_info);
auto* isolate = GetIsolate(request_info);
std::string url;
if (v8_value->IsString()) {
url = FromV8String(isolate, v8_value);
} else {
// Treat like |Request| and get "url" property.
auto v8_url = Get(v8_value, "url");
if (!v8_url) {
return "";
}
url = FromV8String(isolate, v8_url.value());
}
GURL::Replacements replacements;
replacements.ClearUsername();
replacements.ClearPassword();
replacements.ClearRef();
return base_url.Resolve(url).ReplaceComponents(replacements).spec();
}
} // namespace cache_utils
} // namespace web
} // namespace cobalt