Cast: Stop logging kVideoFrameSentToEncoder and rename a couple events.
[chromium-blink-merge.git] / chrome / browser / devtools / devtools_window.cc
blobe1e627db85884f65e6b9024dcce930c161411c59
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/user_prefs/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::ForWebContents(web_contents_);
612 bindings_->SetDelegate(this);
614 g_instances.Get().push_back(this);
616 // There is no inspected_rvh in case of shared workers.
617 if (inspected_rvh)
618 inspected_contents_observer_.reset(new InspectedWebContentsObserver(
619 content::WebContents::FromRenderViewHost(inspected_rvh)));
620 event_forwarder_.reset(new DevToolsEventForwarder(this));
623 // static
624 DevToolsWindow* DevToolsWindow::Create(
625 Profile* profile,
626 const GURL& frontend_url,
627 content::RenderViewHost* inspected_rvh,
628 bool shared_worker_frontend,
629 bool external_frontend,
630 bool can_dock) {
631 if (inspected_rvh) {
632 // Check for a place to dock.
633 Browser* browser = NULL;
634 int tab;
635 WebContents* inspected_web_contents =
636 content::WebContents::FromRenderViewHost(inspected_rvh);
637 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents,
638 &browser, &tab) ||
639 inspected_rvh->GetMainFrame()->IsCrossProcessSubframe() ||
640 browser->is_type_popup()) {
641 can_dock = false;
645 // Create WebContents with devtools.
646 GURL url(GetDevToolsURL(profile, frontend_url,
647 shared_worker_frontend,
648 external_frontend,
649 can_dock));
650 return new DevToolsWindow(profile, url, inspected_rvh, can_dock);
653 // static
654 GURL DevToolsWindow::GetDevToolsURL(Profile* profile,
655 const GURL& base_url,
656 bool shared_worker_frontend,
657 bool external_frontend,
658 bool can_dock) {
659 // Compatibility errors are encoded with data urls, pass them
660 // through with no decoration.
661 if (base_url.SchemeIs("data"))
662 return base_url;
664 std::string frontend_url(
665 base_url.is_empty() ? chrome::kChromeUIDevToolsURL : base_url.spec());
666 std::string url_string(
667 frontend_url +
668 ((frontend_url.find("?") == std::string::npos) ? "?" : "&"));
669 if (shared_worker_frontend)
670 url_string += "&isSharedWorker=true";
671 if (external_frontend)
672 url_string += "&remoteFrontend=true";
673 if (can_dock)
674 url_string += "&can_dock=true";
675 return GURL(url_string);
678 // static
679 DevToolsWindow* DevToolsWindow::FindDevToolsWindow(
680 DevToolsAgentHost* agent_host) {
681 DevToolsWindows* instances = g_instances.Pointer();
682 content::DevToolsManager* manager = content::DevToolsManager::GetInstance();
683 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
684 ++it) {
685 if (manager->GetDevToolsAgentHostFor((*it)->bindings_->frontend_host()) ==
686 agent_host)
687 return *it;
689 return NULL;
692 // static
693 DevToolsWindow* DevToolsWindow::AsDevToolsWindow(
694 content::RenderViewHost* window_rvh) {
695 if (g_instances == NULL)
696 return NULL;
697 DevToolsWindows* instances = g_instances.Pointer();
698 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
699 ++it) {
700 if ((*it)->web_contents_->GetRenderViewHost() == window_rvh)
701 return *it;
703 return NULL;
706 WebContents* DevToolsWindow::OpenURLFromTab(
707 WebContents* source,
708 const content::OpenURLParams& params) {
709 DCHECK(source == web_contents_);
710 if (!params.url.SchemeIs(content::kChromeDevToolsScheme)) {
711 WebContents* inspected_web_contents = GetInspectedWebContents();
712 return inspected_web_contents ?
713 inspected_web_contents->OpenURL(params) : NULL;
716 content::DevToolsManager* manager = content::DevToolsManager::GetInstance();
717 scoped_refptr<DevToolsAgentHost> agent_host(
718 manager->GetDevToolsAgentHostFor(bindings_->frontend_host()));
719 if (!agent_host.get())
720 return NULL;
721 manager->ClientHostClosing(bindings_->frontend_host());
722 manager->RegisterDevToolsClientHostFor(agent_host.get(),
723 bindings_->frontend_host());
725 content::NavigationController::LoadURLParams load_url_params(params.url);
726 web_contents_->GetController().LoadURLWithParams(load_url_params);
727 return web_contents_;
730 void DevToolsWindow::ActivateContents(WebContents* contents) {
731 if (is_docked_) {
732 WebContents* inspected_tab = GetInspectedWebContents();
733 inspected_tab->GetDelegate()->ActivateContents(inspected_tab);
734 } else {
735 browser_->window()->Activate();
739 void DevToolsWindow::AddNewContents(WebContents* source,
740 WebContents* new_contents,
741 WindowOpenDisposition disposition,
742 const gfx::Rect& initial_pos,
743 bool user_gesture,
744 bool* was_blocked) {
745 WebContents* inspected_web_contents = GetInspectedWebContents();
746 if (inspected_web_contents) {
747 inspected_web_contents->GetDelegate()->AddNewContents(
748 source, new_contents, disposition, initial_pos, user_gesture,
749 was_blocked);
753 void DevToolsWindow::CloseContents(WebContents* source) {
754 CHECK(is_docked_);
755 // This will prevent any activity after frontend is loaded.
756 action_on_load_ = DevToolsToggleAction::NoOp();
757 ignore_set_is_docked_ = true;
758 // Update dev tools to reflect removed dev tools window.
759 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
760 if (inspected_window)
761 inspected_window->UpdateDevTools();
762 // In case of docked web_contents_, we own it so delete here.
763 // Embedding DevTools window will be deleted as a result of
764 // WebContentsDestroyed callback.
765 delete web_contents_;
768 void DevToolsWindow::ContentsZoomChange(bool zoom_in) {
769 DCHECK(is_docked_);
770 chrome_page_zoom::Zoom(web_contents_,
771 zoom_in ? content::PAGE_ZOOM_IN : content::PAGE_ZOOM_OUT);
774 void DevToolsWindow::BeforeUnloadFired(WebContents* tab,
775 bool proceed,
776 bool* proceed_to_fire_unload) {
777 if (!intercepted_page_beforeunload_) {
778 // Docked devtools window closed directly.
779 if (proceed) {
780 content::DevToolsManager::GetInstance()->ClientHostClosing(
781 bindings_->frontend_host());
783 *proceed_to_fire_unload = proceed;
784 } else {
785 // Inspected page is attempting to close.
786 WebContents* inspected_web_contents = GetInspectedWebContents();
787 if (proceed) {
788 inspected_web_contents->DispatchBeforeUnload(false);
789 } else {
790 bool should_proceed;
791 inspected_web_contents->GetDelegate()->BeforeUnloadFired(
792 inspected_web_contents, false, &should_proceed);
793 DCHECK(!should_proceed);
795 *proceed_to_fire_unload = false;
799 bool DevToolsWindow::PreHandleKeyboardEvent(
800 WebContents* source,
801 const content::NativeWebKeyboardEvent& event,
802 bool* is_keyboard_shortcut) {
803 if (is_docked_) {
804 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
805 if (inspected_window) {
806 return inspected_window->PreHandleKeyboardEvent(event,
807 is_keyboard_shortcut);
810 return false;
813 void DevToolsWindow::HandleKeyboardEvent(
814 WebContents* source,
815 const content::NativeWebKeyboardEvent& event) {
816 if (is_docked_) {
817 if (event.windowsKeyCode == 0x08) {
818 // Do not navigate back in history on Windows (http://crbug.com/74156).
819 return;
821 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
822 if (inspected_window)
823 inspected_window->HandleKeyboardEvent(event);
827 content::JavaScriptDialogManager* DevToolsWindow::GetJavaScriptDialogManager() {
828 WebContents* inspected_web_contents = GetInspectedWebContents();
829 return (inspected_web_contents && inspected_web_contents->GetDelegate()) ?
830 inspected_web_contents->GetDelegate()->GetJavaScriptDialogManager() :
831 content::WebContentsDelegate::GetJavaScriptDialogManager();
834 content::ColorChooser* DevToolsWindow::OpenColorChooser(
835 WebContents* web_contents,
836 SkColor initial_color,
837 const std::vector<content::ColorSuggestion>& suggestions) {
838 return chrome::ShowColorChooser(web_contents, initial_color);
841 void DevToolsWindow::RunFileChooser(WebContents* web_contents,
842 const content::FileChooserParams& params) {
843 FileSelectHelper::RunFileChooser(web_contents, params);
846 void DevToolsWindow::WebContentsFocused(WebContents* contents) {
847 Browser* inspected_browser = NULL;
848 int inspected_tab_index = -1;
849 if (is_docked_ && FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
850 &inspected_browser,
851 &inspected_tab_index))
852 inspected_browser->window()->WebContentsFocused(contents);
855 bool DevToolsWindow::PreHandleGestureEvent(
856 WebContents* source,
857 const blink::WebGestureEvent& event) {
858 // Disable pinch zooming.
859 return event.type == blink::WebGestureEvent::GesturePinchBegin ||
860 event.type == blink::WebGestureEvent::GesturePinchUpdate ||
861 event.type == blink::WebGestureEvent::GesturePinchEnd;
864 void DevToolsWindow::ActivateWindow() {
865 if (is_docked_ && GetInspectedBrowserWindow())
866 web_contents_->Focus();
867 else if (!is_docked_ && !browser_->window()->IsActive())
868 browser_->window()->Activate();
871 void DevToolsWindow::CloseWindow() {
872 DCHECK(is_docked_);
873 // This will prevent any activity after frontend is loaded.
874 action_on_load_ = DevToolsToggleAction::NoOp();
875 ignore_set_is_docked_ = true;
876 web_contents_->DispatchBeforeUnload(false);
879 void DevToolsWindow::SetContentsInsets(
880 int top, int left, int bottom, int right) {
881 gfx::Insets insets(top, left, bottom, right);
882 SetContentsResizingStrategy(insets, contents_resizing_strategy_.min_size());
885 void DevToolsWindow::SetContentsResizingStrategy(
886 const gfx::Insets& insets, const gfx::Size& min_size) {
887 DevToolsContentsResizingStrategy strategy(insets, min_size);
888 if (contents_resizing_strategy_.Equals(strategy))
889 return;
891 contents_resizing_strategy_.CopyFrom(strategy);
892 if (is_docked_) {
893 // Update inspected window.
894 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
895 if (inspected_window)
896 inspected_window->UpdateDevTools();
900 void DevToolsWindow::InspectElementCompleted() {
901 if (!inspect_element_start_time_.is_null()) {
902 UMA_HISTOGRAM_TIMES("DevTools.InspectElement",
903 base::TimeTicks::Now() - inspect_element_start_time_);
904 inspect_element_start_time_ = base::TimeTicks();
908 void DevToolsWindow::MoveWindow(int x, int y) {
909 if (!is_docked_) {
910 gfx::Rect bounds = browser_->window()->GetBounds();
911 bounds.Offset(x, y);
912 browser_->window()->SetBounds(bounds);
916 void DevToolsWindow::SetIsDockedAndShowImmediatelyForTest(bool is_docked) {
917 DCHECK(!is_docked || can_dock_);
918 if (load_state_ == kLoadCompleted) {
919 SetIsDocked(is_docked);
920 } else {
921 is_docked_ = is_docked;
922 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
923 // Note that kIsDockedSet may be already set when can_dock_ is false.
924 load_state_ = load_state_ == kOnLoadFired ? kLoadCompleted : kIsDockedSet;
925 // Note that action_on_load_ will be performed after the load is actually
926 // completed. For now, just show the window.
927 Show(DevToolsToggleAction::Show());
928 if (load_state_ == kLoadCompleted)
929 LoadCompleted();
931 ignore_set_is_docked_ = true;
934 void DevToolsWindow::SetIsDocked(bool dock_requested) {
935 if (ignore_set_is_docked_)
936 return;
938 DCHECK(can_dock_ || !dock_requested);
939 if (!can_dock_)
940 dock_requested = false;
942 bool was_docked = is_docked_;
943 is_docked_ = dock_requested;
945 if (load_state_ != kLoadCompleted) {
946 // This is a first time call we waited for to initialize.
947 load_state_ = load_state_ == kOnLoadFired ? kLoadCompleted : kIsDockedSet;
948 if (load_state_ == kLoadCompleted)
949 LoadCompleted();
950 return;
953 if (dock_requested == was_docked)
954 return;
956 if (dock_requested && !was_docked) {
957 // Detach window from the external devtools browser. It will lead to
958 // the browser object's close and delete. Remove observer first.
959 TabStripModel* tab_strip_model = browser_->tab_strip_model();
960 tab_strip_model->DetachWebContentsAt(
961 tab_strip_model->GetIndexOfWebContents(web_contents_));
962 browser_ = NULL;
963 } else if (!dock_requested && was_docked) {
964 // Update inspected window to hide split and reset it.
965 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
966 if (inspected_window)
967 inspected_window->UpdateDevTools();
970 Show(DevToolsToggleAction::Show());
973 void DevToolsWindow::OpenInNewTab(const std::string& url) {
974 content::OpenURLParams params(
975 GURL(url), content::Referrer(), NEW_FOREGROUND_TAB,
976 content::PAGE_TRANSITION_LINK, false);
977 WebContents* inspected_web_contents = GetInspectedWebContents();
978 if (inspected_web_contents) {
979 inspected_web_contents->OpenURL(params);
980 } else {
981 chrome::HostDesktopType host_desktop_type;
982 if (browser_) {
983 host_desktop_type = browser_->host_desktop_type();
984 } else {
985 // There should always be a browser when there are no inspected web
986 // contents.
987 NOTREACHED();
988 host_desktop_type = chrome::GetActiveDesktop();
991 const BrowserList* browser_list =
992 BrowserList::GetInstance(host_desktop_type);
993 for (BrowserList::const_iterator it = browser_list->begin();
994 it != browser_list->end(); ++it) {
995 if ((*it)->type() == Browser::TYPE_TABBED) {
996 (*it)->OpenURL(params);
997 break;
1003 void DevToolsWindow::SetWhitelistedShortcuts(
1004 const std::string& message) {
1005 event_forwarder_->SetWhitelistedShortcuts(message);
1008 void DevToolsWindow::InspectedContentsClosing() {
1009 intercepted_page_beforeunload_ = false;
1010 // This will prevent any activity after frontend is loaded.
1011 action_on_load_ = DevToolsToggleAction::NoOp();
1012 ignore_set_is_docked_ = true;
1013 web_contents_->GetRenderViewHost()->ClosePage();
1016 InfoBarService* DevToolsWindow::GetInfoBarService() {
1017 return is_docked_ ?
1018 InfoBarService::FromWebContents(GetInspectedWebContents()) :
1019 InfoBarService::FromWebContents(web_contents_);
1022 void DevToolsWindow::OnLoadCompleted() {
1023 // First seed inspected tab id for extension APIs.
1024 WebContents* inspected_web_contents = GetInspectedWebContents();
1025 if (inspected_web_contents) {
1026 SessionTabHelper* session_tab_helper =
1027 SessionTabHelper::FromWebContents(inspected_web_contents);
1028 if (session_tab_helper) {
1029 base::FundamentalValue tabId(session_tab_helper->session_id().id());
1030 bindings_->CallClientFunction("WebInspector.setInspectedTabId",
1031 &tabId, NULL, NULL);
1035 // We could be in kLoadCompleted state already if frontend reloads itself.
1036 if (load_state_ != kLoadCompleted) {
1037 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
1038 // Here we set kOnLoadFired.
1039 load_state_ = load_state_ == kIsDockedSet ? kLoadCompleted : kOnLoadFired;
1041 if (load_state_ == kLoadCompleted)
1042 LoadCompleted();
1045 void DevToolsWindow::CreateDevToolsBrowser() {
1046 std::string wp_key = GetDevToolsWindowPlacementPrefKey();
1047 PrefService* prefs = profile_->GetPrefs();
1048 const base::DictionaryValue* wp_pref = prefs->GetDictionary(wp_key.c_str());
1049 if (!wp_pref || wp_pref->empty()) {
1050 DictionaryPrefUpdate update(prefs, wp_key.c_str());
1051 base::DictionaryValue* defaults = update.Get();
1052 defaults->SetInteger("left", 100);
1053 defaults->SetInteger("top", 100);
1054 defaults->SetInteger("right", 740);
1055 defaults->SetInteger("bottom", 740);
1056 defaults->SetBoolean("maximized", false);
1057 defaults->SetBoolean("always_on_top", false);
1060 browser_ = new Browser(Browser::CreateParams::CreateForDevTools(
1061 profile_,
1062 chrome::GetHostDesktopTypeForNativeView(
1063 web_contents_->GetNativeView())));
1064 browser_->tab_strip_model()->AddWebContents(
1065 web_contents_, -1, content::PAGE_TRANSITION_AUTO_TOPLEVEL,
1066 TabStripModel::ADD_ACTIVE);
1067 web_contents_->GetRenderViewHost()->SyncRendererPrefs();
1070 // static
1071 bool DevToolsWindow::FindInspectedBrowserAndTabIndex(
1072 WebContents* inspected_web_contents, Browser** browser, int* tab) {
1073 if (!inspected_web_contents)
1074 return false;
1076 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
1077 int tab_index = it->tab_strip_model()->GetIndexOfWebContents(
1078 inspected_web_contents);
1079 if (tab_index != TabStripModel::kNoTab) {
1080 *browser = *it;
1081 *tab = tab_index;
1082 return true;
1085 return false;
1088 BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() {
1089 Browser* browser = NULL;
1090 int tab;
1091 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1092 &browser, &tab) ?
1093 browser->window() : NULL;
1096 void DevToolsWindow::DoAction(const DevToolsToggleAction& action) {
1097 switch (action.type()) {
1098 case DevToolsToggleAction::kShowConsole:
1099 bindings_->CallClientFunction(
1100 "InspectorFrontendAPI.showConsole", NULL, NULL, NULL);
1101 break;
1103 case DevToolsToggleAction::kInspect:
1104 bindings_->CallClientFunction(
1105 "InspectorFrontendAPI.enterInspectElementMode", NULL, NULL, NULL);
1106 break;
1108 case DevToolsToggleAction::kShow:
1109 case DevToolsToggleAction::kToggle:
1110 // Do nothing.
1111 break;
1113 case DevToolsToggleAction::kReveal: {
1114 const DevToolsToggleAction::RevealParams* params =
1115 action.params();
1116 CHECK(params);
1117 base::StringValue url_value(params->url);
1118 base::FundamentalValue line_value(static_cast<int>(params->line_number));
1119 base::FundamentalValue column_value(
1120 static_cast<int>(params->column_number));
1121 bindings_->CallClientFunction("InspectorFrontendAPI.revealSourceLine",
1122 &url_value, &line_value, &column_value);
1123 break;
1125 default:
1126 NOTREACHED();
1127 break;
1131 void DevToolsWindow::UpdateBrowserToolbar() {
1132 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
1133 if (inspected_window)
1134 inspected_window->UpdateToolbar(NULL);
1137 WebContents* DevToolsWindow::GetInspectedWebContents() {
1138 return inspected_contents_observer_ ?
1139 inspected_contents_observer_->web_contents() : NULL;
1142 void DevToolsWindow::LoadCompleted() {
1143 Show(action_on_load_);
1144 action_on_load_ = DevToolsToggleAction::NoOp();
1145 if (!load_completed_callback_.is_null()) {
1146 load_completed_callback_.Run();
1147 load_completed_callback_ = base::Closure();
1151 void DevToolsWindow::SetLoadCompletedCallback(const base::Closure& closure) {
1152 if (load_state_ == kLoadCompleted) {
1153 if (!closure.is_null())
1154 closure.Run();
1155 return;
1157 load_completed_callback_ = closure;
1160 bool DevToolsWindow::ForwardKeyboardEvent(
1161 const content::NativeWebKeyboardEvent& event) {
1162 return event_forwarder_->ForwardEvent(event);