blob: cb1dbb5fba499a6da95c98a1579d70445f0f19db [file] [log] [blame]
// 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/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest.h"
#include "extensions/renderer/bindings/api_signature.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"
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()->CreationContext();
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()));
}
}
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. The original callback is
// curried in through the Data.
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()->CreationContext();
DCHECK(!info.Data().IsEmpty());
if (info.Data()->IsNull())
return;
// 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.Data().As<v8::Function>(),
arraysize(args), args);
}
} // namespace
RuntimeHooksDelegate::RuntimeHooksDelegate(
NativeRendererMessagingService* messaging_service)
: messaging_service_(messaging_service) {}
RuntimeHooksDelegate::~RuntimeHooksDelegate() {}
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 std::vector<v8::Local<v8::Value>>&);
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);
}
std::string error;
std::vector<v8::Local<v8::Value>> parsed_arguments;
if (!signature->ParseArgumentsToV8(context, *arguments, refs,
&parsed_arguments, &error)) {
RequestResult result(RequestResult::INVALID_INVOCATION);
result.error = std::move(error);
return result;
}
return (this->*handler)(script_context, parsed_arguments);
}
void RuntimeHooksDelegate::InitializeTemplate(
v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> object_template,
const APITypeReferenceMap& type_refs) {
object_template->SetAccessor(gin::StringToSymbol(isolate, "id"),
&GetExtensionId);
}
RequestResult RuntimeHooksDelegate::HandleGetManifest(
ScriptContext* script_context,
const std::vector<v8::Local<v8::Value>>& parsed_arguments) {
DCHECK(script_context->extension());
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 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]);
RequestResult result(RequestResult::HANDLED);
std::string url = base::StringPrintf(
"chrome-extension://%s%s%s", script_context->extension()->id().c_str(),
!path.empty() && path[0] == '/' ? "" : "/", path.c_str());
result.return_value = gin::StringToV8(isolate, url);
return result;
}
RequestResult RuntimeHooksDelegate::HandleSendMessage(
ScriptContext* script_context,
const std::vector<v8::Local<v8::Value>>& 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();
messaging_util::MessageOptions options;
if (!arguments[2]->IsNull()) {
options = messaging_util::ParseMessageOptions(
v8_context, arguments[2].As<v8::Object>(),
messaging_util::PARSE_INCLUDE_TLS_CHANNEL_ID);
}
v8::Local<v8::Value> v8_message = arguments[1];
std::unique_ptr<Message> message =
messaging_util::MessageFromV8(v8_context, v8_message, &error);
if (!message) {
RequestResult result(RequestResult::INVALID_INVOCATION);
result.error = std::move(error);
return result;
}
v8::Local<v8::Function> response_callback;
if (!arguments[3]->IsNull())
response_callback = arguments[3].As<v8::Function>();
messaging_service_->SendOneTimeMessage(
script_context, MessageTarget::ForExtension(target_id),
messaging_util::kSendMessageChannel, options.include_tls_channel_id,
*message, response_callback);
return RequestResult(RequestResult::HANDLED);
}
RequestResult RuntimeHooksDelegate::HandleSendNativeMessage(
ScriptContext* script_context,
const std::vector<v8::Local<v8::Value>>& 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;
std::unique_ptr<Message> message = messaging_util::MessageFromV8(
script_context->v8_context(), v8_message, &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>();
messaging_service_->SendOneTimeMessage(
script_context, MessageTarget::ForNativeApp(application_name),
std::string(), false, *message, response_callback);
return RequestResult(RequestResult::HANDLED);
}
RequestResult RuntimeHooksDelegate::HandleConnect(
ScriptContext* script_context,
const std::vector<v8::Local<v8::Value>>& arguments) {
DCHECK_EQ(2u, arguments.size());
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_INCLUDE_TLS_CHANNEL_ID |
messaging_util::PARSE_CHANNEL_NAME);
}
gin::Handle<GinPort> port = messaging_service_->Connect(
script_context, MessageTarget::ForExtension(target_id),
options.channel_name, options.include_tls_channel_id);
DCHECK(!port.IsEmpty());
RequestResult result(RequestResult::HANDLED);
result.return_value = port.ToV8();
return result;
}
RequestResult RuntimeHooksDelegate::HandleConnectNative(
ScriptContext* script_context,
const std::vector<v8::Local<v8::Value>>& arguments) {
DCHECK_EQ(1u, arguments.size());
DCHECK(arguments[0]->IsString());
std::string application_name =
gin::V8ToString(script_context->isolate(), arguments[0]);
gin::Handle<GinPort> port = messaging_service_->Connect(
script_context, MessageTarget::ForNativeApp(application_name),
std::string(), false);
RequestResult result(RequestResult::HANDLED);
result.return_value = port.ToV8();
return result;
}
RequestResult RuntimeHooksDelegate::HandleGetBackgroundPage(
ScriptContext* script_context,
const std::vector<v8::Local<v8::Value>>& arguments) {
DCHECK(script_context->extension());
RequestResult result(RequestResult::NOT_HANDLED);
if (!v8::Function::New(script_context->v8_context(),
&GetBackgroundPageCallback, arguments[0])
.ToLocal(&result.custom_callback)) {
return RequestResult(RequestResult::THROWN);
}
return result;
}
RequestResult RuntimeHooksDelegate::HandleGetPackageDirectoryEntryCallback(
ScriptContext* script_context,
const std::vector<v8::Local<v8::Value>>& arguments) {
// 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());
base::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