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