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/prefs/pref_service_syncable.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/sessions/session_tab_helper.h"
21 #include "chrome/browser/task_management/web_contents_tags.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/browser_dialogs.h"
24 #include "chrome/browser/ui/browser_iterator.h"
25 #include "chrome/browser/ui/browser_list.h"
26 #include "chrome/browser/ui/browser_tabstrip.h"
27 #include "chrome/browser/ui/browser_window.h"
28 #include "chrome/browser/ui/host_desktop.h"
29 #include "chrome/browser/ui/prefs/prefs_tab_helper.h"
30 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
31 #include "chrome/browser/ui/tabs/tab_strip_model.h"
32 #include "chrome/browser/ui/webui/devtools_ui.h"
33 #include "chrome/common/chrome_switches.h"
34 #include "chrome/common/pref_names.h"
35 #include "chrome/common/url_constants.h"
36 #include "components/pref_registry/pref_registry_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
;
531 void DevToolsWindow::ScheduleShow(const DevToolsToggleAction
& action
) {
532 if (life_stage_
== kLoadCompleted
) {
537 // Action will be done only after load completed.
538 action_on_load_
= action
;
541 // No harm to show always-undocked window right away.
543 Show(DevToolsToggleAction::Show());
547 void DevToolsWindow::Show(const DevToolsToggleAction
& action
) {
548 if (life_stage_
== kClosing
)
551 if (action
.type() == DevToolsToggleAction::kNoOp
)
556 Browser
* inspected_browser
= NULL
;
557 int inspected_tab_index
= -1;
558 FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
560 &inspected_tab_index
);
561 DCHECK(inspected_browser
);
562 DCHECK(inspected_tab_index
!= -1);
564 // Tell inspected browser to update splitter and switch to inspected panel.
565 BrowserWindow
* inspected_window
= inspected_browser
->window();
566 main_web_contents_
->SetDelegate(this);
568 TabStripModel
* tab_strip_model
= inspected_browser
->tab_strip_model();
569 tab_strip_model
->ActivateTabAt(inspected_tab_index
, true);
571 inspected_window
->UpdateDevTools();
572 main_web_contents_
->SetInitialFocus();
573 inspected_window
->Show();
574 // On Aura, focusing once is not enough. Do it again.
575 // Note that focusing only here but not before isn't enough either. We just
576 // need to focus twice.
577 main_web_contents_
->SetInitialFocus();
579 PrefsTabHelper::CreateForWebContents(main_web_contents_
);
580 main_web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
586 // Avoid consecutive window switching if the devtools window has been opened
587 // and the Inspect Element shortcut is pressed in the inspected tab.
588 bool should_show_window
=
589 !browser_
|| (action
.type() != DevToolsToggleAction::kInspect
);
592 CreateDevToolsBrowser();
594 if (should_show_window
) {
595 browser_
->window()->Show();
596 main_web_contents_
->SetInitialFocus();
598 if (toolbox_web_contents_
)
599 UpdateBrowserWindow();
605 bool DevToolsWindow::HandleBeforeUnload(WebContents
* frontend_contents
,
606 bool proceed
, bool* proceed_to_fire_unload
) {
607 DevToolsWindow
* window
= AsDevToolsWindow(frontend_contents
);
610 if (!window
->intercepted_page_beforeunload_
)
612 window
->BeforeUnloadFired(frontend_contents
, proceed
,
613 proceed_to_fire_unload
);
618 bool DevToolsWindow::InterceptPageBeforeUnload(WebContents
* contents
) {
619 DevToolsWindow
* window
=
620 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
621 if (!window
|| window
->intercepted_page_beforeunload_
)
624 // Not yet loaded frontend will not handle beforeunload.
625 if (window
->life_stage_
!= kLoadCompleted
)
628 window
->intercepted_page_beforeunload_
= true;
629 // Handle case of devtools inspecting another devtools instance by passing
630 // the call up to the inspecting devtools instance.
631 if (!DevToolsWindow::InterceptPageBeforeUnload(window
->main_web_contents_
)) {
632 window
->main_web_contents_
->DispatchBeforeUnload(false);
638 bool DevToolsWindow::NeedsToInterceptBeforeUnload(
639 WebContents
* contents
) {
640 DevToolsWindow
* window
=
641 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
642 return window
&& !window
->intercepted_page_beforeunload_
;
646 bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(
648 DCHECK(browser
->is_devtools());
649 // When FastUnloadController is used, devtools frontend will be detached
650 // from the browser window at this point which means we've already fired
652 if (browser
->tab_strip_model()->empty())
654 WebContents
* contents
=
655 browser
->tab_strip_model()->GetWebContentsAt(0);
656 DevToolsWindow
* window
= AsDevToolsWindow(contents
);
659 return window
->intercepted_page_beforeunload_
;
663 void DevToolsWindow::OnPageCloseCanceled(WebContents
* contents
) {
664 DevToolsWindow
* window
=
665 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
668 window
->intercepted_page_beforeunload_
= false;
669 // Propagate to devtools opened on devtools if any.
670 DevToolsWindow::OnPageCloseCanceled(window
->main_web_contents_
);
673 DevToolsWindow::DevToolsWindow(Profile
* profile
,
674 WebContents
* main_web_contents
,
675 DevToolsUIBindings
* bindings
,
676 WebContents
* inspected_web_contents
,
679 main_web_contents_(main_web_contents
),
680 toolbox_web_contents_(nullptr),
685 // This initialization allows external front-end to work without changes.
686 // We don't wait for docking call, but instead immediately show undocked.
687 // Passing "dockSide=undocked" parameter ensures proper UI.
688 life_stage_(can_dock
? kNotLoaded
: kIsDockedSet
),
689 action_on_load_(DevToolsToggleAction::NoOp()),
690 intercepted_page_beforeunload_(false) {
691 // Set up delegate, so we get fully-functional window immediately.
692 // It will not appear in UI though until |life_stage_ == kLoadCompleted|.
693 main_web_contents_
->SetDelegate(this);
694 // Bindings take ownership over devtools as its delegate.
695 bindings_
->SetDelegate(this);
696 // DevTools uses PageZoom::Zoom(), so main_web_contents_ requires a
698 ui_zoom::ZoomController::CreateForWebContents(main_web_contents_
);
699 ui_zoom::ZoomController::FromWebContents(main_web_contents_
)
700 ->SetShowsNotificationBubble(false);
702 g_instances
.Get().push_back(this);
704 // There is no inspected_web_contents in case of various workers.
705 if (inspected_web_contents
)
706 inspected_contents_observer_
.reset(
707 new ObserverWithAccessor(inspected_web_contents
));
709 // Initialize docked page to be of the right size.
710 if (can_dock_
&& inspected_web_contents
) {
711 content::RenderWidgetHostView
* inspected_view
=
712 inspected_web_contents
->GetRenderWidgetHostView();
713 if (inspected_view
&& main_web_contents_
->GetRenderWidgetHostView()) {
714 gfx::Size size
= inspected_view
->GetViewBounds().size();
715 main_web_contents_
->GetRenderWidgetHostView()->SetSize(size
);
719 event_forwarder_
.reset(new DevToolsEventForwarder(this));
721 // Tag the DevTools main WebContents with its TaskManager specific UserData
722 // so that it shows up in the task manager.
723 task_management::WebContentsTags::CreateForDevToolsContents(
728 DevToolsWindow
* DevToolsWindow::Create(
730 const GURL
& frontend_url
,
731 content::WebContents
* inspected_web_contents
,
732 bool shared_worker_frontend
,
733 const std::string
& remote_frontend
,
735 const std::string
& settings
) {
736 if (profile
->GetPrefs()->GetBoolean(prefs::kDevToolsDisabled
) ||
737 base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode
))
740 if (inspected_web_contents
) {
741 // Check for a place to dock.
742 Browser
* browser
= NULL
;
744 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents
,
746 browser
->is_type_popup()) {
751 // Create WebContents with devtools.
752 GURL
url(GetDevToolsURL(profile
, frontend_url
,
753 shared_worker_frontend
,
755 can_dock
, settings
));
756 scoped_ptr
<WebContents
> main_web_contents(
757 WebContents::Create(WebContents::CreateParams(profile
)));
758 main_web_contents
->GetController().LoadURL(
759 DecorateFrontendURL(url
), content::Referrer(),
760 ui::PAGE_TRANSITION_AUTO_TOPLEVEL
, std::string());
761 DevToolsUIBindings
* bindings
=
762 DevToolsUIBindings::ForWebContents(main_web_contents
.get());
766 return new DevToolsWindow(profile
, main_web_contents
.release(), bindings
,
767 inspected_web_contents
, can_dock
);
771 GURL
DevToolsWindow::GetDevToolsURL(Profile
* profile
,
772 const GURL
& base_url
,
773 bool shared_worker_frontend
,
774 const std::string
& remote_frontend
,
776 const std::string
& settings
) {
777 // Compatibility errors are encoded with data urls, pass them
778 // through with no decoration.
779 if (base_url
.SchemeIs("data"))
782 std::string
frontend_url(
783 !remote_frontend
.empty() ?
785 base_url
.is_empty() ? chrome::kChromeUIDevToolsURL
: base_url
.spec());
786 std::string
url_string(
788 ((frontend_url
.find("?") == std::string::npos
) ? "?" : "&"));
789 if (shared_worker_frontend
)
790 url_string
+= "&isSharedWorker=true";
791 if (remote_frontend
.size()) {
792 url_string
+= "&remoteFrontend=true";
794 url_string
+= "&remoteBase=" + DevToolsUI::GetRemoteBaseURL().spec();
797 url_string
+= "&can_dock=true";
799 url_string
+= "&settings=" + settings
;
800 return GURL(url_string
);
804 DevToolsWindow
* DevToolsWindow::FindDevToolsWindow(
805 DevToolsAgentHost
* agent_host
) {
806 if (!agent_host
|| g_instances
== NULL
)
808 DevToolsWindows
* instances
= g_instances
.Pointer();
809 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
811 if ((*it
)->bindings_
->IsAttachedTo(agent_host
))
818 DevToolsWindow
* DevToolsWindow::AsDevToolsWindow(
819 content::WebContents
* web_contents
) {
820 if (!web_contents
|| g_instances
== NULL
)
822 DevToolsWindows
* instances
= g_instances
.Pointer();
823 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
825 if ((*it
)->main_web_contents_
== web_contents
)
831 WebContents
* DevToolsWindow::OpenURLFromTab(
833 const content::OpenURLParams
& params
) {
834 DCHECK(source
== main_web_contents_
);
835 if (!params
.url
.SchemeIs(content::kChromeDevToolsScheme
)) {
836 WebContents
* inspected_web_contents
= GetInspectedWebContents();
837 return inspected_web_contents
?
838 inspected_web_contents
->OpenURL(params
) : NULL
;
841 bindings_
->Reattach();
843 content::NavigationController::LoadURLParams
load_url_params(params
.url
);
844 main_web_contents_
->GetController().LoadURLWithParams(load_url_params
);
845 return main_web_contents_
;
848 void DevToolsWindow::ShowCertificateViewer(int certificate_id
) {
849 ::ShowCertificateViewerByID(
850 is_docked_
? GetInspectedWebContents() : main_web_contents_
,
851 nullptr, certificate_id
);
854 void DevToolsWindow::ActivateContents(WebContents
* contents
) {
856 WebContents
* inspected_tab
= GetInspectedWebContents();
858 inspected_tab
->GetDelegate()->ActivateContents(inspected_tab
);
859 } else if (browser_
) {
860 browser_
->window()->Activate();
864 void DevToolsWindow::AddNewContents(WebContents
* source
,
865 WebContents
* new_contents
,
866 WindowOpenDisposition disposition
,
867 const gfx::Rect
& initial_rect
,
870 if (new_contents
== toolbox_web_contents_
) {
871 toolbox_web_contents_
->SetDelegate(
872 new DevToolsToolboxDelegate(toolbox_web_contents_
,
873 inspected_contents_observer_
.get()));
874 if (main_web_contents_
->GetRenderWidgetHostView() &&
875 toolbox_web_contents_
->GetRenderWidgetHostView()) {
877 main_web_contents_
->GetRenderWidgetHostView()->GetViewBounds().size();
878 toolbox_web_contents_
->GetRenderWidgetHostView()->SetSize(size
);
880 UpdateBrowserWindow();
884 WebContents
* inspected_web_contents
= GetInspectedWebContents();
885 if (inspected_web_contents
) {
886 inspected_web_contents
->GetDelegate()->AddNewContents(
887 source
, new_contents
, disposition
, initial_rect
, user_gesture
,
892 void DevToolsWindow::WebContentsCreated(WebContents
* source_contents
,
893 int opener_render_frame_id
,
894 const std::string
& frame_name
,
895 const GURL
& target_url
,
896 WebContents
* new_contents
) {
897 if (target_url
.SchemeIs(content::kChromeDevToolsScheme
) &&
898 target_url
.path().rfind("toolbox.html") != std::string::npos
) {
900 if (toolbox_web_contents_
)
901 delete toolbox_web_contents_
;
902 toolbox_web_contents_
= new_contents
;
904 // Tag the DevTools toolbox WebContents with its TaskManager specific
905 // UserData so that it shows up in the task manager.
906 task_management::WebContentsTags::CreateForDevToolsContents(
907 toolbox_web_contents_
);
911 void DevToolsWindow::CloseContents(WebContents
* source
) {
913 life_stage_
= kClosing
;
914 UpdateBrowserWindow();
915 // In case of docked main_web_contents_, we own it so delete here.
916 // Embedding DevTools window will be deleted as a result of
917 // DevToolsUIBindings destruction.
918 delete main_web_contents_
;
921 void DevToolsWindow::ContentsZoomChange(bool zoom_in
) {
923 ui_zoom::PageZoom::Zoom(main_web_contents_
, zoom_in
? content::PAGE_ZOOM_IN
924 : content::PAGE_ZOOM_OUT
);
927 void DevToolsWindow::BeforeUnloadFired(WebContents
* tab
,
929 bool* proceed_to_fire_unload
) {
930 if (!intercepted_page_beforeunload_
) {
931 // Docked devtools window closed directly.
934 *proceed_to_fire_unload
= proceed
;
936 // Inspected page is attempting to close.
937 WebContents
* inspected_web_contents
= GetInspectedWebContents();
939 inspected_web_contents
->DispatchBeforeUnload(false);
942 inspected_web_contents
->GetDelegate()->BeforeUnloadFired(
943 inspected_web_contents
, false, &should_proceed
);
944 DCHECK(!should_proceed
);
946 *proceed_to_fire_unload
= false;
950 bool DevToolsWindow::PreHandleKeyboardEvent(
952 const content::NativeWebKeyboardEvent
& event
,
953 bool* is_keyboard_shortcut
) {
954 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
955 if (inspected_window
) {
956 return inspected_window
->PreHandleKeyboardEvent(event
,
957 is_keyboard_shortcut
);
962 void DevToolsWindow::HandleKeyboardEvent(
964 const content::NativeWebKeyboardEvent
& event
) {
965 if (event
.windowsKeyCode
== 0x08) {
966 // Do not navigate back in history on Windows (http://crbug.com/74156).
969 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
970 if (inspected_window
)
971 inspected_window
->HandleKeyboardEvent(event
);
974 content::JavaScriptDialogManager
* DevToolsWindow::GetJavaScriptDialogManager(
975 WebContents
* source
) {
976 WebContents
* inspected_web_contents
= GetInspectedWebContents();
977 return (inspected_web_contents
&& inspected_web_contents
->GetDelegate())
978 ? inspected_web_contents
->GetDelegate()
979 ->GetJavaScriptDialogManager(inspected_web_contents
)
980 : content::WebContentsDelegate::GetJavaScriptDialogManager(source
);
983 content::ColorChooser
* DevToolsWindow::OpenColorChooser(
984 WebContents
* web_contents
,
985 SkColor initial_color
,
986 const std::vector
<content::ColorSuggestion
>& suggestions
) {
987 return chrome::ShowColorChooser(web_contents
, initial_color
);
990 void DevToolsWindow::RunFileChooser(WebContents
* web_contents
,
991 const content::FileChooserParams
& params
) {
992 FileSelectHelper::RunFileChooser(web_contents
, params
);
995 bool DevToolsWindow::PreHandleGestureEvent(
997 const blink::WebGestureEvent
& event
) {
998 // Disable pinch zooming.
999 return event
.type
== blink::WebGestureEvent::GesturePinchBegin
||
1000 event
.type
== blink::WebGestureEvent::GesturePinchUpdate
||
1001 event
.type
== blink::WebGestureEvent::GesturePinchEnd
;
1004 void DevToolsWindow::ActivateWindow() {
1005 if (life_stage_
!= kLoadCompleted
)
1007 if (is_docked_
&& GetInspectedBrowserWindow())
1008 main_web_contents_
->Focus();
1009 else if (!is_docked_
&& !browser_
->window()->IsActive())
1010 browser_
->window()->Activate();
1013 void DevToolsWindow::CloseWindow() {
1015 life_stage_
= kClosing
;
1016 main_web_contents_
->DispatchBeforeUnload(false);
1019 void DevToolsWindow::SetInspectedPageBounds(const gfx::Rect
& rect
) {
1020 DevToolsContentsResizingStrategy
strategy(rect
);
1021 if (contents_resizing_strategy_
.Equals(strategy
))
1024 contents_resizing_strategy_
.CopyFrom(strategy
);
1025 UpdateBrowserWindow();
1028 void DevToolsWindow::InspectElementCompleted() {
1029 if (!inspect_element_start_time_
.is_null()) {
1030 UMA_HISTOGRAM_TIMES("DevTools.InspectElement",
1031 base::TimeTicks::Now() - inspect_element_start_time_
);
1032 inspect_element_start_time_
= base::TimeTicks();
1036 void DevToolsWindow::SetIsDocked(bool dock_requested
) {
1037 if (life_stage_
== kClosing
)
1040 DCHECK(can_dock_
|| !dock_requested
);
1042 dock_requested
= false;
1044 bool was_docked
= is_docked_
;
1045 is_docked_
= dock_requested
;
1047 if (life_stage_
!= kLoadCompleted
) {
1048 // This is a first time call we waited for to initialize.
1049 life_stage_
= life_stage_
== kOnLoadFired
? kLoadCompleted
: kIsDockedSet
;
1050 if (life_stage_
== kLoadCompleted
)
1055 if (dock_requested
== was_docked
)
1058 if (dock_requested
&& !was_docked
) {
1059 // Detach window from the external devtools browser. It will lead to
1060 // the browser object's close and delete. Remove observer first.
1061 TabStripModel
* tab_strip_model
= browser_
->tab_strip_model();
1062 tab_strip_model
->DetachWebContentsAt(
1063 tab_strip_model
->GetIndexOfWebContents(main_web_contents_
));
1065 } else if (!dock_requested
&& was_docked
) {
1066 UpdateBrowserWindow();
1069 Show(DevToolsToggleAction::Show());
1072 void DevToolsWindow::OpenInNewTab(const std::string
& url
) {
1073 content::OpenURLParams
params(
1074 GURL(url
), content::Referrer(), NEW_FOREGROUND_TAB
,
1075 ui::PAGE_TRANSITION_LINK
, false);
1076 WebContents
* inspected_web_contents
= GetInspectedWebContents();
1077 if (!inspected_web_contents
|| !inspected_web_contents
->OpenURL(params
)) {
1078 chrome::HostDesktopType host_desktop_type
=
1079 browser_
? browser_
->host_desktop_type() : chrome::GetActiveDesktop();
1081 chrome::ScopedTabbedBrowserDisplayer
displayer(profile_
, host_desktop_type
);
1082 chrome::AddSelectedTabWithURL(displayer
.browser(), GURL(url
),
1083 ui::PAGE_TRANSITION_LINK
);
1087 void DevToolsWindow::SetWhitelistedShortcuts(
1088 const std::string
& message
) {
1089 event_forwarder_
->SetWhitelistedShortcuts(message
);
1092 void DevToolsWindow::InspectedContentsClosing() {
1093 intercepted_page_beforeunload_
= false;
1094 life_stage_
= kClosing
;
1095 main_web_contents_
->ClosePage();
1098 InfoBarService
* DevToolsWindow::GetInfoBarService() {
1100 InfoBarService::FromWebContents(GetInspectedWebContents()) :
1101 InfoBarService::FromWebContents(main_web_contents_
);
1104 void DevToolsWindow::RenderProcessGone(bool crashed
) {
1105 // Docked DevToolsWindow owns its main_web_contents_ and must delete it.
1106 // Undocked main_web_contents_ are owned and handled by browser.
1107 // see crbug.com/369932
1109 CloseContents(main_web_contents_
);
1110 } else if (browser_
&& crashed
) {
1111 browser_
->window()->Close();
1115 void DevToolsWindow::OnLoadCompleted() {
1116 // First seed inspected tab id for extension APIs.
1117 WebContents
* inspected_web_contents
= GetInspectedWebContents();
1118 if (inspected_web_contents
) {
1119 SessionTabHelper
* session_tab_helper
=
1120 SessionTabHelper::FromWebContents(inspected_web_contents
);
1121 if (session_tab_helper
) {
1122 base::FundamentalValue
tabId(session_tab_helper
->session_id().id());
1123 bindings_
->CallClientFunction("DevToolsAPI.setInspectedTabId",
1124 &tabId
, NULL
, NULL
);
1128 if (life_stage_
== kClosing
)
1131 // We could be in kLoadCompleted state already if frontend reloads itself.
1132 if (life_stage_
!= kLoadCompleted
) {
1133 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
1134 // Here we set kOnLoadFired.
1135 life_stage_
= life_stage_
== kIsDockedSet
? kLoadCompleted
: kOnLoadFired
;
1137 if (life_stage_
== kLoadCompleted
)
1141 void DevToolsWindow::CreateDevToolsBrowser() {
1142 PrefService
* prefs
= profile_
->GetPrefs();
1143 if (!prefs
->GetDictionary(prefs::kAppWindowPlacement
)->HasKey(kDevToolsApp
)) {
1144 DictionaryPrefUpdate
update(prefs
, prefs::kAppWindowPlacement
);
1145 base::DictionaryValue
* wp_prefs
= update
.Get();
1146 base::DictionaryValue
* dev_tools_defaults
= new base::DictionaryValue
;
1147 wp_prefs
->Set(kDevToolsApp
, dev_tools_defaults
);
1148 dev_tools_defaults
->SetInteger("left", 100);
1149 dev_tools_defaults
->SetInteger("top", 100);
1150 dev_tools_defaults
->SetInteger("right", 740);
1151 dev_tools_defaults
->SetInteger("bottom", 740);
1152 dev_tools_defaults
->SetBoolean("maximized", false);
1153 dev_tools_defaults
->SetBoolean("always_on_top", false);
1156 browser_
= new Browser(Browser::CreateParams::CreateForDevTools(
1158 chrome::GetHostDesktopTypeForNativeView(
1159 main_web_contents_
->GetNativeView())));
1160 browser_
->tab_strip_model()->AddWebContents(
1161 main_web_contents_
, -1, ui::PAGE_TRANSITION_AUTO_TOPLEVEL
,
1162 TabStripModel::ADD_ACTIVE
);
1163 main_web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
1166 BrowserWindow
* DevToolsWindow::GetInspectedBrowserWindow() {
1167 Browser
* browser
= NULL
;
1169 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1171 browser
->window() : NULL
;
1174 void DevToolsWindow::DoAction(const DevToolsToggleAction
& action
) {
1175 switch (action
.type()) {
1176 case DevToolsToggleAction::kShowConsole
:
1177 bindings_
->CallClientFunction(
1178 "DevToolsAPI.showConsole", NULL
, NULL
, NULL
);
1181 case DevToolsToggleAction::kInspect
:
1182 bindings_
->CallClientFunction(
1183 "DevToolsAPI.enterInspectElementMode", NULL
, NULL
, NULL
);
1186 case DevToolsToggleAction::kShow
:
1187 case DevToolsToggleAction::kToggle
:
1191 case DevToolsToggleAction::kReveal
: {
1192 const DevToolsToggleAction::RevealParams
* params
=
1195 base::StringValue
url_value(params
->url
);
1196 base::FundamentalValue
line_value(static_cast<int>(params
->line_number
));
1197 base::FundamentalValue
column_value(
1198 static_cast<int>(params
->column_number
));
1199 bindings_
->CallClientFunction("DevToolsAPI.revealSourceLine",
1200 &url_value
, &line_value
, &column_value
);
1209 void DevToolsWindow::UpdateBrowserToolbar() {
1210 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
1211 if (inspected_window
)
1212 inspected_window
->UpdateToolbar(NULL
);
1215 void DevToolsWindow::UpdateBrowserWindow() {
1216 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
1217 if (inspected_window
)
1218 inspected_window
->UpdateDevTools();
1221 WebContents
* DevToolsWindow::GetInspectedWebContents() {
1222 return inspected_contents_observer_
1223 ? inspected_contents_observer_
->web_contents()
1227 void DevToolsWindow::LoadCompleted() {
1228 Show(action_on_load_
);
1229 action_on_load_
= DevToolsToggleAction::NoOp();
1230 if (!load_completed_callback_
.is_null()) {
1231 load_completed_callback_
.Run();
1232 load_completed_callback_
= base::Closure();
1236 void DevToolsWindow::SetLoadCompletedCallback(const base::Closure
& closure
) {
1237 if (life_stage_
== kLoadCompleted
|| life_stage_
== kClosing
) {
1238 if (!closure
.is_null())
1242 load_completed_callback_
= closure
;
1245 bool DevToolsWindow::ForwardKeyboardEvent(
1246 const content::NativeWebKeyboardEvent
& event
) {
1247 return event_forwarder_
->ForwardEvent(event
);
1250 void DevToolsWindow::ReloadInspectedWebContents(bool ignore_cache
) {
1251 base::FundamentalValue
ignore_cache_value(ignore_cache
);
1252 bindings_
->CallClientFunction("DevToolsAPI.reloadInspectedPage",
1253 &ignore_cache_value
, nullptr, nullptr);