#include "extensions/renderer/bindings/api_last_error.h"
#include <optional>
#include <tuple>
#include "gin/converter.h"
#include "gin/data_object_builder.h"
#include "gin/object_template_builder.h"
#include "gin/public/wrappable_pointer_tags.h"
#include "gin/wrappable.h"
#include "v8/include/cppgc/allocation.h"
#include "v8/include/v8-cppgc.h"
namespace extensions {
namespace {
constexpr char kLastErrorProperty[] = "lastError";
constexpr char kScriptSuppliedValueKey[] = "script_supplied_value";
constexpr char kUncheckedErrorPrefix[] = "Unchecked runtime.lastError: ";
class LastErrorObject final : public gin::Wrappable<LastErrorObject> {
public:
static constexpr gin::WrapperInfo kWrapperInfo = {{gin::kEmbedderNativeGin},
gin::kLastErrorObject};
const gin::WrapperInfo* wrapper_info() const override { return &kWrapperInfo; }
explicit LastErrorObject(const std::string& error) : error_(error) {}
LastErrorObject(const LastErrorObject&) = delete;
LastErrorObject& operator=(const LastErrorObject&) = delete;
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate) final {
DCHECK(isolate);
return gin::Wrappable<LastErrorObject>::GetObjectTemplateBuilder(
isolate)
.SetProperty("message", &LastErrorObject::error);
}
void Reset(const std::string& error) {
error_ = error;
accessed_ = false;
}
const std::string& error() const { return error_; }
bool accessed() const { return accessed_; }
void set_accessed() { accessed_ = true; }
private:
std::string error_;
bool accessed_ = false;
};
void LastErrorGetter(v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Value>& info) {
v8::Isolate* isolate = info.GetIsolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Object> holder = info.HolderV2();
v8::Local<v8::Context> context = holder->GetCreationContextChecked(isolate);
v8::Local<v8::Value> last_error;
v8::Local<v8::Private> last_error_key = v8::Private::ForApi(
isolate, gin::StringToSymbol(isolate, kLastErrorProperty));
if (!holder->GetPrivate(context, last_error_key).ToLocal(&last_error) ||
last_error != info.Data()) {
NOTREACHED();
}
v8::Local<v8::Value> return_value;
v8::Local<v8::Private> script_value_key = v8::Private::ForApi(
isolate, gin::StringToSymbol(isolate, kScriptSuppliedValueKey));
v8::Local<v8::Value> script_value;
if (holder->GetPrivate(context, script_value_key).ToLocal(&script_value) &&
!script_value->IsUndefined()) {
return_value = script_value;
} else {
LastErrorObject* last_error_obj = nullptr;
CHECK(gin::Converter<LastErrorObject*>::FromV8(isolate, last_error,
&last_error_obj));
last_error_obj->set_accessed();
return_value = last_error;
}
info.GetReturnValue().Set(return_value);
}
void LastErrorSetter(v8::Local<v8::Name> property,
v8::Local<v8::Value> value,
const v8::PropertyCallbackInfo<void>& info) {
v8::Isolate* isolate = info.GetIsolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Object> holder = info.HolderV2();
v8::Local<v8::Context> context = holder->GetCreationContextChecked(isolate);
v8::Local<v8::Private> script_value_key = v8::Private::ForApi(
isolate, gin::StringToSymbol(isolate, kScriptSuppliedValueKey));
v8::Maybe<bool> set_private =
holder->SetPrivate(context, script_value_key, value);
if (!set_private.IsJust() || !set_private.FromJust())
NOTREACHED();
}
}
APILastError::APILastError(GetParent get_parent,
binding::AddConsoleError add_console_error)
: get_parent_(std::move(get_parent)),
add_console_error_(std::move(add_console_error)) {}
APILastError::APILastError(APILastError&& other) = default;
APILastError::~APILastError() = default;
void APILastError::SetError(v8::Local<v8::Context> context,
const std::string& error) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
DCHECK(isolate);
v8::HandleScope handle_scope(isolate);
v8::TryCatch try_catch(isolate);
try_catch.SetVerbose(true);
v8::Local<v8::Object> secondary_parent;
v8::Local<v8::Object> parent = get_parent_.Run(context, &secondary_parent);
SetErrorOnPrimaryParent(context, parent, error);
SetErrorOnSecondaryParent(context, secondary_parent, error);
}
void APILastError::ClearError(v8::Local<v8::Context> context,
bool report_if_unchecked) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Object> parent;
v8::Local<v8::Object> secondary_parent;
LastErrorObject* last_error = nullptr;
v8::Local<v8::String> key;
v8::Local<v8::Private> private_key;
{
v8::TryCatch try_catch(isolate);
try_catch.SetVerbose(true);
parent = get_parent_.Run(context, &secondary_parent);
if (parent.IsEmpty())
return;
key = gin::StringToSymbol(isolate, kLastErrorProperty);
private_key = v8::Private::ForApi(isolate, key);
v8::Local<v8::Value> error;
if (!parent->GetPrivate(context, private_key).ToLocal(&error) ||
!gin::Converter<LastErrorObject*>::FromV8(isolate, error,
&last_error)) {
return;
}
}
if (report_if_unchecked && !last_error->accessed())
ReportUncheckedError(context, last_error->error());
v8::TryCatch try_catch(isolate);
try_catch.SetVerbose(true);
v8::Maybe<bool> delete_private = parent->DeletePrivate(context, private_key);
if (!delete_private.IsJust() || !delete_private.FromJust()) {
NOTREACHED();
}
std::ignore = parent->Delete(context, key);
if (!secondary_parent.IsEmpty())
std::ignore = secondary_parent->Delete(context, key);
}
bool APILastError::HasError(v8::Local<v8::Context> context) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::HandleScope handle_scope(isolate);
v8::TryCatch try_catch(isolate);
try_catch.SetVerbose(true);
v8::Local<v8::Object> parent = get_parent_.Run(context, nullptr);
if (parent.IsEmpty())
return false;
v8::Local<v8::Value> error;
v8::Local<v8::Private> key = v8::Private::ForApi(
isolate, gin::StringToSymbol(isolate, kLastErrorProperty));
if (!parent->GetPrivate(context, key).ToLocal(&error))
return false;
LastErrorObject* last_error = nullptr;
return gin::Converter<LastErrorObject*>::FromV8(isolate, error, &last_error);
}
std::optional<std::string> APILastError::GetErrorMessage(
v8::Local<v8::Context> context) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::HandleScope handle_scope(isolate);
v8::TryCatch try_catch(isolate);
try_catch.SetVerbose(true);
v8::Local<v8::Object> parent = get_parent_.Run(context, nullptr);
if (parent.IsEmpty()) {
return std::nullopt;
}
v8::Local<v8::Value> error;
v8::Local<v8::Private> key = v8::Private::ForApi(
isolate, gin::StringToSymbol(isolate, kLastErrorProperty));
if (!parent->GetPrivate(context, key).ToLocal(&error)) {
return std::nullopt;
}
LastErrorObject* last_error = nullptr;
if (gin::Converter<LastErrorObject*>::FromV8(isolate, error, &last_error)) {
return last_error->error();
}
return std::nullopt;
}
void APILastError::ReportUncheckedError(v8::Local<v8::Context> context,
const std::string& error) {
add_console_error_.Run(context, kUncheckedErrorPrefix + error);
}
void APILastError::SetErrorOnPrimaryParent(v8::Local<v8::Context> context,
v8::Local<v8::Object> parent,
const std::string& error) {
if (parent.IsEmpty())
return;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::Local<v8::String> key = gin::StringToSymbol(isolate, kLastErrorProperty);
v8::Local<v8::Value> v8_error;
if (!parent->Get(context, key).ToLocal(&v8_error))
return;
if (!v8_error->IsUndefined()) {
LastErrorObject* last_error = nullptr;
if (!gin::Converter<LastErrorObject*>::FromV8(isolate, v8_error,
&last_error)) {
return;
}
last_error->Reset(error);
} else {
auto* last_error_obj = cppgc::MakeGarbageCollected<LastErrorObject>(
isolate->GetCppHeap()->GetAllocationHandle(), error);
v8::Local<v8::Value> last_error =
last_error_obj->GetWrapper(isolate).ToLocalChecked();
v8::Maybe<bool> set_private = parent->SetPrivate(
context, v8::Private::ForApi(isolate, key), last_error);
if (!set_private.IsJust() || !set_private.FromJust()) {
NOTREACHED();
}
DCHECK(!last_error.IsEmpty());
std::ignore = parent->SetNativeDataProperty(context, key, &LastErrorGetter,
&LastErrorSetter, last_error);
}
}
void APILastError::SetErrorOnSecondaryParent(
v8::Local<v8::Context> context,
v8::Local<v8::Object> secondary_parent,
const std::string& error) {
if (secondary_parent.IsEmpty())
return;
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::Local<v8::String> key = gin::StringToSymbol(isolate, kLastErrorProperty);
std::ignore = secondary_parent->CreateDataProperty(
context, key,
gin::DataObjectBuilder(isolate).Set("message", error).Build());
}
}