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"
9 #include "base/json/json_reader.h"
10 #include "base/metrics/histogram.h"
11 #include "base/prefs/scoped_user_pref_update.h"
12 #include "base/time/time.h"
13 #include "base/values.h"
14 #include "chrome/browser/chrome_page_zoom.h"
15 #include "chrome/browser/file_select_helper.h"
16 #include "chrome/browser/infobars/infobar_service.h"
17 #include "chrome/browser/prefs/pref_service_syncable.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/sessions/session_tab_helper.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/browser_dialogs.h"
22 #include "chrome/browser/ui/browser_iterator.h"
23 #include "chrome/browser/ui/browser_list.h"
24 #include "chrome/browser/ui/browser_window.h"
25 #include "chrome/browser/ui/host_desktop.h"
26 #include "chrome/browser/ui/prefs/prefs_tab_helper.h"
27 #include "chrome/browser/ui/tabs/tab_strip_model.h"
28 #include "chrome/browser/ui/webui/devtools_ui.h"
29 #include "chrome/browser/ui/zoom/zoom_controller.h"
30 #include "chrome/common/chrome_switches.h"
31 #include "chrome/common/pref_names.h"
32 #include "chrome/common/render_messages.h"
33 #include "chrome/common/url_constants.h"
34 #include "components/pref_registry/pref_registry_syncable.h"
35 #include "content/public/browser/browser_thread.h"
36 #include "content/public/browser/devtools_agent_host.h"
37 #include "content/public/browser/native_web_keyboard_event.h"
38 #include "content/public/browser/navigation_controller.h"
39 #include "content/public/browser/navigation_entry.h"
40 #include "content/public/browser/render_frame_host.h"
41 #include "content/public/browser/render_process_host.h"
42 #include "content/public/browser/render_view_host.h"
43 #include "content/public/browser/render_widget_host_view.h"
44 #include "content/public/browser/user_metrics.h"
45 #include "content/public/browser/web_contents.h"
46 #include "content/public/common/content_client.h"
47 #include "content/public/common/url_constants.h"
48 #include "third_party/WebKit/public/web/WebInputEvent.h"
49 #include "ui/base/page_transition_types.h"
50 #include "ui/events/keycodes/keyboard_codes.h"
52 using base::DictionaryValue;
53 using blink::WebInputEvent;
54 using content::BrowserThread;
55 using content::DevToolsAgentHost;
56 using content::WebContents;
60 typedef std::vector<DevToolsWindow*> DevToolsWindows;
61 base::LazyInstance<DevToolsWindows>::Leaky g_instances =
62 LAZY_INSTANCE_INITIALIZER;
64 static const char kKeyUpEventName[] = "keyup";
65 static const char kKeyDownEventName[] = "keydown";
67 bool FindInspectedBrowserAndTabIndex(
68 WebContents* inspected_web_contents, Browser** browser, int* tab) {
69 if (!inspected_web_contents)
72 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
73 int tab_index = it->tab_strip_model()->GetIndexOfWebContents(
74 inspected_web_contents);
75 if (tab_index != TabStripModel::kNoTab) {
84 // DevToolsToolboxDelegate ----------------------------------------------------
86 class DevToolsToolboxDelegate
87 : public content::WebContentsObserver,
88 public content::WebContentsDelegate {
90 DevToolsToolboxDelegate(
91 WebContents* toolbox_contents,
92 DevToolsWindow::ObserverWithAccessor* web_contents_observer);
93 ~DevToolsToolboxDelegate() override;
95 content::WebContents* OpenURLFromTab(
96 content::WebContents* source,
97 const content::OpenURLParams& params) override;
98 bool PreHandleKeyboardEvent(content::WebContents* source,
99 const content::NativeWebKeyboardEvent& event,
100 bool* is_keyboard_shortcut) override;
101 void HandleKeyboardEvent(
102 content::WebContents* source,
103 const content::NativeWebKeyboardEvent& event) override;
104 void WebContentsDestroyed() override;
107 BrowserWindow* GetInspectedBrowserWindow();
108 DevToolsWindow::ObserverWithAccessor* inspected_contents_observer_;
109 DISALLOW_COPY_AND_ASSIGN(DevToolsToolboxDelegate);
112 DevToolsToolboxDelegate::DevToolsToolboxDelegate(
113 WebContents* toolbox_contents,
114 DevToolsWindow::ObserverWithAccessor* web_contents_observer)
115 : WebContentsObserver(toolbox_contents),
116 inspected_contents_observer_(web_contents_observer) {
119 DevToolsToolboxDelegate::~DevToolsToolboxDelegate() {
122 content::WebContents* DevToolsToolboxDelegate::OpenURLFromTab(
123 content::WebContents* source,
124 const content::OpenURLParams& params) {
125 DCHECK(source == web_contents());
126 if (!params.url.SchemeIs(content::kChromeDevToolsScheme))
128 content::NavigationController::LoadURLParams load_url_params(params.url);
129 source->GetController().LoadURLWithParams(load_url_params);
133 bool DevToolsToolboxDelegate::PreHandleKeyboardEvent(
134 content::WebContents* source,
135 const content::NativeWebKeyboardEvent& event,
136 bool* is_keyboard_shortcut) {
137 BrowserWindow* window = GetInspectedBrowserWindow();
139 return window->PreHandleKeyboardEvent(event, is_keyboard_shortcut);
143 void DevToolsToolboxDelegate::HandleKeyboardEvent(
144 content::WebContents* source,
145 const content::NativeWebKeyboardEvent& event) {
146 if (event.windowsKeyCode == 0x08) {
147 // Do not navigate back in history on Windows (http://crbug.com/74156).
150 BrowserWindow* window = GetInspectedBrowserWindow();
152 window->HandleKeyboardEvent(event);
155 void DevToolsToolboxDelegate::WebContentsDestroyed() {
159 BrowserWindow* DevToolsToolboxDelegate::GetInspectedBrowserWindow() {
160 WebContents* inspected_contents =
161 inspected_contents_observer_->web_contents();
162 if (!inspected_contents)
164 Browser* browser = NULL;
166 if (FindInspectedBrowserAndTabIndex(inspected_contents, &browser, &tab))
167 return browser->window();
173 // DevToolsEventForwarder -----------------------------------------------------
175 class DevToolsEventForwarder {
177 explicit DevToolsEventForwarder(DevToolsWindow* window)
178 : devtools_window_(window) {}
180 // Registers whitelisted shortcuts with the forwarder.
181 // Only registered keys will be forwarded to the DevTools frontend.
182 void SetWhitelistedShortcuts(const std::string& message);
184 // Forwards a keyboard event to the DevTools frontend if it is whitelisted.
185 // Returns |true| if the event has been forwarded, |false| otherwise.
186 bool ForwardEvent(const content::NativeWebKeyboardEvent& event);
189 static int VirtualKeyCodeWithoutLocation(int key_code);
190 static bool KeyWhitelistingAllowed(int key_code, int modifiers);
191 static int CombineKeyCodeAndModifiers(int key_code, int modifiers);
193 DevToolsWindow* devtools_window_;
194 std::set<int> whitelisted_keys_;
196 DISALLOW_COPY_AND_ASSIGN(DevToolsEventForwarder);
199 void DevToolsEventForwarder::SetWhitelistedShortcuts(
200 const std::string& message) {
201 scoped_ptr<base::Value> parsed_message(base::JSONReader::Read(message));
202 base::ListValue* shortcut_list;
203 if (!parsed_message->GetAsList(&shortcut_list))
205 base::ListValue::iterator it = shortcut_list->begin();
206 for (; it != shortcut_list->end(); ++it) {
207 base::DictionaryValue* dictionary;
208 if (!(*it)->GetAsDictionary(&dictionary))
211 dictionary->GetInteger("keyCode", &key_code);
215 dictionary->GetInteger("modifiers", &modifiers);
216 if (!KeyWhitelistingAllowed(key_code, modifiers)) {
217 LOG(WARNING) << "Key whitelisting forbidden: "
218 << "(" << key_code << "," << modifiers << ")";
221 whitelisted_keys_.insert(CombineKeyCodeAndModifiers(key_code, modifiers));
225 bool DevToolsEventForwarder::ForwardEvent(
226 const content::NativeWebKeyboardEvent& event) {
227 std::string event_type;
228 switch (event.type) {
229 case WebInputEvent::KeyDown:
230 case WebInputEvent::RawKeyDown:
231 event_type = kKeyDownEventName;
233 case WebInputEvent::KeyUp:
234 event_type = kKeyUpEventName;
240 int key_code = VirtualKeyCodeWithoutLocation(event.windowsKeyCode);
241 int key = CombineKeyCodeAndModifiers(key_code, event.modifiers);
242 if (whitelisted_keys_.find(key) == whitelisted_keys_.end())
245 base::DictionaryValue event_data;
246 event_data.SetString("type", event_type);
247 event_data.SetString("keyIdentifier", event.keyIdentifier);
248 event_data.SetInteger("keyCode", key_code);
249 event_data.SetInteger("modifiers", event.modifiers);
250 devtools_window_->bindings_->CallClientFunction(
251 "InspectorFrontendAPI.keyEventUnhandled", &event_data, NULL, NULL);
255 int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code,
257 return key_code | (modifiers << 16);
260 bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code,
262 return (ui::VKEY_F1 <= key_code && key_code <= ui::VKEY_F12) ||
266 // Mapping copied from Blink's KeyboardEvent.cpp.
267 int DevToolsEventForwarder::VirtualKeyCodeWithoutLocation(int key_code)
270 case ui::VKEY_LCONTROL:
271 case ui::VKEY_RCONTROL:
272 return ui::VKEY_CONTROL;
273 case ui::VKEY_LSHIFT:
274 case ui::VKEY_RSHIFT:
275 return ui::VKEY_SHIFT;
278 return ui::VKEY_MENU;
284 // DevToolsWindow::ObserverWithAccessor -------------------------------
286 DevToolsWindow::ObserverWithAccessor::ObserverWithAccessor(
287 WebContents* web_contents)
288 : WebContentsObserver(web_contents) {
291 DevToolsWindow::ObserverWithAccessor::~ObserverWithAccessor() {
294 // DevToolsWindow -------------------------------------------------------------
296 const char DevToolsWindow::kDevToolsApp[] = "DevToolsApp";
298 DevToolsWindow::~DevToolsWindow() {
299 life_stage_ = kClosing;
301 UpdateBrowserWindow();
302 UpdateBrowserToolbar();
304 if (toolbox_web_contents_)
305 delete toolbox_web_contents_;
307 DevToolsWindows* instances = g_instances.Pointer();
308 DevToolsWindows::iterator it(
309 std::find(instances->begin(), instances->end(), this));
310 DCHECK(it != instances->end());
311 instances->erase(it);
313 if (!close_callback_.is_null()) {
314 close_callback_.Run();
315 close_callback_ = base::Closure();
320 void DevToolsWindow::RegisterProfilePrefs(
321 user_prefs::PrefRegistrySyncable* registry) {
322 registry->RegisterDictionaryPref(
323 prefs::kDevToolsEditedFiles,
324 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
325 registry->RegisterDictionaryPref(
326 prefs::kDevToolsFileSystemPaths,
327 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
328 registry->RegisterStringPref(
329 prefs::kDevToolsAdbKey, std::string(),
330 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
332 registry->RegisterBooleanPref(
333 prefs::kDevToolsDiscoverUsbDevicesEnabled,
335 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
336 registry->RegisterBooleanPref(
337 prefs::kDevToolsPortForwardingEnabled,
339 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
340 registry->RegisterBooleanPref(
341 prefs::kDevToolsPortForwardingDefaultSet,
343 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
344 registry->RegisterDictionaryPref(
345 prefs::kDevToolsPortForwardingConfig,
346 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
350 content::WebContents* DevToolsWindow::GetInTabWebContents(
351 WebContents* inspected_web_contents,
352 DevToolsContentsResizingStrategy* out_strategy) {
353 DevToolsWindow* window = GetInstanceForInspectedWebContents(
354 inspected_web_contents);
355 if (!window || window->life_stage_ == kClosing)
358 // Not yet loaded window is treated as docked, but we should not present it
359 // until we decided on docking.
360 bool is_docked_set = window->life_stage_ == kLoadCompleted ||
361 window->life_stage_ == kIsDockedSet;
365 // Undocked window should have toolbox web contents.
366 if (!window->is_docked_ && !window->toolbox_web_contents_)
370 out_strategy->CopyFrom(window->contents_resizing_strategy_);
372 return window->is_docked_ ? window->main_web_contents_ :
373 window->toolbox_web_contents_;
377 DevToolsWindow* DevToolsWindow::GetInstanceForInspectedWebContents(
378 WebContents* inspected_web_contents) {
379 if (!inspected_web_contents || g_instances == NULL)
381 DevToolsWindows* instances = g_instances.Pointer();
382 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
384 if ((*it)->GetInspectedWebContents() == inspected_web_contents)
391 bool DevToolsWindow::IsDevToolsWindow(content::WebContents* web_contents) {
392 if (!web_contents || g_instances == NULL)
394 DevToolsWindows* instances = g_instances.Pointer();
395 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
397 if ((*it)->main_web_contents_ == web_contents ||
398 (*it)->toolbox_web_contents_ == web_contents)
405 DevToolsWindow* DevToolsWindow::OpenDevToolsWindowForWorker(
407 const scoped_refptr<DevToolsAgentHost>& worker_agent) {
408 DevToolsWindow* window = FindDevToolsWindow(worker_agent.get());
410 window = DevToolsWindow::CreateDevToolsWindowForWorker(profile);
411 window->bindings_->AttachTo(worker_agent);
413 window->ScheduleShow(DevToolsToggleAction::Show());
418 DevToolsWindow* DevToolsWindow::CreateDevToolsWindowForWorker(
420 content::RecordAction(base::UserMetricsAction("DevTools_InspectWorker"));
421 return Create(profile, GURL(), NULL, true, false, false, "");
425 DevToolsWindow* DevToolsWindow::OpenDevToolsWindow(
426 content::WebContents* inspected_web_contents) {
427 return ToggleDevToolsWindow(
428 inspected_web_contents, true, DevToolsToggleAction::Show(), "");
432 DevToolsWindow* DevToolsWindow::OpenDevToolsWindow(
433 content::WebContents* inspected_web_contents,
434 const DevToolsToggleAction& action) {
435 return ToggleDevToolsWindow(inspected_web_contents, true, action, "");
439 DevToolsWindow* DevToolsWindow::ToggleDevToolsWindow(
441 const DevToolsToggleAction& action) {
442 if (action.type() == DevToolsToggleAction::kToggle &&
443 browser->is_devtools()) {
444 browser->tab_strip_model()->CloseAllTabs();
448 return ToggleDevToolsWindow(
449 browser->tab_strip_model()->GetActiveWebContents(),
450 action.type() == DevToolsToggleAction::kInspect,
455 void DevToolsWindow::OpenExternalFrontend(
457 const std::string& frontend_url,
458 const scoped_refptr<content::DevToolsAgentHost>& agent_host,
460 DevToolsWindow* window = FindDevToolsWindow(agent_host.get());
462 window = Create(profile, DevToolsUI::GetProxyURL(frontend_url), NULL,
463 isWorker, true, false, "");
464 window->bindings_->AttachTo(agent_host);
466 window->ScheduleShow(DevToolsToggleAction::Show());
470 DevToolsWindow* DevToolsWindow::ToggleDevToolsWindow(
471 content::WebContents* inspected_web_contents,
473 const DevToolsToggleAction& action,
474 const std::string& settings) {
475 scoped_refptr<DevToolsAgentHost> agent(
476 DevToolsAgentHost::GetOrCreateFor(inspected_web_contents));
477 DevToolsWindow* window = FindDevToolsWindow(agent.get());
478 bool do_open = force_open;
480 Profile* profile = Profile::FromBrowserContext(
481 inspected_web_contents->GetBrowserContext());
482 content::RecordAction(
483 base::UserMetricsAction("DevTools_InspectRenderer"));
485 profile, GURL(), inspected_web_contents, false, false, true, settings);
486 window->bindings_->AttachTo(agent.get());
490 // Update toolbar to reflect DevTools changes.
491 window->UpdateBrowserToolbar();
493 // If window is docked and visible, we hide it on toggle. If window is
494 // undocked, we show (activate) it.
495 if (!window->is_docked_ || do_open)
496 window->ScheduleShow(action);
498 window->CloseWindow();
504 void DevToolsWindow::InspectElement(
505 content::WebContents* inspected_web_contents,
508 scoped_refptr<DevToolsAgentHost> agent(
509 DevToolsAgentHost::GetOrCreateFor(inspected_web_contents));
510 agent->InspectElement(x, y);
511 bool should_measure_time = FindDevToolsWindow(agent.get()) == NULL;
512 base::TimeTicks start_time = base::TimeTicks::Now();
513 // TODO(loislo): we should initiate DevTools window opening from within
514 // renderer. Otherwise, we still can hit a race condition here.
515 DevToolsWindow* window = OpenDevToolsWindow(inspected_web_contents);
516 if (should_measure_time)
517 window->inspect_element_start_time_ = start_time;
520 void DevToolsWindow::ScheduleShow(const DevToolsToggleAction& action) {
521 if (life_stage_ == kLoadCompleted) {
526 // Action will be done only after load completed.
527 action_on_load_ = action;
530 // No harm to show always-undocked window right away.
532 Show(DevToolsToggleAction::Show());
536 void DevToolsWindow::Show(const DevToolsToggleAction& action) {
537 if (life_stage_ == kClosing)
540 if (action.type() == DevToolsToggleAction::kNoOp)
545 Browser* inspected_browser = NULL;
546 int inspected_tab_index = -1;
547 FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
549 &inspected_tab_index);
550 DCHECK(inspected_browser);
551 DCHECK(inspected_tab_index != -1);
553 // Tell inspected browser to update splitter and switch to inspected panel.
554 BrowserWindow* inspected_window = inspected_browser->window();
555 main_web_contents_->SetDelegate(this);
557 TabStripModel* tab_strip_model = inspected_browser->tab_strip_model();
558 tab_strip_model->ActivateTabAt(inspected_tab_index, true);
560 inspected_window->UpdateDevTools();
561 main_web_contents_->SetInitialFocus();
562 inspected_window->Show();
563 // On Aura, focusing once is not enough. Do it again.
564 // Note that focusing only here but not before isn't enough either. We just
565 // need to focus twice.
566 main_web_contents_->SetInitialFocus();
568 PrefsTabHelper::CreateForWebContents(main_web_contents_);
569 main_web_contents_->GetRenderViewHost()->SyncRendererPrefs();
575 // Avoid consecutive window switching if the devtools window has been opened
576 // and the Inspect Element shortcut is pressed in the inspected tab.
577 bool should_show_window =
578 !browser_ || (action.type() != DevToolsToggleAction::kInspect);
581 CreateDevToolsBrowser();
583 if (should_show_window) {
584 browser_->window()->Show();
585 main_web_contents_->SetInitialFocus();
587 if (toolbox_web_contents_)
588 UpdateBrowserWindow();
594 bool DevToolsWindow::HandleBeforeUnload(WebContents* frontend_contents,
595 bool proceed, bool* proceed_to_fire_unload) {
596 DevToolsWindow* window = AsDevToolsWindow(frontend_contents);
599 if (!window->intercepted_page_beforeunload_)
601 window->BeforeUnloadFired(frontend_contents, proceed,
602 proceed_to_fire_unload);
607 bool DevToolsWindow::InterceptPageBeforeUnload(WebContents* contents) {
608 DevToolsWindow* window =
609 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
610 if (!window || window->intercepted_page_beforeunload_)
613 // Not yet loaded frontend will not handle beforeunload.
614 if (window->life_stage_ != kLoadCompleted)
617 window->intercepted_page_beforeunload_ = true;
618 // Handle case of devtools inspecting another devtools instance by passing
619 // the call up to the inspecting devtools instance.
620 if (!DevToolsWindow::InterceptPageBeforeUnload(window->main_web_contents_)) {
621 window->main_web_contents_->DispatchBeforeUnload(false);
627 bool DevToolsWindow::NeedsToInterceptBeforeUnload(
628 WebContents* contents) {
629 DevToolsWindow* window =
630 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
631 return window && !window->intercepted_page_beforeunload_;
635 bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(
637 DCHECK(browser->is_devtools());
638 // When FastUnloadController is used, devtools frontend will be detached
639 // from the browser window at this point which means we've already fired
641 if (browser->tab_strip_model()->empty())
643 WebContents* contents =
644 browser->tab_strip_model()->GetWebContentsAt(0);
645 DevToolsWindow* window = AsDevToolsWindow(contents);
648 return window->intercepted_page_beforeunload_;
652 void DevToolsWindow::OnPageCloseCanceled(WebContents* contents) {
653 DevToolsWindow *window =
654 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
657 window->intercepted_page_beforeunload_ = false;
658 // Propagate to devtools opened on devtools if any.
659 DevToolsWindow::OnPageCloseCanceled(window->main_web_contents_);
662 DevToolsWindow::DevToolsWindow(Profile* profile,
664 content::WebContents* inspected_web_contents,
668 WebContents::Create(WebContents::CreateParams(profile))),
669 toolbox_web_contents_(NULL),
674 // This initialization allows external front-end to work without changes.
675 // We don't wait for docking call, but instead immediately show undocked.
676 // Passing "dockSide=undocked" parameter ensures proper UI.
677 life_stage_(can_dock ? kNotLoaded : kIsDockedSet),
678 action_on_load_(DevToolsToggleAction::NoOp()),
679 intercepted_page_beforeunload_(false) {
680 // Set up delegate, so we get fully-functional window immediately.
681 // It will not appear in UI though until |life_stage_ == kLoadCompleted|.
682 main_web_contents_->SetDelegate(this);
684 main_web_contents_->GetController().LoadURL(
685 DevToolsUIBindings::ApplyThemeToURL(profile, url), content::Referrer(),
686 ui::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string());
688 bindings_ = DevToolsUIBindings::ForWebContents(main_web_contents_);
691 // Bindings take ownership over devtools as its delegate.
692 bindings_->SetDelegate(this);
693 // DevTools uses chrome_page_zoom::Zoom(), so main_web_contents_ requires a
695 ZoomController::CreateForWebContents(main_web_contents_);
696 ZoomController::FromWebContents(main_web_contents_)
697 ->SetShowsNotificationBubble(false);
699 g_instances.Get().push_back(this);
701 // There is no inspected_web_contents in case of various workers.
702 if (inspected_web_contents)
703 inspected_contents_observer_.reset(
704 new ObserverWithAccessor(inspected_web_contents));
706 // Initialize docked page to be of the right size.
707 if (can_dock_ && inspected_web_contents) {
708 content::RenderWidgetHostView* inspected_view =
709 inspected_web_contents->GetRenderWidgetHostView();
710 if (inspected_view && main_web_contents_->GetRenderWidgetHostView()) {
711 gfx::Size size = inspected_view->GetViewBounds().size();
712 main_web_contents_->GetRenderWidgetHostView()->SetSize(size);
716 event_forwarder_.reset(new DevToolsEventForwarder(this));
720 DevToolsWindow* DevToolsWindow::Create(
722 const GURL& frontend_url,
723 content::WebContents* inspected_web_contents,
724 bool shared_worker_frontend,
725 bool external_frontend,
727 const std::string& settings) {
728 if (inspected_web_contents) {
729 // Check for a place to dock.
730 Browser* browser = NULL;
732 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents,
734 browser->is_type_popup()) {
739 // Create WebContents with devtools.
740 GURL url(GetDevToolsURL(profile, frontend_url,
741 shared_worker_frontend,
743 can_dock, settings));
744 return new DevToolsWindow(profile, url, inspected_web_contents, can_dock);
748 GURL DevToolsWindow::GetDevToolsURL(Profile* profile,
749 const GURL& base_url,
750 bool shared_worker_frontend,
751 bool external_frontend,
753 const std::string& settings) {
754 // Compatibility errors are encoded with data urls, pass them
755 // through with no decoration.
756 if (base_url.SchemeIs("data"))
759 std::string frontend_url(
760 base_url.is_empty() ? chrome::kChromeUIDevToolsURL : base_url.spec());
761 std::string url_string(
763 ((frontend_url.find("?") == std::string::npos) ? "?" : "&"));
764 if (shared_worker_frontend)
765 url_string += "&isSharedWorker=true";
766 if (external_frontend)
767 url_string += "&remoteFrontend=true";
769 url_string += "&can_dock=true";
771 url_string += "&settings=" + settings;
772 return GURL(url_string);
776 DevToolsWindow* DevToolsWindow::FindDevToolsWindow(
777 DevToolsAgentHost* agent_host) {
778 if (!agent_host || g_instances == NULL)
780 DevToolsWindows* instances = g_instances.Pointer();
781 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
783 if ((*it)->bindings_->IsAttachedTo(agent_host))
790 DevToolsWindow* DevToolsWindow::AsDevToolsWindow(
791 content::WebContents* web_contents) {
792 if (!web_contents || g_instances == NULL)
794 DevToolsWindows* instances = g_instances.Pointer();
795 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
797 if ((*it)->main_web_contents_ == web_contents)
803 WebContents* DevToolsWindow::OpenURLFromTab(
805 const content::OpenURLParams& params) {
806 DCHECK(source == main_web_contents_);
807 if (!params.url.SchemeIs(content::kChromeDevToolsScheme)) {
808 WebContents* inspected_web_contents = GetInspectedWebContents();
809 return inspected_web_contents ?
810 inspected_web_contents->OpenURL(params) : NULL;
813 bindings_->Reattach();
815 content::NavigationController::LoadURLParams load_url_params(params.url);
816 main_web_contents_->GetController().LoadURLWithParams(load_url_params);
817 return main_web_contents_;
820 void DevToolsWindow::ActivateContents(WebContents* contents) {
822 WebContents* inspected_tab = GetInspectedWebContents();
823 inspected_tab->GetDelegate()->ActivateContents(inspected_tab);
824 } else if (browser_) {
825 browser_->window()->Activate();
829 void DevToolsWindow::AddNewContents(WebContents* source,
830 WebContents* new_contents,
831 WindowOpenDisposition disposition,
832 const gfx::Rect& initial_pos,
835 if (new_contents == toolbox_web_contents_) {
836 toolbox_web_contents_->SetDelegate(
837 new DevToolsToolboxDelegate(toolbox_web_contents_,
838 inspected_contents_observer_.get()));
839 if (main_web_contents_->GetRenderWidgetHostView() &&
840 toolbox_web_contents_->GetRenderWidgetHostView()) {
842 main_web_contents_->GetRenderWidgetHostView()->GetViewBounds().size();
843 toolbox_web_contents_->GetRenderWidgetHostView()->SetSize(size);
845 UpdateBrowserWindow();
849 WebContents* inspected_web_contents = GetInspectedWebContents();
850 if (inspected_web_contents) {
851 inspected_web_contents->GetDelegate()->AddNewContents(
852 source, new_contents, disposition, initial_pos, user_gesture,
857 void DevToolsWindow::WebContentsCreated(WebContents* source_contents,
858 int opener_render_frame_id,
859 const base::string16& frame_name,
860 const GURL& target_url,
861 WebContents* new_contents) {
862 if (target_url.SchemeIs(content::kChromeDevToolsScheme) &&
863 target_url.path().rfind("toolbox.html") != std::string::npos) {
865 toolbox_web_contents_ = new_contents;
869 void DevToolsWindow::CloseContents(WebContents* source) {
871 life_stage_ = kClosing;
872 UpdateBrowserWindow();
873 // In case of docked main_web_contents_, we own it so delete here.
874 // Embedding DevTools window will be deleted as a result of
875 // DevToolsUIBindings destruction.
876 delete main_web_contents_;
879 void DevToolsWindow::ContentsZoomChange(bool zoom_in) {
881 chrome_page_zoom::Zoom(main_web_contents_,
882 zoom_in ? content::PAGE_ZOOM_IN : content::PAGE_ZOOM_OUT);
885 void DevToolsWindow::BeforeUnloadFired(WebContents* tab,
887 bool* proceed_to_fire_unload) {
888 if (!intercepted_page_beforeunload_) {
889 // Docked devtools window closed directly.
892 *proceed_to_fire_unload = proceed;
894 // Inspected page is attempting to close.
895 WebContents* inspected_web_contents = GetInspectedWebContents();
897 inspected_web_contents->DispatchBeforeUnload(false);
900 inspected_web_contents->GetDelegate()->BeforeUnloadFired(
901 inspected_web_contents, false, &should_proceed);
902 DCHECK(!should_proceed);
904 *proceed_to_fire_unload = false;
908 bool DevToolsWindow::PreHandleKeyboardEvent(
910 const content::NativeWebKeyboardEvent& event,
911 bool* is_keyboard_shortcut) {
912 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
913 if (inspected_window) {
914 return inspected_window->PreHandleKeyboardEvent(event,
915 is_keyboard_shortcut);
920 void DevToolsWindow::HandleKeyboardEvent(
922 const content::NativeWebKeyboardEvent& event) {
923 if (event.windowsKeyCode == 0x08) {
924 // Do not navigate back in history on Windows (http://crbug.com/74156).
927 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
928 if (inspected_window)
929 inspected_window->HandleKeyboardEvent(event);
932 content::JavaScriptDialogManager* DevToolsWindow::GetJavaScriptDialogManager() {
933 WebContents* inspected_web_contents = GetInspectedWebContents();
934 return (inspected_web_contents && inspected_web_contents->GetDelegate()) ?
935 inspected_web_contents->GetDelegate()->GetJavaScriptDialogManager() :
936 content::WebContentsDelegate::GetJavaScriptDialogManager();
939 content::ColorChooser* DevToolsWindow::OpenColorChooser(
940 WebContents* web_contents,
941 SkColor initial_color,
942 const std::vector<content::ColorSuggestion>& suggestions) {
943 return chrome::ShowColorChooser(web_contents, initial_color);
946 void DevToolsWindow::RunFileChooser(WebContents* web_contents,
947 const content::FileChooserParams& params) {
948 FileSelectHelper::RunFileChooser(web_contents, params);
951 void DevToolsWindow::WebContentsFocused(WebContents* contents) {
952 Browser* inspected_browser = NULL;
953 int inspected_tab_index = -1;
954 if (is_docked_ && FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
956 &inspected_tab_index))
957 inspected_browser->window()->WebContentsFocused(contents);
960 bool DevToolsWindow::PreHandleGestureEvent(
962 const blink::WebGestureEvent& event) {
963 // Disable pinch zooming.
964 return event.type == blink::WebGestureEvent::GesturePinchBegin ||
965 event.type == blink::WebGestureEvent::GesturePinchUpdate ||
966 event.type == blink::WebGestureEvent::GesturePinchEnd;
969 void DevToolsWindow::ActivateWindow() {
970 if (life_stage_ != kLoadCompleted)
972 if (is_docked_ && GetInspectedBrowserWindow())
973 main_web_contents_->Focus();
974 else if (!is_docked_ && !browser_->window()->IsActive())
975 browser_->window()->Activate();
978 void DevToolsWindow::CloseWindow() {
980 life_stage_ = kClosing;
981 main_web_contents_->DispatchBeforeUnload(false);
984 void DevToolsWindow::SetInspectedPageBounds(const gfx::Rect& rect) {
985 DevToolsContentsResizingStrategy strategy(rect);
986 if (contents_resizing_strategy_.Equals(strategy))
989 contents_resizing_strategy_.CopyFrom(strategy);
990 UpdateBrowserWindow();
993 void DevToolsWindow::InspectElementCompleted() {
994 if (!inspect_element_start_time_.is_null()) {
995 UMA_HISTOGRAM_TIMES("DevTools.InspectElement",
996 base::TimeTicks::Now() - inspect_element_start_time_);
997 inspect_element_start_time_ = base::TimeTicks();
1001 void DevToolsWindow::MoveWindow(int x, int y) {
1002 if (life_stage_ != kLoadCompleted)
1005 gfx::Rect bounds = browser_->window()->GetBounds();
1006 bounds.Offset(x, y);
1007 browser_->window()->SetBounds(bounds);
1011 void DevToolsWindow::SetIsDocked(bool dock_requested) {
1012 if (life_stage_ == kClosing)
1015 DCHECK(can_dock_ || !dock_requested);
1017 dock_requested = false;
1019 bool was_docked = is_docked_;
1020 is_docked_ = dock_requested;
1022 if (life_stage_ != kLoadCompleted) {
1023 // This is a first time call we waited for to initialize.
1024 life_stage_ = life_stage_ == kOnLoadFired ? kLoadCompleted : kIsDockedSet;
1025 if (life_stage_ == kLoadCompleted)
1030 if (dock_requested == was_docked)
1033 if (dock_requested && !was_docked) {
1034 // Detach window from the external devtools browser. It will lead to
1035 // the browser object's close and delete. Remove observer first.
1036 TabStripModel* tab_strip_model = browser_->tab_strip_model();
1037 tab_strip_model->DetachWebContentsAt(
1038 tab_strip_model->GetIndexOfWebContents(main_web_contents_));
1040 } else if (!dock_requested && was_docked) {
1041 UpdateBrowserWindow();
1044 Show(DevToolsToggleAction::Show());
1047 void DevToolsWindow::OpenInNewTab(const std::string& url) {
1048 content::OpenURLParams params(
1049 GURL(url), content::Referrer(), NEW_FOREGROUND_TAB,
1050 ui::PAGE_TRANSITION_LINK, false);
1051 WebContents* inspected_web_contents = GetInspectedWebContents();
1052 if (inspected_web_contents) {
1053 inspected_web_contents->OpenURL(params);
1055 chrome::HostDesktopType host_desktop_type;
1057 host_desktop_type = browser_->host_desktop_type();
1059 // There should always be a browser when there are no inspected web
1062 host_desktop_type = chrome::GetActiveDesktop();
1065 const BrowserList* browser_list =
1066 BrowserList::GetInstance(host_desktop_type);
1067 for (BrowserList::const_iterator it = browser_list->begin();
1068 it != browser_list->end(); ++it) {
1069 if ((*it)->type() == Browser::TYPE_TABBED) {
1070 (*it)->OpenURL(params);
1077 void DevToolsWindow::SetWhitelistedShortcuts(
1078 const std::string& message) {
1079 event_forwarder_->SetWhitelistedShortcuts(message);
1082 void DevToolsWindow::InspectedContentsClosing() {
1083 intercepted_page_beforeunload_ = false;
1084 life_stage_ = kClosing;
1085 main_web_contents_->GetRenderViewHost()->ClosePage();
1088 InfoBarService* DevToolsWindow::GetInfoBarService() {
1090 InfoBarService::FromWebContents(GetInspectedWebContents()) :
1091 InfoBarService::FromWebContents(main_web_contents_);
1094 void DevToolsWindow::RenderProcessGone(bool crashed) {
1095 // Docked DevToolsWindow owns its main_web_contents_ and must delete it.
1096 // Undocked main_web_contents_ are owned and handled by browser.
1097 // see crbug.com/369932
1099 CloseContents(main_web_contents_);
1100 } else if (browser_ && crashed) {
1101 browser_->window()->Close();
1105 void DevToolsWindow::OnLoadCompleted() {
1106 // First seed inspected tab id for extension APIs.
1107 WebContents* inspected_web_contents = GetInspectedWebContents();
1108 if (inspected_web_contents) {
1109 SessionTabHelper* session_tab_helper =
1110 SessionTabHelper::FromWebContents(inspected_web_contents);
1111 if (session_tab_helper) {
1112 base::FundamentalValue tabId(session_tab_helper->session_id().id());
1113 bindings_->CallClientFunction("WebInspector.setInspectedTabId",
1114 &tabId, NULL, NULL);
1118 if (life_stage_ == kClosing)
1121 // We could be in kLoadCompleted state already if frontend reloads itself.
1122 if (life_stage_ != kLoadCompleted) {
1123 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
1124 // Here we set kOnLoadFired.
1125 life_stage_ = life_stage_ == kIsDockedSet ? kLoadCompleted : kOnLoadFired;
1127 if (life_stage_ == kLoadCompleted)
1131 void DevToolsWindow::CreateDevToolsBrowser() {
1132 PrefService* prefs = profile_->GetPrefs();
1133 if (!prefs->GetDictionary(prefs::kAppWindowPlacement)->HasKey(kDevToolsApp)) {
1134 DictionaryPrefUpdate update(prefs, prefs::kAppWindowPlacement);
1135 base::DictionaryValue* wp_prefs = update.Get();
1136 base::DictionaryValue* dev_tools_defaults = new base::DictionaryValue;
1137 wp_prefs->Set(kDevToolsApp, dev_tools_defaults);
1138 dev_tools_defaults->SetInteger("left", 100);
1139 dev_tools_defaults->SetInteger("top", 100);
1140 dev_tools_defaults->SetInteger("right", 740);
1141 dev_tools_defaults->SetInteger("bottom", 740);
1142 dev_tools_defaults->SetBoolean("maximized", false);
1143 dev_tools_defaults->SetBoolean("always_on_top", false);
1146 browser_ = new Browser(Browser::CreateParams::CreateForDevTools(
1148 chrome::GetHostDesktopTypeForNativeView(
1149 main_web_contents_->GetNativeView())));
1150 browser_->tab_strip_model()->AddWebContents(
1151 main_web_contents_, -1, ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
1152 TabStripModel::ADD_ACTIVE);
1153 main_web_contents_->GetRenderViewHost()->SyncRendererPrefs();
1156 BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() {
1157 Browser* browser = NULL;
1159 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1161 browser->window() : NULL;
1164 void DevToolsWindow::DoAction(const DevToolsToggleAction& action) {
1165 switch (action.type()) {
1166 case DevToolsToggleAction::kShowConsole:
1167 bindings_->CallClientFunction(
1168 "InspectorFrontendAPI.showConsole", NULL, NULL, NULL);
1171 case DevToolsToggleAction::kInspect:
1172 bindings_->CallClientFunction(
1173 "InspectorFrontendAPI.enterInspectElementMode", NULL, NULL, NULL);
1176 case DevToolsToggleAction::kShow:
1177 case DevToolsToggleAction::kToggle:
1181 case DevToolsToggleAction::kReveal: {
1182 const DevToolsToggleAction::RevealParams* params =
1185 base::StringValue url_value(params->url);
1186 base::FundamentalValue line_value(static_cast<int>(params->line_number));
1187 base::FundamentalValue column_value(
1188 static_cast<int>(params->column_number));
1189 bindings_->CallClientFunction("InspectorFrontendAPI.revealSourceLine",
1190 &url_value, &line_value, &column_value);
1199 void DevToolsWindow::UpdateBrowserToolbar() {
1200 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
1201 if (inspected_window)
1202 inspected_window->UpdateToolbar(NULL);
1205 void DevToolsWindow::UpdateBrowserWindow() {
1206 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
1207 if (inspected_window)
1208 inspected_window->UpdateDevTools();
1211 WebContents* DevToolsWindow::GetInspectedWebContents() {
1212 return inspected_contents_observer_
1213 ? inspected_contents_observer_->web_contents()
1217 void DevToolsWindow::LoadCompleted() {
1218 Show(action_on_load_);
1219 action_on_load_ = DevToolsToggleAction::NoOp();
1220 if (!load_completed_callback_.is_null()) {
1221 load_completed_callback_.Run();
1222 load_completed_callback_ = base::Closure();
1226 void DevToolsWindow::SetLoadCompletedCallback(const base::Closure& closure) {
1227 if (life_stage_ == kLoadCompleted || life_stage_ == kClosing) {
1228 if (!closure.is_null())
1232 load_completed_callback_ = closure;
1235 bool DevToolsWindow::ForwardKeyboardEvent(
1236 const content::NativeWebKeyboardEvent& event) {
1237 return event_forwarder_->ForwardEvent(event);