blob: efcd2cad7976aaa790e45b3bb40f6134f67b17de [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/renderer_messaging_service.h"
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/unguessable_token.h"
#include "base/values.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h"
#include "content/public/renderer/v8_value_converter.h"
#include "extensions/common/api/messaging/message.h"
#include "extensions/common/api/messaging/messaging_endpoint.h"
#include "extensions/common/api/messaging/port_id.h"
#include "extensions/common/extension_messages.h"
#include "extensions/renderer/extension_bindings_system.h"
#include "extensions/renderer/extension_port.h"
#include "extensions/renderer/ipc_message_sender.h"
#include "extensions/renderer/messaging_util.h"
#include "extensions/renderer/script_context.h"
#include "extensions/renderer/script_context_set_iterable.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_scoped_user_gesture.h"
#include "third_party/blink/public/web/web_scoped_window_focus_allowed_indicator.h"
#include "third_party/blink/public/web/web_user_gesture_indicator.h"
#include "v8/include/v8.h"
namespace extensions {
RendererMessagingService::RendererMessagingService(
ExtensionBindingsSystem* bindings_system)
: bindings_system_(bindings_system) {}
RendererMessagingService::~RendererMessagingService() {}
void RendererMessagingService::ValidateMessagePort(
ScriptContextSetIterable* context_set,
const PortId& port_id,
content::RenderFrame* render_frame) {
// TODO(devlin): In practice, |render_frame| should never be null here (unlike
// in other methods, where it legitimately can), but it can be in testing. It
// would be better to fake it somehow, but unfortunately, there's no good way
// to have a RenderFrame in a unittest. :(
int routing_id =
render_frame ? render_frame->GetRoutingID() : MSG_ROUTING_NONE;
bool has_port = false;
// The base::Unretained() below is safe since ScriptContextSet::ForEach is
// synchronous.
context_set->ForEach(
render_frame,
base::Bind(&RendererMessagingService::ValidateMessagePortInContext,
base::Unretained(this), port_id, &has_port));
// A reply is only sent if the port is missing, because the port is assumed to
// exist unless stated otherwise.
if (!has_port) {
bindings_system_->GetIPCMessageSender()->SendCloseMessagePort(
routing_id, port_id, false);
}
}
void RendererMessagingService::DispatchOnConnect(
ScriptContextSetIterable* context_set,
const PortId& target_port_id,
const std::string& channel_name,
const ExtensionMsg_TabConnectionInfo& source,
const ExtensionMsg_ExternalConnectionInfo& info,
content::RenderFrame* restrict_to_render_frame) {
DCHECK(!target_port_id.is_opener);
int routing_id = restrict_to_render_frame
? restrict_to_render_frame->GetRoutingID()
: MSG_ROUTING_NONE;
bool port_created = false;
context_set->ForEach(
info.target_id, restrict_to_render_frame,
base::Bind(&RendererMessagingService::DispatchOnConnectToScriptContext,
base::Unretained(this), target_port_id, channel_name, &source,
info, &port_created));
// Note: |restrict_to_render_frame| may have been deleted at this point!
IPCMessageSender* ipc_sender = bindings_system_->GetIPCMessageSender();
if (port_created) {
ipc_sender->SendOpenMessagePort(routing_id, target_port_id);
} else {
ipc_sender->SendCloseMessagePort(routing_id, target_port_id, false);
}
}
void RendererMessagingService::DeliverMessage(
ScriptContextSetIterable* context_set,
const PortId& target_port_id,
const Message& message,
content::RenderFrame* restrict_to_render_frame) {
context_set->ForEach(
restrict_to_render_frame,
base::Bind(&RendererMessagingService::DeliverMessageToScriptContext,
base::Unretained(this), message, target_port_id));
}
void RendererMessagingService::DispatchOnDisconnect(
ScriptContextSetIterable* context_set,
const PortId& port_id,
const std::string& error_message,
content::RenderFrame* restrict_to_render_frame) {
context_set->ForEach(
restrict_to_render_frame,
base::Bind(&RendererMessagingService::DispatchOnDisconnectToScriptContext,
base::Unretained(this), port_id, error_message));
}
void RendererMessagingService::ValidateMessagePortInContext(
const PortId& port_id,
bool* has_port,
ScriptContext* script_context) {
if (*has_port)
return; // Stop checking if the port was found.
// No need for |=; we know this is false right now from above.
*has_port = ContextHasMessagePort(script_context, port_id);
}
void RendererMessagingService::DispatchOnConnectToScriptContext(
const PortId& target_port_id,
const std::string& channel_name,
const ExtensionMsg_TabConnectionInfo* source,
const ExtensionMsg_ExternalConnectionInfo& info,
bool* port_created,
ScriptContext* script_context) {
// If the channel was opened by this same context, ignore it. This should only
// happen when messages are sent to an entire process (rather than a single
// frame) as an optimization; otherwise the browser process filters this out.
if (script_context->context_id() == target_port_id.context_id)
return;
// First, determine the event we'll use to connect.
std::string target_extension_id = script_context->GetExtensionID();
bool is_external =
(info.source_endpoint.type == MessagingEndpoint::Type::kExtension ||
info.source_endpoint.type == MessagingEndpoint::Type::kTab) &&
info.source_endpoint.extension_id != target_extension_id;
std::string event_name;
if (info.source_endpoint.type == MessagingEndpoint::Type::kNativeApp) {
event_name = messaging_util::kOnConnectNativeEvent;
} else if (channel_name == messaging_util::kSendRequestChannel) {
event_name = is_external ? messaging_util::kOnRequestExternalEvent
: messaging_util::kOnRequestEvent;
} else if (channel_name == messaging_util::kSendMessageChannel) {
event_name = is_external ? messaging_util::kOnMessageExternalEvent
: messaging_util::kOnMessageEvent;
} else {
event_name = is_external ? messaging_util::kOnConnectExternalEvent
: messaging_util::kOnConnectEvent;
}
// If there are no listeners for the given event, then we know the port won't
// be used in this context.
if (!bindings_system_->HasEventListenerInContext(event_name,
script_context)) {
return;
}
*port_created = true;
DispatchOnConnectToListeners(script_context, target_port_id,
target_extension_id, channel_name, source, info,
event_name);
}
void RendererMessagingService::DeliverMessageToScriptContext(
const Message& message,
const PortId& target_port_id,
ScriptContext* script_context) {
if (!ContextHasMessagePort(script_context, target_port_id))
return;
std::unique_ptr<blink::WebScopedUserGesture> web_user_gesture;
std::unique_ptr<blink::WebScopedWindowFocusAllowedIndicator>
allow_window_focus;
if (message.user_gesture) {
web_user_gesture = std::make_unique<blink::WebScopedUserGesture>(
script_context->web_frame());
if (script_context->web_frame()) {
blink::WebDocument document = script_context->web_frame()->GetDocument();
allow_window_focus =
std::make_unique<blink::WebScopedWindowFocusAllowedIndicator>(
&document);
}
}
DispatchOnMessageToListeners(script_context, message, target_port_id);
}
void RendererMessagingService::DispatchOnDisconnectToScriptContext(
const PortId& port_id,
const std::string& error_message,
ScriptContext* script_context) {
if (!ContextHasMessagePort(script_context, port_id))
return;
DispatchOnDisconnectToListeners(script_context, port_id, error_message);
}
} // namespace extensions