123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- // Copyright 2017 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "extensions/renderer/runtime_hooks_delegate.h"
- #include "base/containers/span.h"
- #include "base/strings/string_piece.h"
- #include "base/strings/stringprintf.h"
- #include "content/public/renderer/render_frame.h"
- #include "content/public/renderer/v8_value_converter.h"
- #include "extensions/common/api/messaging/message.h"
- #include "extensions/common/api/messaging/serialization_format.h"
- #include "extensions/common/constants.h"
- #include "extensions/common/extension.h"
- #include "extensions/common/extension_features.h"
- #include "extensions/common/manifest.h"
- #include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
- #include "extensions/renderer/bindings/api_binding_types.h"
- #include "extensions/renderer/bindings/js_runner.h"
- #include "extensions/renderer/extension_frame_helper.h"
- #include "extensions/renderer/get_script_context.h"
- #include "extensions/renderer/message_target.h"
- #include "extensions/renderer/messaging_util.h"
- #include "extensions/renderer/native_renderer_messaging_service.h"
- #include "extensions/renderer/script_context.h"
- #include "gin/converter.h"
- #include "third_party/blink/public/web/web_local_frame.h"
- #include "v8/include/v8-function-callback.h"
- #include "v8/include/v8-function.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"
- namespace extensions {
- namespace {
- using RequestResult = APIBindingHooks::RequestResult;
- // Handler for the extensionId property on chrome.runtime.
- void GetExtensionId(v8::Local<v8::Name> property_name,
- const v8::PropertyCallbackInfo<v8::Value>& info) {
- v8::Isolate* isolate = info.GetIsolate();
- v8::HandleScope handle_scope(isolate);
- v8::Local<v8::Context> context = info.Holder()->GetCreationContextChecked();
- ScriptContext* script_context = GetScriptContextFromV8Context(context);
- // This could potentially be invoked after the script context is removed
- // (unlike the handler calls, which should only be invoked for valid
- // contexts).
- if (script_context && script_context->extension()) {
- info.GetReturnValue().Set(
- gin::StringToSymbol(isolate, script_context->extension()->id()));
- }
- }
- // Handler for the dynamicId property on chrome.runtime.
- void GetDynamicId(v8::Local<v8::Name> property_name,
- const v8::PropertyCallbackInfo<v8::Value>& info) {
- v8::Isolate* isolate = info.GetIsolate();
- v8::HandleScope handle_scope(isolate);
- v8::Local<v8::Context> context = info.Holder()->GetCreationContextChecked();
- ScriptContext* script_context = GetScriptContextFromV8Context(context);
- // This could potentially be invoked after the script context is removed
- // (unlike the handler calls, which should only be invoked for valid
- // contexts).
- if (script_context && script_context->extension()) {
- info.GetReturnValue().Set(
- gin::StringToSymbol(isolate, script_context->extension()->guid()));
- }
- }
- constexpr char kGetManifest[] = "runtime.getManifest";
- constexpr char kGetURL[] = "runtime.getURL";
- constexpr char kConnect[] = "runtime.connect";
- constexpr char kConnectNative[] = "runtime.connectNative";
- constexpr char kSendMessage[] = "runtime.sendMessage";
- constexpr char kSendNativeMessage[] = "runtime.sendNativeMessage";
- constexpr char kGetBackgroundPage[] = "runtime.getBackgroundPage";
- constexpr char kGetPackageDirectoryEntry[] = "runtime.getPackageDirectoryEntry";
- // The custom callback supplied to runtime.getBackgroundPage to find and return
- // the background page to the original callback.
- void GetBackgroundPageCallback(
- const v8::FunctionCallbackInfo<v8::Value>& info) {
- v8::Isolate* isolate = info.GetIsolate();
- v8::HandleScope handle_scope(isolate);
- v8::Local<v8::Context> context = info.Holder()->GetCreationContextChecked();
- // Custom callbacks are called with the arguments of the callback function and
- // the response from the API. Since the custom callback here handles all the
- // logic we should have just received the callback function to call with the
- // result.
- DCHECK_EQ(1, info.Length());
- CHECK(info[0]->IsFunction());
- // The ScriptContext should always be valid, because otherwise the
- // getBackgroundPage() request should have been invalidated (and this should
- // never run).
- ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
- v8::Local<v8::Value> background_page =
- ExtensionFrameHelper::GetV8BackgroundPageMainFrame(
- isolate, script_context->extension()->id());
- v8::Local<v8::Value> args[] = {background_page};
- script_context->SafeCallFunction(info[0].As<v8::Function>(), std::size(args),
- args);
- }
- } // namespace
- RuntimeHooksDelegate::RuntimeHooksDelegate(
- NativeRendererMessagingService* messaging_service)
- : messaging_service_(messaging_service) {}
- RuntimeHooksDelegate::~RuntimeHooksDelegate() {}
- // static
- RequestResult RuntimeHooksDelegate::GetURL(
- ScriptContext* script_context,
- const std::vector<v8::Local<v8::Value>>& arguments) {
- DCHECK_EQ(1u, arguments.size());
- DCHECK(arguments[0]->IsString());
- DCHECK(script_context->extension());
- v8::Isolate* isolate = script_context->isolate();
- std::string path = gin::V8ToString(isolate, arguments[0]);
- const auto* extension = script_context->extension();
- bool use_dynamic_url = false;
- if (base::FeatureList::IsEnabled(
- extensions_features::kExtensionDynamicURLRedirection)) {
- use_dynamic_url =
- WebAccessibleResourcesInfo::ShouldUseDynamicUrl(extension, path);
- }
- std::string id = use_dynamic_url ? extension->guid() : extension->id();
- RequestResult result(RequestResult::HANDLED);
- std::string url = base::StringPrintf(
- "chrome-extension://%s%s%s", id.c_str(),
- !path.empty() && path[0] == '/' ? "" : "/", path.c_str());
- // GURL considers any possible path valid. Since the argument is only appended
- // as part of the path, there should be no way this could conceivably fail.
- DCHECK(GURL(url).is_valid());
- result.return_value = gin::StringToV8(isolate, url);
- return result;
- }
- RequestResult RuntimeHooksDelegate::HandleRequest(
- const std::string& method_name,
- const APISignature* signature,
- v8::Local<v8::Context> context,
- std::vector<v8::Local<v8::Value>>* arguments,
- const APITypeReferenceMap& refs) {
- using Handler = RequestResult (RuntimeHooksDelegate::*)(
- ScriptContext*, const APISignature::V8ParseResult&);
- static const struct {
- Handler handler;
- base::StringPiece method;
- } kHandlers[] = {
- {&RuntimeHooksDelegate::HandleSendMessage, kSendMessage},
- {&RuntimeHooksDelegate::HandleConnect, kConnect},
- {&RuntimeHooksDelegate::HandleGetURL, kGetURL},
- {&RuntimeHooksDelegate::HandleGetManifest, kGetManifest},
- {&RuntimeHooksDelegate::HandleConnectNative, kConnectNative},
- {&RuntimeHooksDelegate::HandleSendNativeMessage, kSendNativeMessage},
- {&RuntimeHooksDelegate::HandleGetBackgroundPage, kGetBackgroundPage},
- {&RuntimeHooksDelegate::HandleGetPackageDirectoryEntryCallback,
- kGetPackageDirectoryEntry},
- };
- ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
- Handler handler = nullptr;
- for (const auto& handler_entry : kHandlers) {
- if (handler_entry.method == method_name) {
- handler = handler_entry.handler;
- break;
- }
- }
- if (!handler)
- return RequestResult(RequestResult::NOT_HANDLED);
- bool should_massage = false;
- bool allow_options = false;
- if (method_name == kSendMessage) {
- should_massage = true;
- allow_options = true;
- } else if (method_name == kSendNativeMessage) {
- should_massage = true;
- }
- if (should_massage) {
- messaging_util::MassageSendMessageArguments(context->GetIsolate(),
- allow_options, arguments);
- }
- APISignature::V8ParseResult parse_result =
- signature->ParseArgumentsToV8(context, *arguments, refs);
- if (!parse_result.succeeded()) {
- RequestResult result(RequestResult::INVALID_INVOCATION);
- result.error = std::move(*parse_result.error);
- return result;
- }
- return (this->*handler)(script_context, parse_result);
- }
- void RuntimeHooksDelegate::InitializeTemplate(
- v8::Isolate* isolate,
- v8::Local<v8::ObjectTemplate> object_template,
- const APITypeReferenceMap& type_refs) {
- object_template->SetAccessor(gin::StringToSymbol(isolate, "id"),
- &GetExtensionId);
- if (base::FeatureList::IsEnabled(
- extensions_features::kExtensionDynamicURLRedirection)) {
- object_template->SetAccessor(gin::StringToSymbol(isolate, "dynamicId"),
- &GetDynamicId);
- }
- }
- RequestResult RuntimeHooksDelegate::HandleGetManifest(
- ScriptContext* script_context,
- const APISignature::V8ParseResult& parse_result) {
- DCHECK_EQ(binding::AsyncResponseType::kNone, parse_result.async_type);
- CHECK(script_context->extension());
- CHECK(script_context->extension()->manifest());
- CHECK(script_context->extension()->manifest()->value());
- RequestResult result(RequestResult::HANDLED);
- result.return_value = content::V8ValueConverter::Create()->ToV8Value(
- *script_context->extension()->manifest()->value(),
- script_context->v8_context());
- return result;
- }
- RequestResult RuntimeHooksDelegate::HandleGetURL(
- ScriptContext* script_context,
- const APISignature::V8ParseResult& parse_result) {
- DCHECK_EQ(binding::AsyncResponseType::kNone, parse_result.async_type);
- return GetURL(script_context, *parse_result.arguments);
- }
- RequestResult RuntimeHooksDelegate::HandleSendMessage(
- ScriptContext* script_context,
- const APISignature::V8ParseResult& parse_result) {
- const std::vector<v8::Local<v8::Value>>& arguments = *parse_result.arguments;
- DCHECK_EQ(4u, arguments.size());
- std::string target_id;
- std::string error;
- if (!messaging_util::GetTargetExtensionId(script_context, arguments[0],
- "runtime.sendMessage", &target_id,
- &error)) {
- RequestResult result(RequestResult::INVALID_INVOCATION);
- result.error = std::move(error);
- return result;
- }
- v8::Local<v8::Context> v8_context = script_context->v8_context();
- v8::Local<v8::Value> v8_message = arguments[1];
- std::unique_ptr<Message> message = messaging_util::MessageFromV8(
- v8_context, v8_message,
- messaging_util::GetSerializationFormat(*script_context), &error);
- if (!message) {
- RequestResult result(RequestResult::INVALID_INVOCATION);
- result.error = std::move(error);
- return result;
- }
- // Note: arguments[2] is the options argument. However, the only available
- // option for sendMessage() is includeTlsChannelId. That option has no effect
- // since M72, but it is still part of the public spec for compatibility and is
- // parsed into |arguments|. See crbug.com/1045232.
- v8::Local<v8::Function> response_callback;
- if (!arguments[3]->IsNull())
- response_callback = arguments[3].As<v8::Function>();
- v8::Local<v8::Promise> promise = messaging_service_->SendOneTimeMessage(
- script_context, MessageTarget::ForExtension(target_id),
- messaging_util::kSendMessageChannel, *message, parse_result.async_type,
- response_callback);
- DCHECK_EQ(parse_result.async_type == binding::AsyncResponseType::kPromise,
- !promise.IsEmpty())
- << "SendOneTimeMessage should only return a Promise for promise based "
- "API calls, otherwise it should be empty";
- RequestResult result(RequestResult::HANDLED);
- if (parse_result.async_type == binding::AsyncResponseType::kPromise) {
- result.return_value = promise;
- }
- return result;
- }
- RequestResult RuntimeHooksDelegate::HandleSendNativeMessage(
- ScriptContext* script_context,
- const APISignature::V8ParseResult& parse_result) {
- const std::vector<v8::Local<v8::Value>>& arguments = *parse_result.arguments;
- DCHECK_EQ(3u, arguments.size());
- std::string application_name =
- gin::V8ToString(script_context->isolate(), arguments[0]);
- v8::Local<v8::Value> v8_message = arguments[1];
- DCHECK(!v8_message.IsEmpty());
- std::string error;
- // Native messaging always uses JSON since a native host doesn't understand
- // structured cloning serialization.
- std::unique_ptr<Message> message =
- messaging_util::MessageFromV8(script_context->v8_context(), v8_message,
- SerializationFormat::kJson, &error);
- if (!message) {
- RequestResult result(RequestResult::INVALID_INVOCATION);
- result.error = std::move(error);
- return result;
- }
- v8::Local<v8::Function> response_callback;
- if (!arguments[2]->IsNull())
- response_callback = arguments[2].As<v8::Function>();
- v8::Local<v8::Promise> promise = messaging_service_->SendOneTimeMessage(
- script_context, MessageTarget::ForNativeApp(application_name),
- std::string(), *message, parse_result.async_type, response_callback);
- DCHECK_EQ(parse_result.async_type == binding::AsyncResponseType::kPromise,
- !promise.IsEmpty())
- << "SendOneTimeMessage should only return a Promise for promise based "
- "API calls, otherwise it should be empty";
- RequestResult result(RequestResult::HANDLED);
- if (parse_result.async_type == binding::AsyncResponseType::kPromise) {
- result.return_value = promise;
- }
- return result;
- }
- RequestResult RuntimeHooksDelegate::HandleConnect(
- ScriptContext* script_context,
- const APISignature::V8ParseResult& parse_result) {
- const std::vector<v8::Local<v8::Value>>& arguments = *parse_result.arguments;
- DCHECK_EQ(2u, arguments.size());
- DCHECK_EQ(binding::AsyncResponseType::kNone, parse_result.async_type);
- std::string target_id;
- std::string error;
- if (!messaging_util::GetTargetExtensionId(script_context, arguments[0],
- "runtime.connect", &target_id,
- &error)) {
- RequestResult result(RequestResult::INVALID_INVOCATION);
- result.error = std::move(error);
- return result;
- }
- messaging_util::MessageOptions options;
- if (!arguments[1]->IsNull()) {
- options = messaging_util::ParseMessageOptions(
- script_context->v8_context(), arguments[1].As<v8::Object>(),
- messaging_util::PARSE_CHANNEL_NAME);
- }
- gin::Handle<GinPort> port = messaging_service_->Connect(
- script_context, MessageTarget::ForExtension(target_id),
- options.channel_name,
- messaging_util::GetSerializationFormat(*script_context));
- DCHECK(!port.IsEmpty());
- DCHECK_EQ(binding::AsyncResponseType::kNone, parse_result.async_type);
- RequestResult result(RequestResult::HANDLED);
- result.return_value = port.ToV8();
- return result;
- }
- RequestResult RuntimeHooksDelegate::HandleConnectNative(
- ScriptContext* script_context,
- const APISignature::V8ParseResult& parse_result) {
- const std::vector<v8::Local<v8::Value>>& arguments = *parse_result.arguments;
- DCHECK_EQ(1u, arguments.size());
- DCHECK(arguments[0]->IsString());
- DCHECK_EQ(binding::AsyncResponseType::kNone, parse_result.async_type);
- std::string application_name =
- gin::V8ToString(script_context->isolate(), arguments[0]);
- // Native messaging always uses JSON since a native host doesn't understand
- // structured cloning serialization.
- auto format = SerializationFormat::kJson;
- gin::Handle<GinPort> port = messaging_service_->Connect(
- script_context, MessageTarget::ForNativeApp(application_name),
- std::string(), format);
- RequestResult result(RequestResult::HANDLED);
- result.return_value = port.ToV8();
- return result;
- }
- RequestResult RuntimeHooksDelegate::HandleGetBackgroundPage(
- ScriptContext* script_context,
- const APISignature::V8ParseResult& parse_result) {
- DCHECK(script_context->extension());
- RequestResult result(RequestResult::NOT_HANDLED);
- if (!v8::Function::New(script_context->v8_context(),
- &GetBackgroundPageCallback)
- .ToLocal(&result.custom_callback)) {
- return RequestResult(RequestResult::THROWN);
- }
- return result;
- }
- RequestResult RuntimeHooksDelegate::HandleGetPackageDirectoryEntryCallback(
- ScriptContext* script_context,
- const APISignature::V8ParseResult& parse_result) {
- // TODO(devlin): This is basically just copied and translated from
- // the JS bindings, and still relies on the custom JS bindings for
- // getBindDirectoryEntryCallback. This entire API is a bit crazy, and needs
- // some help.
- v8::Isolate* isolate = script_context->isolate();
- v8::Local<v8::Context> v8_context = script_context->v8_context();
- v8::MaybeLocal<v8::Value> maybe_custom_callback;
- { // Begin natives enabled scope (for requiring the module).
- ModuleSystem::NativesEnabledScope enable_natives(
- script_context->module_system());
- content::RenderFrame* background_page =
- ExtensionFrameHelper::GetBackgroundPageFrame(
- script_context->extension()->id());
- // The JS function will sometimes use the background page's context to do
- // some work (see also
- // extensions/renderer/resources/file_entry_binding_util.js). In order to
- // allow native code to run in the background page, we'll also need a
- // NativesEnabledScope for that context.
- DCHECK(v8_context == isolate->GetCurrentContext());
- absl::optional<ModuleSystem::NativesEnabledScope> background_page_natives;
- if (background_page &&
- background_page != script_context->GetRenderFrame() &&
- blink::WebFrame::ScriptCanAccess(background_page->GetWebFrame())) {
- ScriptContext* background_page_script_context =
- GetScriptContextFromV8Context(
- background_page->GetWebFrame()->MainWorldScriptContext());
- if (background_page_script_context) {
- background_page_natives.emplace(
- background_page_script_context->module_system());
- }
- }
- v8::Local<v8::Object> file_entry_binding_util;
- // ModuleSystem::Require can return an empty Maybe when it fails for any
- // number of reasons. It *shouldn't* ever throw, but it is technically
- // possible. This makes the handling the failure result complicated. Since
- // this shouldn't happen at all, bail and consider it handled if it fails.
- if (!script_context->module_system()
- ->Require("fileEntryBindingUtil")
- .ToLocal(&file_entry_binding_util)) {
- NOTREACHED();
- // Abort, and consider the request handled.
- return RequestResult(RequestResult::HANDLED);
- }
- v8::Local<v8::Value> get_bind_directory_entry_callback_value;
- if (!file_entry_binding_util
- ->Get(v8_context, gin::StringToSymbol(
- isolate, "getBindDirectoryEntryCallback"))
- .ToLocal(&get_bind_directory_entry_callback_value)) {
- NOTREACHED();
- return RequestResult(RequestResult::THROWN);
- }
- if (!get_bind_directory_entry_callback_value->IsFunction()) {
- NOTREACHED();
- // Abort, and consider the request handled.
- return RequestResult(RequestResult::HANDLED);
- }
- v8::Local<v8::Function> get_bind_directory_entry_callback =
- get_bind_directory_entry_callback_value.As<v8::Function>();
- maybe_custom_callback =
- JSRunner::Get(v8_context)
- ->RunJSFunctionSync(get_bind_directory_entry_callback, v8_context,
- 0, nullptr);
- } // End modules enabled scope.
- v8::Local<v8::Value> callback;
- if (!maybe_custom_callback.ToLocal(&callback)) {
- NOTREACHED();
- return RequestResult(RequestResult::THROWN);
- }
- if (!callback->IsFunction()) {
- NOTREACHED();
- // Abort, and consider the request handled.
- return RequestResult(RequestResult::HANDLED);
- }
- RequestResult result(RequestResult::NOT_HANDLED);
- result.custom_callback = callback.As<v8::Function>();
- return result;
- }
- } // namespace extensions
|