#include "extensions/renderer/native_extension_bindings_system.h"
#include <string_view>
#include <utility>
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/typed_macros.h"
#include "base/tracing/protos/chrome_track_event.pbzero.h"
#include "components/crx_file/id_util.h"
#include "content/public/common/content_switches.h"
#include "content/public/renderer/render_thread.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension_api.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/features/feature_provider.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/content_capabilities_handler.h"
#include "extensions/common/manifest_handlers/externally_connectable.h"
#include "extensions/common/mojom/api_permission_id.mojom.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "extensions/common/mojom/event_dispatcher.mojom.h"
#include "extensions/common/mojom/frame.mojom.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/switches.h"
#include "extensions/common/utils/extension_utils.h"
#include "extensions/renderer/api_activity_logger.h"
#include "extensions/renderer/bindings/api_binding_bridge.h"
#include "extensions/renderer/bindings/api_binding_hooks.h"
#include "extensions/renderer/bindings/api_binding_js_util.h"
#include "extensions/renderer/bindings/api_binding_util.h"
#include "extensions/renderer/console.h"
#include "extensions/renderer/extension_frame_helper.h"
#include "extensions/renderer/extension_interaction_provider.h"
#include "extensions/renderer/extension_js_runner.h"
#include "extensions/renderer/get_script_context.h"
#include "extensions/renderer/ipc_message_sender.h"
#include "extensions/renderer/module_system.h"
#include "extensions/renderer/renderer_extension_registry.h"
#include "extensions/renderer/renderer_frame_context_data.h"
#include "extensions/renderer/script_context.h"
#include "extensions/renderer/script_context_set_iterable.h"
#include "extensions/renderer/trace_util.h"
#include "extensions/renderer/worker_thread_util.h"
#include "gin/converter.h"
#include "gin/data_object_builder.h"
#include "gin/handle.h"
#include "gin/per_context_data.h"
#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
#include "third_party/blink/public/platform/web_runtime_features.h"
#include "third_party/blink/public/web/web_document.h"
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/public/web/web_origin_trials.h"
#include "v8/include/cppgc/allocation.h"
#include "v8/include/v8-context.h"
#include "v8/include/v8-cppgc.h"
#include "v8/include/v8-isolate.h"
#include "v8/include/v8-object.h"
#include "v8/include/v8-primitive.h"
#include "v8/include/v8-template.h"
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
#include "arkweb/chromium_ext/extensions/renderer/native_extension_bindings_system_for_include.cc"
#endif
using perfetto::protos::pbzero::ChromeTrackEvent;
namespace extensions {
namespace {
constexpr char kBindingsSystemPerContextKey[] = "extension_bindings_system";
bool IsPrefixedAPI(std::string_view api, std::string_view root_api) {
CHECK_GT(api, root_api);
return base::StartsWith(api, root_api, base::CompareCase::SENSITIVE) &&
api[root_api.size()] == '.';
}
std::string_view GetFirstDifferentAPIName(std::string_view api_name,
std::string_view reference) {
std::string_view::size_type dot =
api_name.find('.', reference.empty() ? 0 : reference.size() + 1);
if (dot == std::string_view::npos) {
return api_name;
}
return api_name.substr(0, dot);
}
struct BindingsSystemPerContextData : public base::SupportsUserData::Data {
BindingsSystemPerContextData(
base::WeakPtr<NativeExtensionBindingsSystem> bindings_system)
: bindings_system(bindings_system) {}
~BindingsSystemPerContextData() override {}
v8::Global<v8::Object> api_object;
v8::Global<v8::Object> internal_apis;
base::WeakPtr<NativeExtensionBindingsSystem> bindings_system;
};
v8::Local<v8::Object> GetOrCreateGlobalObjectProperty(
v8::Local<v8::Context> context,
const char* object_name) {
v8::Context::Scope context_scope(context);
v8::Local<v8::String> object_string =
gin::StringToSymbol(v8::Isolate::GetCurrent(), object_name);
v8::Local<v8::Value> object_value;
if (!context->Global()->Get(context, object_string).ToLocal(&object_value)) {
return v8::Local<v8::Object>();
}
v8::Local<v8::Object> requested_object;
if (object_value->IsUndefined()) {
requested_object = v8::Object::New(v8::Isolate::GetCurrent());
v8::Maybe<bool> success = context->Global()->CreateDataProperty(
context, object_string, requested_object);
if (!success.IsJust() || !success.FromJust())
return v8::Local<v8::Object>();
} else if (object_value->IsObject()) {
v8::Local<v8::Object> obj = object_value.As<v8::Object>();
if (obj->GetCreationContextChecked() == context)
requested_object = obj;
}
#if BUILDFLAG(ARKWEB_ARKWEB_EXTENSIONS)
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line != nullptr &&
command_line->HasSwitch(::switches::kSetExtensionName)) {
CreateAliasName(
context, requested_object,
command_line->GetSwitchValueASCII(::switches::kSetExtensionName));
}
#endif
return requested_object;
}
BindingsSystemPerContextData* GetBindingsDataFromContext(
v8::Local<v8::Context> context) {
gin::PerContextData* per_context_data = gin::PerContextData::From(context);
if (!per_context_data || !binding::IsContextValid(context))
return nullptr;
auto* data = static_cast<BindingsSystemPerContextData*>(
per_context_data->GetUserData(kBindingsSystemPerContextKey));
CHECK(data);
if (!data->bindings_system) {
NOTREACHED() << "Context outlived bindings system.";
}
return data;
}
void AddConsoleError(v8::Local<v8::Context> context, const std::string& error) {
ScriptContext* script_context = GetScriptContextFromV8Context(context);
console::AddMessage(script_context, blink::mojom::ConsoleMessageLevel::kError,
error);
}
const base::Value::Dict& GetAPISchema(const std::string& api_name) {
const base::Value::Dict* schema =
ExtensionAPI::GetSharedInstance()->GetSchema(api_name);
LOG_IF(FATAL, !schema) << "Unknown API " << api_name;
return *schema;
}
bool IsAPIFeatureAvailable(v8::Local<v8::Context> context,
const std::string& name) {
ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
return script_context->GetAvailability(name).is_available();
}
bool ArePromisesAllowed(v8::Local<v8::Context> context) {
ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
const Extension* extension = script_context->extension();
if (extension && extension->manifest_version() >= 3) {
return true;
}
switch (script_context->context_type()) {
case mojom::ContextType::kWebUi:
case mojom::ContextType::kUntrustedWebUi:
case mojom::ContextType::kWebPage:
return true;
case mojom::ContextType::kUnspecified:
case mojom::ContextType::kPrivilegedWebPage:
case mojom::ContextType::kPrivilegedExtension:
case mojom::ContextType::kOffscreenExtension:
case mojom::ContextType::kUnprivilegedExtension:
case mojom::ContextType::kUserScript:
case mojom::ContextType::kContentScript:
return false;
}
}
v8::Local<v8::Object> CreateRootBinding(v8::Local<v8::Context> context,
ScriptContext* script_context,
const std::string& name,
APIBindingsSystem* bindings_system) {
APIBindingHooks* hooks = nullptr;
v8::Local<v8::Object> binding_object =
bindings_system->CreateAPIInstance(name, context, &hooks);
v8::Isolate* isolate = v8::Isolate::GetCurrent();
auto* bridge = cppgc::MakeGarbageCollected<APIBindingBridge>(
isolate->GetCppHeap()->GetAllocationHandle(), hooks, context,
binding_object, script_context->GetExtensionID(),
script_context->GetContextTypeDescription());
v8::Local<v8::Value> native_api_bridge =
bridge->GetWrapper(isolate).ToLocalChecked();
script_context->module_system()->OnNativeBindingCreated(name,
native_api_bridge);
return binding_object;
}
v8::Local<v8::Object> CreateFullBinding(
v8::Local<v8::Context> context,
ScriptContext* script_context,
APIBindingsSystem* bindings_system,
const FeatureProvider* api_feature_provider,
const std::string& root_name) {
const FeatureMap& features = api_feature_provider->GetAllFeatures();
auto lower = features.lower_bound(root_name);
CHECK(lower != features.end());
v8::Local<v8::Object> root_binding;
if (lower->first == root_name) {
const Feature* feature = lower->second.get();
if (script_context->IsAnyFeatureAvailableToContext(
*feature, CheckAliasStatus::NOT_ALLOWED)) {
const std::string& source_name =
feature->source().empty() ? root_name : feature->source();
root_binding = CreateRootBinding(context, script_context, source_name,
bindings_system);
}
++lower;
}
std::string upper = root_name + static_cast<char>('.' + 1);
std::string_view last_binding_name;
for (auto iter = lower; iter != features.end() && iter->first < upper;
++iter) {
if (iter->second->IsInternal())
continue;
if (IsPrefixedAPI(iter->first, last_binding_name)) {
continue;
}
if (api_feature_provider->GetParent(*iter->second) != nullptr)
continue;
std::string_view binding_name =
GetFirstDifferentAPIName(iter->first, root_name);
v8::Local<v8::Object> nested_binding =
CreateFullBinding(context, script_context, bindings_system,
api_feature_provider, std::string(binding_name));
if (nested_binding.IsEmpty())
continue;
if (root_binding.IsEmpty())
root_binding = v8::Object::New(v8::Isolate::GetCurrent());
last_binding_name = binding_name;
DCHECK_NE(std::string_view::npos, binding_name.rfind('.'));
std::string_view accessor_name =
binding_name.substr(binding_name.rfind('.') + 1);
v8::Local<v8::String> nested_name =
gin::StringToSymbol(v8::Isolate::GetCurrent(), accessor_name);
v8::Maybe<bool> success =
root_binding->CreateDataProperty(context, nested_name, nested_binding);
if (!success.IsJust() || !success.FromJust())
return v8::Local<v8::Object>();
}
return root_binding;
}
std::string GetContextOwner(v8::Local<v8::Context> context) {
ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
const ExtensionId& extension_id = script_context->GetExtensionID();
bool id_is_valid = crx_file::id_util::IdIsValid(extension_id);
CHECK(id_is_valid || script_context->url().is_valid());
return id_is_valid
? extension_id
: url::Origin::Create(script_context->url()).GetURL().spec();
}
bool CanWebpageContextConnectExternally(ScriptContext* context) {
for (const auto& extension :
*RendererExtensionRegistry::Get()->GetMainThreadExtensionSet()) {
ExternallyConnectableInfo* info = static_cast<ExternallyConnectableInfo*>(
extension->GetManifestData(manifest_keys::kExternallyConnectable));
if (info && info->matches.MatchesURL(context->url())) {
return true;
}
}
return false;
}
const std::string_view kWebAvailableFeatures[] = {
"app",
"webstorePrivate",
"management",
};
bool ShouldCollectJSStackTrace(const APIRequestHandler::Request& request) {
static constexpr const char* kApiMethods[] = {
"tabs.create", "tabs.update", "tabs.remove", "tabs.captureVisibleTab",
"cookies.get", "cookies.getAll"};
if (!base::FeatureList::IsEnabled(
extensions_features::kIncludeJSCallStackInExtensionApiRequest)) {
return false;
}
if (!base::Contains(kApiMethods, request.method_name)) {
return false;
}
return true;
}
}
NativeExtensionBindingsSystem::NativeExtensionBindingsSystem(
Delegate* delegate,
std::unique_ptr<IPCMessageSender> ipc_message_sender)
: delegate_(delegate),
ipc_message_sender_(std::move(ipc_message_sender)),
api_system_(
base::BindRepeating(&GetAPISchema),
base::BindRepeating(&IsAPIFeatureAvailable),
base::BindRepeating(&ArePromisesAllowed),
base::BindRepeating(&NativeExtensionBindingsSystem::SendRequest,
base::Unretained(this)),
std::make_unique<ExtensionInteractionProvider>(),
base::BindRepeating(
&NativeExtensionBindingsSystem::OnEventListenerChanged,
base::Unretained(this)),
base::BindRepeating(&GetContextOwner),
base::BindRepeating(&APIActivityLogger::LogAPICall,
ipc_message_sender_.get()),
base::BindRepeating(&AddConsoleError),
APILastError(base::BindRepeating(&GetLastErrorParents),
base::BindRepeating(&AddConsoleError))),
messaging_service_(this) {}
NativeExtensionBindingsSystem::~NativeExtensionBindingsSystem() = default;
void NativeExtensionBindingsSystem::DidCreateScriptContext(
ScriptContext* context) {
v8::Isolate* isolate = context->isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> v8_context = context->v8_context();
JSRunner::SetInstanceForContext(v8_context,
std::make_unique<ExtensionJSRunner>(context));
gin::PerContextData* per_context_data = gin::PerContextData::From(v8_context);
DCHECK(per_context_data);
DCHECK(!per_context_data->GetUserData(kBindingsSystemPerContextKey));
api_system_.DidCreateContext(v8_context);
auto data = std::make_unique<BindingsSystemPerContextData>(
weak_factory_.GetWeakPtr());
per_context_data->SetUserData(kBindingsSystemPerContextKey, std::move(data));
if (get_internal_api_.IsEmpty()) {
get_internal_api_.Set(
isolate, v8::FunctionTemplate::New(
isolate, &NativeExtensionBindingsSystem::GetInternalAPI,
v8::Local<v8::Value>(), v8::Local<v8::Signature>(), 0,
v8::ConstructorBehavior::kThrow));
}
context->module_system()->SetGetInternalAPIHook(
get_internal_api_.Get(isolate));
context->module_system()->SetJSBindingUtilGetter(
base::BindRepeating(&NativeExtensionBindingsSystem::GetJSBindingUtil,
weak_factory_.GetWeakPtr()));
UpdateBindingsForContext(context);
if (context->context_type() == mojom::ContextType::kContentScript) {
SetScriptingParams(context);
}
}
void NativeExtensionBindingsSystem::WillReleaseScriptContext(
ScriptContext* context) {
v8::HandleScope handle_scope(context->isolate());
v8::Local<v8::Context> v8_context = context->v8_context();
api_system_.WillReleaseContext(v8_context);
JSRunner::ClearInstanceForContext(v8_context);
}
void NativeExtensionBindingsSystem::UpdateBindingsForContext(
ScriptContext* context) {
v8::Isolate* isolate = context->isolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> v8_context = context->v8_context();
v8::Local<v8::Object> chrome =
GetOrCreateGlobalObjectProperty(v8_context, "chrome");
if (chrome.IsEmpty()) {
return;
}
bool is_webpage = false;
switch (context->context_type()) {
case mojom::ContextType::kUnspecified:
case mojom::ContextType::kWebPage:
case mojom::ContextType::kPrivilegedWebPage:
is_webpage = true;
break;
case mojom::ContextType::kPrivilegedExtension:
case mojom::ContextType::kOffscreenExtension:
case mojom::ContextType::kUnprivilegedExtension:
case mojom::ContextType::kUserScript:
case mojom::ContextType::kContentScript:
case mojom::ContextType::kWebUi:
case mojom::ContextType::kUntrustedWebUi:
is_webpage = false;
}
std::optional<v8::Local<v8::Object>> browser;
bool browser_namespace_enabled = base::FeatureList::IsEnabled(
extensions_features::kExtensionBrowserNamespaceAlternative);
bool set_accessor_on_browser = false;
if (browser_namespace_enabled) {
if (context->extension() && context->extension()->manifest_version() >= 3) {
set_accessor_on_browser = true;
} else if (is_webpage && CanWebpageContextConnectExternally(context)) {
set_accessor_on_browser = true;
}
}
DCHECK(GetBindingsDataFromContext(v8_context));
auto set_accessor =
[chrome, &browser, isolate, v8_context,
set_accessor_on_browser](std::string_view accessor_name) {
v8::Local<v8::String> api_name =
gin::StringToSymbol(isolate, accessor_name);
v8::Maybe<bool> chrome_success = chrome->SetLazyDataProperty(
v8_context, api_name, &BindingAccessor, api_name);
if (!chrome_success.IsJust() || !chrome_success.FromJust()) {
return false;
}
if (set_accessor_on_browser && accessor_name != "app") {
if (!browser) {
browser = GetOrCreateGlobalObjectProperty(v8_context, "browser");
}
if (browser->IsEmpty()) {
return false;
}
v8::Maybe<bool> browser_success = (*browser)->SetLazyDataProperty(
v8_context, api_name, &BindingAccessor, api_name);
if (!browser_success.IsJust() || !browser_success.FromJust()) {
return false;
}
}
return true;
};
auto set_restricted_accessor = [chrome, &browser, isolate, v8_context,
set_accessor_on_browser](
std::string_view accessor_name) {
v8::Local<v8::String> api_name =
gin::StringToSymbol(isolate, accessor_name);
v8::Maybe<bool> chrome_success = chrome->SetLazyDataProperty(
v8_context, api_name, &ThrowDeveloperModeRestrictedError, api_name);
if (!chrome_success.IsJust() || !chrome_success.FromJust()) {
return false;
}
if (set_accessor_on_browser && accessor_name != "app") {
if (!browser) {
browser = GetOrCreateGlobalObjectProperty(v8_context, "browser");
}
if (browser->IsEmpty()) {
return false;
}
v8::Maybe<bool> browser_success = (*browser)->SetLazyDataProperty(
v8_context, api_name, &ThrowDeveloperModeRestrictedError, api_name);
if (!browser_success.IsJust() || !browser_success.FromJust()) {
return false;
}
}
return true;
};
if (is_webpage) {
bool is_any_feature_available_to_page = false;
for (std::string_view feature_name : kWebAvailableFeatures) {
if (context->GetAvailability(feature_name).is_available()) {
if (feature_name != "app") {
is_any_feature_available_to_page = true;
}
if (!set_accessor(feature_name)) {
LOG(ERROR) << "Failed to create API on Chrome object.";
return;
}
}
}
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kExtensionTestApiOnWebPages) &&
context->GetAvailability("test").is_available()) {
is_any_feature_available_to_page = true;
if (!set_accessor("test")) {
LOG(ERROR) << "Failed to create API on Chrome object.";
}
}
if (CanWebpageContextConnectExternally(context) ||
is_any_feature_available_to_page) {
if (!set_accessor("runtime")) {
LOG(ERROR) << "Failed to create API on Chrome object.";
}
}
UpdateContentCapabilities(context);
return;
}
FeatureCache::FeatureNameVector features =
feature_cache_.GetAvailableFeatures(
context->context_type(), context->extension(), context->url(),
RendererFrameContextData(context->web_frame()));
std::string_view last_accessor;
for (const std::string& feature : features) {
if (IsPrefixedAPI(feature, last_accessor))
continue;
std::string_view accessor_name =
GetFirstDifferentAPIName(feature, std::string_view());
last_accessor = accessor_name;
if (!set_accessor(accessor_name)) {
LOG(ERROR) << "Failed to create API on Chrome object.";
return;
}
}
FeatureCache::FeatureNameVector dev_mode_features =
feature_cache_.GetDeveloperModeRestrictedFeatures(
context->context_type(), context->extension(), context->url(),
RendererFrameContextData(context->web_frame()));
for (const std::string& feature : dev_mode_features) {
std::string_view accessor_name =
GetFirstDifferentAPIName(feature, std::string_view());
DCHECK_EQ(accessor_name, feature);
if (!set_restricted_accessor(accessor_name)) {
LOG(ERROR) << "Failed to create API on Chrome object.";
return;
}
}
}
void NativeExtensionBindingsSystem::DispatchEventInContext(
const std::string& event_name,
const base::Value::List& event_args,
const mojom::EventFilteringInfoPtr& filtering_info,
ScriptContext* context) {
v8::HandleScope handle_scope(context->isolate());
v8::Context::Scope context_scope(context->v8_context());
api_system_.FireEventInContext(event_name, context->v8_context(), event_args,
filtering_info.Clone());
}
bool NativeExtensionBindingsSystem::HasEventListenerInContext(
const std::string& event_name,
ScriptContext* context) {
v8::HandleScope handle_scope(context->isolate());
return api_system_.event_handler()->HasListenerForEvent(
event_name, context->v8_context());
}
void NativeExtensionBindingsSystem::HandleResponse(
int request_id,
bool success,
const base::Value::List& response,
const std::string& error,
mojom::ExtraResponseDataPtr extra_data) {
api_system_.CompleteRequest(
request_id, response,
!success && error.empty() ? "Unknown error." : error,
std::move(extra_data));
}
IPCMessageSender* NativeExtensionBindingsSystem::GetIPCMessageSender() {
return ipc_message_sender_.get();
}
void NativeExtensionBindingsSystem::UpdateBindings(
const ExtensionId& extension_id,
bool permissions_changed,
ScriptContextSetIterable* script_context_set) {
if (permissions_changed) {
if (extension_id.empty()) {
feature_cache_.InvalidateAllExtensions();
} else {
feature_cache_.InvalidateExtension(extension_id);
}
}
script_context_set->ForEach(
GenerateHostIdFromExtensionId(extension_id),
base::BindRepeating(
&NativeExtensionBindingsSystem::UpdateBindingsForContext,
base::Unretained(this)));
}
void NativeExtensionBindingsSystem::OnExtensionRemoved(const ExtensionId& id) {
feature_cache_.InvalidateExtension(id);
}
v8::Local<v8::Object> NativeExtensionBindingsSystem::GetAPIObjectForTesting(
ScriptContext* context,
const std::string& api_name) {
return GetAPIHelper(context->v8_context(),
gin::StringToSymbol(context->isolate(), api_name));
}
void NativeExtensionBindingsSystem::BindingAccessor(
v8::Local<v8::Name> name,
const v8::PropertyCallbackInfo<v8::Value>& info) {
v8::Isolate* isolate = info.GetIsolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = info.HolderV2()->GetCreationContextChecked();
v8::Context::Scope context_scope(context);
v8::Local<v8::String> api_name = info.Data().As<v8::String>();
v8::Local<v8::Object> binding = GetAPIHelper(context, api_name);
if (!binding.IsEmpty())
info.GetReturnValue().Set(binding);
}
void NativeExtensionBindingsSystem::ThrowDeveloperModeRestrictedError(
v8::Local<v8::Name> name,
const v8::PropertyCallbackInfo<v8::Value>& info) {
v8::Isolate* isolate = info.GetIsolate();
isolate->ThrowException(v8::Exception::Error(gin::StringToV8(
isolate,
base::StringPrintf(
"The '%s' API is only available for users in developer mode.",
gin::V8ToString(isolate, info.Data()).c_str()))));
return;
}
v8::Local<v8::Object> NativeExtensionBindingsSystem::GetAPIHelper(
v8::Local<v8::Context> context,
v8::Local<v8::String> api_name) {
BindingsSystemPerContextData* data = GetBindingsDataFromContext(context);
if (!data)
return v8::Local<v8::Object>();
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::Local<v8::Object> apis;
if (data->api_object.IsEmpty()) {
apis = v8::Object::New(isolate);
data->api_object = v8::Global<v8::Object>(isolate, apis);
} else {
apis = data->api_object.Get(isolate);
}
v8::Maybe<bool> has_property = apis->HasRealNamedProperty(context, api_name);
if (!has_property.IsJust())
return v8::Local<v8::Object>();
if (has_property.FromJust()) {
v8::Local<v8::Value> value =
apis->GetRealNamedProperty(context, api_name).ToLocalChecked();
DCHECK(value->IsObject());
return value.As<v8::Object>();
}
ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
std::string api_name_string;
CHECK(
gin::Converter<std::string>::FromV8(isolate, api_name, &api_name_string));
v8::Local<v8::Object> root_binding = CreateFullBinding(
context, script_context, &data->bindings_system->api_system_,
FeatureProvider::GetAPIFeatures(), api_name_string);
if (root_binding.IsEmpty())
return v8::Local<v8::Object>();
v8::Maybe<bool> success =
apis->CreateDataProperty(context, api_name, root_binding);
if (!success.IsJust() || !success.FromJust())
return v8::Local<v8::Object>();
return root_binding;
}
v8::Local<v8::Object> NativeExtensionBindingsSystem::GetLastErrorParents(
v8::Local<v8::Context> context,
v8::Local<v8::Object>* secondary_parent) {
if (secondary_parent &&
IsAPIFeatureAvailable(context, "extension.lastError")) {
*secondary_parent = GetAPIHelper(
context, gin::StringToSymbol(v8::Isolate::GetCurrent(), "extension"));
}
return GetAPIHelper(
context, gin::StringToSymbol(v8::Isolate::GetCurrent(), "runtime"));
}
void NativeExtensionBindingsSystem::GetInternalAPI(
const v8::FunctionCallbackInfo<v8::Value>& info) {
CHECK_EQ(1, info.Length());
CHECK(info[0]->IsString());
v8::Isolate* isolate = info.GetIsolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::String> v8_name = info[0].As<v8::String>();
BindingsSystemPerContextData* data = GetBindingsDataFromContext(context);
CHECK(data);
v8::Local<v8::Object> internal_apis;
if (data->internal_apis.IsEmpty()) {
internal_apis = v8::Object::New(isolate);
data->internal_apis.Reset(isolate, internal_apis);
} else {
internal_apis = data->internal_apis.Get(isolate);
}
v8::Maybe<bool> has_property =
internal_apis->HasOwnProperty(context, v8_name);
if (!has_property.IsJust())
return;
if (has_property.FromJust()) {
info.GetReturnValue().Set(
internal_apis->GetRealNamedProperty(context, v8_name).ToLocalChecked());
return;
}
std::string api_name = gin::V8ToString(isolate, info[0]);
const Feature* feature = FeatureProvider::GetAPIFeature(api_name);
ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
if (!feature || !script_context->IsAnyFeatureAvailableToContext(
*feature, CheckAliasStatus::NOT_ALLOWED)) {
NOTREACHED();
}
CHECK(feature->IsInternal());
v8::Local<v8::Object> api_binding = CreateRootBinding(
context, script_context, api_name, &data->bindings_system->api_system_);
if (api_binding.IsEmpty())
return;
v8::Maybe<bool> success =
internal_apis->CreateDataProperty(context, v8_name, api_binding);
if (!success.IsJust() || !success.FromJust())
return;
info.GetReturnValue().Set(api_binding);
}
void NativeExtensionBindingsSystem::SendRequest(
std::unique_ptr<APIRequestHandler::Request> request,
v8::Local<v8::Context> context) {
ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
CHECK_NE(mojom::ContextType::kUnspecified, script_context->context_type())
<< "Attempting to send a request from an unspecified context type. "
<< "Request: " << request->method_name
<< ", Context: " << script_context->GetDebugString();
TRACE_RENDERER_EXTENSION_EVENT("NativeExtensionBindingsSystem::SendRequest",
script_context->GetExtensionID());
GURL url;
blink::WebLocalFrame* frame = script_context->web_frame();
if (frame && !frame->GetDocument().IsNull())
url = frame->GetDocument().Url();
else
url = script_context->url();
auto params = mojom::RequestParams::New();
params->name = request->method_name;
params->arguments = std::move(request->arguments_list);
params->extension_id = script_context->GetExtensionID();
params->source_url = url;
params->context_type = script_context->context_type();
params->request_id = request->request_id;
params->has_callback = request->has_async_response_handler;
params->user_gesture = request->has_user_gesture;
params->worker_thread_id = kMainThreadId;
params->service_worker_version_id =
blink::mojom::kInvalidServiceWorkerVersionId;
CHECK_NE(mojom::ContextType::kUnspecified, script_context->context_type())
<< script_context->GetDebugString();
if (!params->extension_id.empty() && ShouldCollectJSStackTrace(*request)) {
params->js_callstack = script_context->GetStackTrace(5);
}
ipc_message_sender_->SendRequestIPC(script_context, std::move(params));
}
void NativeExtensionBindingsSystem::OnEventListenerChanged(
const std::string& event_name,
binding::EventListenersChanged change,
const base::Value::Dict* filter,
bool update_lazy_listeners,
v8::Local<v8::Context> context) {
ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
bool is_lazy = update_lazy_listeners &&
(script_context->IsForServiceWorker() ||
ExtensionFrameHelper::IsContextForEventPage(script_context));
switch (change) {
case binding::EventListenersChanged::
kFirstUnfilteredListenerForContextOwnerAdded:
ipc_message_sender_->SendAddUnfilteredEventListenerIPC(script_context,
event_name);
[[fallthrough]];
case binding::EventListenersChanged::
kFirstUnfilteredListenerForContextAdded: {
if (is_lazy) {
ipc_message_sender_->SendAddUnfilteredLazyEventListenerIPC(
script_context, event_name);
}
break;
}
case binding::EventListenersChanged::
kLastUnfilteredListenerForContextOwnerRemoved:
ipc_message_sender_->SendRemoveUnfilteredEventListenerIPC(script_context,
event_name);
[[fallthrough]];
case binding::EventListenersChanged::
kLastUnfilteredListenerForContextRemoved: {
if (is_lazy) {
ipc_message_sender_->SendRemoveUnfilteredLazyEventListenerIPC(
script_context, event_name);
}
break;
}
case binding::EventListenersChanged::
kFirstListenerWithFilterForContextOwnerAdded:
DCHECK(filter);
ipc_message_sender_->SendAddFilteredEventListenerIPC(
script_context, event_name, *filter, is_lazy);
break;
case binding::EventListenersChanged::
kLastListenerWithFilterForContextOwnerRemoved:
DCHECK(filter);
ipc_message_sender_->SendRemoveFilteredEventListenerIPC(
script_context, event_name, *filter, is_lazy);
break;
}
}
void NativeExtensionBindingsSystem::GetJSBindingUtil(
v8::Local<v8::Context> context,
v8::Local<v8::Value>* binding_util_out) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
APIBindingJSUtil* util = cppgc::MakeGarbageCollected<APIBindingJSUtil>(
isolate->GetCppHeap()->GetAllocationHandle(),
api_system_.type_reference_map(), api_system_.request_handler(),
api_system_.event_handler(), api_system_.exception_handler());
*binding_util_out = util->GetWrapper(isolate).ToLocalChecked();
}
void NativeExtensionBindingsSystem::UpdateContentCapabilities(
ScriptContext* context) {
mojom::ContextType context_type = context->context_type();
if (context_type != mojom::ContextType::kWebPage &&
context_type != mojom::ContextType::kPrivilegedWebPage) {
return;
}
DCHECK(!worker_thread_util::IsWorkerThread());
APIPermissionSet permissions;
for (const auto& extension :
*RendererExtensionRegistry::Get()->GetMainThreadExtensionSet()) {
blink::WebLocalFrame* web_frame = context->web_frame();
GURL url = context->url();
if (web_frame && !web_frame->GetSecurityOrigin().IsOpaque()) {
url = ScriptContext::GetEffectiveDocumentURLForContext(web_frame, url,
true);
}
const ContentCapabilitiesInfo& info =
ContentCapabilitiesInfo::Get(extension.get());
if (info.url_patterns.MatchesURL(url)) {
APIPermissionSet new_permissions;
APIPermissionSet::Union(permissions, info.permissions, &new_permissions);
permissions = std::move(new_permissions);
}
}
context->set_content_capabilities(std::move(permissions));
}
void NativeExtensionBindingsSystem::SetScriptingParams(ScriptContext* context) {
if (!IsAPIFeatureAvailable(context->v8_context(), "scripting.globalParams"))
return;
v8::Local<v8::Object> scripting_object =
GetAPIHelper(context->v8_context(),
gin::StringToSymbol(context->isolate(), "scripting"));
if (scripting_object.IsEmpty())
return;
scripting_object
->CreateDataProperty(
context->v8_context(),
gin::StringToSymbol(context->isolate(), "globalParams"),
gin::DataObjectBuilder(context->isolate()).Build())
.Check();
}
}