• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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