| // Copyright (c) 2011 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 "chrome/browser/debugger/devtools_manager.h" |
| |
| #include <vector> |
| |
| #include "base/auto_reset.h" |
| #include "base/message_loop.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/debugger/devtools_client_host.h" |
| #include "chrome/browser/debugger/devtools_netlog_observer.h" |
| #include "chrome/browser/debugger/devtools_window.h" |
| #include "chrome/browser/io_thread.h" |
| #include "chrome/browser/prefs/pref_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" |
| #include "chrome/common/devtools_messages.h" |
| #include "chrome/common/pref_names.h" |
| #include "content/browser/browsing_instance.h" |
| #include "content/browser/child_process_security_policy.h" |
| #include "content/browser/renderer_host/render_view_host.h" |
| #include "content/browser/site_instance.h" |
| #include "content/common/notification_service.h" |
| #include "googleurl/src/gurl.h" |
| |
| // static |
| DevToolsManager* DevToolsManager::GetInstance() { |
| // http://crbug.com/47806 this method may be called when BrowserProcess |
| // has already been destroyed. |
| if (!g_browser_process) |
| return NULL; |
| return g_browser_process->devtools_manager(); |
| } |
| |
| // static |
| void DevToolsManager::RegisterUserPrefs(PrefService* prefs) { |
| prefs->RegisterBooleanPref(prefs::kDevToolsOpenDocked, true); |
| } |
| |
| DevToolsManager::DevToolsManager() |
| : inspected_rvh_for_reopen_(NULL), |
| in_initial_show_(false), |
| last_orphan_cookie_(0) { |
| registrar_.Add(this, NotificationType::RENDER_VIEW_HOST_DELETED, |
| NotificationService::AllSources()); |
| } |
| |
| DevToolsManager::~DevToolsManager() { |
| DCHECK(inspected_rvh_to_client_host_.empty()); |
| DCHECK(client_host_to_inspected_rvh_.empty()); |
| // By the time we destroy devtools manager, all orphan client hosts should |
| // have been delelted, no need to notify them upon tab closing. |
| DCHECK(orphan_client_hosts_.empty()); |
| } |
| |
| DevToolsClientHost* DevToolsManager::GetDevToolsClientHostFor( |
| RenderViewHost* inspected_rvh) { |
| InspectedRvhToClientHostMap::iterator it = |
| inspected_rvh_to_client_host_.find(inspected_rvh); |
| if (it != inspected_rvh_to_client_host_.end()) |
| return it->second; |
| return NULL; |
| } |
| |
| void DevToolsManager::RegisterDevToolsClientHostFor( |
| RenderViewHost* inspected_rvh, |
| DevToolsClientHost* client_host) { |
| DCHECK(!GetDevToolsClientHostFor(inspected_rvh)); |
| |
| DevToolsRuntimeProperties initial_properties; |
| BindClientHost(inspected_rvh, client_host, initial_properties); |
| client_host->set_close_listener(this); |
| SendAttachToAgent(inspected_rvh); |
| } |
| |
| void DevToolsManager::ForwardToDevToolsAgent( |
| RenderViewHost* client_rvh, |
| const IPC::Message& message) { |
| DevToolsClientHost* client_host = FindOwnerDevToolsClientHost(client_rvh); |
| if (client_host) |
| ForwardToDevToolsAgent(client_host, message); |
| } |
| |
| void DevToolsManager::ForwardToDevToolsAgent(DevToolsClientHost* from, |
| const IPC::Message& message) { |
| RenderViewHost* inspected_rvh = GetInspectedRenderViewHost(from); |
| if (!inspected_rvh) { |
| // TODO(yurys): notify client that the agent is no longer available |
| NOTREACHED(); |
| return; |
| } |
| |
| IPC::Message* m = new IPC::Message(message); |
| m->set_routing_id(inspected_rvh->routing_id()); |
| inspected_rvh->Send(m); |
| } |
| |
| void DevToolsManager::ForwardToDevToolsClient(RenderViewHost* inspected_rvh, |
| const IPC::Message& message) { |
| DevToolsClientHost* client_host = GetDevToolsClientHostFor(inspected_rvh); |
| if (!client_host) { |
| // Client window was closed while there were messages |
| // being sent to it. |
| return; |
| } |
| client_host->SendMessageToClient(message); |
| } |
| |
| void DevToolsManager::ActivateWindow(RenderViewHost* client_rvh) { |
| DevToolsClientHost* client_host = FindOwnerDevToolsClientHost(client_rvh); |
| if (!client_host) |
| return; |
| |
| DevToolsWindow* window = client_host->AsDevToolsWindow(); |
| DCHECK(window); |
| window->Activate(); |
| } |
| |
| void DevToolsManager::CloseWindow(RenderViewHost* client_rvh) { |
| DevToolsClientHost* client_host = FindOwnerDevToolsClientHost(client_rvh); |
| if (client_host) { |
| RenderViewHost* inspected_rvh = GetInspectedRenderViewHost(client_host); |
| DCHECK(inspected_rvh); |
| UnregisterDevToolsClientHostFor(inspected_rvh); |
| } |
| } |
| |
| void DevToolsManager::RequestDockWindow(RenderViewHost* client_rvh) { |
| ReopenWindow(client_rvh, true); |
| } |
| |
| void DevToolsManager::RequestUndockWindow(RenderViewHost* client_rvh) { |
| ReopenWindow(client_rvh, false); |
| } |
| |
| void DevToolsManager::OpenDevToolsWindow(RenderViewHost* inspected_rvh) { |
| ToggleDevToolsWindow( |
| inspected_rvh, |
| true, |
| DEVTOOLS_TOGGLE_ACTION_NONE); |
| } |
| |
| void DevToolsManager::ToggleDevToolsWindow( |
| RenderViewHost* inspected_rvh, |
| DevToolsToggleAction action) { |
| ToggleDevToolsWindow(inspected_rvh, false, action); |
| } |
| |
| void DevToolsManager::RuntimePropertyChanged(RenderViewHost* inspected_rvh, |
| const std::string& name, |
| const std::string& value) { |
| RuntimePropertiesMap::iterator it = |
| runtime_properties_map_.find(inspected_rvh); |
| if (it == runtime_properties_map_.end()) { |
| std::pair<RenderViewHost*, DevToolsRuntimeProperties> value( |
| inspected_rvh, |
| DevToolsRuntimeProperties()); |
| it = runtime_properties_map_.insert(value).first; |
| } |
| it->second[name] = value; |
| } |
| |
| void DevToolsManager::InspectElement(RenderViewHost* inspected_rvh, |
| int x, |
| int y) { |
| IPC::Message* m = new DevToolsAgentMsg_InspectElement(x, y); |
| m->set_routing_id(inspected_rvh->routing_id()); |
| inspected_rvh->Send(m); |
| // TODO(loislo): we should initiate DevTools window opening from within |
| // renderer. Otherwise, we still can hit a race condition here. |
| OpenDevToolsWindow(inspected_rvh); |
| } |
| |
| void DevToolsManager::ClientHostClosing(DevToolsClientHost* host) { |
| RenderViewHost* inspected_rvh = GetInspectedRenderViewHost(host); |
| if (!inspected_rvh) { |
| // It might be in the list of orphan client hosts, remove it from there. |
| for (OrphanClientHosts::iterator it = orphan_client_hosts_.begin(); |
| it != orphan_client_hosts_.end(); ++it) { |
| if (it->second.first == host) { |
| orphan_client_hosts_.erase(it->first); |
| return; |
| } |
| } |
| return; |
| } |
| |
| NotificationService::current()->Notify( |
| NotificationType::DEVTOOLS_WINDOW_CLOSING, |
| Source<Profile>(inspected_rvh->site_instance()->GetProcess()->profile()), |
| Details<RenderViewHost>(inspected_rvh)); |
| |
| UnbindClientHost(inspected_rvh, host); |
| } |
| |
| void DevToolsManager::Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| DCHECK(type == NotificationType::RENDER_VIEW_HOST_DELETED); |
| UnregisterDevToolsClientHostFor(Source<RenderViewHost>(source).ptr()); |
| } |
| |
| RenderViewHost* DevToolsManager::GetInspectedRenderViewHost( |
| DevToolsClientHost* client_host) { |
| ClientHostToInspectedRvhMap::iterator it = |
| client_host_to_inspected_rvh_.find(client_host); |
| if (it != client_host_to_inspected_rvh_.end()) |
| return it->second; |
| return NULL; |
| } |
| |
| void DevToolsManager::UnregisterDevToolsClientHostFor( |
| RenderViewHost* inspected_rvh) { |
| DevToolsClientHost* host = GetDevToolsClientHostFor(inspected_rvh); |
| if (!host) |
| return; |
| UnbindClientHost(inspected_rvh, host); |
| host->InspectedTabClosing(); |
| } |
| |
| void DevToolsManager::OnNavigatingToPendingEntry(RenderViewHost* rvh, |
| RenderViewHost* dest_rvh, |
| const GURL& gurl) { |
| if (in_initial_show_) { |
| // Mute this even in case it is caused by the initial show routines. |
| return; |
| } |
| |
| int cookie = DetachClientHost(rvh); |
| if (cookie != -1) { |
| // Navigating to URL in the inspected window. |
| AttachClientHost(cookie, dest_rvh); |
| |
| DevToolsClientHost* client_host = GetDevToolsClientHostFor(dest_rvh); |
| client_host->FrameNavigating(gurl.spec()); |
| |
| return; |
| } |
| |
| // Iterate over client hosts and if there is one that has render view host |
| // changing, reopen entire client window (this must be caused by the user |
| // manually refreshing its content). |
| for (ClientHostToInspectedRvhMap::iterator it = |
| client_host_to_inspected_rvh_.begin(); |
| it != client_host_to_inspected_rvh_.end(); ++it) { |
| DevToolsWindow* window = it->first->AsDevToolsWindow(); |
| if (window && window->GetRenderViewHost() == rvh) { |
| inspected_rvh_for_reopen_ = it->second; |
| MessageLoop::current()->PostTask(FROM_HERE, |
| NewRunnableMethod(this, |
| &DevToolsManager::ForceReopenWindow)); |
| return; |
| } |
| } |
| } |
| |
| void DevToolsManager::TabReplaced(TabContentsWrapper* old_tab, |
| TabContentsWrapper* new_tab) { |
| RenderViewHost* old_rvh = old_tab->tab_contents()->render_view_host(); |
| DevToolsClientHost* client_host = GetDevToolsClientHostFor(old_rvh); |
| if (!client_host) |
| return; // Didn't know about old_tab. |
| int cookie = DetachClientHost(old_rvh); |
| if (cookie == -1) |
| return; // Didn't know about old_tab. |
| |
| client_host->TabReplaced(new_tab); |
| AttachClientHost(cookie, new_tab->tab_contents()->render_view_host()); |
| } |
| |
| int DevToolsManager::DetachClientHost(RenderViewHost* from_rvh) { |
| DevToolsClientHost* client_host = GetDevToolsClientHostFor(from_rvh); |
| if (!client_host) |
| return -1; |
| |
| int cookie = last_orphan_cookie_++; |
| orphan_client_hosts_[cookie] = |
| std::pair<DevToolsClientHost*, DevToolsRuntimeProperties>( |
| client_host, runtime_properties_map_[from_rvh]); |
| |
| UnbindClientHost(from_rvh, client_host); |
| return cookie; |
| } |
| |
| void DevToolsManager::AttachClientHost(int client_host_cookie, |
| RenderViewHost* to_rvh) { |
| OrphanClientHosts::iterator it = orphan_client_hosts_.find( |
| client_host_cookie); |
| if (it == orphan_client_hosts_.end()) |
| return; |
| |
| DevToolsClientHost* client_host = (*it).second.first; |
| BindClientHost(to_rvh, client_host, (*it).second.second); |
| SendAttachToAgent(to_rvh); |
| |
| orphan_client_hosts_.erase(client_host_cookie); |
| } |
| |
| void DevToolsManager::SendAttachToAgent(RenderViewHost* inspected_rvh) { |
| if (inspected_rvh) { |
| ChildProcessSecurityPolicy::GetInstance()->GrantReadRawCookies( |
| inspected_rvh->process()->id()); |
| |
| DevToolsRuntimeProperties properties; |
| RuntimePropertiesMap::iterator it = |
| runtime_properties_map_.find(inspected_rvh); |
| if (it != runtime_properties_map_.end()) { |
| properties = DevToolsRuntimeProperties(it->second.begin(), |
| it->second.end()); |
| } |
| IPC::Message* m = new DevToolsAgentMsg_Attach(properties); |
| m->set_routing_id(inspected_rvh->routing_id()); |
| inspected_rvh->Send(m); |
| } |
| } |
| |
| void DevToolsManager::SendDetachToAgent(RenderViewHost* inspected_rvh) { |
| if (inspected_rvh) { |
| IPC::Message* m = new DevToolsAgentMsg_Detach(); |
| m->set_routing_id(inspected_rvh->routing_id()); |
| inspected_rvh->Send(m); |
| } |
| } |
| |
| void DevToolsManager::ForceReopenWindow() { |
| if (inspected_rvh_for_reopen_) { |
| RenderViewHost* inspected_rvn = inspected_rvh_for_reopen_; |
| UnregisterDevToolsClientHostFor(inspected_rvn); |
| OpenDevToolsWindow(inspected_rvn); |
| } |
| } |
| |
| DevToolsClientHost* DevToolsManager::FindOwnerDevToolsClientHost( |
| RenderViewHost* client_rvh) { |
| for (InspectedRvhToClientHostMap::iterator it = |
| inspected_rvh_to_client_host_.begin(); |
| it != inspected_rvh_to_client_host_.end(); |
| ++it) { |
| DevToolsWindow* win = it->second->AsDevToolsWindow(); |
| if (!win) |
| continue; |
| if (client_rvh == win->GetRenderViewHost()) |
| return it->second; |
| } |
| return NULL; |
| } |
| |
| void DevToolsManager::ReopenWindow(RenderViewHost* client_rvh, bool docked) { |
| DevToolsClientHost* client_host = FindOwnerDevToolsClientHost(client_rvh); |
| if (!client_host) |
| return; |
| RenderViewHost* inspected_rvh = GetInspectedRenderViewHost(client_host); |
| DCHECK(inspected_rvh); |
| inspected_rvh->process()->profile()->GetPrefs()->SetBoolean( |
| prefs::kDevToolsOpenDocked, docked); |
| |
| DevToolsWindow* window = client_host->AsDevToolsWindow(); |
| DCHECK(window); |
| window->SetDocked(docked); |
| } |
| |
| void DevToolsManager::ToggleDevToolsWindow( |
| RenderViewHost* inspected_rvh, |
| bool force_open, |
| DevToolsToggleAction action) { |
| bool do_open = force_open; |
| DevToolsClientHost* host = GetDevToolsClientHostFor(inspected_rvh); |
| |
| if (host != NULL && host->AsDevToolsWindow() == NULL) { |
| // Break remote debugging / extension debugging session. |
| UnregisterDevToolsClientHostFor(inspected_rvh); |
| host = NULL; |
| } |
| |
| if (!host) { |
| bool docked = inspected_rvh->process()->profile()->GetPrefs()-> |
| GetBoolean(prefs::kDevToolsOpenDocked); |
| host = new DevToolsWindow( |
| inspected_rvh->site_instance()->browsing_instance()->profile(), |
| inspected_rvh, |
| docked); |
| RegisterDevToolsClientHostFor(inspected_rvh, host); |
| do_open = true; |
| } |
| |
| DevToolsWindow* window = host->AsDevToolsWindow(); |
| // If window is docked and visible, we hide it on toggle. If window is |
| // undocked, we show (activate) it. |
| if (!window->is_docked() || do_open) { |
| AutoReset<bool> auto_reset_in_initial_show(&in_initial_show_, true); |
| window->Show(action); |
| } else { |
| UnregisterDevToolsClientHostFor(inspected_rvh); |
| } |
| } |
| |
| void DevToolsManager::BindClientHost( |
| RenderViewHost* inspected_rvh, |
| DevToolsClientHost* client_host, |
| const DevToolsRuntimeProperties& runtime_properties) { |
| DCHECK(inspected_rvh_to_client_host_.find(inspected_rvh) == |
| inspected_rvh_to_client_host_.end()); |
| DCHECK(client_host_to_inspected_rvh_.find(client_host) == |
| client_host_to_inspected_rvh_.end()); |
| |
| if (client_host_to_inspected_rvh_.empty()) { |
| BrowserThread::PostTask( |
| BrowserThread::IO, |
| FROM_HERE, |
| NewRunnableFunction(&DevToolsNetLogObserver::Attach, |
| g_browser_process->io_thread())); |
| } |
| inspected_rvh_to_client_host_[inspected_rvh] = client_host; |
| client_host_to_inspected_rvh_[client_host] = inspected_rvh; |
| runtime_properties_map_[inspected_rvh] = runtime_properties; |
| } |
| |
| void DevToolsManager::UnbindClientHost(RenderViewHost* inspected_rvh, |
| DevToolsClientHost* client_host) { |
| DCHECK(inspected_rvh_to_client_host_.find(inspected_rvh)->second == |
| client_host); |
| DCHECK(client_host_to_inspected_rvh_.find(client_host)->second == |
| inspected_rvh); |
| |
| inspected_rvh_to_client_host_.erase(inspected_rvh); |
| client_host_to_inspected_rvh_.erase(client_host); |
| runtime_properties_map_.erase(inspected_rvh); |
| |
| if (client_host_to_inspected_rvh_.empty()) { |
| BrowserThread::PostTask( |
| BrowserThread::IO, |
| FROM_HERE, |
| NewRunnableFunction(&DevToolsNetLogObserver::Detach)); |
| } |
| SendDetachToAgent(inspected_rvh); |
| if (inspected_rvh_for_reopen_ == inspected_rvh) |
| inspected_rvh_for_reopen_ = NULL; |
| |
| int process_id = inspected_rvh->process()->id(); |
| for (InspectedRvhToClientHostMap::iterator it = |
| inspected_rvh_to_client_host_.begin(); |
| it != inspected_rvh_to_client_host_.end(); |
| ++it) { |
| if (it->first->process()->id() == process_id) |
| return; |
| } |
| // We've disconnected from the last renderer -> revoke cookie permissions. |
| ChildProcessSecurityPolicy::GetInstance()->RevokeReadRawCookies(process_id); |
| } |
| |
| void DevToolsManager::CloseAllClientHosts() { |
| std::vector<RenderViewHost*> rhvs; |
| for (InspectedRvhToClientHostMap::iterator it = |
| inspected_rvh_to_client_host_.begin(); |
| it != inspected_rvh_to_client_host_.end(); ++it) { |
| rhvs.push_back(it->first); |
| } |
| for (std::vector<RenderViewHost*>::iterator it = rhvs.begin(); |
| it != rhvs.end(); ++it) { |
| UnregisterDevToolsClientHostFor(*it); |
| } |
| } |