devtools_window.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
1// Copyright (c) 2012 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/devtools/devtools_window.h"
6
7#include <algorithm>
8
9#include "base/json/json_reader.h"
10#include "base/metrics/histogram.h"
11#include "base/prefs/scoped_user_pref_update.h"
12#include "base/time/time.h"
13#include "base/values.h"
14#include "chrome/browser/chrome_page_zoom.h"
15#include "chrome/browser/file_select_helper.h"
16#include "chrome/browser/prefs/pref_service_syncable.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/browser/sessions/session_tab_helper.h"
19#include "chrome/browser/ui/browser.h"
20#include "chrome/browser/ui/browser_dialogs.h"
21#include "chrome/browser/ui/browser_iterator.h"
22#include "chrome/browser/ui/browser_list.h"
23#include "chrome/browser/ui/browser_window.h"
24#include "chrome/browser/ui/host_desktop.h"
25#include "chrome/browser/ui/prefs/prefs_tab_helper.h"
26#include "chrome/browser/ui/tabs/tab_strip_model.h"
27#include "chrome/browser/ui/webui/devtools_ui.h"
28#include "chrome/common/chrome_switches.h"
29#include "chrome/common/pref_names.h"
30#include "chrome/common/render_messages.h"
31#include "chrome/common/url_constants.h"
32#include "components/user_prefs/pref_registry_syncable.h"
33#include "content/public/browser/browser_thread.h"
34#include "content/public/browser/devtools_agent_host.h"
35#include "content/public/browser/devtools_client_host.h"
36#include "content/public/browser/devtools_manager.h"
37#include "content/public/browser/native_web_keyboard_event.h"
38#include "content/public/browser/navigation_controller.h"
39#include "content/public/browser/navigation_entry.h"
40#include "content/public/browser/notification_source.h"
41#include "content/public/browser/render_frame_host.h"
42#include "content/public/browser/render_process_host.h"
43#include "content/public/browser/render_view_host.h"
44#include "content/public/browser/user_metrics.h"
45#include "content/public/browser/web_contents.h"
46#include "content/public/browser/web_contents_observer.h"
47#include "content/public/browser/web_contents_view.h"
48#include "content/public/common/content_client.h"
49#include "content/public/common/page_transition_types.h"
50#include "content/public/common/url_constants.h"
51#include "content/public/test/test_utils.h"
52#include "third_party/WebKit/public/web/WebInputEvent.h"
53#include "ui/events/keycodes/keyboard_codes.h"
54
55using base::DictionaryValue;
56using blink::WebInputEvent;
57using content::BrowserThread;
58using content::DevToolsAgentHost;
59using content::WebContents;
60
61namespace {
62
63typedef std::vector<DevToolsWindow*> DevToolsWindows;
64base::LazyInstance<DevToolsWindows>::Leaky g_instances =
65    LAZY_INSTANCE_INITIALIZER;
66
67static const char kKeyUpEventName[] = "keyup";
68static const char kKeyDownEventName[] = "keydown";
69
70}  // namespace
71
72// DevToolsEventForwarder -----------------------------------------------------
73
74class DevToolsEventForwarder {
75 public:
76  explicit DevToolsEventForwarder(DevToolsWindow* window)
77     : devtools_window_(window) {}
78
79  // Registers whitelisted shortcuts with the forwarder.
80  // Only registered keys will be forwarded to the DevTools frontend.
81  void SetWhitelistedShortcuts(const std::string& message);
82
83  // Forwards a keyboard event to the DevTools frontend if it is whitelisted.
84  // Returns |true| if the event has been forwarded, |false| otherwise.
85  bool ForwardEvent(const content::NativeWebKeyboardEvent& event);
86
87 private:
88  static int VirtualKeyCodeWithoutLocation(int key_code);
89  static bool KeyWhitelistingAllowed(int key_code, int modifiers);
90  static int CombineKeyCodeAndModifiers(int key_code, int modifiers);
91
92  DevToolsWindow* devtools_window_;
93  std::set<int> whitelisted_keys_;
94
95  DISALLOW_COPY_AND_ASSIGN(DevToolsEventForwarder);
96};
97
98void DevToolsEventForwarder::SetWhitelistedShortcuts(
99    const std::string& message) {
100  scoped_ptr<base::Value> parsed_message(base::JSONReader::Read(message));
101  base::ListValue* shortcut_list;
102  if (!parsed_message->GetAsList(&shortcut_list))
103      return;
104  base::ListValue::iterator it = shortcut_list->begin();
105  for (; it != shortcut_list->end(); ++it) {
106    base::DictionaryValue* dictionary;
107    if (!(*it)->GetAsDictionary(&dictionary))
108      continue;
109    int key_code = 0;
110    dictionary->GetInteger("keyCode", &key_code);
111    if (key_code == 0)
112      continue;
113    int modifiers = 0;
114    dictionary->GetInteger("modifiers", &modifiers);
115    if (!KeyWhitelistingAllowed(key_code, modifiers)) {
116      LOG(WARNING) << "Key whitelisting forbidden: "
117                   << "(" << key_code << "," << modifiers << ")";
118      continue;
119    }
120    whitelisted_keys_.insert(CombineKeyCodeAndModifiers(key_code, modifiers));
121  }
122}
123
124bool DevToolsEventForwarder::ForwardEvent(
125    const content::NativeWebKeyboardEvent& event) {
126  std::string event_type;
127  switch (event.type) {
128    case WebInputEvent::KeyDown:
129    case WebInputEvent::RawKeyDown:
130      event_type = kKeyDownEventName;
131      break;
132    case WebInputEvent::KeyUp:
133      event_type = kKeyUpEventName;
134      break;
135    default:
136      return false;
137  }
138
139  int key_code = VirtualKeyCodeWithoutLocation(event.windowsKeyCode);
140  int key = CombineKeyCodeAndModifiers(key_code, event.modifiers);
141  if (whitelisted_keys_.find(key) == whitelisted_keys_.end())
142    return false;
143
144  base::DictionaryValue event_data;
145  event_data.SetString("type", event_type);
146  event_data.SetString("keyIdentifier", event.keyIdentifier);
147  event_data.SetInteger("keyCode", key_code);
148  event_data.SetInteger("modifiers", event.modifiers);
149  devtools_window_->bindings_->CallClientFunction(
150      "InspectorFrontendAPI.keyEventUnhandled", &event_data, NULL, NULL);
151  return true;
152}
153
154int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code,
155                                                       int modifiers) {
156  return key_code | (modifiers << 16);
157}
158
159bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code,
160                                                    int modifiers) {
161  return (ui::VKEY_F1 <= key_code && key_code <= ui::VKEY_F12) ||
162      modifiers != 0;
163}
164
165// Mapping copied from Blink's KeyboardEvent.cpp.
166int DevToolsEventForwarder::VirtualKeyCodeWithoutLocation(int key_code)
167{
168  switch (key_code) {
169    case ui::VKEY_LCONTROL:
170    case ui::VKEY_RCONTROL:
171        return ui::VKEY_CONTROL;
172    case ui::VKEY_LSHIFT:
173    case ui::VKEY_RSHIFT:
174        return ui::VKEY_SHIFT;
175    case ui::VKEY_LMENU:
176    case ui::VKEY_RMENU:
177        return ui::VKEY_MENU;
178    default:
179        return key_code;
180  }
181}
182
183// DevToolsWindow::InspectedWebContentsObserver -------------------------------
184
185class DevToolsWindow::InspectedWebContentsObserver
186    : public content::WebContentsObserver {
187 public:
188  explicit InspectedWebContentsObserver(WebContents* web_contents);
189  virtual ~InspectedWebContentsObserver();
190
191  WebContents* web_contents() {
192    return WebContentsObserver::web_contents();
193  }
194
195 private:
196  DISALLOW_COPY_AND_ASSIGN(InspectedWebContentsObserver);
197};
198
199DevToolsWindow::InspectedWebContentsObserver::InspectedWebContentsObserver(
200    WebContents* web_contents)
201    : WebContentsObserver(web_contents) {
202}
203
204DevToolsWindow::InspectedWebContentsObserver::~InspectedWebContentsObserver() {
205}
206
207// DevToolsWindow -------------------------------------------------------------
208
209const char DevToolsWindow::kDevToolsApp[] = "DevToolsApp";
210
211DevToolsWindow::~DevToolsWindow() {
212  UpdateBrowserToolbar();
213
214  DevToolsWindows* instances = g_instances.Pointer();
215  DevToolsWindows::iterator it(
216      std::find(instances->begin(), instances->end(), this));
217  DCHECK(it != instances->end());
218  instances->erase(it);
219}
220
221// static
222std::string DevToolsWindow::GetDevToolsWindowPlacementPrefKey() {
223  return std::string(prefs::kBrowserWindowPlacement) + "_" +
224      std::string(kDevToolsApp);
225}
226
227// static
228void DevToolsWindow::RegisterProfilePrefs(
229    user_prefs::PrefRegistrySyncable* registry) {
230  registry->RegisterDictionaryPref(
231      prefs::kDevToolsEditedFiles,
232      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
233  registry->RegisterDictionaryPref(
234      prefs::kDevToolsFileSystemPaths,
235      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
236  registry->RegisterStringPref(
237      prefs::kDevToolsAdbKey, std::string(),
238      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
239
240  registry->RegisterDictionaryPref(
241      GetDevToolsWindowPlacementPrefKey().c_str(),
242      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
243
244  registry->RegisterBooleanPref(
245      prefs::kDevToolsDiscoverUsbDevicesEnabled,
246      true,
247      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
248  registry->RegisterBooleanPref(
249      prefs::kDevToolsPortForwardingEnabled,
250      false,
251      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
252  registry->RegisterBooleanPref(
253      prefs::kDevToolsPortForwardingDefaultSet,
254      false,
255      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
256  registry->RegisterDictionaryPref(
257      prefs::kDevToolsPortForwardingConfig,
258      user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
259}
260
261// static
262DevToolsWindow* DevToolsWindow::GetDockedInstanceForInspectedTab(
263    WebContents* inspected_web_contents) {
264  DevToolsWindow* window = GetInstanceForInspectedRenderViewHost(
265      inspected_web_contents->GetRenderViewHost());
266  if (!window)
267    return NULL;
268  // Not yet loaded window is treated as docked, but we should not present it
269  // until we decided on docking.
270  bool is_docked_set = window->load_state_ == kLoadCompleted ||
271      window->load_state_ == kIsDockedSet;
272  return window->is_docked_ && is_docked_set ? window : NULL;
273}
274
275// static
276DevToolsWindow* DevToolsWindow::GetInstanceForInspectedRenderViewHost(
277      content::RenderViewHost* inspected_rvh) {
278  if (!inspected_rvh || !DevToolsAgentHost::HasFor(inspected_rvh))
279    return NULL;
280
281  scoped_refptr<DevToolsAgentHost> agent(DevToolsAgentHost::GetOrCreateFor(
282      inspected_rvh));
283  return FindDevToolsWindow(agent.get());
284}
285
286// static
287DevToolsWindow* DevToolsWindow::GetInstanceForInspectedWebContents(
288    WebContents* inspected_web_contents) {
289  if (!inspected_web_contents)
290    return NULL;
291  return GetInstanceForInspectedRenderViewHost(
292      inspected_web_contents->GetRenderViewHost());
293}
294
295// static
296bool DevToolsWindow::IsDevToolsWindow(content::RenderViewHost* window_rvh) {
297  return AsDevToolsWindow(window_rvh) != NULL;
298}
299
300// static
301DevToolsWindow* DevToolsWindow::OpenDevToolsWindowForWorker(
302    Profile* profile,
303    DevToolsAgentHost* worker_agent) {
304  DevToolsWindow* window = FindDevToolsWindow(worker_agent);
305  if (!window) {
306    window = DevToolsWindow::CreateDevToolsWindowForWorker(profile);
307    // Will disconnect the current client host if there is one.
308    content::DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor(
309        worker_agent, window->bindings_->frontend_host());
310  }
311  window->ScheduleShow(DevToolsToggleAction::Show());
312  return window;
313}
314
315// static
316DevToolsWindow* DevToolsWindow::CreateDevToolsWindowForWorker(
317    Profile* profile) {
318  content::RecordAction(base::UserMetricsAction("DevTools_InspectWorker"));
319  return Create(profile, GURL(), NULL, true, false, false);
320}
321
322// static
323DevToolsWindow* DevToolsWindow::OpenDevToolsWindow(
324    content::RenderViewHost* inspected_rvh) {
325  return ToggleDevToolsWindow(
326      inspected_rvh, true, DevToolsToggleAction::Show());
327}
328
329// static
330DevToolsWindow* DevToolsWindow::OpenDevToolsWindow(
331    content::RenderViewHost* inspected_rvh,
332    const DevToolsToggleAction& action) {
333  return ToggleDevToolsWindow(
334      inspected_rvh, true, action);
335}
336
337// static
338DevToolsWindow* DevToolsWindow::OpenDevToolsWindowForTest(
339    content::RenderViewHost* inspected_rvh,
340    bool is_docked) {
341  DevToolsWindow* window = OpenDevToolsWindow(inspected_rvh);
342  window->SetIsDockedAndShowImmediatelyForTest(is_docked);
343  return window;
344}
345
346// static
347DevToolsWindow* DevToolsWindow::OpenDevToolsWindowForTest(
348    Browser* browser,
349    bool is_docked) {
350  return OpenDevToolsWindowForTest(
351      browser->tab_strip_model()->GetActiveWebContents()->GetRenderViewHost(),
352      is_docked);
353}
354
355// static
356DevToolsWindow* DevToolsWindow::ToggleDevToolsWindow(
357    Browser* browser,
358    const DevToolsToggleAction& action) {
359  if (action.type() == DevToolsToggleAction::kToggle &&
360      browser->is_devtools()) {
361    browser->tab_strip_model()->CloseAllTabs();
362    return NULL;
363  }
364
365  return ToggleDevToolsWindow(
366      browser->tab_strip_model()->GetActiveWebContents()->GetRenderViewHost(),
367      action.type() == DevToolsToggleAction::kInspect, action);
368}
369
370// static
371void DevToolsWindow::OpenExternalFrontend(
372    Profile* profile,
373    const std::string& frontend_url,
374    content::DevToolsAgentHost* agent_host) {
375  DevToolsWindow* window = FindDevToolsWindow(agent_host);
376  if (!window) {
377    window = Create(profile, DevToolsUI::GetProxyURL(frontend_url), NULL,
378                    false, true, false);
379    content::DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor(
380        agent_host, window->bindings_->frontend_host());
381  }
382  window->ScheduleShow(DevToolsToggleAction::Show());
383}
384
385// static
386DevToolsWindow* DevToolsWindow::ToggleDevToolsWindow(
387    content::RenderViewHost* inspected_rvh,
388    bool force_open,
389    const DevToolsToggleAction& action) {
390  scoped_refptr<DevToolsAgentHost> agent(
391      DevToolsAgentHost::GetOrCreateFor(inspected_rvh));
392  content::DevToolsManager* manager = content::DevToolsManager::GetInstance();
393  DevToolsWindow* window = FindDevToolsWindow(agent.get());
394  bool do_open = force_open;
395  if (!window) {
396    Profile* profile = Profile::FromBrowserContext(
397        inspected_rvh->GetProcess()->GetBrowserContext());
398    content::RecordAction(
399        base::UserMetricsAction("DevTools_InspectRenderer"));
400    window = Create(profile, GURL(), inspected_rvh, false, false, true);
401    manager->RegisterDevToolsClientHostFor(agent.get(),
402                                           window->bindings_->frontend_host());
403    do_open = true;
404  }
405
406  // Update toolbar to reflect DevTools changes.
407  window->UpdateBrowserToolbar();
408
409  // If window is docked and visible, we hide it on toggle. If window is
410  // undocked, we show (activate) it.
411  if (!window->is_docked_ || do_open)
412    window->ScheduleShow(action);
413  else
414    window->CloseWindow();
415
416  return window;
417}
418
419// static
420void DevToolsWindow::InspectElement(content::RenderViewHost* inspected_rvh,
421                                    int x,
422                                    int y) {
423  scoped_refptr<DevToolsAgentHost> agent(
424      DevToolsAgentHost::GetOrCreateFor(inspected_rvh));
425  agent->InspectElement(x, y);
426  bool should_measure_time = FindDevToolsWindow(agent.get()) == NULL;
427  base::TimeTicks start_time = base::TimeTicks::Now();
428  // TODO(loislo): we should initiate DevTools window opening from within
429  // renderer. Otherwise, we still can hit a race condition here.
430  DevToolsWindow* window = OpenDevToolsWindow(inspected_rvh);
431  if (should_measure_time)
432    window->inspect_element_start_time_ = start_time;
433}
434
435const DevToolsContentsResizingStrategy&
436DevToolsWindow::GetContentsResizingStrategy() const {
437  return contents_resizing_strategy_;
438}
439
440gfx::Size DevToolsWindow::GetMinimumSize() const {
441  const gfx::Size kMinDevToolsSize = gfx::Size(230, 100);
442  return kMinDevToolsSize;
443}
444
445void DevToolsWindow::ScheduleShow(const DevToolsToggleAction& action) {
446  if (load_state_ == kLoadCompleted) {
447    Show(action);
448    return;
449  }
450
451  // Action will be done only after load completed.
452  action_on_load_ = action;
453
454  if (!can_dock_) {
455    // No harm to show always-undocked window right away.
456    is_docked_ = false;
457    Show(DevToolsToggleAction::Show());
458  }
459}
460
461void DevToolsWindow::Show(const DevToolsToggleAction& action) {
462  if (action.type() == DevToolsToggleAction::kNoOp)
463    return;
464
465  if (is_docked_) {
466    DCHECK(can_dock_);
467    Browser* inspected_browser = NULL;
468    int inspected_tab_index = -1;
469    FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
470                                    &inspected_browser,
471                                    &inspected_tab_index);
472    DCHECK(inspected_browser);
473    DCHECK(inspected_tab_index != -1);
474
475    // Tell inspected browser to update splitter and switch to inspected panel.
476    BrowserWindow* inspected_window = inspected_browser->window();
477    web_contents_->SetDelegate(this);
478
479    TabStripModel* tab_strip_model = inspected_browser->tab_strip_model();
480    tab_strip_model->ActivateTabAt(inspected_tab_index, true);
481
482    inspected_window->UpdateDevTools();
483    web_contents_->GetView()->SetInitialFocus();
484    inspected_window->Show();
485    // On Aura, focusing once is not enough. Do it again.
486    // Note that focusing only here but not before isn't enough either. We just
487    // need to focus twice.
488    web_contents_->GetView()->SetInitialFocus();
489
490    PrefsTabHelper::CreateForWebContents(web_contents_);
491    web_contents_->GetRenderViewHost()->SyncRendererPrefs();
492
493    DoAction(action);
494    return;
495  }
496
497  // Avoid consecutive window switching if the devtools window has been opened
498  // and the Inspect Element shortcut is pressed in the inspected tab.
499  bool should_show_window =
500      !browser_ || (action.type() != DevToolsToggleAction::kInspect);
501
502  if (!browser_)
503    CreateDevToolsBrowser();
504
505  if (should_show_window) {
506    browser_->window()->Show();
507    web_contents_->GetView()->SetInitialFocus();
508  }
509
510  DoAction(action);
511}
512
513// static
514bool DevToolsWindow::HandleBeforeUnload(WebContents* frontend_contents,
515    bool proceed, bool* proceed_to_fire_unload) {
516  DevToolsWindow* window = AsDevToolsWindow(
517      frontend_contents->GetRenderViewHost());
518  if (!window)
519    return false;
520  if (!window->intercepted_page_beforeunload_)
521    return false;
522  window->BeforeUnloadFired(frontend_contents, proceed,
523      proceed_to_fire_unload);
524  return true;
525}
526
527// static
528bool DevToolsWindow::InterceptPageBeforeUnload(WebContents* contents) {
529  DevToolsWindow* window =
530      DevToolsWindow::GetInstanceForInspectedRenderViewHost(
531          contents->GetRenderViewHost());
532  if (!window || window->intercepted_page_beforeunload_)
533    return false;
534
535  // Not yet loaded frontend will not handle beforeunload.
536  if (window->load_state_ != kLoadCompleted)
537    return false;
538
539  window->intercepted_page_beforeunload_ = true;
540  // Handle case of devtools inspecting another devtools instance by passing
541  // the call up to the inspecting devtools instance.
542  if (!DevToolsWindow::InterceptPageBeforeUnload(window->web_contents_)) {
543    window->web_contents_->DispatchBeforeUnload(false);
544  }
545  return true;
546}
547
548// static
549bool DevToolsWindow::NeedsToInterceptBeforeUnload(
550    WebContents* contents) {
551  DevToolsWindow* window =
552      DevToolsWindow::GetInstanceForInspectedRenderViewHost(
553          contents->GetRenderViewHost());
554  return window && !window->intercepted_page_beforeunload_;
555}
556
557// static
558bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(
559    Browser* browser) {
560  DCHECK(browser->is_devtools());
561  // When FastUnloadController is used, devtools frontend will be detached
562  // from the browser window at this point which means we've already fired
563  // beforeunload.
564  if (browser->tab_strip_model()->empty())
565    return true;
566  WebContents* contents =
567      browser->tab_strip_model()->GetWebContentsAt(0);
568  DevToolsWindow* window = AsDevToolsWindow(contents->GetRenderViewHost());
569  if (!window)
570    return false;
571  return window->intercepted_page_beforeunload_;
572}
573
574// static
575void DevToolsWindow::OnPageCloseCanceled(WebContents* contents) {
576  DevToolsWindow *window =
577      DevToolsWindow::GetInstanceForInspectedRenderViewHost(
578          contents->GetRenderViewHost());
579  if (!window)
580    return;
581  window->intercepted_page_beforeunload_ = false;
582  // Propagate to devtools opened on devtools if any.
583  DevToolsWindow::OnPageCloseCanceled(window->web_contents_);
584}
585
586DevToolsWindow::DevToolsWindow(Profile* profile,
587                               const GURL& url,
588                               content::RenderViewHost* inspected_rvh,
589                               bool can_dock)
590    : profile_(profile),
591      web_contents_(WebContents::Create(WebContents::CreateParams(profile))),
592      bindings_(NULL),
593      browser_(NULL),
594      is_docked_(true),
595      can_dock_(can_dock),
596      // This initialization allows external front-end to work without changes.
597      // We don't wait for docking call, but instead immediately show undocked.
598      // Passing "dockSide=undocked" parameter ensures proper UI.
599      load_state_(can_dock ? kNotLoaded : kIsDockedSet),
600      action_on_load_(DevToolsToggleAction::NoOp()),
601      ignore_set_is_docked_(false),
602      intercepted_page_beforeunload_(false) {
603  // Set up delegate, so we get fully-functional window immediately.
604  // It will not appear in UI though until |load_state_ == kLoadCompleted|.
605  web_contents_->SetDelegate(this);
606  web_contents_->GetController().LoadURL(
607      DevToolsUIBindings::ApplyThemeToURL(profile, url), content::Referrer(),
608      content::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string());
609
610  // Lookup bindings and pass ownership over self into them.
611  bindings_ = DevToolsUIBindings::ForWebContents(web_contents_);
612  bindings_->SetDelegate(this);
613
614  g_instances.Get().push_back(this);
615
616  // There is no inspected_rvh in case of shared workers.
617  if (inspected_rvh)
618    inspected_contents_observer_.reset(new InspectedWebContentsObserver(
619        content::WebContents::FromRenderViewHost(inspected_rvh)));
620  event_forwarder_.reset(new DevToolsEventForwarder(this));
621}
622
623// static
624DevToolsWindow* DevToolsWindow::Create(
625    Profile* profile,
626    const GURL& frontend_url,
627    content::RenderViewHost* inspected_rvh,
628    bool shared_worker_frontend,
629    bool external_frontend,
630    bool can_dock) {
631  if (inspected_rvh) {
632    // Check for a place to dock.
633    Browser* browser = NULL;
634    int tab;
635    WebContents* inspected_web_contents =
636        content::WebContents::FromRenderViewHost(inspected_rvh);
637    if (!FindInspectedBrowserAndTabIndex(inspected_web_contents,
638                                         &browser, &tab) ||
639        inspected_rvh->GetMainFrame()->IsCrossProcessSubframe() ||
640        browser->is_type_popup()) {
641      can_dock = false;
642    }
643  }
644
645  // Create WebContents with devtools.
646  GURL url(GetDevToolsURL(profile, frontend_url,
647                          shared_worker_frontend,
648                          external_frontend,
649                          can_dock));
650  return new DevToolsWindow(profile, url, inspected_rvh, can_dock);
651}
652
653// static
654GURL DevToolsWindow::GetDevToolsURL(Profile* profile,
655                                    const GURL& base_url,
656                                    bool shared_worker_frontend,
657                                    bool external_frontend,
658                                    bool can_dock) {
659  // Compatibility errors are encoded with data urls, pass them
660  // through with no decoration.
661  if (base_url.SchemeIs("data"))
662    return base_url;
663
664  std::string frontend_url(
665      base_url.is_empty() ? chrome::kChromeUIDevToolsURL : base_url.spec());
666  std::string url_string(
667      frontend_url +
668      ((frontend_url.find("?") == std::string::npos) ? "?" : "&"));
669  if (shared_worker_frontend)
670    url_string += "&isSharedWorker=true";
671  if (external_frontend)
672    url_string += "&remoteFrontend=true";
673  if (can_dock)
674    url_string += "&can_dock=true";
675  return GURL(url_string);
676}
677
678// static
679DevToolsWindow* DevToolsWindow::FindDevToolsWindow(
680    DevToolsAgentHost* agent_host) {
681  DevToolsWindows* instances = g_instances.Pointer();
682  content::DevToolsManager* manager = content::DevToolsManager::GetInstance();
683  for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
684       ++it) {
685    if (manager->GetDevToolsAgentHostFor((*it)->bindings_->frontend_host()) ==
686        agent_host)
687      return *it;
688  }
689  return NULL;
690}
691
692// static
693DevToolsWindow* DevToolsWindow::AsDevToolsWindow(
694    content::RenderViewHost* window_rvh) {
695  if (g_instances == NULL)
696    return NULL;
697  DevToolsWindows* instances = g_instances.Pointer();
698  for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
699       ++it) {
700    if ((*it)->web_contents_->GetRenderViewHost() == window_rvh)
701      return *it;
702  }
703  return NULL;
704}
705
706WebContents* DevToolsWindow::OpenURLFromTab(
707    WebContents* source,
708    const content::OpenURLParams& params) {
709  DCHECK(source == web_contents_);
710  if (!params.url.SchemeIs(content::kChromeDevToolsScheme)) {
711    WebContents* inspected_web_contents = GetInspectedWebContents();
712    return inspected_web_contents ?
713        inspected_web_contents->OpenURL(params) : NULL;
714  }
715
716  content::DevToolsManager* manager = content::DevToolsManager::GetInstance();
717  scoped_refptr<DevToolsAgentHost> agent_host(
718      manager->GetDevToolsAgentHostFor(bindings_->frontend_host()));
719  if (!agent_host.get())
720    return NULL;
721  manager->ClientHostClosing(bindings_->frontend_host());
722  manager->RegisterDevToolsClientHostFor(agent_host.get(),
723                                         bindings_->frontend_host());
724
725  content::NavigationController::LoadURLParams load_url_params(params.url);
726  web_contents_->GetController().LoadURLWithParams(load_url_params);
727  return web_contents_;
728}
729
730void DevToolsWindow::ActivateContents(WebContents* contents) {
731  if (is_docked_) {
732    WebContents* inspected_tab = GetInspectedWebContents();
733    inspected_tab->GetDelegate()->ActivateContents(inspected_tab);
734  } else {
735    browser_->window()->Activate();
736  }
737}
738
739void DevToolsWindow::AddNewContents(WebContents* source,
740                                    WebContents* new_contents,
741                                    WindowOpenDisposition disposition,
742                                    const gfx::Rect& initial_pos,
743                                    bool user_gesture,
744                                    bool* was_blocked) {
745  WebContents* inspected_web_contents = GetInspectedWebContents();
746  if (inspected_web_contents) {
747    inspected_web_contents->GetDelegate()->AddNewContents(
748        source, new_contents, disposition, initial_pos, user_gesture,
749        was_blocked);
750  }
751}
752
753void DevToolsWindow::CloseContents(WebContents* source) {
754  CHECK(is_docked_);
755  // This will prevent any activity after frontend is loaded.
756  action_on_load_ = DevToolsToggleAction::NoOp();
757  ignore_set_is_docked_ = true;
758  // Update dev tools to reflect removed dev tools window.
759  BrowserWindow* inspected_window = GetInspectedBrowserWindow();
760  if (inspected_window)
761    inspected_window->UpdateDevTools();
762  // In case of docked web_contents_, we own it so delete here.
763  // Embedding DevTools window will be deleted as a result of
764  // WebContentsDestroyed callback.
765  delete web_contents_;
766}
767
768void DevToolsWindow::ContentsZoomChange(bool zoom_in) {
769  DCHECK(is_docked_);
770  chrome_page_zoom::Zoom(web_contents_,
771      zoom_in ? content::PAGE_ZOOM_IN : content::PAGE_ZOOM_OUT);
772}
773
774void DevToolsWindow::BeforeUnloadFired(WebContents* tab,
775                                       bool proceed,
776                                       bool* proceed_to_fire_unload) {
777  if (!intercepted_page_beforeunload_) {
778    // Docked devtools window closed directly.
779    if (proceed) {
780      content::DevToolsManager::GetInstance()->ClientHostClosing(
781          bindings_->frontend_host());
782    }
783    *proceed_to_fire_unload = proceed;
784  } else {
785    // Inspected page is attempting to close.
786    WebContents* inspected_web_contents = GetInspectedWebContents();
787    if (proceed) {
788      inspected_web_contents->DispatchBeforeUnload(false);
789    } else {
790      bool should_proceed;
791      inspected_web_contents->GetDelegate()->BeforeUnloadFired(
792          inspected_web_contents, false, &should_proceed);
793      DCHECK(!should_proceed);
794    }
795    *proceed_to_fire_unload = false;
796  }
797}
798
799bool DevToolsWindow::PreHandleKeyboardEvent(
800    WebContents* source,
801    const content::NativeWebKeyboardEvent& event,
802    bool* is_keyboard_shortcut) {
803  if (is_docked_) {
804    BrowserWindow* inspected_window = GetInspectedBrowserWindow();
805    if (inspected_window) {
806      return inspected_window->PreHandleKeyboardEvent(event,
807                                                      is_keyboard_shortcut);
808    }
809  }
810  return false;
811}
812
813void DevToolsWindow::HandleKeyboardEvent(
814    WebContents* source,
815    const content::NativeWebKeyboardEvent& event) {
816  if (is_docked_) {
817    if (event.windowsKeyCode == 0x08) {
818      // Do not navigate back in history on Windows (http://crbug.com/74156).
819      return;
820    }
821    BrowserWindow* inspected_window = GetInspectedBrowserWindow();
822    if (inspected_window)
823      inspected_window->HandleKeyboardEvent(event);
824  }
825}
826
827content::JavaScriptDialogManager* DevToolsWindow::GetJavaScriptDialogManager() {
828  WebContents* inspected_web_contents = GetInspectedWebContents();
829  return (inspected_web_contents && inspected_web_contents->GetDelegate()) ?
830      inspected_web_contents->GetDelegate()->GetJavaScriptDialogManager() :
831      content::WebContentsDelegate::GetJavaScriptDialogManager();
832}
833
834content::ColorChooser* DevToolsWindow::OpenColorChooser(
835    WebContents* web_contents,
836    SkColor initial_color,
837    const std::vector<content::ColorSuggestion>& suggestions) {
838  return chrome::ShowColorChooser(web_contents, initial_color);
839}
840
841void DevToolsWindow::RunFileChooser(WebContents* web_contents,
842                                    const content::FileChooserParams& params) {
843  FileSelectHelper::RunFileChooser(web_contents, params);
844}
845
846void DevToolsWindow::WebContentsFocused(WebContents* contents) {
847  Browser* inspected_browser = NULL;
848  int inspected_tab_index = -1;
849  if (is_docked_ && FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
850                                                    &inspected_browser,
851                                                    &inspected_tab_index))
852    inspected_browser->window()->WebContentsFocused(contents);
853}
854
855bool DevToolsWindow::PreHandleGestureEvent(
856    WebContents* source,
857    const blink::WebGestureEvent& event) {
858  // Disable pinch zooming.
859  return event.type == blink::WebGestureEvent::GesturePinchBegin ||
860      event.type == blink::WebGestureEvent::GesturePinchUpdate ||
861      event.type == blink::WebGestureEvent::GesturePinchEnd;
862}
863
864void DevToolsWindow::ActivateWindow() {
865  if (is_docked_ && GetInspectedBrowserWindow())
866    web_contents_->GetView()->Focus();
867  else if (!is_docked_ && !browser_->window()->IsActive())
868    browser_->window()->Activate();
869}
870
871void DevToolsWindow::CloseWindow() {
872  DCHECK(is_docked_);
873  // This will prevent any activity after frontend is loaded.
874  action_on_load_ = DevToolsToggleAction::NoOp();
875  ignore_set_is_docked_ = true;
876  web_contents_->DispatchBeforeUnload(false);
877}
878
879void DevToolsWindow::SetContentsInsets(
880    int top, int left, int bottom, int right) {
881  gfx::Insets insets(top, left, bottom, right);
882  SetContentsResizingStrategy(insets, contents_resizing_strategy_.min_size());
883}
884
885void DevToolsWindow::SetContentsResizingStrategy(
886    const gfx::Insets& insets, const gfx::Size& min_size) {
887  DevToolsContentsResizingStrategy strategy(insets, min_size);
888  if (contents_resizing_strategy_.Equals(strategy))
889    return;
890
891  contents_resizing_strategy_.CopyFrom(strategy);
892  if (is_docked_) {
893    // Update inspected window.
894    BrowserWindow* inspected_window = GetInspectedBrowserWindow();
895    if (inspected_window)
896      inspected_window->UpdateDevTools();
897  }
898}
899
900void DevToolsWindow::InspectElementCompleted() {
901  if (!inspect_element_start_time_.is_null()) {
902    UMA_HISTOGRAM_TIMES("DevTools.InspectElement",
903        base::TimeTicks::Now() - inspect_element_start_time_);
904    inspect_element_start_time_ = base::TimeTicks();
905  }
906}
907
908void DevToolsWindow::MoveWindow(int x, int y) {
909  if (!is_docked_) {
910    gfx::Rect bounds = browser_->window()->GetBounds();
911    bounds.Offset(x, y);
912    browser_->window()->SetBounds(bounds);
913  }
914}
915
916void DevToolsWindow::SetIsDockedAndShowImmediatelyForTest(bool is_docked) {
917  DCHECK(!is_docked || can_dock_);
918  if (load_state_ == kLoadCompleted) {
919    SetIsDocked(is_docked);
920  } else {
921    is_docked_ = is_docked;
922    // Load is completed when both kIsDockedSet and kOnLoadFired happened.
923    // Note that kIsDockedSet may be already set when can_dock_ is false.
924    load_state_ = load_state_ == kOnLoadFired ? kLoadCompleted : kIsDockedSet;
925    // Note that action_on_load_ will be performed after the load is actually
926    // completed. For now, just show the window.
927    Show(DevToolsToggleAction::Show());
928    if (load_state_ == kLoadCompleted)
929      LoadCompleted();
930  }
931  ignore_set_is_docked_ = true;
932}
933
934void DevToolsWindow::SetIsDocked(bool dock_requested) {
935  if (ignore_set_is_docked_)
936    return;
937
938  DCHECK(can_dock_ || !dock_requested);
939  if (!can_dock_)
940    dock_requested = false;
941
942  bool was_docked = is_docked_;
943  is_docked_ = dock_requested;
944
945  if (load_state_ != kLoadCompleted) {
946    // This is a first time call we waited for to initialize.
947    load_state_ = load_state_ == kOnLoadFired ? kLoadCompleted : kIsDockedSet;
948    if (load_state_ == kLoadCompleted)
949      LoadCompleted();
950    return;
951  }
952
953  if (dock_requested == was_docked)
954    return;
955
956  if (dock_requested && !was_docked) {
957    // Detach window from the external devtools browser. It will lead to
958    // the browser object's close and delete. Remove observer first.
959    TabStripModel* tab_strip_model = browser_->tab_strip_model();
960    tab_strip_model->DetachWebContentsAt(
961        tab_strip_model->GetIndexOfWebContents(web_contents_));
962    browser_ = NULL;
963  } else if (!dock_requested && was_docked) {
964    // Update inspected window to hide split and reset it.
965    BrowserWindow* inspected_window = GetInspectedBrowserWindow();
966    if (inspected_window)
967      inspected_window->UpdateDevTools();
968  }
969
970  Show(DevToolsToggleAction::Show());
971}
972
973void DevToolsWindow::OpenInNewTab(const std::string& url) {
974  content::OpenURLParams params(
975      GURL(url), content::Referrer(), NEW_FOREGROUND_TAB,
976      content::PAGE_TRANSITION_LINK, false);
977  WebContents* inspected_web_contents = GetInspectedWebContents();
978  if (inspected_web_contents) {
979    inspected_web_contents->OpenURL(params);
980  } else {
981    chrome::HostDesktopType host_desktop_type;
982    if (browser_) {
983      host_desktop_type = browser_->host_desktop_type();
984    } else {
985      // There should always be a browser when there are no inspected web
986      // contents.
987      NOTREACHED();
988      host_desktop_type = chrome::GetActiveDesktop();
989    }
990
991    const BrowserList* browser_list =
992        BrowserList::GetInstance(host_desktop_type);
993    for (BrowserList::const_iterator it = browser_list->begin();
994         it != browser_list->end(); ++it) {
995      if ((*it)->type() == Browser::TYPE_TABBED) {
996        (*it)->OpenURL(params);
997        break;
998      }
999    }
1000  }
1001}
1002
1003void DevToolsWindow::SetWhitelistedShortcuts(
1004    const std::string& message) {
1005  event_forwarder_->SetWhitelistedShortcuts(message);
1006}
1007
1008void DevToolsWindow::InspectedContentsClosing() {
1009  intercepted_page_beforeunload_ = false;
1010  // This will prevent any activity after frontend is loaded.
1011  action_on_load_ = DevToolsToggleAction::NoOp();
1012  ignore_set_is_docked_ = true;
1013  web_contents_->GetRenderViewHost()->ClosePage();
1014}
1015
1016void DevToolsWindow::OnLoadCompleted() {
1017  // First seed inspected tab id for extension APIs.
1018  WebContents* inspected_web_contents = GetInspectedWebContents();
1019  if (inspected_web_contents) {
1020    SessionTabHelper* session_tab_helper =
1021        SessionTabHelper::FromWebContents(inspected_web_contents);
1022    if (session_tab_helper) {
1023      base::FundamentalValue tabId(session_tab_helper->session_id().id());
1024      bindings_->CallClientFunction("WebInspector.setInspectedTabId",
1025          &tabId, NULL, NULL);
1026    }
1027  }
1028
1029  // We could be in kLoadCompleted state already if frontend reloads itself.
1030  if (load_state_ != kLoadCompleted) {
1031    // Load is completed when both kIsDockedSet and kOnLoadFired happened.
1032    // Here we set kOnLoadFired.
1033    load_state_ = load_state_ == kIsDockedSet ? kLoadCompleted : kOnLoadFired;
1034  }
1035  if (load_state_ == kLoadCompleted)
1036    LoadCompleted();
1037}
1038
1039void DevToolsWindow::CreateDevToolsBrowser() {
1040  std::string wp_key = GetDevToolsWindowPlacementPrefKey();
1041  PrefService* prefs = profile_->GetPrefs();
1042  const base::DictionaryValue* wp_pref = prefs->GetDictionary(wp_key.c_str());
1043  if (!wp_pref || wp_pref->empty()) {
1044    DictionaryPrefUpdate update(prefs, wp_key.c_str());
1045    base::DictionaryValue* defaults = update.Get();
1046    defaults->SetInteger("left", 100);
1047    defaults->SetInteger("top", 100);
1048    defaults->SetInteger("right", 740);
1049    defaults->SetInteger("bottom", 740);
1050    defaults->SetBoolean("maximized", false);
1051    defaults->SetBoolean("always_on_top", false);
1052  }
1053
1054  browser_ = new Browser(Browser::CreateParams::CreateForDevTools(
1055      profile_,
1056      chrome::GetHostDesktopTypeForNativeView(
1057          web_contents_->GetView()->GetNativeView())));
1058  browser_->tab_strip_model()->AddWebContents(
1059      web_contents_, -1, content::PAGE_TRANSITION_AUTO_TOPLEVEL,
1060      TabStripModel::ADD_ACTIVE);
1061  web_contents_->GetRenderViewHost()->SyncRendererPrefs();
1062}
1063
1064// static
1065bool DevToolsWindow::FindInspectedBrowserAndTabIndex(
1066    WebContents* inspected_web_contents, Browser** browser, int* tab) {
1067  if (!inspected_web_contents)
1068    return false;
1069
1070  for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1071    int tab_index = it->tab_strip_model()->GetIndexOfWebContents(
1072        inspected_web_contents);
1073    if (tab_index != TabStripModel::kNoTab) {
1074      *browser = *it;
1075      *tab = tab_index;
1076      return true;
1077    }
1078  }
1079  return false;
1080}
1081
1082BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() {
1083  Browser* browser = NULL;
1084  int tab;
1085  return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1086                                         &browser, &tab) ?
1087      browser->window() : NULL;
1088}
1089
1090void DevToolsWindow::DoAction(const DevToolsToggleAction& action) {
1091  switch (action.type()) {
1092    case DevToolsToggleAction::kShowConsole:
1093      bindings_->CallClientFunction(
1094          "InspectorFrontendAPI.showConsole", NULL, NULL, NULL);
1095      break;
1096
1097    case DevToolsToggleAction::kInspect:
1098      bindings_->CallClientFunction(
1099          "InspectorFrontendAPI.enterInspectElementMode", NULL, NULL, NULL);
1100      break;
1101
1102    case DevToolsToggleAction::kShow:
1103    case DevToolsToggleAction::kToggle:
1104      // Do nothing.
1105      break;
1106
1107    case DevToolsToggleAction::kReveal: {
1108      const DevToolsToggleAction::RevealParams* params =
1109          action.params();
1110      CHECK(params);
1111      base::StringValue url_value(params->url);
1112      base::FundamentalValue line_value(static_cast<int>(params->line_number));
1113      base::FundamentalValue column_value(
1114          static_cast<int>(params->column_number));
1115      bindings_->CallClientFunction("InspectorFrontendAPI.revealSourceLine",
1116                                    &url_value, &line_value, &column_value);
1117      break;
1118    }
1119    default:
1120      NOTREACHED();
1121      break;
1122  }
1123}
1124
1125void DevToolsWindow::UpdateBrowserToolbar() {
1126  BrowserWindow* inspected_window = GetInspectedBrowserWindow();
1127  if (inspected_window)
1128    inspected_window->UpdateToolbar(NULL);
1129}
1130
1131WebContents* DevToolsWindow::GetInspectedWebContents() {
1132  return inspected_contents_observer_ ?
1133      inspected_contents_observer_->web_contents() : NULL;
1134}
1135
1136void DevToolsWindow::LoadCompleted() {
1137  Show(action_on_load_);
1138  action_on_load_ = DevToolsToggleAction::NoOp();
1139  if (!load_completed_callback_.is_null()) {
1140    load_completed_callback_.Run();
1141    load_completed_callback_ = base::Closure();
1142  }
1143}
1144
1145void DevToolsWindow::SetLoadCompletedCallback(const base::Closure& closure) {
1146  if (load_state_ == kLoadCompleted) {
1147    if (!closure.is_null())
1148      closure.Run();
1149    return;
1150  }
1151  load_completed_callback_ = closure;
1152}
1153
1154bool DevToolsWindow::ForwardKeyboardEvent(
1155    const content::NativeWebKeyboardEvent& event) {
1156  return event_forwarder_->ForwardEvent(event);
1157}
1158