| // 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 "base/basictypes.h" |
| #include "base/json/json_writer.h" |
| #include "base/lazy_instance.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "content/browser/child_process_security_policy_impl.h" |
| #include "content/browser/devtools/devtools_manager.h" |
| #include "content/browser/devtools/protocol/devtools_protocol_handler.h" |
| #include "content/browser/devtools/protocol/dom_handler.h" |
| #include "content/browser/devtools/protocol/input_handler.h" |
| #include "content/browser/devtools/protocol/inspector_handler.h" |
| #include "content/browser/devtools/protocol/network_handler.h" |
| #include "content/browser/devtools/protocol/page_handler.h" |
| #include "content/browser/devtools/protocol/power_handler.h" |
| #include "content/browser/devtools/protocol/tracing_handler.h" |
| #include "content/browser/frame_host/render_frame_host_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/devtools_manager_delegate.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_types.h" |
| #include "content/public/browser/render_widget_host_iterator.h" |
| #include "content/public/browser/web_contents_delegate.h" |
| |
| #if defined(OS_ANDROID) |
| #include "content/browser/power_save_blocker_impl.h" |
| #include "content/public/browser/render_widget_host_view.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; |
| } |
| |
| // Returns RenderFrameDevToolsAgentHost attached to any of RenderFrameHost |
| // instances associated with |web_contents| |
| static RenderFrameDevToolsAgentHost* FindAgentHost(WebContents* web_contents) { |
| if (g_instances == NULL) |
| return NULL; |
| for (Instances::iterator it = g_instances.Get().begin(); |
| it != g_instances.Get().end(); ++it) { |
| if ((*it)->GetWebContents() == web_contents) |
| return *it; |
| } |
| return NULL; |
| } |
| |
| } // namespace |
| |
| scoped_refptr<DevToolsAgentHost> |
| DevToolsAgentHost::GetOrCreateFor(WebContents* web_contents) { |
| RenderFrameDevToolsAgentHost* result = FindAgentHost(web_contents); |
| if (!result) |
| result = new RenderFrameDevToolsAgentHost(web_contents->GetMainFrame()); |
| return result; |
| } |
| |
| // 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 |
| std::vector<WebContents*> DevToolsAgentHostImpl::GetInspectableWebContents() { |
| std::set<WebContents*> set; |
| scoped_ptr<RenderWidgetHostIterator> widgets( |
| RenderWidgetHost::GetRenderWidgetHosts()); |
| while (RenderWidgetHost* widget = widgets->GetNextHost()) { |
| // Ignore processes that don't have a connection, such as crashed contents. |
| if (!widget->GetProcess()->HasConnection()) |
| continue; |
| if (!widget->IsRenderView()) |
| continue; |
| |
| RenderViewHost* rvh = RenderViewHost::From(widget); |
| WebContents* web_contents = WebContents::FromRenderViewHost(rvh); |
| if (web_contents) |
| set.insert(web_contents); |
| } |
| std::vector<WebContents*> result(set.size()); |
| std::copy(set.begin(), set.end(), result.begin()); |
| return result; |
| } |
| |
| // static |
| void RenderFrameDevToolsAgentHost::OnCancelPendingNavigation( |
| RenderFrameHost* pending, |
| RenderFrameHost* current) { |
| RenderFrameDevToolsAgentHost* agent_host = FindAgentHost(pending); |
| if (!agent_host) |
| return; |
| agent_host->DisconnectRenderFrameHost(); |
| agent_host->ConnectRenderFrameHost(current); |
| } |
| |
| RenderFrameDevToolsAgentHost::RenderFrameDevToolsAgentHost(RenderFrameHost* rfh) |
| : render_frame_host_(NULL), |
| dom_handler_(new devtools::dom::DOMHandler()), |
| input_handler_(new devtools::input::InputHandler()), |
| inspector_handler_(new devtools::inspector::InspectorHandler()), |
| network_handler_(new devtools::network::NetworkHandler()), |
| page_handler_(new devtools::page::PageHandler()), |
| power_handler_(new devtools::power::PowerHandler()), |
| tracing_handler_(new devtools::tracing::TracingHandler( |
| devtools::tracing::TracingHandler::Renderer)), |
| protocol_handler_(new DevToolsProtocolHandler( |
| base::Bind(&RenderFrameDevToolsAgentHost::DispatchOnInspectorFrontend, |
| base::Unretained(this)))), |
| reattaching_(false) { |
| DevToolsProtocolDispatcher* dispatcher = protocol_handler_->dispatcher(); |
| dispatcher->SetDOMHandler(dom_handler_.get()); |
| dispatcher->SetInputHandler(input_handler_.get()); |
| dispatcher->SetInspectorHandler(inspector_handler_.get()); |
| dispatcher->SetNetworkHandler(network_handler_.get()); |
| dispatcher->SetPageHandler(page_handler_.get()); |
| dispatcher->SetPowerHandler(power_handler_.get()); |
| dispatcher->SetTracingHandler(tracing_handler_.get()); |
| SetRenderFrameHost(rfh); |
| g_instances.Get().push_back(this); |
| AddRef(); // Balanced in RenderFrameHostDestroyed. |
| DevToolsManager::GetInstance()->AgentHostChanged(this); |
| } |
| |
| BrowserContext* RenderFrameDevToolsAgentHost::GetBrowserContext() { |
| WebContents* contents = web_contents(); |
| return contents ? contents->GetBrowserContext() : nullptr; |
| } |
| |
| WebContents* RenderFrameDevToolsAgentHost::GetWebContents() { |
| return web_contents(); |
| } |
| |
| void RenderFrameDevToolsAgentHost::DispatchProtocolMessage( |
| const std::string& message) { |
| scoped_ptr<base::DictionaryValue> command = |
| protocol_handler_->ParseCommand(message); |
| if (!command) |
| return; |
| |
| DevToolsManagerDelegate* delegate = |
| DevToolsManager::GetInstance()->delegate(); |
| if (delegate) { |
| scoped_ptr<base::DictionaryValue> response( |
| delegate->HandleCommand(this, command.get())); |
| if (response) { |
| std::string json_response; |
| base::JSONWriter::Write(response.get(), &json_response); |
| DispatchOnInspectorFrontend(json_response); |
| return; |
| } |
| } |
| |
| if (protocol_handler_->HandleOptionalCommand(command.Pass())) |
| return; |
| |
| IPCDevToolsAgentHost::DispatchProtocolMessage(message); |
| } |
| |
| void RenderFrameDevToolsAgentHost::SendMessageToAgent(IPC::Message* msg) { |
| if (!render_frame_host_) |
| return; |
| msg->set_routing_id(render_frame_host_->GetRoutingID()); |
| render_frame_host_->Send(msg); |
| } |
| |
| void RenderFrameDevToolsAgentHost::OnClientAttached() { |
| if (!render_frame_host_) |
| return; |
| |
| InnerOnClientAttached(); |
| |
| // TODO(kaznacheev): Move this call back to DevToolsManager when |
| // extensions::ProcessManager no longer relies on this notification. |
| if (!reattaching_) |
| DevToolsAgentHostImpl::NotifyCallbacks(this, true); |
| } |
| |
| void RenderFrameDevToolsAgentHost::InnerOnClientAttached() { |
| ChildProcessSecurityPolicyImpl::GetInstance()->GrantReadRawCookies( |
| render_frame_host_->GetProcess()->GetID()); |
| |
| #if defined(OS_ANDROID) |
| power_save_blocker_.reset( |
| static_cast<PowerSaveBlockerImpl*>( |
| PowerSaveBlocker::Create( |
| PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep, |
| "DevTools").release())); |
| RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( |
| render_frame_host_->GetRenderViewHost()); |
| if (rvh->GetView()) { |
| power_save_blocker_.get()-> |
| InitDisplaySleepBlocker(rvh->GetView()->GetNativeView()); |
| } |
| #endif |
| } |
| |
| void RenderFrameDevToolsAgentHost::OnClientDetached() { |
| #if defined(OS_ANDROID) |
| power_save_blocker_.reset(); |
| #endif |
| page_handler_->Detached(); |
| power_handler_->Detached(); |
| tracing_handler_->Detached(); |
| ClientDetachedFromRenderer(); |
| |
| // TODO(kaznacheev): Move this call back to DevToolsManager when |
| // extensions::ProcessManager no longer relies on this notification. |
| if (!reattaching_) |
| DevToolsAgentHostImpl::NotifyCallbacks(this, false); |
| } |
| |
| void RenderFrameDevToolsAgentHost::ClientDetachedFromRenderer() { |
| if (!render_frame_host_) |
| return; |
| |
| InnerClientDetachedFromRenderer(); |
| } |
| |
| void RenderFrameDevToolsAgentHost::InnerClientDetachedFromRenderer() { |
| bool process_has_agents = false; |
| RenderProcessHost* render_process_host = render_frame_host_->GetProcess(); |
| for (Instances::iterator it = g_instances.Get().begin(); |
| it != g_instances.Get().end(); ++it) { |
| if (*it == this || !(*it)->IsAttached()) |
| continue; |
| RenderFrameHost* rfh = (*it)->render_frame_host_; |
| if (rfh && rfh->GetProcess() == render_process_host) |
| process_has_agents = true; |
| } |
| |
| // We are the last to disconnect from the renderer -> revoke permissions. |
| if (!process_has_agents) { |
| ChildProcessSecurityPolicyImpl::GetInstance()->RevokeReadRawCookies( |
| render_process_host->GetID()); |
| } |
| } |
| |
| 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); |
| } |
| |
| // TODO(creis): Consider removing this in favor of RenderFrameHostChanged. |
| void RenderFrameDevToolsAgentHost::AboutToNavigateRenderFrame( |
| RenderFrameHost* old_host, |
| RenderFrameHost* new_host) { |
| if (render_frame_host_ != old_host) |
| return; |
| |
| // TODO(creis): This will need to be updated for --site-per-process, since |
| // RenderViewHost is going away and navigations could happen in any frame. |
| if (render_frame_host_ == new_host) { |
| RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( |
| render_frame_host_->GetRenderViewHost()); |
| if (rvh->render_view_termination_status() == |
| base::TERMINATION_STATUS_STILL_RUNNING) |
| return; |
| } |
| ReattachToRenderFrameHost(new_host); |
| } |
| |
| void RenderFrameDevToolsAgentHost::RenderFrameHostChanged( |
| RenderFrameHost* old_host, |
| RenderFrameHost* new_host) { |
| if (old_host == render_frame_host_ && new_host != render_frame_host_) { |
| // AboutToNavigateRenderFrame was not called for renderer-initiated |
| // navigation. |
| ReattachToRenderFrameHost(new_host); |
| } |
| } |
| |
| void |
| RenderFrameDevToolsAgentHost::ReattachToRenderFrameHost(RenderFrameHost* rfh) { |
| DCHECK(!reattaching_); |
| reattaching_ = true; |
| DisconnectRenderFrameHost(); |
| ConnectRenderFrameHost(rfh); |
| reattaching_ = false; |
| } |
| |
| void RenderFrameDevToolsAgentHost::RenderFrameDeleted(RenderFrameHost* rfh) { |
| if (rfh != render_frame_host_) |
| return; |
| |
| DCHECK(render_frame_host_); |
| scoped_refptr<RenderFrameDevToolsAgentHost> protect(this); |
| HostClosed(); |
| ClearRenderFrameHost(); |
| DevToolsManager::GetInstance()->AgentHostChanged(this); |
| Release(); |
| } |
| |
| void RenderFrameDevToolsAgentHost::RenderProcessGone( |
| base::TerminationStatus status) { |
| switch(status) { |
| case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: |
| case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: |
| case base::TERMINATION_STATUS_PROCESS_CRASHED: |
| #if defined(OS_ANDROID) |
| case base::TERMINATION_STATUS_OOM_PROTECTED: |
| #endif |
| RenderFrameCrashed(); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| bool RenderFrameDevToolsAgentHost::OnMessageReceived( |
| const IPC::Message& message) { |
| if (!render_frame_host_) |
| return false; |
| if (message.type() == ViewHostMsg_SwapCompositorFrame::ID) |
| OnSwapCompositorFrame(message); |
| return false; |
| } |
| |
| bool RenderFrameDevToolsAgentHost::OnMessageReceived( |
| const IPC::Message& message, |
| RenderFrameHost* render_frame_host) { |
| if (!render_frame_host_ || render_frame_host != render_frame_host_) |
| return false; |
| |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(RenderFrameDevToolsAgentHost, message) |
| IPC_MESSAGE_HANDLER(DevToolsClientMsg_DispatchOnInspectorFrontend, |
| OnDispatchOnInspectorFrontend) |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| return handled; |
| } |
| |
| void RenderFrameDevToolsAgentHost::DidAttachInterstitialPage() { |
| page_handler_->DidAttachInterstitialPage(); |
| |
| if (!render_frame_host_) |
| return; |
| // The rvh set in AboutToNavigateRenderFrame turned out to be interstitial. |
| // Connect back to the real one. |
| WebContents* web_contents = |
| WebContents::FromRenderFrameHost(render_frame_host_); |
| if (!web_contents) |
| return; |
| DisconnectRenderFrameHost(); |
| ConnectRenderFrameHost(web_contents->GetMainFrame()); |
| } |
| |
| void RenderFrameDevToolsAgentHost::DidDetachInterstitialPage() { |
| page_handler_->DidDetachInterstitialPage(); |
| } |
| |
| void RenderFrameDevToolsAgentHost::TitleWasSet( |
| NavigationEntry* entry, bool explicit_set) { |
| DevToolsManager::GetInstance()->AgentHostChanged(this); |
| } |
| |
| void RenderFrameDevToolsAgentHost::NavigationEntryCommitted( |
| const LoadCommittedDetails& load_details) { |
| DevToolsManager::GetInstance()->AgentHostChanged(this); |
| } |
| |
| void RenderFrameDevToolsAgentHost::Observe(int type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| if (type == content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED) { |
| bool visible = *Details<bool>(details).ptr(); |
| page_handler_->OnVisibilityChanged(visible); |
| } |
| } |
| |
| void RenderFrameDevToolsAgentHost::SetRenderFrameHost(RenderFrameHost* rfh) { |
| DCHECK(!render_frame_host_); |
| render_frame_host_ = static_cast<RenderFrameHostImpl*>(rfh); |
| |
| WebContentsObserver::Observe(WebContents::FromRenderFrameHost(rfh)); |
| RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( |
| rfh->GetRenderViewHost()); |
| dom_handler_->SetRenderViewHost(rvh); |
| input_handler_->SetRenderViewHost(rvh); |
| network_handler_->SetRenderViewHost(rvh); |
| page_handler_->SetRenderViewHost(rvh); |
| |
| registrar_.Add( |
| this, |
| content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED, |
| content::Source<RenderWidgetHost>(rvh)); |
| } |
| |
| void RenderFrameDevToolsAgentHost::ClearRenderFrameHost() { |
| DCHECK(render_frame_host_); |
| RenderViewHostImpl* rvh = static_cast<RenderViewHostImpl*>( |
| render_frame_host_->GetRenderViewHost()); |
| registrar_.Remove( |
| this, |
| content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED, |
| content::Source<RenderWidgetHost>(rvh)); |
| render_frame_host_ = nullptr; |
| dom_handler_->SetRenderViewHost(nullptr); |
| input_handler_->SetRenderViewHost(nullptr); |
| network_handler_->SetRenderViewHost(nullptr); |
| page_handler_->SetRenderViewHost(nullptr); |
| } |
| |
| void RenderFrameDevToolsAgentHost::DisconnectWebContents() { |
| DisconnectRenderFrameHost(); |
| } |
| |
| void RenderFrameDevToolsAgentHost::ConnectWebContents(WebContents* wc) { |
| ConnectRenderFrameHost(wc->GetMainFrame()); |
| } |
| |
| DevToolsAgentHost::Type RenderFrameDevToolsAgentHost::GetType() { |
| return TYPE_WEB_CONTENTS; |
| } |
| |
| std::string RenderFrameDevToolsAgentHost::GetTitle() { |
| if (WebContents* web_contents = GetWebContents()) |
| return base::UTF16ToUTF8(web_contents->GetTitle()); |
| return ""; |
| } |
| |
| GURL RenderFrameDevToolsAgentHost::GetURL() { |
| if (WebContents* web_contents = GetWebContents()) |
| return web_contents->GetVisibleURL(); |
| return render_frame_host_ ? |
| render_frame_host_->GetLastCommittedURL() : GURL(); |
| } |
| |
| bool RenderFrameDevToolsAgentHost::Activate() { |
| if (render_frame_host_) { |
| render_frame_host_->GetRenderViewHost()->GetDelegate()->Activate(); |
| return true; |
| } |
| return false; |
| } |
| |
| bool RenderFrameDevToolsAgentHost::Close() { |
| if (render_frame_host_) { |
| render_frame_host_->GetRenderViewHost()->ClosePage(); |
| return true; |
| } |
| return false; |
| } |
| |
| void RenderFrameDevToolsAgentHost::ConnectRenderFrameHost( |
| RenderFrameHost* rfh) { |
| SetRenderFrameHost(rfh); |
| if (IsAttached()) |
| Reattach(); |
| } |
| |
| void RenderFrameDevToolsAgentHost::DisconnectRenderFrameHost() { |
| ClientDetachedFromRenderer(); |
| ClearRenderFrameHost(); |
| } |
| |
| void RenderFrameDevToolsAgentHost::RenderFrameCrashed() { |
| inspector_handler_->TargetCrashed(); |
| } |
| |
| void RenderFrameDevToolsAgentHost::OnSwapCompositorFrame( |
| const IPC::Message& message) { |
| ViewHostMsg_SwapCompositorFrame::Param param; |
| if (!ViewHostMsg_SwapCompositorFrame::Read(&message, ¶m)) |
| return; |
| page_handler_->OnSwapCompositorFrame(get<1>(param).metadata); |
| } |
| |
| void RenderFrameDevToolsAgentHost::SynchronousSwapCompositorFrame( |
| const cc::CompositorFrameMetadata& frame_metadata) { |
| if (!render_frame_host_) |
| return; |
| page_handler_->OnSwapCompositorFrame(frame_metadata); |
| } |
| |
| bool RenderFrameDevToolsAgentHost::HasRenderFrameHost( |
| RenderFrameHost* host) { |
| return host == render_frame_host_; |
| } |
| |
| void RenderFrameDevToolsAgentHost::OnDispatchOnInspectorFrontend( |
| const DevToolsMessageChunk& message) { |
| if (!IsAttached() || !render_frame_host_) |
| return; |
| ProcessChunkedMessageFromAgent(message); |
| } |
| |
| void RenderFrameDevToolsAgentHost::DispatchOnInspectorFrontend( |
| const std::string& message) { |
| if (!IsAttached() || !render_frame_host_) |
| return; |
| SendMessageToClient(message); |
| } |
| |
| } // namespace content |