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
29DevToolsManager* 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
38void DevToolsManager::RegisterUserPrefs(PrefService* prefs) {
39  prefs->RegisterBooleanPref(prefs::kDevToolsOpenDocked, true);
40}
41
42DevToolsManager::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
50DevToolsManager::~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
58DevToolsClientHost* 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
67void 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
78void 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
86void 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
100void 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
111void 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
121void 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
130void DevToolsManager::RequestDockWindow(RenderViewHost* client_rvh) {
131  ReopenWindow(client_rvh, true);
132}
133
134void DevToolsManager::RequestUndockWindow(RenderViewHost* client_rvh) {
135  ReopenWindow(client_rvh, false);
136}
137
138void DevToolsManager::OpenDevToolsWindow(RenderViewHost* inspected_rvh) {
139  ToggleDevToolsWindow(
140      inspected_rvh,
141      true,
142      DEVTOOLS_TOGGLE_ACTION_NONE);
143}
144
145void DevToolsManager::ToggleDevToolsWindow(
146    RenderViewHost* inspected_rvh,
147    DevToolsToggleAction action) {
148  ToggleDevToolsWindow(inspected_rvh, false, action);
149}
150
151void 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
165void 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
176void 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
198void 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
205RenderViewHost* 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
214void 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
223void 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
259void 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
273int 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
287void 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
301void 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
319void 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
327void 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
335DevToolsClientHost* 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
350void 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
364void 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
399void 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
420void 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
453void 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