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