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/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/certificate_viewer.h"
16 #include "chrome/browser/file_select_helper.h"
17 #include "chrome/browser/infobars/infobar_service.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/syncable_prefs/pref_service_syncable.h"
37 #include "components/ui/zoom/page_zoom.h"
38 #include "components/ui/zoom/zoom_controller.h"
39 #include "content/public/browser/browser_thread.h"
40 #include "content/public/browser/devtools_agent_host.h"
41 #include "content/public/browser/native_web_keyboard_event.h"
42 #include "content/public/browser/navigation_controller.h"
43 #include "content/public/browser/navigation_entry.h"
44 #include "content/public/browser/render_frame_host.h"
45 #include "content/public/browser/render_process_host.h"
46 #include "content/public/browser/render_view_host.h"
47 #include "content/public/browser/render_widget_host_view.h"
48 #include "content/public/browser/user_metrics.h"
49 #include "content/public/browser/web_contents.h"
50 #include "content/public/common/content_client.h"
51 #include "content/public/common/url_constants.h"
52 #include "net/base/escape.h"
53 #include "third_party/WebKit/public/web/WebInputEvent.h"
54 #include "ui/base/page_transition_types.h"
55 #include "ui/events/keycodes/keyboard_code_conversion.h"
56 #include "ui/events/keycodes/keyboard_codes.h"
58 using base::DictionaryValue
;
59 using blink::WebInputEvent
;
60 using content::BrowserThread
;
61 using content::DevToolsAgentHost
;
62 using content::WebContents
;
66 typedef std::vector
<DevToolsWindow
*> DevToolsWindows
;
67 base::LazyInstance
<DevToolsWindows
>::Leaky g_instances
=
68 LAZY_INSTANCE_INITIALIZER
;
70 static const char kKeyUpEventName
[] = "keyup";
71 static const char kKeyDownEventName
[] = "keydown";
73 bool FindInspectedBrowserAndTabIndex(
74 WebContents
* inspected_web_contents
, Browser
** browser
, int* tab
) {
75 if (!inspected_web_contents
)
78 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
79 int tab_index
= it
->tab_strip_model()->GetIndexOfWebContents(
80 inspected_web_contents
);
81 if (tab_index
!= TabStripModel::kNoTab
) {
90 // DevToolsToolboxDelegate ----------------------------------------------------
92 class DevToolsToolboxDelegate
93 : public content::WebContentsObserver
,
94 public content::WebContentsDelegate
{
96 DevToolsToolboxDelegate(
97 WebContents
* toolbox_contents
,
98 DevToolsWindow::ObserverWithAccessor
* web_contents_observer
);
99 ~DevToolsToolboxDelegate() override
;
101 content::WebContents
* OpenURLFromTab(
102 content::WebContents
* source
,
103 const content::OpenURLParams
& params
) override
;
104 bool PreHandleKeyboardEvent(content::WebContents
* source
,
105 const content::NativeWebKeyboardEvent
& event
,
106 bool* is_keyboard_shortcut
) override
;
107 void HandleKeyboardEvent(
108 content::WebContents
* source
,
109 const content::NativeWebKeyboardEvent
& event
) override
;
110 void WebContentsDestroyed() override
;
113 BrowserWindow
* GetInspectedBrowserWindow();
114 DevToolsWindow::ObserverWithAccessor
* inspected_contents_observer_
;
115 DISALLOW_COPY_AND_ASSIGN(DevToolsToolboxDelegate
);
118 DevToolsToolboxDelegate::DevToolsToolboxDelegate(
119 WebContents
* toolbox_contents
,
120 DevToolsWindow::ObserverWithAccessor
* web_contents_observer
)
121 : WebContentsObserver(toolbox_contents
),
122 inspected_contents_observer_(web_contents_observer
) {
125 DevToolsToolboxDelegate::~DevToolsToolboxDelegate() {
128 content::WebContents
* DevToolsToolboxDelegate::OpenURLFromTab(
129 content::WebContents
* source
,
130 const content::OpenURLParams
& params
) {
131 DCHECK(source
== web_contents());
132 if (!params
.url
.SchemeIs(content::kChromeDevToolsScheme
))
134 content::NavigationController::LoadURLParams
load_url_params(params
.url
);
135 source
->GetController().LoadURLWithParams(load_url_params
);
139 bool DevToolsToolboxDelegate::PreHandleKeyboardEvent(
140 content::WebContents
* source
,
141 const content::NativeWebKeyboardEvent
& event
,
142 bool* is_keyboard_shortcut
) {
143 BrowserWindow
* window
= GetInspectedBrowserWindow();
145 return window
->PreHandleKeyboardEvent(event
, is_keyboard_shortcut
);
149 void DevToolsToolboxDelegate::HandleKeyboardEvent(
150 content::WebContents
* source
,
151 const content::NativeWebKeyboardEvent
& event
) {
152 if (event
.windowsKeyCode
== 0x08) {
153 // Do not navigate back in history on Windows (http://crbug.com/74156).
156 BrowserWindow
* window
= GetInspectedBrowserWindow();
158 window
->HandleKeyboardEvent(event
);
161 void DevToolsToolboxDelegate::WebContentsDestroyed() {
165 BrowserWindow
* DevToolsToolboxDelegate::GetInspectedBrowserWindow() {
166 WebContents
* inspected_contents
=
167 inspected_contents_observer_
->web_contents();
168 if (!inspected_contents
)
170 Browser
* browser
= NULL
;
172 if (FindInspectedBrowserAndTabIndex(inspected_contents
, &browser
, &tab
))
173 return browser
->window();
178 GURL
DecorateFrontendURL(const GURL
& base_url
) {
179 std::string frontend_url
= base_url
.spec();
180 std::string
url_string(
182 ((frontend_url
.find("?") == std::string::npos
) ? "?" : "&") +
183 "dockSide=undocked"); // TODO(dgozman): remove this support in M38.
184 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
185 switches::kEnableDevToolsExperiments
))
186 url_string
+= "&experiments=true";
187 #if defined(DEBUG_DEVTOOLS)
188 url_string
+= "&debugFrontend=true";
189 #endif // defined(DEBUG_DEVTOOLS)
190 return GURL(url_string
);
195 // DevToolsEventForwarder -----------------------------------------------------
197 class DevToolsEventForwarder
{
199 explicit DevToolsEventForwarder(DevToolsWindow
* window
)
200 : devtools_window_(window
) {}
202 // Registers whitelisted shortcuts with the forwarder.
203 // Only registered keys will be forwarded to the DevTools frontend.
204 void SetWhitelistedShortcuts(const std::string
& message
);
206 // Forwards a keyboard event to the DevTools frontend if it is whitelisted.
207 // Returns |true| if the event has been forwarded, |false| otherwise.
208 bool ForwardEvent(const content::NativeWebKeyboardEvent
& event
);
211 static bool KeyWhitelistingAllowed(int key_code
, int modifiers
);
212 static int CombineKeyCodeAndModifiers(int key_code
, int modifiers
);
214 DevToolsWindow
* devtools_window_
;
215 std::set
<int> whitelisted_keys_
;
217 DISALLOW_COPY_AND_ASSIGN(DevToolsEventForwarder
);
220 void DevToolsEventForwarder::SetWhitelistedShortcuts(
221 const std::string
& message
) {
222 scoped_ptr
<base::Value
> parsed_message
= base::JSONReader::Read(message
);
223 base::ListValue
* shortcut_list
;
224 if (!parsed_message
->GetAsList(&shortcut_list
))
226 base::ListValue::iterator it
= shortcut_list
->begin();
227 for (; it
!= shortcut_list
->end(); ++it
) {
228 base::DictionaryValue
* dictionary
;
229 if (!(*it
)->GetAsDictionary(&dictionary
))
232 dictionary
->GetInteger("keyCode", &key_code
);
236 dictionary
->GetInteger("modifiers", &modifiers
);
237 if (!KeyWhitelistingAllowed(key_code
, modifiers
)) {
238 LOG(WARNING
) << "Key whitelisting forbidden: "
239 << "(" << key_code
<< "," << modifiers
<< ")";
242 whitelisted_keys_
.insert(CombineKeyCodeAndModifiers(key_code
, modifiers
));
246 bool DevToolsEventForwarder::ForwardEvent(
247 const content::NativeWebKeyboardEvent
& event
) {
248 std::string event_type
;
249 switch (event
.type
) {
250 case WebInputEvent::KeyDown
:
251 case WebInputEvent::RawKeyDown
:
252 event_type
= kKeyDownEventName
;
254 case WebInputEvent::KeyUp
:
255 event_type
= kKeyUpEventName
;
261 int key_code
= ui::LocatedToNonLocatedKeyboardCode(
262 static_cast<ui::KeyboardCode
>(event
.windowsKeyCode
));
263 int key
= CombineKeyCodeAndModifiers(key_code
, event
.modifiers
);
264 if (whitelisted_keys_
.find(key
) == whitelisted_keys_
.end())
267 base::DictionaryValue event_data
;
268 event_data
.SetString("type", event_type
);
269 event_data
.SetString("keyIdentifier", event
.keyIdentifier
);
270 event_data
.SetInteger("keyCode", key_code
);
271 event_data
.SetInteger("modifiers", event
.modifiers
);
272 devtools_window_
->bindings_
->CallClientFunction(
273 "DevToolsAPI.keyEventUnhandled", &event_data
, NULL
, NULL
);
277 int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code
,
279 return key_code
| (modifiers
<< 16);
282 bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code
,
284 return (ui::VKEY_F1
<= key_code
&& key_code
<= ui::VKEY_F12
) ||
288 // DevToolsWindow::ObserverWithAccessor -------------------------------
290 DevToolsWindow::ObserverWithAccessor::ObserverWithAccessor(
291 WebContents
* web_contents
)
292 : WebContentsObserver(web_contents
) {
295 DevToolsWindow::ObserverWithAccessor::~ObserverWithAccessor() {
298 // DevToolsWindow -------------------------------------------------------------
300 const char DevToolsWindow::kDevToolsApp
[] = "DevToolsApp";
302 DevToolsWindow::~DevToolsWindow() {
303 life_stage_
= kClosing
;
305 UpdateBrowserWindow();
306 UpdateBrowserToolbar();
308 if (toolbox_web_contents_
)
309 delete toolbox_web_contents_
;
311 DevToolsWindows
* instances
= g_instances
.Pointer();
312 DevToolsWindows::iterator
it(
313 std::find(instances
->begin(), instances
->end(), this));
314 DCHECK(it
!= instances
->end());
315 instances
->erase(it
);
317 if (!close_callback_
.is_null()) {
318 close_callback_
.Run();
319 close_callback_
= base::Closure();
324 void DevToolsWindow::RegisterProfilePrefs(
325 user_prefs::PrefRegistrySyncable
* registry
) {
326 registry
->RegisterDictionaryPref(prefs::kDevToolsEditedFiles
);
327 registry
->RegisterDictionaryPref(prefs::kDevToolsFileSystemPaths
);
328 registry
->RegisterStringPref(prefs::kDevToolsAdbKey
, std::string());
330 registry
->RegisterBooleanPref(prefs::kDevToolsDiscoverUsbDevicesEnabled
,
332 registry
->RegisterBooleanPref(prefs::kDevToolsPortForwardingEnabled
, false);
333 registry
->RegisterBooleanPref(prefs::kDevToolsPortForwardingDefaultSet
,
335 registry
->RegisterDictionaryPref(prefs::kDevToolsPortForwardingConfig
);
336 registry
->RegisterDictionaryPref(prefs::kDevToolsPreferences
);
340 content::WebContents
* DevToolsWindow::GetInTabWebContents(
341 WebContents
* inspected_web_contents
,
342 DevToolsContentsResizingStrategy
* out_strategy
) {
343 DevToolsWindow
* window
= GetInstanceForInspectedWebContents(
344 inspected_web_contents
);
345 if (!window
|| window
->life_stage_
== kClosing
)
348 // Not yet loaded window is treated as docked, but we should not present it
349 // until we decided on docking.
350 bool is_docked_set
= window
->life_stage_
== kLoadCompleted
||
351 window
->life_stage_
== kIsDockedSet
;
355 // Undocked window should have toolbox web contents.
356 if (!window
->is_docked_
&& !window
->toolbox_web_contents_
)
360 out_strategy
->CopyFrom(window
->contents_resizing_strategy_
);
362 return window
->is_docked_
? window
->main_web_contents_
:
363 window
->toolbox_web_contents_
;
367 DevToolsWindow
* DevToolsWindow::GetInstanceForInspectedWebContents(
368 WebContents
* inspected_web_contents
) {
369 if (!inspected_web_contents
|| g_instances
== NULL
)
371 DevToolsWindows
* instances
= g_instances
.Pointer();
372 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
374 if ((*it
)->GetInspectedWebContents() == inspected_web_contents
)
381 bool DevToolsWindow::IsDevToolsWindow(content::WebContents
* web_contents
) {
382 if (!web_contents
|| g_instances
== NULL
)
384 DevToolsWindows
* instances
= g_instances
.Pointer();
385 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
387 if ((*it
)->main_web_contents_
== web_contents
||
388 (*it
)->toolbox_web_contents_
== web_contents
)
395 void DevToolsWindow::OpenDevToolsWindowForWorker(
397 const scoped_refptr
<DevToolsAgentHost
>& worker_agent
) {
398 DevToolsWindow
* window
= FindDevToolsWindow(worker_agent
.get());
400 window
= DevToolsWindow::CreateDevToolsWindowForWorker(profile
);
403 window
->bindings_
->AttachTo(worker_agent
);
405 window
->ScheduleShow(DevToolsToggleAction::Show());
409 DevToolsWindow
* DevToolsWindow::CreateDevToolsWindowForWorker(
411 content::RecordAction(base::UserMetricsAction("DevTools_InspectWorker"));
412 return Create(profile
, GURL(), NULL
, true, std::string(), false, "");
416 void DevToolsWindow::OpenDevToolsWindow(
417 content::WebContents
* inspected_web_contents
) {
418 ToggleDevToolsWindow(
419 inspected_web_contents
, true, DevToolsToggleAction::Show(), "");
423 void DevToolsWindow::OpenDevToolsWindow(
424 content::WebContents
* inspected_web_contents
,
425 const DevToolsToggleAction
& action
) {
426 ToggleDevToolsWindow(inspected_web_contents
, true, action
, "");
430 void DevToolsWindow::OpenDevToolsWindow(
432 const scoped_refptr
<content::DevToolsAgentHost
>& agent_host
) {
433 DevToolsWindow
* window
= FindDevToolsWindow(agent_host
.get());
435 window
= DevToolsWindow::Create(
436 profile
, GURL(), nullptr, false, std::string(), false, std::string());
439 window
->bindings_
->AttachTo(agent_host
);
441 window
->ScheduleShow(DevToolsToggleAction::Show());
445 void DevToolsWindow::ToggleDevToolsWindow(
447 const DevToolsToggleAction
& action
) {
448 if (action
.type() == DevToolsToggleAction::kToggle
&&
449 browser
->is_devtools()) {
450 browser
->tab_strip_model()->CloseAllTabs();
454 ToggleDevToolsWindow(
455 browser
->tab_strip_model()->GetActiveWebContents(),
456 action
.type() == DevToolsToggleAction::kInspect
,
461 void DevToolsWindow::OpenExternalFrontend(
463 const std::string
& frontend_url
,
464 const scoped_refptr
<content::DevToolsAgentHost
>& agent_host
,
466 DevToolsWindow
* window
= FindDevToolsWindow(agent_host
.get());
468 window
= Create(profile
, GURL(), nullptr, isWorker
,
469 DevToolsUI::GetProxyURL(frontend_url
).spec(), false, std::string());
472 window
->bindings_
->AttachTo(agent_host
);
475 window
->ScheduleShow(DevToolsToggleAction::Show());
479 void DevToolsWindow::ToggleDevToolsWindow(
480 content::WebContents
* inspected_web_contents
,
482 const DevToolsToggleAction
& action
,
483 const std::string
& settings
) {
484 scoped_refptr
<DevToolsAgentHost
> agent(
485 DevToolsAgentHost::GetOrCreateFor(inspected_web_contents
));
486 DevToolsWindow
* window
= FindDevToolsWindow(agent
.get());
487 bool do_open
= force_open
;
489 Profile
* profile
= Profile::FromBrowserContext(
490 inspected_web_contents
->GetBrowserContext());
491 content::RecordAction(
492 base::UserMetricsAction("DevTools_InspectRenderer"));
493 window
= Create(profile
, GURL(), inspected_web_contents
,
494 false, std::string(), true, settings
);
497 window
->bindings_
->AttachTo(agent
.get());
501 // Update toolbar to reflect DevTools changes.
502 window
->UpdateBrowserToolbar();
504 // If window is docked and visible, we hide it on toggle. If window is
505 // undocked, we show (activate) it.
506 if (!window
->is_docked_
|| do_open
)
507 window
->ScheduleShow(action
);
509 window
->CloseWindow();
513 void DevToolsWindow::InspectElement(
514 content::WebContents
* inspected_web_contents
,
517 scoped_refptr
<DevToolsAgentHost
> agent(
518 DevToolsAgentHost::GetOrCreateFor(inspected_web_contents
));
519 agent
->InspectElement(x
, y
);
520 bool should_measure_time
= FindDevToolsWindow(agent
.get()) == NULL
;
521 base::TimeTicks start_time
= base::TimeTicks::Now();
522 // TODO(loislo): we should initiate DevTools window opening from within
523 // renderer. Otherwise, we still can hit a race condition here.
524 OpenDevToolsWindow(inspected_web_contents
);
526 DevToolsWindow
* window
= FindDevToolsWindow(agent
.get());
527 if (should_measure_time
&& window
)
528 window
->inspect_element_start_time_
= start_time
;
532 content::DevToolsExternalAgentProxyDelegate
*
533 DevToolsWindow::CreateWebSocketAPIChannel(const std::string
& path
) {
534 if (path
.find("/devtools/frontend_api") != 0)
536 DevToolsWindows
* instances
= g_instances
.Pointer();
537 if (g_instances
== nullptr)
539 for (DevToolsWindow
* window
: *instances
) {
540 auto result
= window
->bindings_
->CreateWebSocketAPIChannel();
547 void DevToolsWindow::ScheduleShow(const DevToolsToggleAction
& action
) {
548 if (life_stage_
== kLoadCompleted
) {
553 // Action will be done only after load completed.
554 action_on_load_
= action
;
557 // No harm to show always-undocked window right away.
559 Show(DevToolsToggleAction::Show());
563 void DevToolsWindow::Show(const DevToolsToggleAction
& action
) {
564 if (life_stage_
== kClosing
)
567 if (action
.type() == DevToolsToggleAction::kNoOp
)
572 Browser
* inspected_browser
= NULL
;
573 int inspected_tab_index
= -1;
574 FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
576 &inspected_tab_index
);
577 DCHECK(inspected_browser
);
578 DCHECK(inspected_tab_index
!= -1);
580 // Tell inspected browser to update splitter and switch to inspected panel.
581 BrowserWindow
* inspected_window
= inspected_browser
->window();
582 main_web_contents_
->SetDelegate(this);
584 TabStripModel
* tab_strip_model
= inspected_browser
->tab_strip_model();
585 tab_strip_model
->ActivateTabAt(inspected_tab_index
, true);
587 inspected_window
->UpdateDevTools();
588 main_web_contents_
->SetInitialFocus();
589 inspected_window
->Show();
590 // On Aura, focusing once is not enough. Do it again.
591 // Note that focusing only here but not before isn't enough either. We just
592 // need to focus twice.
593 main_web_contents_
->SetInitialFocus();
595 PrefsTabHelper::CreateForWebContents(main_web_contents_
);
596 main_web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
602 // Avoid consecutive window switching if the devtools window has been opened
603 // and the Inspect Element shortcut is pressed in the inspected tab.
604 bool should_show_window
=
605 !browser_
|| (action
.type() != DevToolsToggleAction::kInspect
);
608 CreateDevToolsBrowser();
610 if (should_show_window
) {
611 browser_
->window()->Show();
612 main_web_contents_
->SetInitialFocus();
614 if (toolbox_web_contents_
)
615 UpdateBrowserWindow();
621 bool DevToolsWindow::HandleBeforeUnload(WebContents
* frontend_contents
,
622 bool proceed
, bool* proceed_to_fire_unload
) {
623 DevToolsWindow
* window
= AsDevToolsWindow(frontend_contents
);
626 if (!window
->intercepted_page_beforeunload_
)
628 window
->BeforeUnloadFired(frontend_contents
, proceed
,
629 proceed_to_fire_unload
);
634 bool DevToolsWindow::InterceptPageBeforeUnload(WebContents
* contents
) {
635 DevToolsWindow
* window
=
636 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
637 if (!window
|| window
->intercepted_page_beforeunload_
)
640 // Not yet loaded frontend will not handle beforeunload.
641 if (window
->life_stage_
!= kLoadCompleted
)
644 window
->intercepted_page_beforeunload_
= true;
645 // Handle case of devtools inspecting another devtools instance by passing
646 // the call up to the inspecting devtools instance.
647 if (!DevToolsWindow::InterceptPageBeforeUnload(window
->main_web_contents_
)) {
648 window
->main_web_contents_
->DispatchBeforeUnload(false);
654 bool DevToolsWindow::NeedsToInterceptBeforeUnload(
655 WebContents
* contents
) {
656 DevToolsWindow
* window
=
657 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
658 return window
&& !window
->intercepted_page_beforeunload_
;
662 bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(
664 DCHECK(browser
->is_devtools());
665 // When FastUnloadController is used, devtools frontend will be detached
666 // from the browser window at this point which means we've already fired
668 if (browser
->tab_strip_model()->empty())
670 WebContents
* contents
=
671 browser
->tab_strip_model()->GetWebContentsAt(0);
672 DevToolsWindow
* window
= AsDevToolsWindow(contents
);
675 return window
->intercepted_page_beforeunload_
;
679 void DevToolsWindow::OnPageCloseCanceled(WebContents
* contents
) {
680 DevToolsWindow
* window
=
681 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
684 window
->intercepted_page_beforeunload_
= false;
685 // Propagate to devtools opened on devtools if any.
686 DevToolsWindow::OnPageCloseCanceled(window
->main_web_contents_
);
689 DevToolsWindow::DevToolsWindow(Profile
* profile
,
690 WebContents
* main_web_contents
,
691 DevToolsUIBindings
* bindings
,
692 WebContents
* inspected_web_contents
,
695 main_web_contents_(main_web_contents
),
696 toolbox_web_contents_(nullptr),
701 // This initialization allows external front-end to work without changes.
702 // We don't wait for docking call, but instead immediately show undocked.
703 // Passing "dockSide=undocked" parameter ensures proper UI.
704 life_stage_(can_dock
? kNotLoaded
: kIsDockedSet
),
705 action_on_load_(DevToolsToggleAction::NoOp()),
706 intercepted_page_beforeunload_(false) {
707 // Set up delegate, so we get fully-functional window immediately.
708 // It will not appear in UI though until |life_stage_ == kLoadCompleted|.
709 main_web_contents_
->SetDelegate(this);
710 // Bindings take ownership over devtools as its delegate.
711 bindings_
->SetDelegate(this);
712 // DevTools uses PageZoom::Zoom(), so main_web_contents_ requires a
714 ui_zoom::ZoomController::CreateForWebContents(main_web_contents_
);
715 ui_zoom::ZoomController::FromWebContents(main_web_contents_
)
716 ->SetShowsNotificationBubble(false);
718 g_instances
.Get().push_back(this);
720 // There is no inspected_web_contents in case of various workers.
721 if (inspected_web_contents
)
722 inspected_contents_observer_
.reset(
723 new ObserverWithAccessor(inspected_web_contents
));
725 // Initialize docked page to be of the right size.
726 if (can_dock_
&& inspected_web_contents
) {
727 content::RenderWidgetHostView
* inspected_view
=
728 inspected_web_contents
->GetRenderWidgetHostView();
729 if (inspected_view
&& main_web_contents_
->GetRenderWidgetHostView()) {
730 gfx::Size size
= inspected_view
->GetViewBounds().size();
731 main_web_contents_
->GetRenderWidgetHostView()->SetSize(size
);
735 event_forwarder_
.reset(new DevToolsEventForwarder(this));
737 // Tag the DevTools main WebContents with its TaskManager specific UserData
738 // so that it shows up in the task manager.
739 task_management::WebContentsTags::CreateForDevToolsContents(
744 DevToolsWindow
* DevToolsWindow::Create(
746 const GURL
& frontend_url
,
747 content::WebContents
* inspected_web_contents
,
748 bool shared_worker_frontend
,
749 const std::string
& remote_frontend
,
751 const std::string
& settings
) {
752 if (profile
->GetPrefs()->GetBoolean(prefs::kDevToolsDisabled
) ||
753 base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode
))
756 if (inspected_web_contents
) {
757 // Check for a place to dock.
758 Browser
* browser
= NULL
;
760 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents
,
762 browser
->is_type_popup()) {
767 // Create WebContents with devtools.
768 GURL
url(GetDevToolsURL(profile
, frontend_url
,
769 shared_worker_frontend
,
771 can_dock
, settings
));
772 scoped_ptr
<WebContents
> main_web_contents(
773 WebContents::Create(WebContents::CreateParams(profile
)));
774 main_web_contents
->GetController().LoadURL(
775 DecorateFrontendURL(url
), content::Referrer(),
776 ui::PAGE_TRANSITION_AUTO_TOPLEVEL
, std::string());
777 DevToolsUIBindings
* bindings
=
778 DevToolsUIBindings::ForWebContents(main_web_contents
.get());
782 return new DevToolsWindow(profile
, main_web_contents
.release(), bindings
,
783 inspected_web_contents
, can_dock
);
787 GURL
DevToolsWindow::GetDevToolsURL(Profile
* profile
,
788 const GURL
& base_url
,
789 bool shared_worker_frontend
,
790 const std::string
& remote_frontend
,
792 const std::string
& settings
) {
793 // Compatibility errors are encoded with data urls, pass them
794 // through with no decoration.
795 if (base_url
.SchemeIs("data"))
798 std::string
frontend_url(
799 !remote_frontend
.empty() ?
801 base_url
.is_empty() ? chrome::kChromeUIDevToolsURL
: base_url
.spec());
802 std::string
url_string(
804 ((frontend_url
.find("?") == std::string::npos
) ? "?" : "&"));
805 if (shared_worker_frontend
)
806 url_string
+= "&isSharedWorker=true";
807 if (remote_frontend
.size()) {
808 url_string
+= "&remoteFrontend=true";
810 url_string
+= "&remoteBase=" + DevToolsUI::GetRemoteBaseURL().spec();
813 url_string
+= "&can_dock=true";
815 url_string
+= "&settings=" + settings
;
816 return GURL(url_string
);
820 DevToolsWindow
* DevToolsWindow::FindDevToolsWindow(
821 DevToolsAgentHost
* agent_host
) {
822 if (!agent_host
|| g_instances
== NULL
)
824 DevToolsWindows
* instances
= g_instances
.Pointer();
825 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
827 if ((*it
)->bindings_
->IsAttachedTo(agent_host
))
834 DevToolsWindow
* DevToolsWindow::AsDevToolsWindow(
835 content::WebContents
* web_contents
) {
836 if (!web_contents
|| g_instances
== NULL
)
838 DevToolsWindows
* instances
= g_instances
.Pointer();
839 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
841 if ((*it
)->main_web_contents_
== web_contents
)
847 WebContents
* DevToolsWindow::OpenURLFromTab(
849 const content::OpenURLParams
& params
) {
850 DCHECK(source
== main_web_contents_
);
851 if (!params
.url
.SchemeIs(content::kChromeDevToolsScheme
)) {
852 WebContents
* inspected_web_contents
= GetInspectedWebContents();
853 return inspected_web_contents
?
854 inspected_web_contents
->OpenURL(params
) : NULL
;
857 bindings_
->Reattach();
859 content::NavigationController::LoadURLParams
load_url_params(params
.url
);
860 main_web_contents_
->GetController().LoadURLWithParams(load_url_params
);
861 return main_web_contents_
;
864 void DevToolsWindow::ShowCertificateViewer(int certificate_id
) {
865 ::ShowCertificateViewerByID(
866 is_docked_
? GetInspectedWebContents() : main_web_contents_
,
867 nullptr, certificate_id
);
870 void DevToolsWindow::ActivateContents(WebContents
* contents
) {
872 WebContents
* inspected_tab
= GetInspectedWebContents();
874 inspected_tab
->GetDelegate()->ActivateContents(inspected_tab
);
875 } else if (browser_
) {
876 browser_
->window()->Activate();
880 void DevToolsWindow::AddNewContents(WebContents
* source
,
881 WebContents
* new_contents
,
882 WindowOpenDisposition disposition
,
883 const gfx::Rect
& initial_rect
,
886 if (new_contents
== toolbox_web_contents_
) {
887 toolbox_web_contents_
->SetDelegate(
888 new DevToolsToolboxDelegate(toolbox_web_contents_
,
889 inspected_contents_observer_
.get()));
890 if (main_web_contents_
->GetRenderWidgetHostView() &&
891 toolbox_web_contents_
->GetRenderWidgetHostView()) {
893 main_web_contents_
->GetRenderWidgetHostView()->GetViewBounds().size();
894 toolbox_web_contents_
->GetRenderWidgetHostView()->SetSize(size
);
896 UpdateBrowserWindow();
900 WebContents
* inspected_web_contents
= GetInspectedWebContents();
901 if (inspected_web_contents
) {
902 inspected_web_contents
->GetDelegate()->AddNewContents(
903 source
, new_contents
, disposition
, initial_rect
, user_gesture
,
908 void DevToolsWindow::WebContentsCreated(WebContents
* source_contents
,
909 int opener_render_frame_id
,
910 const std::string
& frame_name
,
911 const GURL
& target_url
,
912 WebContents
* new_contents
) {
913 if (target_url
.SchemeIs(content::kChromeDevToolsScheme
) &&
914 target_url
.path().rfind("toolbox.html") != std::string::npos
) {
916 if (toolbox_web_contents_
)
917 delete toolbox_web_contents_
;
918 toolbox_web_contents_
= new_contents
;
920 // Tag the DevTools toolbox WebContents with its TaskManager specific
921 // UserData so that it shows up in the task manager.
922 task_management::WebContentsTags::CreateForDevToolsContents(
923 toolbox_web_contents_
);
927 void DevToolsWindow::CloseContents(WebContents
* source
) {
929 life_stage_
= kClosing
;
930 UpdateBrowserWindow();
931 // In case of docked main_web_contents_, we own it so delete here.
932 // Embedding DevTools window will be deleted as a result of
933 // DevToolsUIBindings destruction.
934 delete main_web_contents_
;
937 void DevToolsWindow::ContentsZoomChange(bool zoom_in
) {
939 ui_zoom::PageZoom::Zoom(main_web_contents_
, zoom_in
? content::PAGE_ZOOM_IN
940 : content::PAGE_ZOOM_OUT
);
943 void DevToolsWindow::BeforeUnloadFired(WebContents
* tab
,
945 bool* proceed_to_fire_unload
) {
946 if (!intercepted_page_beforeunload_
) {
947 // Docked devtools window closed directly.
950 *proceed_to_fire_unload
= proceed
;
952 // Inspected page is attempting to close.
953 WebContents
* inspected_web_contents
= GetInspectedWebContents();
955 inspected_web_contents
->DispatchBeforeUnload(false);
958 inspected_web_contents
->GetDelegate()->BeforeUnloadFired(
959 inspected_web_contents
, false, &should_proceed
);
960 DCHECK(!should_proceed
);
962 *proceed_to_fire_unload
= false;
966 bool DevToolsWindow::PreHandleKeyboardEvent(
968 const content::NativeWebKeyboardEvent
& event
,
969 bool* is_keyboard_shortcut
) {
970 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
971 if (inspected_window
) {
972 return inspected_window
->PreHandleKeyboardEvent(event
,
973 is_keyboard_shortcut
);
978 void DevToolsWindow::HandleKeyboardEvent(
980 const content::NativeWebKeyboardEvent
& event
) {
981 if (event
.windowsKeyCode
== 0x08) {
982 // Do not navigate back in history on Windows (http://crbug.com/74156).
985 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
986 if (inspected_window
)
987 inspected_window
->HandleKeyboardEvent(event
);
990 content::JavaScriptDialogManager
* DevToolsWindow::GetJavaScriptDialogManager(
991 WebContents
* source
) {
992 WebContents
* inspected_web_contents
= GetInspectedWebContents();
993 return (inspected_web_contents
&& inspected_web_contents
->GetDelegate())
994 ? inspected_web_contents
->GetDelegate()
995 ->GetJavaScriptDialogManager(inspected_web_contents
)
996 : content::WebContentsDelegate::GetJavaScriptDialogManager(source
);
999 content::ColorChooser
* DevToolsWindow::OpenColorChooser(
1000 WebContents
* web_contents
,
1001 SkColor initial_color
,
1002 const std::vector
<content::ColorSuggestion
>& suggestions
) {
1003 return chrome::ShowColorChooser(web_contents
, initial_color
);
1006 void DevToolsWindow::RunFileChooser(WebContents
* web_contents
,
1007 const content::FileChooserParams
& params
) {
1008 FileSelectHelper::RunFileChooser(web_contents
, params
);
1011 bool DevToolsWindow::PreHandleGestureEvent(
1012 WebContents
* source
,
1013 const blink::WebGestureEvent
& event
) {
1014 // Disable pinch zooming.
1015 return event
.type
== blink::WebGestureEvent::GesturePinchBegin
||
1016 event
.type
== blink::WebGestureEvent::GesturePinchUpdate
||
1017 event
.type
== blink::WebGestureEvent::GesturePinchEnd
;
1020 void DevToolsWindow::ActivateWindow() {
1021 if (life_stage_
!= kLoadCompleted
)
1023 if (is_docked_
&& GetInspectedBrowserWindow())
1024 main_web_contents_
->Focus();
1025 else if (!is_docked_
&& !browser_
->window()->IsActive())
1026 browser_
->window()->Activate();
1029 void DevToolsWindow::CloseWindow() {
1031 life_stage_
= kClosing
;
1032 main_web_contents_
->DispatchBeforeUnload(false);
1035 void DevToolsWindow::SetInspectedPageBounds(const gfx::Rect
& rect
) {
1036 DevToolsContentsResizingStrategy
strategy(rect
);
1037 if (contents_resizing_strategy_
.Equals(strategy
))
1040 contents_resizing_strategy_
.CopyFrom(strategy
);
1041 UpdateBrowserWindow();
1044 void DevToolsWindow::InspectElementCompleted() {
1045 if (!inspect_element_start_time_
.is_null()) {
1046 UMA_HISTOGRAM_TIMES("DevTools.InspectElement",
1047 base::TimeTicks::Now() - inspect_element_start_time_
);
1048 inspect_element_start_time_
= base::TimeTicks();
1052 void DevToolsWindow::SetIsDocked(bool dock_requested
) {
1053 if (life_stage_
== kClosing
)
1056 DCHECK(can_dock_
|| !dock_requested
);
1058 dock_requested
= false;
1060 bool was_docked
= is_docked_
;
1061 is_docked_
= dock_requested
;
1063 if (life_stage_
!= kLoadCompleted
) {
1064 // This is a first time call we waited for to initialize.
1065 life_stage_
= life_stage_
== kOnLoadFired
? kLoadCompleted
: kIsDockedSet
;
1066 if (life_stage_
== kLoadCompleted
)
1071 if (dock_requested
== was_docked
)
1074 if (dock_requested
&& !was_docked
) {
1075 // Detach window from the external devtools browser. It will lead to
1076 // the browser object's close and delete. Remove observer first.
1077 TabStripModel
* tab_strip_model
= browser_
->tab_strip_model();
1078 tab_strip_model
->DetachWebContentsAt(
1079 tab_strip_model
->GetIndexOfWebContents(main_web_contents_
));
1081 } else if (!dock_requested
&& was_docked
) {
1082 UpdateBrowserWindow();
1085 Show(DevToolsToggleAction::Show());
1088 void DevToolsWindow::OpenInNewTab(const std::string
& url
) {
1089 content::OpenURLParams
params(
1090 GURL(url
), content::Referrer(), NEW_FOREGROUND_TAB
,
1091 ui::PAGE_TRANSITION_LINK
, false);
1092 WebContents
* inspected_web_contents
= GetInspectedWebContents();
1093 if (!inspected_web_contents
|| !inspected_web_contents
->OpenURL(params
)) {
1094 chrome::HostDesktopType host_desktop_type
=
1095 browser_
? browser_
->host_desktop_type() : chrome::GetActiveDesktop();
1097 chrome::ScopedTabbedBrowserDisplayer
displayer(profile_
, host_desktop_type
);
1098 chrome::AddSelectedTabWithURL(displayer
.browser(), GURL(url
),
1099 ui::PAGE_TRANSITION_LINK
);
1103 void DevToolsWindow::SetWhitelistedShortcuts(
1104 const std::string
& message
) {
1105 event_forwarder_
->SetWhitelistedShortcuts(message
);
1108 void DevToolsWindow::InspectedContentsClosing() {
1109 intercepted_page_beforeunload_
= false;
1110 life_stage_
= kClosing
;
1111 main_web_contents_
->ClosePage();
1114 InfoBarService
* DevToolsWindow::GetInfoBarService() {
1116 InfoBarService::FromWebContents(GetInspectedWebContents()) :
1117 InfoBarService::FromWebContents(main_web_contents_
);
1120 void DevToolsWindow::RenderProcessGone(bool crashed
) {
1121 // Docked DevToolsWindow owns its main_web_contents_ and must delete it.
1122 // Undocked main_web_contents_ are owned and handled by browser.
1123 // see crbug.com/369932
1125 CloseContents(main_web_contents_
);
1126 } else if (browser_
&& crashed
) {
1127 browser_
->window()->Close();
1131 void DevToolsWindow::OnLoadCompleted() {
1132 // First seed inspected tab id for extension APIs.
1133 WebContents
* inspected_web_contents
= GetInspectedWebContents();
1134 if (inspected_web_contents
) {
1135 SessionTabHelper
* session_tab_helper
=
1136 SessionTabHelper::FromWebContents(inspected_web_contents
);
1137 if (session_tab_helper
) {
1138 base::FundamentalValue
tabId(session_tab_helper
->session_id().id());
1139 bindings_
->CallClientFunction("DevToolsAPI.setInspectedTabId",
1140 &tabId
, NULL
, NULL
);
1144 if (life_stage_
== kClosing
)
1147 // We could be in kLoadCompleted state already if frontend reloads itself.
1148 if (life_stage_
!= kLoadCompleted
) {
1149 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
1150 // Here we set kOnLoadFired.
1151 life_stage_
= life_stage_
== kIsDockedSet
? kLoadCompleted
: kOnLoadFired
;
1153 if (life_stage_
== kLoadCompleted
)
1157 void DevToolsWindow::CreateDevToolsBrowser() {
1158 PrefService
* prefs
= profile_
->GetPrefs();
1159 if (!prefs
->GetDictionary(prefs::kAppWindowPlacement
)->HasKey(kDevToolsApp
)) {
1160 DictionaryPrefUpdate
update(prefs
, prefs::kAppWindowPlacement
);
1161 base::DictionaryValue
* wp_prefs
= update
.Get();
1162 base::DictionaryValue
* dev_tools_defaults
= new base::DictionaryValue
;
1163 wp_prefs
->Set(kDevToolsApp
, dev_tools_defaults
);
1164 dev_tools_defaults
->SetInteger("left", 100);
1165 dev_tools_defaults
->SetInteger("top", 100);
1166 dev_tools_defaults
->SetInteger("right", 740);
1167 dev_tools_defaults
->SetInteger("bottom", 740);
1168 dev_tools_defaults
->SetBoolean("maximized", false);
1169 dev_tools_defaults
->SetBoolean("always_on_top", false);
1172 browser_
= new Browser(Browser::CreateParams::CreateForDevTools(
1174 chrome::GetHostDesktopTypeForNativeView(
1175 main_web_contents_
->GetNativeView())));
1176 browser_
->tab_strip_model()->AddWebContents(
1177 main_web_contents_
, -1, ui::PAGE_TRANSITION_AUTO_TOPLEVEL
,
1178 TabStripModel::ADD_ACTIVE
);
1179 main_web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
1182 BrowserWindow
* DevToolsWindow::GetInspectedBrowserWindow() {
1183 Browser
* browser
= NULL
;
1185 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1187 browser
->window() : NULL
;
1190 void DevToolsWindow::DoAction(const DevToolsToggleAction
& action
) {
1191 switch (action
.type()) {
1192 case DevToolsToggleAction::kShowConsole
:
1193 bindings_
->CallClientFunction(
1194 "DevToolsAPI.showConsole", NULL
, NULL
, NULL
);
1197 case DevToolsToggleAction::kInspect
:
1198 bindings_
->CallClientFunction(
1199 "DevToolsAPI.enterInspectElementMode", NULL
, NULL
, NULL
);
1202 case DevToolsToggleAction::kShow
:
1203 case DevToolsToggleAction::kToggle
:
1207 case DevToolsToggleAction::kReveal
: {
1208 const DevToolsToggleAction::RevealParams
* params
=
1211 base::StringValue
url_value(params
->url
);
1212 base::FundamentalValue
line_value(static_cast<int>(params
->line_number
));
1213 base::FundamentalValue
column_value(
1214 static_cast<int>(params
->column_number
));
1215 bindings_
->CallClientFunction("DevToolsAPI.revealSourceLine",
1216 &url_value
, &line_value
, &column_value
);
1225 void DevToolsWindow::UpdateBrowserToolbar() {
1226 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
1227 if (inspected_window
)
1228 inspected_window
->UpdateToolbar(NULL
);
1231 void DevToolsWindow::UpdateBrowserWindow() {
1232 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
1233 if (inspected_window
)
1234 inspected_window
->UpdateDevTools();
1237 WebContents
* DevToolsWindow::GetInspectedWebContents() {
1238 return inspected_contents_observer_
1239 ? inspected_contents_observer_
->web_contents()
1243 void DevToolsWindow::LoadCompleted() {
1244 Show(action_on_load_
);
1245 action_on_load_
= DevToolsToggleAction::NoOp();
1246 if (!load_completed_callback_
.is_null()) {
1247 load_completed_callback_
.Run();
1248 load_completed_callback_
= base::Closure();
1252 void DevToolsWindow::SetLoadCompletedCallback(const base::Closure
& closure
) {
1253 if (life_stage_
== kLoadCompleted
|| life_stage_
== kClosing
) {
1254 if (!closure
.is_null())
1258 load_completed_callback_
= closure
;
1261 bool DevToolsWindow::ForwardKeyboardEvent(
1262 const content::NativeWebKeyboardEvent
& event
) {
1263 return event_forwarder_
->ForwardEvent(event
);
1266 void DevToolsWindow::ReloadInspectedWebContents(bool ignore_cache
) {
1267 base::FundamentalValue
ignore_cache_value(ignore_cache
);
1268 bindings_
->CallClientFunction("DevToolsAPI.reloadInspectedPage",
1269 &ignore_cache_value
, nullptr, nullptr);