1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/debugger/devtools_manager.h" 6 7 #include <vector> 8 9 #include "base/auto_reset.h" 10 #include "base/message_loop.h" 11 #include "chrome/browser/browser_process.h" 12 #include "chrome/browser/debugger/devtools_client_host.h" 13 #include "chrome/browser/debugger/devtools_netlog_observer.h" 14 #include "chrome/browser/debugger/devtools_window.h" 15 #include "chrome/browser/io_thread.h" 16 #include "chrome/browser/prefs/pref_service.h" 17 #include "chrome/browser/profiles/profile.h" 18 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" 19 #include "chrome/common/devtools_messages.h" 20 #include "chrome/common/pref_names.h" 21 #include "content/browser/browsing_instance.h" 22 #include "content/browser/child_process_security_policy.h" 23 #include "content/browser/renderer_host/render_view_host.h" 24 #include "content/browser/site_instance.h" 25 #include "content/common/notification_service.h" 26 #include "googleurl/src/gurl.h" 27 28 // static 29 DevToolsManager* DevToolsManager::GetInstance() { 30 // http://crbug.com/47806 this method may be called when BrowserProcess 31 // has already been destroyed. 32 if (!g_browser_process) 33 return NULL; 34 return g_browser_process->devtools_manager(); 35 } 36 37 // static 38 void DevToolsManager::RegisterUserPrefs(PrefService* prefs) { 39 prefs->RegisterBooleanPref(prefs::kDevToolsOpenDocked, true); 40 } 41 42 DevToolsManager::DevToolsManager() 43 : inspected_rvh_for_reopen_(NULL), 44 in_initial_show_(false), 45 last_orphan_cookie_(0) { 46 registrar_.Add(this, NotificationType::RENDER_VIEW_HOST_DELETED, 47 NotificationService::AllSources()); 48 } 49 50 DevToolsManager::~DevToolsManager() { 51 DCHECK(inspected_rvh_to_client_host_.empty()); 52 DCHECK(client_host_to_inspected_rvh_.empty()); 53 // By the time we destroy devtools manager, all orphan client hosts should 54 // have been delelted, no need to notify them upon tab closing. 55 DCHECK(orphan_client_hosts_.empty()); 56 } 57 58 DevToolsClientHost* DevToolsManager::GetDevToolsClientHostFor( 59 RenderViewHost* inspected_rvh) { 60 InspectedRvhToClientHostMap::iterator it = 61 inspected_rvh_to_client_host_.find(inspected_rvh); 62 if (it != inspected_rvh_to_client_host_.end()) 63 return it->second; 64 return NULL; 65 } 66 67 void DevToolsManager::RegisterDevToolsClientHostFor( 68 RenderViewHost* inspected_rvh, 69 DevToolsClientHost* client_host) { 70 DCHECK(!GetDevToolsClientHostFor(inspected_rvh)); 71 72 DevToolsRuntimeProperties initial_properties; 73 BindClientHost(inspected_rvh, client_host, initial_properties); 74 client_host->set_close_listener(this); 75 SendAttachToAgent(inspected_rvh); 76 } 77 78 void DevToolsManager::ForwardToDevToolsAgent( 79 RenderViewHost* client_rvh, 80 const IPC::Message& message) { 81 DevToolsClientHost* client_host = FindOwnerDevToolsClientHost(client_rvh); 82 if (client_host) 83 ForwardToDevToolsAgent(client_host, message); 84 } 85 86 void DevToolsManager::ForwardToDevToolsAgent(DevToolsClientHost* from, 87 const IPC::Message& message) { 88 RenderViewHost* inspected_rvh = GetInspectedRenderViewHost(from); 89 if (!inspected_rvh) { 90 // TODO(yurys): notify client that the agent is no longer available 91 NOTREACHED(); 92 return; 93 } 94 95 IPC::Message* m = new IPC::Message(message); 96 m->set_routing_id(inspected_rvh->routing_id()); 97 inspected_rvh->Send(m); 98 } 99 100 void DevToolsManager::ForwardToDevToolsClient(RenderViewHost* inspected_rvh, 101 const IPC::Message& message) { 102 DevToolsClientHost* client_host = GetDevToolsClientHostFor(inspected_rvh); 103 if (!client_host) { 104 // Client window was closed while there were messages 105 // being sent to it. 106 return; 107 } 108 client_host->SendMessageToClient(message); 109 } 110 111 void DevToolsManager::ActivateWindow(RenderViewHost* client_rvh) { 112 DevToolsClientHost* client_host = FindOwnerDevToolsClientHost(client_rvh); 113 if (!client_host) 114 return; 115 116 DevToolsWindow* window = client_host->AsDevToolsWindow(); 117 DCHECK(window); 118 window->Activate(); 119 } 120 121 void DevToolsManager::CloseWindow(RenderViewHost* client_rvh) { 122 DevToolsClientHost* client_host = FindOwnerDevToolsClientHost(client_rvh); 123 if (client_host) { 124 RenderViewHost* inspected_rvh = GetInspectedRenderViewHost(client_host); 125 DCHECK(inspected_rvh); 126 UnregisterDevToolsClientHostFor(inspected_rvh); 127 } 128 } 129 130 void DevToolsManager::RequestDockWindow(RenderViewHost* client_rvh) { 131 ReopenWindow(client_rvh, true); 132 } 133 134 void DevToolsManager::RequestUndockWindow(RenderViewHost* client_rvh) { 135 ReopenWindow(client_rvh, false); 136 } 137 138 void DevToolsManager::OpenDevToolsWindow(RenderViewHost* inspected_rvh) { 139 ToggleDevToolsWindow( 140 inspected_rvh, 141 true, 142 DEVTOOLS_TOGGLE_ACTION_NONE); 143 } 144 145 void DevToolsManager::ToggleDevToolsWindow( 146 RenderViewHost* inspected_rvh, 147 DevToolsToggleAction action) { 148 ToggleDevToolsWindow(inspected_rvh, false, action); 149 } 150 151 void DevToolsManager::RuntimePropertyChanged(RenderViewHost* inspected_rvh, 152 const std::string& name, 153 const std::string& value) { 154 RuntimePropertiesMap::iterator it = 155 runtime_properties_map_.find(inspected_rvh); 156 if (it == runtime_properties_map_.end()) { 157 std::pair<RenderViewHost*, DevToolsRuntimeProperties> value( 158 inspected_rvh, 159 DevToolsRuntimeProperties()); 160 it = runtime_properties_map_.insert(value).first; 161 } 162 it->second[name] = value; 163 } 164 165 void DevToolsManager::InspectElement(RenderViewHost* inspected_rvh, 166 int x, 167 int y) { 168 IPC::Message* m = new DevToolsAgentMsg_InspectElement(x, y); 169 m->set_routing_id(inspected_rvh->routing_id()); 170 inspected_rvh->Send(m); 171 // TODO(loislo): we should initiate DevTools window opening from within 172 // renderer. Otherwise, we still can hit a race condition here. 173 OpenDevToolsWindow(inspected_rvh); 174 } 175 176 void DevToolsManager::ClientHostClosing(DevToolsClientHost* host) { 177 RenderViewHost* inspected_rvh = GetInspectedRenderViewHost(host); 178 if (!inspected_rvh) { 179 // It might be in the list of orphan client hosts, remove it from there. 180 for (OrphanClientHosts::iterator it = orphan_client_hosts_.begin(); 181 it != orphan_client_hosts_.end(); ++it) { 182 if (it->second.first == host) { 183 orphan_client_hosts_.erase(it->first); 184 return; 185 } 186 } 187 return; 188 } 189 190 NotificationService::current()->Notify( 191 NotificationType::DEVTOOLS_WINDOW_CLOSING, 192 Source<Profile>(inspected_rvh->site_instance()->GetProcess()->profile()), 193 Details<RenderViewHost>(inspected_rvh)); 194 195 UnbindClientHost(inspected_rvh, host); 196 } 197 198 void DevToolsManager::Observe(NotificationType type, 199 const NotificationSource& source, 200 const NotificationDetails& details) { 201 DCHECK(type == NotificationType::RENDER_VIEW_HOST_DELETED); 202 UnregisterDevToolsClientHostFor(Source<RenderViewHost>(source).ptr()); 203 } 204 205 RenderViewHost* DevToolsManager::GetInspectedRenderViewHost( 206 DevToolsClientHost* client_host) { 207 ClientHostToInspectedRvhMap::iterator it = 208 client_host_to_inspected_rvh_.find(client_host); 209 if (it != client_host_to_inspected_rvh_.end()) 210 return it->second; 211 return NULL; 212 } 213 214 void DevToolsManager::UnregisterDevToolsClientHostFor( 215 RenderViewHost* inspected_rvh) { 216 DevToolsClientHost* host = GetDevToolsClientHostFor(inspected_rvh); 217 if (!host) 218 return; 219 UnbindClientHost(inspected_rvh, host); 220 host->InspectedTabClosing(); 221 } 222 223 void DevToolsManager::OnNavigatingToPendingEntry(RenderViewHost* rvh, 224 RenderViewHost* dest_rvh, 225 const GURL& gurl) { 226 if (in_initial_show_) { 227 // Mute this even in case it is caused by the initial show routines. 228 return; 229 } 230 231 int cookie = DetachClientHost(rvh); 232 if (cookie != -1) { 233 // Navigating to URL in the inspected window. 234 AttachClientHost(cookie, dest_rvh); 235 236 DevToolsClientHost* client_host = GetDevToolsClientHostFor(dest_rvh); 237 client_host->FrameNavigating(gurl.spec()); 238 239 return; 240 } 241 242 // Iterate over client hosts and if there is one that has render view host 243 // changing, reopen entire client window (this must be caused by the user 244 // manually refreshing its content). 245 for (ClientHostToInspectedRvhMap::iterator it = 246 client_host_to_inspected_rvh_.begin(); 247 it != client_host_to_inspected_rvh_.end(); ++it) { 248 DevToolsWindow* window = it->first->AsDevToolsWindow(); 249 if (window && window->GetRenderViewHost() == rvh) { 250 inspected_rvh_for_reopen_ = it->second; 251 MessageLoop::current()->PostTask(FROM_HERE, 252 NewRunnableMethod(this, 253 &DevToolsManager::ForceReopenWindow)); 254 return; 255 } 256 } 257 } 258 259 void DevToolsManager::TabReplaced(TabContentsWrapper* old_tab, 260 TabContentsWrapper* new_tab) { 261 RenderViewHost* old_rvh = old_tab->tab_contents()->render_view_host(); 262 DevToolsClientHost* client_host = GetDevToolsClientHostFor(old_rvh); 263 if (!client_host) 264 return; // Didn't know about old_tab. 265 int cookie = DetachClientHost(old_rvh); 266 if (cookie == -1) 267 return; // Didn't know about old_tab. 268 269 client_host->TabReplaced(new_tab); 270 AttachClientHost(cookie, new_tab->tab_contents()->render_view_host()); 271 } 272 273 int DevToolsManager::DetachClientHost(RenderViewHost* from_rvh) { 274 DevToolsClientHost* client_host = GetDevToolsClientHostFor(from_rvh); 275 if (!client_host) 276 return -1; 277 278 int cookie = last_orphan_cookie_++; 279 orphan_client_hosts_[cookie] = 280 std::pair<DevToolsClientHost*, DevToolsRuntimeProperties>( 281 client_host, runtime_properties_map_[from_rvh]); 282 283 UnbindClientHost(from_rvh, client_host); 284 return cookie; 285 } 286 287 void DevToolsManager::AttachClientHost(int client_host_cookie, 288 RenderViewHost* to_rvh) { 289 OrphanClientHosts::iterator it = orphan_client_hosts_.find( 290 client_host_cookie); 291 if (it == orphan_client_hosts_.end()) 292 return; 293 294 DevToolsClientHost* client_host = (*it).second.first; 295 BindClientHost(to_rvh, client_host, (*it).second.second); 296 SendAttachToAgent(to_rvh); 297 298 orphan_client_hosts_.erase(client_host_cookie); 299 } 300 301 void DevToolsManager::SendAttachToAgent(RenderViewHost* inspected_rvh) { 302 if (inspected_rvh) { 303 ChildProcessSecurityPolicy::GetInstance()->GrantReadRawCookies( 304 inspected_rvh->process()->id()); 305 306 DevToolsRuntimeProperties properties; 307 RuntimePropertiesMap::iterator it = 308 runtime_properties_map_.find(inspected_rvh); 309 if (it != runtime_properties_map_.end()) { 310 properties = DevToolsRuntimeProperties(it->second.begin(), 311 it->second.end()); 312 } 313 IPC::Message* m = new DevToolsAgentMsg_Attach(properties); 314 m->set_routing_id(inspected_rvh->routing_id()); 315 inspected_rvh->Send(m); 316 } 317 } 318 319 void DevToolsManager::SendDetachToAgent(RenderViewHost* inspected_rvh) { 320 if (inspected_rvh) { 321 IPC::Message* m = new DevToolsAgentMsg_Detach(); 322 m->set_routing_id(inspected_rvh->routing_id()); 323 inspected_rvh->Send(m); 324 } 325 } 326 327 void DevToolsManager::ForceReopenWindow() { 328 if (inspected_rvh_for_reopen_) { 329 RenderViewHost* inspected_rvn = inspected_rvh_for_reopen_; 330 UnregisterDevToolsClientHostFor(inspected_rvn); 331 OpenDevToolsWindow(inspected_rvn); 332 } 333 } 334 335 DevToolsClientHost* DevToolsManager::FindOwnerDevToolsClientHost( 336 RenderViewHost* client_rvh) { 337 for (InspectedRvhToClientHostMap::iterator it = 338 inspected_rvh_to_client_host_.begin(); 339 it != inspected_rvh_to_client_host_.end(); 340 ++it) { 341 DevToolsWindow* win = it->second->AsDevToolsWindow(); 342 if (!win) 343 continue; 344 if (client_rvh == win->GetRenderViewHost()) 345 return it->second; 346 } 347 return NULL; 348 } 349 350 void DevToolsManager::ReopenWindow(RenderViewHost* client_rvh, bool docked) { 351 DevToolsClientHost* client_host = FindOwnerDevToolsClientHost(client_rvh); 352 if (!client_host) 353 return; 354 RenderViewHost* inspected_rvh = GetInspectedRenderViewHost(client_host); 355 DCHECK(inspected_rvh); 356 inspected_rvh->process()->profile()->GetPrefs()->SetBoolean( 357 prefs::kDevToolsOpenDocked, docked); 358 359 DevToolsWindow* window = client_host->AsDevToolsWindow(); 360 DCHECK(window); 361 window->SetDocked(docked); 362 } 363 364 void DevToolsManager::ToggleDevToolsWindow( 365 RenderViewHost* inspected_rvh, 366 bool force_open, 367 DevToolsToggleAction action) { 368 bool do_open = force_open; 369 DevToolsClientHost* host = GetDevToolsClientHostFor(inspected_rvh); 370 371 if (host != NULL && host->AsDevToolsWindow() == NULL) { 372 // Break remote debugging / extension debugging session. 373 UnregisterDevToolsClientHostFor(inspected_rvh); 374 host = NULL; 375 } 376 377 if (!host) { 378 bool docked = inspected_rvh->process()->profile()->GetPrefs()-> 379 GetBoolean(prefs::kDevToolsOpenDocked); 380 host = new DevToolsWindow( 381 inspected_rvh->site_instance()->browsing_instance()->profile(), 382 inspected_rvh, 383 docked); 384 RegisterDevToolsClientHostFor(inspected_rvh, host); 385 do_open = true; 386 } 387 388 DevToolsWindow* window = host->AsDevToolsWindow(); 389 // If window is docked and visible, we hide it on toggle. If window is 390 // undocked, we show (activate) it. 391 if (!window->is_docked() || do_open) { 392 AutoReset<bool> auto_reset_in_initial_show(&in_initial_show_, true); 393 window->Show(action); 394 } else { 395 UnregisterDevToolsClientHostFor(inspected_rvh); 396 } 397 } 398 399 void DevToolsManager::BindClientHost( 400 RenderViewHost* inspected_rvh, 401 DevToolsClientHost* client_host, 402 const DevToolsRuntimeProperties& runtime_properties) { 403 DCHECK(inspected_rvh_to_client_host_.find(inspected_rvh) == 404 inspected_rvh_to_client_host_.end()); 405 DCHECK(client_host_to_inspected_rvh_.find(client_host) == 406 client_host_to_inspected_rvh_.end()); 407 408 if (client_host_to_inspected_rvh_.empty()) { 409 BrowserThread::PostTask( 410 BrowserThread::IO, 411 FROM_HERE, 412 NewRunnableFunction(&DevToolsNetLogObserver::Attach, 413 g_browser_process->io_thread())); 414 } 415 inspected_rvh_to_client_host_[inspected_rvh] = client_host; 416 client_host_to_inspected_rvh_[client_host] = inspected_rvh; 417 runtime_properties_map_[inspected_rvh] = runtime_properties; 418 } 419 420 void DevToolsManager::UnbindClientHost(RenderViewHost* inspected_rvh, 421 DevToolsClientHost* client_host) { 422 DCHECK(inspected_rvh_to_client_host_.find(inspected_rvh)->second == 423 client_host); 424 DCHECK(client_host_to_inspected_rvh_.find(client_host)->second == 425 inspected_rvh); 426 427 inspected_rvh_to_client_host_.erase(inspected_rvh); 428 client_host_to_inspected_rvh_.erase(client_host); 429 runtime_properties_map_.erase(inspected_rvh); 430 431 if (client_host_to_inspected_rvh_.empty()) { 432 BrowserThread::PostTask( 433 BrowserThread::IO, 434 FROM_HERE, 435 NewRunnableFunction(&DevToolsNetLogObserver::Detach)); 436 } 437 SendDetachToAgent(inspected_rvh); 438 if (inspected_rvh_for_reopen_ == inspected_rvh) 439 inspected_rvh_for_reopen_ = NULL; 440 441 int process_id = inspected_rvh->process()->id(); 442 for (InspectedRvhToClientHostMap::iterator it = 443 inspected_rvh_to_client_host_.begin(); 444 it != inspected_rvh_to_client_host_.end(); 445 ++it) { 446 if (it->first->process()->id() == process_id) 447 return; 448 } 449 // We've disconnected from the last renderer -> revoke cookie permissions. 450 ChildProcessSecurityPolicy::GetInstance()->RevokeReadRawCookies(process_id); 451 } 452 453 void DevToolsManager::CloseAllClientHosts() { 454 std::vector<RenderViewHost*> rhvs; 455 for (InspectedRvhToClientHostMap::iterator it = 456 inspected_rvh_to_client_host_.begin(); 457 it != inspected_rvh_to_client_host_.end(); ++it) { 458 rhvs.push_back(it->first); 459 } 460 for (std::vector<RenderViewHost*>::iterator it = rhvs.begin(); 461 it != rhvs.end(); ++it) { 462 UnregisterDevToolsClientHostFor(*it); 463 } 464 } 465