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