Home | History | Annotate | Download | only in debugger
      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