No longer register app window placement preference keys on
[chromium-blink-merge.git] / chrome / browser / devtools / devtools_window.cc
blob78e75f4882a865c39600ecaff9374b965337ac7a
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.
5 #include "chrome/browser/devtools/devtools_window.h"
7 #include <algorithm>
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/page_transition_types.h"
48 #include "content/public/common/url_constants.h"
49 #include "third_party/WebKit/public/web/WebInputEvent.h"
50 #include "ui/events/keycodes/keyboard_codes.h"
52 using base::DictionaryValue;
53 using blink::WebInputEvent;
54 using content::BrowserThread;
55 using content::DevToolsAgentHost;
56 using content::WebContents;
58 namespace {
60 typedef std::vector<DevToolsWindow*> DevToolsWindows;
61 base::LazyInstance<DevToolsWindows>::Leaky g_instances =
62 LAZY_INSTANCE_INITIALIZER;
64 static const char kKeyUpEventName[] = "keyup";
65 static const char kKeyDownEventName[] = "keydown";
67 bool FindInspectedBrowserAndTabIndex(
68 WebContents* inspected_web_contents, Browser** browser, int* tab) {
69 if (!inspected_web_contents)
70 return false;
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;
81 return false;
84 // DevToolsToolboxDelegate ----------------------------------------------------
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 virtual ~DevToolsToolboxDelegate();
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;
107 private:
108 BrowserWindow* GetInspectedBrowserWindow();
109 DevToolsWindow::ObserverWithAccessor* inspected_contents_observer_;
110 DISALLOW_COPY_AND_ASSIGN(DevToolsToolboxDelegate);
113 DevToolsToolboxDelegate::DevToolsToolboxDelegate(
114 WebContents* toolbox_contents,
115 DevToolsWindow::ObserverWithAccessor* web_contents_observer)
116 : WebContentsObserver(toolbox_contents),
117 inspected_contents_observer_(web_contents_observer) {
120 DevToolsToolboxDelegate::~DevToolsToolboxDelegate() {
123 content::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;
134 bool 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;
144 void 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;
151 BrowserWindow* window = GetInspectedBrowserWindow();
152 if (window)
153 window->HandleKeyboardEvent(event);
156 void DevToolsToolboxDelegate::WebContentsDestroyed() {
157 delete this;
160 BrowserWindow* 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;
172 } // namespace
174 // DevToolsEventForwarder -----------------------------------------------------
176 class DevToolsEventForwarder {
177 public:
178 explicit DevToolsEventForwarder(DevToolsWindow* window)
179 : devtools_window_(window) {}
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);
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);
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);
194 DevToolsWindow* devtools_window_;
195 std::set<int> whitelisted_keys_;
197 DISALLOW_COPY_AND_ASSIGN(DevToolsEventForwarder);
200 void 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;
222 whitelisted_keys_.insert(CombineKeyCodeAndModifiers(key_code, modifiers));
226 bool 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;
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;
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;
256 int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code,
257 int modifiers) {
258 return key_code | (modifiers << 16);
261 bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code,
262 int modifiers) {
263 return (ui::VKEY_F1 <= key_code && key_code <= ui::VKEY_F12) ||
264 modifiers != 0;
267 // Mapping copied from Blink's KeyboardEvent.cpp.
268 int DevToolsEventForwarder::VirtualKeyCodeWithoutLocation(int key_code)
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;
285 // DevToolsWindow::ObserverWithAccessor -------------------------------
287 DevToolsWindow::ObserverWithAccessor::ObserverWithAccessor(
288 WebContents* web_contents)
289 : WebContentsObserver(web_contents) {
292 DevToolsWindow::ObserverWithAccessor::~ObserverWithAccessor() {
295 // DevToolsWindow -------------------------------------------------------------
297 const char DevToolsWindow::kDevToolsApp[] = "DevToolsApp";
299 DevToolsWindow::~DevToolsWindow() {
300 life_stage_ = kClosing;
302 UpdateBrowserWindow();
303 UpdateBrowserToolbar();
305 if (toolbox_web_contents_)
306 delete toolbox_web_contents_;
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);
314 if (!close_callback_.is_null()) {
315 close_callback_.Run();
316 close_callback_ = base::Closure();
320 // static
321 void 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);
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);
350 // static
351 content::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;
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;
366 // Undocked window should have toolbox web contents.
367 if (!window->is_docked_ && !window->toolbox_web_contents_)
368 return NULL;
370 if (out_strategy)
371 out_strategy->CopyFrom(window->contents_resizing_strategy_);
373 return window->is_docked_ ? window->main_web_contents_ :
374 window->toolbox_web_contents_;
377 // static
378 DevToolsWindow* 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;
388 return NULL;
391 // static
392 bool 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;
402 return false;
405 // static
406 DevToolsWindow* 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);
414 window->ScheduleShow(DevToolsToggleAction::Show());
415 return window;
418 // static
419 DevToolsWindow* DevToolsWindow::CreateDevToolsWindowForWorker(
420 Profile* profile) {
421 content::RecordAction(base::UserMetricsAction("DevTools_InspectWorker"));
422 return Create(profile, GURL(), NULL, true, false, false, "");
425 // static
426 DevToolsWindow* DevToolsWindow::OpenDevToolsWindow(
427 content::WebContents* inspected_web_contents) {
428 return ToggleDevToolsWindow(
429 inspected_web_contents, true, DevToolsToggleAction::Show(), "");
432 // static
433 DevToolsWindow* DevToolsWindow::OpenDevToolsWindow(
434 content::WebContents* inspected_web_contents,
435 const DevToolsToggleAction& action) {
436 return ToggleDevToolsWindow(inspected_web_contents, true, action, "");
439 // static
440 DevToolsWindow* 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;
449 return ToggleDevToolsWindow(
450 browser->tab_strip_model()->GetActiveWebContents(),
451 action.type() == DevToolsToggleAction::kInspect,
452 action, "");
455 // static
456 void 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);
467 window->ScheduleShow(DevToolsToggleAction::Show());
470 // static
471 DevToolsWindow* 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;
491 // Update toolbar to reflect DevTools changes.
492 window->UpdateBrowserToolbar();
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();
501 return window;
504 // static
505 void 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;
521 void DevToolsWindow::ScheduleShow(const DevToolsToggleAction& action) {
522 if (life_stage_ == kLoadCompleted) {
523 Show(action);
524 return;
527 // Action will be done only after load completed.
528 action_on_load_ = action;
530 if (!can_dock_) {
531 // No harm to show always-undocked window right away.
532 is_docked_ = false;
533 Show(DevToolsToggleAction::Show());
537 void DevToolsWindow::Show(const DevToolsToggleAction& action) {
538 if (life_stage_ == kClosing)
539 return;
541 if (action.type() == DevToolsToggleAction::kNoOp)
542 return;
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);
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);
558 TabStripModel* tab_strip_model = inspected_browser->tab_strip_model();
559 tab_strip_model->ActivateTabAt(inspected_tab_index, true);
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();
569 PrefsTabHelper::CreateForWebContents(main_web_contents_);
570 main_web_contents_->GetRenderViewHost()->SyncRendererPrefs();
572 DoAction(action);
573 return;
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);
581 if (!browser_)
582 CreateDevToolsBrowser();
584 if (should_show_window) {
585 browser_->window()->Show();
586 main_web_contents_->SetInitialFocus();
588 if (toolbox_web_contents_)
589 UpdateBrowserWindow();
591 DoAction(action);
594 // static
595 bool 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;
607 // static
608 bool DevToolsWindow::InterceptPageBeforeUnload(WebContents* contents) {
609 DevToolsWindow* window =
610 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
611 if (!window || window->intercepted_page_beforeunload_)
612 return false;
614 // Not yet loaded frontend will not handle beforeunload.
615 if (window->life_stage_ != kLoadCompleted)
616 return false;
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);
624 return true;
627 // static
628 bool DevToolsWindow::NeedsToInterceptBeforeUnload(
629 WebContents* contents) {
630 DevToolsWindow* window =
631 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
632 return window && !window->intercepted_page_beforeunload_;
635 // static
636 bool 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_;
652 // static
653 void 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_);
663 DevToolsWindow::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);
685 main_web_contents_->GetController().LoadURL(
686 DevToolsUIBindings::ApplyThemeToURL(profile, url), content::Referrer(),
687 content::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string());
689 bindings_ = DevToolsUIBindings::ForWebContents(main_web_contents_);
690 DCHECK(bindings_);
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);
700 g_instances.Get().push_back(this);
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));
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);
717 event_forwarder_.reset(new DevToolsEventForwarder(this));
720 // static
721 DevToolsWindow* 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;
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);
748 // static
749 GURL 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;
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);
776 // static
777 DevToolsWindow* 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;
787 return NULL;
790 // static
791 DevToolsWindow* 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;
801 return NULL;
804 WebContents* 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;
814 bindings_->Reattach();
816 content::NavigationController::LoadURLParams load_url_params(params.url);
817 main_web_contents_->GetController().LoadURLWithParams(load_url_params);
818 return main_web_contents_;
821 void 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();
830 void 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);
846 UpdateBrowserWindow();
847 return;
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);
858 void 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;
870 void 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_;
880 void 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);
886 void 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);
905 *proceed_to_fire_unload = false;
909 bool 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);
918 return false;
921 void 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;
928 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
929 if (inspected_window)
930 inspected_window->HandleKeyboardEvent(event);
933 content::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();
940 content::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);
947 void DevToolsWindow::RunFileChooser(WebContents* web_contents,
948 const content::FileChooserParams& params) {
949 FileSelectHelper::RunFileChooser(web_contents, params);
952 void 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);
961 bool 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;
970 void DevToolsWindow::ActivateWindow() {
971 if (is_docked_ && GetInspectedBrowserWindow())
972 main_web_contents_->Focus();
973 else if (!is_docked_ && !browser_->window()->IsActive())
974 browser_->window()->Activate();
977 void DevToolsWindow::CloseWindow() {
978 DCHECK(is_docked_);
979 life_stage_ = kClosing;
980 main_web_contents_->DispatchBeforeUnload(false);
983 void DevToolsWindow::SetInspectedPageBounds(const gfx::Rect& rect) {
984 DevToolsContentsResizingStrategy strategy(rect);
985 if (contents_resizing_strategy_.Equals(strategy))
986 return;
988 contents_resizing_strategy_.CopyFrom(strategy);
989 UpdateBrowserWindow();
992 void 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();
1000 void 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);
1008 void DevToolsWindow::SetIsDocked(bool dock_requested) {
1009 if (life_stage_ == kClosing)
1010 return;
1012 DCHECK(can_dock_ || !dock_requested);
1013 if (!can_dock_)
1014 dock_requested = false;
1016 bool was_docked = is_docked_;
1017 is_docked_ = dock_requested;
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;
1027 if (dock_requested == was_docked)
1028 return;
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();
1041 Show(DevToolsToggleAction::Show());
1044 void DevToolsWindow::OpenInNewTab(const std::string& url) {
1045 content::OpenURLParams params(
1046 GURL(url), content::Referrer(), NEW_FOREGROUND_TAB,
1047 content::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();
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;
1074 void DevToolsWindow::SetWhitelistedShortcuts(
1075 const std::string& message) {
1076 event_forwarder_->SetWhitelistedShortcuts(message);
1079 void DevToolsWindow::InspectedContentsClosing() {
1080 intercepted_page_beforeunload_ = false;
1081 life_stage_ = kClosing;
1082 main_web_contents_->GetRenderViewHost()->ClosePage();
1085 InfoBarService* DevToolsWindow::GetInfoBarService() {
1086 return is_docked_ ?
1087 InfoBarService::FromWebContents(GetInspectedWebContents()) :
1088 InfoBarService::FromWebContents(main_web_contents_);
1091 void 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_);
1099 void 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);
1112 if (life_stage_ == kClosing)
1113 return;
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;
1121 if (life_stage_ == kLoadCompleted)
1122 LoadCompleted();
1125 void 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);
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, content::PAGE_TRANSITION_AUTO_TOPLEVEL,
1146 TabStripModel::ADD_ACTIVE);
1147 main_web_contents_->GetRenderViewHost()->SyncRendererPrefs();
1150 BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() {
1151 Browser* browser = NULL;
1152 int tab;
1153 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1154 &browser, &tab) ?
1155 browser->window() : NULL;
1158 void DevToolsWindow::DoAction(const DevToolsToggleAction& action) {
1159 switch (action.type()) {
1160 case DevToolsToggleAction::kShowConsole:
1161 bindings_->CallClientFunction(
1162 "InspectorFrontendAPI.showConsole", NULL, NULL, NULL);
1163 break;
1165 case DevToolsToggleAction::kInspect:
1166 bindings_->CallClientFunction(
1167 "InspectorFrontendAPI.enterInspectElementMode", NULL, NULL, NULL);
1168 break;
1170 case DevToolsToggleAction::kShow:
1171 case DevToolsToggleAction::kToggle:
1172 // Do nothing.
1173 break;
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;
1187 default:
1188 NOTREACHED();
1189 break;
1193 void DevToolsWindow::UpdateBrowserToolbar() {
1194 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
1195 if (inspected_window)
1196 inspected_window->UpdateToolbar(NULL);
1199 void DevToolsWindow::UpdateBrowserWindow() {
1200 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
1201 if (inspected_window)
1202 inspected_window->UpdateDevTools();
1205 WebContents* DevToolsWindow::GetInspectedWebContents() {
1206 return inspected_contents_observer_
1207 ? inspected_contents_observer_->web_contents()
1208 : NULL;
1211 void 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();
1220 void DevToolsWindow::SetLoadCompletedCallback(const base::Closure& closure) {
1221 if (life_stage_ == kLoadCompleted || life_stage_ == kClosing) {
1222 if (!closure.is_null())
1223 closure.Run();
1224 return;
1226 load_completed_callback_ = closure;
1229 bool DevToolsWindow::ForwardKeyboardEvent(
1230 const content::NativeWebKeyboardEvent& event) {
1231 return event_forwarder_->ForwardEvent(event);