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