blob: 1a6be758941cbf54069fae1b0f0be90c8bf61527 [file] [log] [blame]
// Copyright (c) 2012 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 "content/browser/devtools/render_frame_devtools_agent_host.h"
#include <tuple>
#include <utility>
#include "base/guid.h"
#include "base/json/json_reader.h"
#include "base/lazy_instance.h"
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "content/browser/bad_message.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/devtools/devtools_frame_trace_recorder.h"
#include "content/browser/devtools/devtools_manager.h"
#include "content/browser/devtools/devtools_session.h"
#include "content/browser/devtools/page_navigation_throttle.h"
#include "content/browser/devtools/protocol/dom_handler.h"
#include "content/browser/devtools/protocol/emulation_handler.h"
#include "content/browser/devtools/protocol/input_handler.h"
#include "content/browser/devtools/protocol/inspector_handler.h"
#include "content/browser/devtools/protocol/io_handler.h"
#include "content/browser/devtools/protocol/network_handler.h"
#include "content/browser/devtools/protocol/page_handler.h"
#include "content/browser/devtools/protocol/protocol.h"
#include "content/browser/devtools/protocol/schema_handler.h"
#include "content/browser/devtools/protocol/security_handler.h"
#include "content/browser/devtools/protocol/service_worker_handler.h"
#include "content/browser/devtools/protocol/storage_handler.h"
#include "content/browser/devtools/protocol/target_handler.h"
#include "content/browser/devtools/protocol/tracing_handler.h"
#include "content/browser/frame_host/navigation_handle_impl.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/input/input_router_impl.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/site_instance_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/view_messages.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_widget_host_iterator.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/browser_side_navigation_policy.h"
#if defined(OS_ANDROID)
#include "content/public/browser/render_widget_host_view.h"
#include "device/power_save_blocker/power_save_blocker.h"
#endif
namespace content {
typedef std::vector<RenderFrameDevToolsAgentHost*> Instances;
namespace {
base::LazyInstance<Instances>::Leaky g_instances = LAZY_INSTANCE_INITIALIZER;
static RenderFrameDevToolsAgentHost* FindAgentHost(RenderFrameHost* host) {
if (g_instances == NULL)
return NULL;
for (Instances::iterator it = g_instances.Get().begin();
it != g_instances.Get().end(); ++it) {
if ((*it)->HasRenderFrameHost(host))
return *it;
}
return NULL;
}
static RenderFrameDevToolsAgentHost* FindAgentHost(
FrameTreeNode* frame_tree_node) {
if (g_instances == NULL)
return NULL;
for (Instances::iterator it = g_instances.Get().begin();
it != g_instances.Get().end(); ++it) {
if ((*it)->frame_tree_node() == frame_tree_node)
return *it;
}
return NULL;
}
static RenderFrameDevToolsAgentHost* FindAgentHost(WebContents* web_contents) {
if (!web_contents->GetMainFrame())
return nullptr;
return FindAgentHost(web_contents->GetMainFrame());
}
bool ShouldCreateDevToolsFor(RenderFrameHost* rfh) {
return rfh->IsCrossProcessSubframe() || !rfh->GetParent();
}
bool ShouldCreateDevToolsForNode(FrameTreeNode* ftn) {
return ShouldCreateDevToolsFor(ftn->current_frame_host());
}
static RenderFrameDevToolsAgentHost* GetAgentHostFor(FrameTreeNode* ftn) {
while (ftn && !ShouldCreateDevToolsForNode(ftn))
ftn = ftn->parent();
return FindAgentHost(ftn);
}
} // namespace
// RenderFrameDevToolsAgentHost::FrameHostHolder -------------------------------
class RenderFrameDevToolsAgentHost::FrameHostHolder {
public:
FrameHostHolder(
RenderFrameDevToolsAgentHost* agent, RenderFrameHostImpl* host);
~FrameHostHolder();
RenderFrameHostImpl* host() const { return host_; }
void Attach(DevToolsSession* session);
void Reattach(FrameHostHolder* old);
void Detach(int session_id);
void DispatchProtocolMessage(int session_id,
int call_id,
const std::string& method,
const std::string& message);
void InspectElement(int session_id, int x, int y);
bool ProcessChunkedMessageFromAgent(const DevToolsMessageChunk& chunk);
void Suspend();
void Resume();
private:
void GrantPolicy();
void RevokePolicy();
void SendMessageToClient(int session_id, const std::string& message);
RenderFrameDevToolsAgentHost* agent_;
RenderFrameHostImpl* host_;
bool attached_;
bool suspended_;
DevToolsMessageChunkProcessor chunk_processor_;
// <session_id, message>
std::vector<std::pair<int, std::string>> pending_messages_;
// <call_id> -> PendingMessage
std::map<int, PendingMessage> sent_messages_;
// These are sent messages for which we got a reply while suspended.
std::map<int, PendingMessage> sent_messages_whose_reply_came_while_suspended_;
};
RenderFrameDevToolsAgentHost::FrameHostHolder::FrameHostHolder(
RenderFrameDevToolsAgentHost* agent, RenderFrameHostImpl* host)
: agent_(agent),
host_(host),
attached_(false),
suspended_(false),
chunk_processor_(base::Bind(
&RenderFrameDevToolsAgentHost::FrameHostHolder::SendMessageToClient,
base::Unretained(this))) {
DCHECK(agent_);
DCHECK(host_);
}
RenderFrameDevToolsAgentHost::FrameHostHolder::~FrameHostHolder() {
if (attached_)
RevokePolicy();
}
void RenderFrameDevToolsAgentHost::FrameHostHolder::Attach(
DevToolsSession* session) {
host_->Send(new DevToolsAgentMsg_Attach(
host_->GetRoutingID(), agent_->GetId(), session->session_id()));
GrantPolicy();
attached_ = true;
}
void RenderFrameDevToolsAgentHost::FrameHostHolder::Reattach(
FrameHostHolder* old) {
if (old)
chunk_processor_.set_state_cookie(old->chunk_processor_.state_cookie());
host_->Send(new DevToolsAgentMsg_Reattach(
host_->GetRoutingID(), agent_->GetId(), agent_->session()->session_id(),
chunk_processor_.state_cookie()));
if (old) {
if (IsBrowserSideNavigationEnabled()) {
for (const auto& pair :
old->sent_messages_whose_reply_came_while_suspended_) {
DispatchProtocolMessage(pair.second.session_id, pair.first,
pair.second.method, pair.second.message);
}
}
for (const auto& pair : old->sent_messages_) {
DispatchProtocolMessage(pair.second.session_id, pair.first,
pair.second.method, pair.second.message);
}
}
GrantPolicy();
attached_ = true;
}
void RenderFrameDevToolsAgentHost::FrameHostHolder::Detach(int session_id) {
host_->Send(new DevToolsAgentMsg_Detach(host_->GetRoutingID()));
RevokePolicy();
attached_ = false;
}
void RenderFrameDevToolsAgentHost::FrameHostHolder::GrantPolicy() {
ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadRawCookies(
host_->GetProcess()->GetID());
}
void RenderFrameDevToolsAgentHost::FrameHostHolder::RevokePolicy() {
bool process_has_agents = false;
RenderProcessHost* process_host = host_->GetProcess();
for (RenderFrameDevToolsAgentHost* agent : g_instances.Get()) {
if (!agent->IsAttached())
continue;
if (agent->current_ && agent->current_->host() != host_ &&
agent->current_->host()->GetProcess() == process_host) {
process_has_agents = true;
}
if (agent->pending_ && agent->pending_->host() != host_ &&
agent->pending_->host()->GetProcess() == process_host) {
process_has_agents = true;
}
}
// We are the last to disconnect from the renderer -> revoke permissions.
if (!process_has_agents) {
ChildProcessSecurityPolicyImpl::GetInstance()->RevokeReadRawCookies(
process_host->GetID());
}
}
void RenderFrameDevToolsAgentHost::FrameHostHolder::DispatchProtocolMessage(
int session_id,
int call_id,
const std::string& method,
const std::string& message) {
host_->Send(new DevToolsAgentMsg_DispatchOnInspectorBackend(
host_->GetRoutingID(), session_id, call_id, method, message));
sent_messages_[call_id] = { session_id, method, message };
}
void RenderFrameDevToolsAgentHost::FrameHostHolder::InspectElement(
int session_id, int x, int y) {
DCHECK(attached_);
host_->Send(new DevToolsAgentMsg_InspectElement(
host_->GetRoutingID(), session_id, x, y));
}
bool
RenderFrameDevToolsAgentHost::FrameHostHolder::ProcessChunkedMessageFromAgent(
const DevToolsMessageChunk& chunk) {
return chunk_processor_.ProcessChunkedMessageFromAgent(chunk);
}
void RenderFrameDevToolsAgentHost::FrameHostHolder::SendMessageToClient(
int session_id,
const std::string& message) {
int id = chunk_processor_.last_call_id();
PendingMessage sent_message = sent_messages_[id];
sent_messages_.erase(id);
if (suspended_) {
sent_messages_whose_reply_came_while_suspended_[id] = sent_message;
pending_messages_.push_back(std::make_pair(session_id, message));
} else {
agent_->SendMessageToClient(session_id, message);
// |this| may be deleted at this point.
}
}
void RenderFrameDevToolsAgentHost::FrameHostHolder::Suspend() {
suspended_ = true;
}
void RenderFrameDevToolsAgentHost::FrameHostHolder::Resume() {
suspended_ = false;
for (const auto& pair : pending_messages_)
agent_->SendMessageToClient(pair.first, pair.second);
std::vector<std::pair<int, std::string>> empty;
pending_messages_.swap(empty);
sent_messages_whose_reply_came_while_suspended_.clear();
}
// RenderFrameDevToolsAgentHost ------------------------------------------------
// static
scoped_refptr<DevToolsAgentHost>
DevToolsAgentHost::GetOrCreateFor(RenderFrameHost* frame_host) {
while (frame_host && !ShouldCreateDevToolsFor(frame_host))
frame_host = frame_host->GetParent();
DCHECK(frame_host);
RenderFrameDevToolsAgentHost* result = FindAgentHost(frame_host);
if (!result) {
result = new RenderFrameDevToolsAgentHost(
static_cast<RenderFrameHostImpl*>(frame_host));
}
return result;
}
// static
scoped_refptr<DevToolsAgentHost>
DevToolsAgentHost::GetOrCreateFor(WebContents* web_contents) {
// TODO(dgozman): this check should not be necessary. See
// http://crbug.com/489664.
if (!web_contents->GetMainFrame())
return nullptr;
return DevToolsAgentHost::GetOrCreateFor(web_contents->GetMainFrame());
}
// static
scoped_refptr<DevToolsAgentHost> RenderFrameDevToolsAgentHost::GetOrCreateFor(
RenderFrameHostImpl* host) {
RenderFrameDevToolsAgentHost* result = FindAgentHost(host);
if (!result)
result = new RenderFrameDevToolsAgentHost(host);
return result;
}
// static
void RenderFrameDevToolsAgentHost::AppendAgentHostForFrameIfApplicable(
DevToolsAgentHost::List* result,
RenderFrameHost* host) {
RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(host);
if (!rfh->IsRenderFrameLive())
return;
if (ShouldCreateDevToolsFor(rfh))
result->push_back(RenderFrameDevToolsAgentHost::GetOrCreateFor(rfh));
}
// static
bool DevToolsAgentHost::HasFor(WebContents* web_contents) {
return FindAgentHost(web_contents) != NULL;
}
// static
bool DevToolsAgentHost::IsDebuggerAttached(WebContents* web_contents) {
RenderFrameDevToolsAgentHost* agent_host = FindAgentHost(web_contents);
return agent_host && agent_host->IsAttached();
}
// static
void RenderFrameDevToolsAgentHost::AddAllAgentHosts(
DevToolsAgentHost::List* result) {
base::Callback<void(RenderFrameHost*)> callback = base::Bind(
RenderFrameDevToolsAgentHost::AppendAgentHostForFrameIfApplicable,
base::Unretained(result));
for (auto* wc : WebContentsImpl::GetAllWebContents())
wc->ForEachFrame(callback);
}
// static
void RenderFrameDevToolsAgentHost::OnCancelPendingNavigation(
RenderFrameHost* pending,
RenderFrameHost* current) {
if (IsBrowserSideNavigationEnabled())
return;
RenderFrameDevToolsAgentHost* agent_host = FindAgentHost(pending);
if (!agent_host)
return;
if (agent_host->pending_ && agent_host->pending_->host() == pending) {
DCHECK(agent_host->current_ && agent_host->current_->host() == current);
agent_host->DiscardPending();
DCHECK(agent_host->CheckConsistency());
}
}
// static
void RenderFrameDevToolsAgentHost::OnBeforeNavigation(
RenderFrameHost* current, RenderFrameHost* pending) {
RenderFrameDevToolsAgentHost* agent_host = FindAgentHost(current);
if (agent_host)
agent_host->AboutToNavigateRenderFrame(current, pending);
}
// static
void RenderFrameDevToolsAgentHost::OnBeforeNavigation(
NavigationHandle* navigation_handle) {
FrameTreeNode* frame_tree_node =
static_cast<NavigationHandleImpl*>(navigation_handle)->frame_tree_node();
RenderFrameDevToolsAgentHost* agent_host = FindAgentHost(frame_tree_node);
if (agent_host)
agent_host->AboutToNavigate(navigation_handle);
}
// static
void RenderFrameDevToolsAgentHost::OnFailedNavigation(
RenderFrameHost* host,
const CommonNavigationParams& common_params,
const BeginNavigationParams& begin_params,
net::Error error_code) {
RenderFrameDevToolsAgentHost* agent_host = FindAgentHost(host);
if (agent_host)
agent_host->OnFailedNavigation(common_params, begin_params, error_code);
}
// static
std::unique_ptr<NavigationThrottle>
RenderFrameDevToolsAgentHost::CreateThrottleForNavigation(
NavigationHandle* navigation_handle) {
FrameTreeNode* frame_tree_node =
static_cast<NavigationHandleImpl*>(navigation_handle)->frame_tree_node();
while (frame_tree_node && frame_tree_node->parent()) {
frame_tree_node = frame_tree_node->parent();
}
RenderFrameDevToolsAgentHost* agent_host = FindAgentHost(frame_tree_node);
// Note Page.setControlNavigations is intended to control navigations in the
// main frame and all child frames and |page_handler_| only exists for the
// main frame.
if (!agent_host || !agent_host->session())
return nullptr;
protocol::PageHandler* page_handler =
protocol::PageHandler::FromSession(agent_host->session());
if (!page_handler)
return nullptr;
return page_handler->CreateThrottleForNavigation(navigation_handle);
}
// static
bool RenderFrameDevToolsAgentHost::IsNetworkHandlerEnabled(
FrameTreeNode* frame_tree_node) {
RenderFrameDevToolsAgentHost* agent_host = GetAgentHostFor(frame_tree_node);
if (!agent_host || !agent_host->session())
return false;
return protocol::NetworkHandler::FromSession(agent_host->session())
->enabled();
}
// static
std::string RenderFrameDevToolsAgentHost::UserAgentOverride(
FrameTreeNode* frame_tree_node) {
RenderFrameDevToolsAgentHost* agent_host = GetAgentHostFor(frame_tree_node);
if (!agent_host || !agent_host->session())
return std::string();
return protocol::NetworkHandler::FromSession(agent_host->session())
->UserAgentOverride();
}
// static
void RenderFrameDevToolsAgentHost::WebContentsCreated(
WebContents* web_contents) {
if (ShouldForceCreation()) {
// Force agent host.
DevToolsAgentHost::GetOrCreateFor(web_contents);
}
}
RenderFrameDevToolsAgentHost::RenderFrameDevToolsAgentHost(
RenderFrameHostImpl* host)
: DevToolsAgentHostImpl(base::GenerateGUID()),
frame_trace_recorder_(nullptr),
handlers_frame_host_(nullptr),
current_frame_crashed_(false),
pending_handle_(nullptr),
frame_tree_node_(host->frame_tree_node()) {
SetPending(host);
CommitPending();
WebContentsObserver::Observe(WebContents::FromRenderFrameHost(host));
if (web_contents() && web_contents()->GetCrashedStatus() !=
base::TERMINATION_STATUS_STILL_RUNNING) {
current_frame_crashed_ = true;
}
g_instances.Get().push_back(this);
AddRef(); // Balanced in RenderFrameHostDestroyed.
DevToolsManager* manager = DevToolsManager::GetInstance();
if (manager->delegate()) {
type_ = manager->delegate()->GetTargetType(host);
title_ = manager->delegate()->GetTargetTitle(host);
}
NotifyCreated();
}
void RenderFrameDevToolsAgentHost::SetPending(RenderFrameHostImpl* host) {
DCHECK(!pending_);
current_frame_crashed_ = false;
pending_.reset(new FrameHostHolder(this, host));
if (IsAttached())
pending_->Reattach(current_.get());
// Can only be null in constructor.
if (current_)
current_->Suspend();
pending_->Suspend();
UpdateProtocolHandlers(host);
}
void RenderFrameDevToolsAgentHost::CommitPending() {
DCHECK(pending_);
current_frame_crashed_ = false;
if (!ShouldCreateDevToolsFor(pending_->host())) {
DestroyOnRenderFrameGone();
// |this| may be deleted at this point.
return;
}
current_ = std::move(pending_);
UpdateProtocolHandlers(current_->host());
current_->Resume();
}
void RenderFrameDevToolsAgentHost::DiscardPending() {
DCHECK(pending_);
DCHECK(current_);
pending_.reset();
UpdateProtocolHandlers(current_->host());
current_->Resume();
}
BrowserContext* RenderFrameDevToolsAgentHost::GetBrowserContext() {
WebContents* contents = web_contents();
return contents ? contents->GetBrowserContext() : nullptr;
}
WebContents* RenderFrameDevToolsAgentHost::GetWebContents() {
return web_contents();
}
void RenderFrameDevToolsAgentHost::AttachSession(DevToolsSession* session) {
session->SetFallThroughForNotFound(true);
session->SetRenderFrameHost(handlers_frame_host_);
if (frame_tree_node_ && !frame_tree_node_->parent()) {
session->AddHandler(base::WrapUnique(new protocol::PageHandler()));
session->AddHandler(base::WrapUnique(new protocol::SecurityHandler()));
}
session->AddHandler(base::WrapUnique(new protocol::DOMHandler()));
session->AddHandler(base::WrapUnique(new protocol::EmulationHandler()));
session->AddHandler(base::WrapUnique(new protocol::InputHandler()));
session->AddHandler(base::WrapUnique(new protocol::InspectorHandler()));
session->AddHandler(base::WrapUnique(new protocol::IOHandler(
GetIOContext())));
session->AddHandler(base::WrapUnique(new protocol::NetworkHandler()));
session->AddHandler(base::WrapUnique(new protocol::SchemaHandler()));
session->AddHandler(base::WrapUnique(new protocol::ServiceWorkerHandler()));
session->AddHandler(base::WrapUnique(new protocol::StorageHandler()));
session->AddHandler(base::WrapUnique(new protocol::TargetHandler()));
session->AddHandler(base::WrapUnique(new protocol::TracingHandler(
protocol::TracingHandler::Renderer,
frame_tree_node_ ? frame_tree_node_->frame_tree_node_id() : 0,
GetIOContext())));
if (current_)
current_->Attach(session);
if (pending_)
pending_->Attach(session);
OnClientAttached();
}
void RenderFrameDevToolsAgentHost::DetachSession(int session_id) {
if (current_)
current_->Detach(session_id);
if (pending_)
pending_->Detach(session_id);
OnClientDetached();
}
bool RenderFrameDevToolsAgentHost::DispatchProtocolMessage(
DevToolsSession* session,
const std::string& message) {
int call_id = 0;
std::string method;
if (session->Dispatch(message, &call_id, &method) !=
protocol::Response::kFallThrough) {
return true;
}
if (!navigating_handles_.empty()) {
DCHECK(IsBrowserSideNavigationEnabled());
in_navigation_protocol_message_buffer_[call_id] =
{ session->session_id(), method, message };
return true;
}
if (current_) {
current_->DispatchProtocolMessage(
session->session_id(), call_id, method, message);
}
if (pending_) {
pending_->DispatchProtocolMessage(
session->session_id(), call_id, method, message);
}
return true;
}
void RenderFrameDevToolsAgentHost::InspectElement(
DevToolsSession* session,
int x,
int y) {
if (current_)
current_->InspectElement(session->session_id(), x, y);
if (pending_)
pending_->InspectElement(session->session_id(), x, y);
}
void RenderFrameDevToolsAgentHost::OnClientAttached() {
if (!web_contents())
return;
frame_trace_recorder_.reset(new DevToolsFrameTraceRecorder());
CreatePowerSaveBlocker();
}
void RenderFrameDevToolsAgentHost::OnClientDetached() {
#if defined(OS_ANDROID)
power_save_blocker_.reset();
#endif
frame_trace_recorder_.reset();
in_navigation_protocol_message_buffer_.clear();
}
RenderFrameDevToolsAgentHost::~RenderFrameDevToolsAgentHost() {
Instances::iterator it = std::find(g_instances.Get().begin(),
g_instances.Get().end(),
this);
if (it != g_instances.Get().end())
g_instances.Get().erase(it);
}
void RenderFrameDevToolsAgentHost::ReadyToCommitNavigation(
NavigationHandle* navigation_handle) {
// CommitPending may destruct |this|.
scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
// TODO(clamy): Switch RenderFrameDevToolsAgentHost to always buffer messages
// until ReadyToCommitNavigation is called, now that it is also called in
// non-PlzNavigate mode.
if (!IsBrowserSideNavigationEnabled())
return;
// If the navigation is not tracked, return;
if (navigating_handles_.count(navigation_handle) == 0)
return;
RenderFrameHostImpl* render_frame_host_impl =
static_cast<RenderFrameHostImpl*>(
navigation_handle->GetRenderFrameHost());
if (current_->host() != render_frame_host_impl || current_frame_crashed_) {
SetPending(render_frame_host_impl);
pending_handle_ = navigation_handle;
// Commit when navigating the same frame after crash, avoiding the same
// host in current_ and pending_.
if (current_->host() == render_frame_host_impl) {
pending_handle_ = nullptr;
CommitPending();
}
}
DCHECK(CheckConsistency());
}
void RenderFrameDevToolsAgentHost::DidFinishNavigation(
NavigationHandle* navigation_handle) {
// CommitPending may destruct |this|.
scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
if (!IsBrowserSideNavigationEnabled()) {
if (navigation_handle->HasCommitted() &&
!navigation_handle->IsErrorPage()) {
if (pending_ &&
pending_->host() == navigation_handle->GetRenderFrameHost()) {
CommitPending();
}
if (session())
protocol::TargetHandler::FromSession(session())->UpdateServiceWorkers();
} else if (pending_ && pending_->host()->GetFrameTreeNodeId() ==
navigation_handle->GetFrameTreeNodeId()) {
DiscardPending();
}
DCHECK(CheckConsistency());
return;
}
// If the navigation is not tracked, return;
if (navigating_handles_.count(navigation_handle) == 0)
return;
// Now that the navigation is finished, remove the handle from the list of
// navigating handles.
navigating_handles_.erase(navigation_handle);
if (pending_handle_ == navigation_handle) {
// This navigation handle did set the pending FrameHostHolder.
DCHECK(pending_);
if (navigation_handle->HasCommitted()) {
DCHECK(pending_->host() == navigation_handle->GetRenderFrameHost());
CommitPending();
} else {
DiscardPending();
}
pending_handle_ = nullptr;
} else if (navigating_handles_.empty()) {
current_->Resume();
}
DispatchBufferedProtocolMessagesIfNecessary();
DCHECK(CheckConsistency());
if (session() && navigation_handle->HasCommitted())
protocol::TargetHandler::FromSession(session())->UpdateServiceWorkers();
}
void RenderFrameDevToolsAgentHost::AboutToNavigateRenderFrame(
RenderFrameHost* old_host,
RenderFrameHost* new_host) {
// CommitPending may destruct |this|.
scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
if (IsBrowserSideNavigationEnabled())
return;
DCHECK(!pending_ || pending_->host() != old_host);
if (!current_ || current_->host() != old_host) {
DCHECK(CheckConsistency());
return;
}
if (old_host == new_host && !current_frame_crashed_) {
DCHECK(CheckConsistency());
return;
}
DCHECK(!pending_);
SetPending(static_cast<RenderFrameHostImpl*>(new_host));
// Commit when navigating the same frame after crash, avoiding the same
// host in current_ and pending_.
if (old_host == new_host)
CommitPending();
DCHECK(CheckConsistency());
}
void RenderFrameDevToolsAgentHost::AboutToNavigate(
NavigationHandle* navigation_handle) {
if (!IsBrowserSideNavigationEnabled())
return;
DCHECK(current_);
navigating_handles_.insert(navigation_handle);
current_->Suspend();
DCHECK(CheckConsistency());
}
void RenderFrameDevToolsAgentHost::OnFailedNavigation(
const CommonNavigationParams& common_params,
const BeginNavigationParams& begin_params,
net::Error error_code) {
DCHECK(IsBrowserSideNavigationEnabled());
if (!session())
return;
protocol::NetworkHandler* handler =
protocol::NetworkHandler::FromSession(session());
if (!handler)
return;
handler->NavigationFailed(common_params, begin_params, error_code);
}
void RenderFrameDevToolsAgentHost::RenderFrameHostChanged(
RenderFrameHost* old_host,
RenderFrameHost* new_host) {
// CommitPending may destruct |this|.
scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
if (session())
protocol::TargetHandler::FromSession(session())->UpdateFrames();
if (IsBrowserSideNavigationEnabled() && !current_frame_crashed_)
return;
DCHECK(!pending_ || pending_->host() != old_host);
if (!current_ || current_->host() != old_host) {
DCHECK(CheckConsistency());
return;
}
// AboutToNavigateRenderFrame was not called for renderer-initiated
// navigation.
if (!pending_)
SetPending(static_cast<RenderFrameHostImpl*>(new_host));
CommitPending();
DCHECK(CheckConsistency());
}
void RenderFrameDevToolsAgentHost::FrameDeleted(RenderFrameHost* rfh) {
if (pending_ && pending_->host() == rfh) {
if (!IsBrowserSideNavigationEnabled())
DiscardPending();
DCHECK(CheckConsistency());
return;
}
if (current_ && current_->host() == rfh)
DestroyOnRenderFrameGone(); // |this| may be deleted at this point.
}
void RenderFrameDevToolsAgentHost::RenderFrameDeleted(RenderFrameHost* rfh) {
if (!current_frame_crashed_)
FrameDeleted(rfh);
else
DCHECK(CheckConsistency());
}
void RenderFrameDevToolsAgentHost::DestroyOnRenderFrameGone() {
DCHECK(current_);
scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
UpdateProtocolHandlers(nullptr);
if (IsAttached())
OnClientDetached();
ForceDetach(false);
pending_.reset();
current_.reset();
frame_tree_node_ = nullptr;
pending_handle_ = nullptr;
WebContentsObserver::Observe(nullptr);
Release();
}
bool RenderFrameDevToolsAgentHost::CheckConsistency() {
if (current_ && pending_ && current_->host() == pending_->host())
return false;
if (IsBrowserSideNavigationEnabled())
return true;
if (!frame_tree_node_)
return !handlers_frame_host_;
RenderFrameHostManager* manager = frame_tree_node_->render_manager();
return handlers_frame_host_ == manager->current_frame_host() ||
handlers_frame_host_ == manager->pending_frame_host();
}
void RenderFrameDevToolsAgentHost::CreatePowerSaveBlocker() {
#if defined(OS_ANDROID)
power_save_blocker_.reset(new device::PowerSaveBlocker(
device::PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep,
device::PowerSaveBlocker::kReasonOther, "DevTools",
BrowserThread::GetTaskRunnerForThread(BrowserThread::UI),
BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE)));
if (web_contents()->GetNativeView()) {
power_save_blocker_->InitDisplaySleepBlocker(
web_contents()->GetNativeView());
}
#endif
}
void RenderFrameDevToolsAgentHost::RenderProcessGone(
base::TerminationStatus status) {
switch(status) {
case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
#if defined(OS_CHROMEOS)
case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
#endif
case base::TERMINATION_STATUS_PROCESS_CRASHED:
#if defined(OS_ANDROID)
case base::TERMINATION_STATUS_OOM_PROTECTED:
#endif
case base::TERMINATION_STATUS_LAUNCH_FAILED:
if (session())
protocol::InspectorHandler::FromSession(session())->TargetCrashed();
current_frame_crashed_ = true;
break;
default:
if (session()) {
protocol::InspectorHandler::FromSession(session())
->TargetDetached("Render process gone.");
}
break;
}
DCHECK(CheckConsistency());
}
bool RenderFrameDevToolsAgentHost::OnMessageReceived(
const IPC::Message& message,
RenderFrameHost* render_frame_host) {
bool is_current = current_ && current_->host() == render_frame_host;
bool is_pending = pending_ && pending_->host() == render_frame_host;
if (!is_current && !is_pending)
return false;
if (!IsAttached())
return false;
bool handled = true;
IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(RenderFrameDevToolsAgentHost, message,
render_frame_host)
IPC_MESSAGE_HANDLER(DevToolsClientMsg_DispatchOnInspectorFrontend,
OnDispatchOnInspectorFrontend)
IPC_MESSAGE_HANDLER(DevToolsAgentHostMsg_RequestNewWindow,
OnRequestNewWindow)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void RenderFrameDevToolsAgentHost::DidAttachInterstitialPage() {
protocol::PageHandler* page_handler =
session() ? protocol::PageHandler::FromSession(session()) : nullptr;
if (page_handler)
page_handler->DidAttachInterstitialPage();
// TODO(dgozman): this may break for cross-process subframes.
if (!pending_) {
DCHECK(CheckConsistency());
return;
}
// Pending set in AboutToNavigateRenderFrame turned out to be interstitial.
// Connect back to the real one.
DiscardPending();
pending_handle_ = nullptr;
DCHECK(CheckConsistency());
}
void RenderFrameDevToolsAgentHost::DidDetachInterstitialPage() {
protocol::PageHandler* page_handler =
session() ? protocol::PageHandler::FromSession(session()) : nullptr;
if (page_handler)
page_handler->DidDetachInterstitialPage();
}
void RenderFrameDevToolsAgentHost::WasShown() {
CreatePowerSaveBlocker();
}
void RenderFrameDevToolsAgentHost::WasHidden() {
#if defined(OS_ANDROID)
power_save_blocker_.reset();
#endif
}
void RenderFrameDevToolsAgentHost::DidReceiveCompositorFrame() {
if (!session())
return;
const cc::CompositorFrameMetadata& metadata =
RenderWidgetHostImpl::From(
web_contents()->GetRenderViewHost()->GetWidget())
->last_frame_metadata();
protocol::PageHandler* page_handler =
protocol::PageHandler::FromSession(session());
if (page_handler)
page_handler->OnSwapCompositorFrame(metadata.Clone());
protocol::InputHandler::FromSession(session())->OnSwapCompositorFrame(
metadata);
protocol::TracingHandler* tracing_handler =
protocol::TracingHandler::FromSession(session());
if (frame_trace_recorder_ && tracing_handler->did_initiate_recording()) {
frame_trace_recorder_->OnSwapCompositorFrame(
current_ ? current_->host() : nullptr, metadata);
}
}
void RenderFrameDevToolsAgentHost::
DispatchBufferedProtocolMessagesIfNecessary() {
if (navigating_handles_.empty() &&
in_navigation_protocol_message_buffer_.size()) {
DCHECK(current_);
for (const auto& pair : in_navigation_protocol_message_buffer_) {
current_->DispatchProtocolMessage(
pair.second.session_id, pair.first, pair.second.method,
pair.second.message);
}
in_navigation_protocol_message_buffer_.clear();
}
}
void RenderFrameDevToolsAgentHost::UpdateProtocolHandlers(
RenderFrameHostImpl* host) {
#if DCHECK_IS_ON()
// TODO(dgozman): fix this for browser side navigation.
if (!IsBrowserSideNavigationEnabled()) {
// Check that we don't have stale host object here by accessing some random
// properties inside.
if (handlers_frame_host_ && handlers_frame_host_->GetRenderWidgetHost())
handlers_frame_host_->GetRenderWidgetHost()->GetRoutingID();
}
#endif
handlers_frame_host_ = host;
if (session())
session()->SetRenderFrameHost(host);
}
void RenderFrameDevToolsAgentHost::DisconnectWebContents() {
if (pending_)
DiscardPending();
UpdateProtocolHandlers(nullptr);
disconnected_ = std::move(current_);
if (session())
disconnected_->Detach(session()->session_id());
frame_tree_node_ = nullptr;
in_navigation_protocol_message_buffer_.clear();
navigating_handles_.clear();
pending_handle_ = nullptr;
WebContentsObserver::Observe(nullptr);
}
void RenderFrameDevToolsAgentHost::ConnectWebContents(WebContents* wc) {
// CommitPending may destruct |this|.
scoped_refptr<RenderFrameDevToolsAgentHost> protect(this);
DCHECK(!current_);
DCHECK(!pending_);
RenderFrameHostImpl* host =
static_cast<RenderFrameHostImpl*>(wc->GetMainFrame());
DCHECK(host);
frame_tree_node_ = host->frame_tree_node();
current_ = std::move(disconnected_);
SetPending(host);
CommitPending();
WebContentsObserver::Observe(WebContents::FromRenderFrameHost(host));
}
std::string RenderFrameDevToolsAgentHost::GetParentId() {
if (IsChildFrame() && current_) {
RenderFrameHostImpl* frame_host = current_->host()->GetParent();
while (frame_host && !ShouldCreateDevToolsFor(frame_host))
frame_host = frame_host->GetParent();
if (frame_host)
return DevToolsAgentHost::GetOrCreateFor(frame_host)->GetId();
}
WebContentsImpl* contents = static_cast<WebContentsImpl*>(web_contents());
if (!contents)
return "";
contents = contents->GetOuterWebContents();
if (contents)
return DevToolsAgentHost::GetOrCreateFor(contents)->GetId();
return "";
}
std::string RenderFrameDevToolsAgentHost::GetType() {
if (!type_.empty())
return type_;
if (IsChildFrame())
return kTypeFrame;
return kTypePage;
}
std::string RenderFrameDevToolsAgentHost::GetTitle() {
if (!title_.empty())
return title_;
if (current_ && current_->host()->GetParent())
return current_->host()->GetLastCommittedURL().spec();
content::WebContents* web_contents = GetWebContents();
if (web_contents)
return base::UTF16ToUTF8(web_contents->GetTitle());
return GetURL().spec();
}
std::string RenderFrameDevToolsAgentHost::GetDescription() {
DevToolsManager* manager = DevToolsManager::GetInstance();
if (manager->delegate() && current_)
return manager->delegate()->GetTargetDescription(current_->host());
return "";
}
GURL RenderFrameDevToolsAgentHost::GetURL() {
// Order is important here.
WebContents* web_contents = GetWebContents();
if (web_contents && !IsChildFrame())
return web_contents->GetVisibleURL();
if (pending_)
return pending_->host()->GetLastCommittedURL();
if (current_)
return current_->host()->GetLastCommittedURL();
return GURL();
}
GURL RenderFrameDevToolsAgentHost::GetFaviconURL() {
WebContents* wc = web_contents();
if (!wc)
return GURL();
NavigationEntry* entry = wc->GetController().GetLastCommittedEntry();
if (entry)
return entry->GetFavicon().url;
return GURL();
}
bool RenderFrameDevToolsAgentHost::Activate() {
WebContentsImpl* wc = static_cast<WebContentsImpl*>(web_contents());
if (wc) {
wc->Activate();
return true;
}
return false;
}
void RenderFrameDevToolsAgentHost::Reload() {
WebContentsImpl* wc = static_cast<WebContentsImpl*>(web_contents());
if (wc)
wc->GetController().Reload(ReloadType::NORMAL, true);
}
bool RenderFrameDevToolsAgentHost::Close() {
if (web_contents()) {
web_contents()->ClosePage();
return true;
}
return false;
}
base::TimeTicks RenderFrameDevToolsAgentHost::GetLastActivityTime() {
if (content::WebContents* contents = web_contents())
return contents->GetLastActiveTime();
return base::TimeTicks();
}
void RenderFrameDevToolsAgentHost::SignalSynchronousSwapCompositorFrame(
RenderFrameHost* frame_host,
cc::CompositorFrameMetadata frame_metadata) {
scoped_refptr<RenderFrameDevToolsAgentHost> dtah(FindAgentHost(frame_host));
if (dtah) {
// Unblock the compositor.
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
&RenderFrameDevToolsAgentHost::SynchronousSwapCompositorFrame,
dtah.get(),
base::Passed(std::move(frame_metadata))));
}
}
void RenderFrameDevToolsAgentHost::SynchronousSwapCompositorFrame(
cc::CompositorFrameMetadata frame_metadata) {
if (!session())
return;
protocol::PageHandler* page_handler =
protocol::PageHandler::FromSession(session());
if (page_handler)
page_handler->OnSynchronousSwapCompositorFrame(std::move(frame_metadata));
protocol::InputHandler::FromSession(session())
->OnSwapCompositorFrame(frame_metadata);
protocol::TracingHandler* tracing_handler =
protocol::TracingHandler::FromSession(session());
if (frame_trace_recorder_ && tracing_handler->did_initiate_recording()) {
frame_trace_recorder_->OnSynchronousSwapCompositorFrame(
current_ ? current_->host() : nullptr,
frame_metadata);
}
}
void RenderFrameDevToolsAgentHost::OnDispatchOnInspectorFrontend(
RenderFrameHost* sender,
const DevToolsMessageChunk& message) {
bool success = true;
if (current_ && current_->host() == sender)
success = current_->ProcessChunkedMessageFromAgent(message);
else if (pending_ && pending_->host() == sender)
success = pending_->ProcessChunkedMessageFromAgent(message);
if (!success) {
bad_message::ReceivedBadMessage(
sender->GetProcess(),
bad_message::RFH_INCONSISTENT_DEVTOOLS_MESSAGE);
}
}
void RenderFrameDevToolsAgentHost::OnRequestNewWindow(
RenderFrameHost* sender,
int new_routing_id) {
RenderFrameHostImpl* frame_host = RenderFrameHostImpl::FromID(
sender->GetProcess()->GetID(), new_routing_id);
bool success = false;
if (IsAttached() && sender->GetRoutingID() != new_routing_id && frame_host) {
scoped_refptr<DevToolsAgentHost> agent =
DevToolsAgentHost::GetOrCreateFor(frame_host);
success = static_cast<DevToolsAgentHostImpl*>(agent.get())->Inspect();
}
sender->Send(new DevToolsAgentMsg_RequestNewWindow_ACK(
sender->GetRoutingID(), success));
}
bool RenderFrameDevToolsAgentHost::HasRenderFrameHost(
RenderFrameHost* host) {
return (current_ && current_->host() == host) ||
(pending_ && pending_->host() == host);
}
bool RenderFrameDevToolsAgentHost::IsChildFrame() {
return current_ && current_->host()->GetParent();
}
} // namespace content