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
GetInstance()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
RegisterUserPrefs(PrefService * prefs)38 void DevToolsManager::RegisterUserPrefs(PrefService* prefs) {
39 prefs->RegisterBooleanPref(prefs::kDevToolsOpenDocked, true);
40 }
41
DevToolsManager()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
~DevToolsManager()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
GetDevToolsClientHostFor(RenderViewHost * inspected_rvh)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
RegisterDevToolsClientHostFor(RenderViewHost * inspected_rvh,DevToolsClientHost * client_host)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
ForwardToDevToolsAgent(RenderViewHost * client_rvh,const IPC::Message & message)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
ForwardToDevToolsAgent(DevToolsClientHost * from,const IPC::Message & message)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
ForwardToDevToolsClient(RenderViewHost * inspected_rvh,const IPC::Message & message)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
ActivateWindow(RenderViewHost * client_rvh)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
CloseWindow(RenderViewHost * client_rvh)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
RequestDockWindow(RenderViewHost * client_rvh)130 void DevToolsManager::RequestDockWindow(RenderViewHost* client_rvh) {
131 ReopenWindow(client_rvh, true);
132 }
133
RequestUndockWindow(RenderViewHost * client_rvh)134 void DevToolsManager::RequestUndockWindow(RenderViewHost* client_rvh) {
135 ReopenWindow(client_rvh, false);
136 }
137
OpenDevToolsWindow(RenderViewHost * inspected_rvh)138 void DevToolsManager::OpenDevToolsWindow(RenderViewHost* inspected_rvh) {
139 ToggleDevToolsWindow(
140 inspected_rvh,
141 true,
142 DEVTOOLS_TOGGLE_ACTION_NONE);
143 }
144
ToggleDevToolsWindow(RenderViewHost * inspected_rvh,DevToolsToggleAction action)145 void DevToolsManager::ToggleDevToolsWindow(
146 RenderViewHost* inspected_rvh,
147 DevToolsToggleAction action) {
148 ToggleDevToolsWindow(inspected_rvh, false, action);
149 }
150
RuntimePropertyChanged(RenderViewHost * inspected_rvh,const std::string & name,const std::string & value)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
InspectElement(RenderViewHost * inspected_rvh,int x,int y)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
ClientHostClosing(DevToolsClientHost * host)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
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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
GetInspectedRenderViewHost(DevToolsClientHost * client_host)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
UnregisterDevToolsClientHostFor(RenderViewHost * inspected_rvh)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
OnNavigatingToPendingEntry(RenderViewHost * rvh,RenderViewHost * dest_rvh,const GURL & gurl)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
TabReplaced(TabContentsWrapper * old_tab,TabContentsWrapper * new_tab)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
DetachClientHost(RenderViewHost * from_rvh)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
AttachClientHost(int client_host_cookie,RenderViewHost * to_rvh)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
SendAttachToAgent(RenderViewHost * inspected_rvh)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
SendDetachToAgent(RenderViewHost * inspected_rvh)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
ForceReopenWindow()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
FindOwnerDevToolsClientHost(RenderViewHost * client_rvh)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
ReopenWindow(RenderViewHost * client_rvh,bool docked)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
ToggleDevToolsWindow(RenderViewHost * inspected_rvh,bool force_open,DevToolsToggleAction action)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
BindClientHost(RenderViewHost * inspected_rvh,DevToolsClientHost * client_host,const DevToolsRuntimeProperties & runtime_properties)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
UnbindClientHost(RenderViewHost * inspected_rvh,DevToolsClientHost * client_host)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
CloseAllClientHosts()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