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