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/page_transition_types.h"
48 #include "content/public/common/url_constants.h"
49 #include "third_party/WebKit/public/web/WebInputEvent.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 virtual ~DevToolsToolboxDelegate();
95 virtual content::WebContents
* OpenURLFromTab(
96 content::WebContents
* source
,
97 const content::OpenURLParams
& params
) OVERRIDE
;
98 virtual bool PreHandleKeyboardEvent(
99 content::WebContents
* source
,
100 const content::NativeWebKeyboardEvent
& event
,
101 bool* is_keyboard_shortcut
) OVERRIDE
;
102 virtual void HandleKeyboardEvent(
103 content::WebContents
* source
,
104 const content::NativeWebKeyboardEvent
& event
) OVERRIDE
;
105 virtual void WebContentsDestroyed() OVERRIDE
;
108 BrowserWindow
* GetInspectedBrowserWindow();
109 DevToolsWindow::ObserverWithAccessor
* inspected_contents_observer_
;
110 DISALLOW_COPY_AND_ASSIGN(DevToolsToolboxDelegate
);
113 DevToolsToolboxDelegate::DevToolsToolboxDelegate(
114 WebContents
* toolbox_contents
,
115 DevToolsWindow::ObserverWithAccessor
* web_contents_observer
)
116 : WebContentsObserver(toolbox_contents
),
117 inspected_contents_observer_(web_contents_observer
) {
120 DevToolsToolboxDelegate::~DevToolsToolboxDelegate() {
123 content::WebContents
* DevToolsToolboxDelegate::OpenURLFromTab(
124 content::WebContents
* source
,
125 const content::OpenURLParams
& params
) {
126 DCHECK(source
== web_contents());
127 if (!params
.url
.SchemeIs(content::kChromeDevToolsScheme
))
129 content::NavigationController::LoadURLParams
load_url_params(params
.url
);
130 source
->GetController().LoadURLWithParams(load_url_params
);
134 bool DevToolsToolboxDelegate::PreHandleKeyboardEvent(
135 content::WebContents
* source
,
136 const content::NativeWebKeyboardEvent
& event
,
137 bool* is_keyboard_shortcut
) {
138 BrowserWindow
* window
= GetInspectedBrowserWindow();
140 return window
->PreHandleKeyboardEvent(event
, is_keyboard_shortcut
);
144 void DevToolsToolboxDelegate::HandleKeyboardEvent(
145 content::WebContents
* source
,
146 const content::NativeWebKeyboardEvent
& event
) {
147 if (event
.windowsKeyCode
== 0x08) {
148 // Do not navigate back in history on Windows (http://crbug.com/74156).
151 BrowserWindow
* window
= GetInspectedBrowserWindow();
153 window
->HandleKeyboardEvent(event
);
156 void DevToolsToolboxDelegate::WebContentsDestroyed() {
160 BrowserWindow
* DevToolsToolboxDelegate::GetInspectedBrowserWindow() {
161 WebContents
* inspected_contents
=
162 inspected_contents_observer_
->web_contents();
163 if (!inspected_contents
)
165 Browser
* browser
= NULL
;
167 if (FindInspectedBrowserAndTabIndex(inspected_contents
, &browser
, &tab
))
168 return browser
->window();
174 // DevToolsEventForwarder -----------------------------------------------------
176 class DevToolsEventForwarder
{
178 explicit DevToolsEventForwarder(DevToolsWindow
* window
)
179 : devtools_window_(window
) {}
181 // Registers whitelisted shortcuts with the forwarder.
182 // Only registered keys will be forwarded to the DevTools frontend.
183 void SetWhitelistedShortcuts(const std::string
& message
);
185 // Forwards a keyboard event to the DevTools frontend if it is whitelisted.
186 // Returns |true| if the event has been forwarded, |false| otherwise.
187 bool ForwardEvent(const content::NativeWebKeyboardEvent
& event
);
190 static int VirtualKeyCodeWithoutLocation(int key_code
);
191 static bool KeyWhitelistingAllowed(int key_code
, int modifiers
);
192 static int CombineKeyCodeAndModifiers(int key_code
, int modifiers
);
194 DevToolsWindow
* devtools_window_
;
195 std::set
<int> whitelisted_keys_
;
197 DISALLOW_COPY_AND_ASSIGN(DevToolsEventForwarder
);
200 void DevToolsEventForwarder::SetWhitelistedShortcuts(
201 const std::string
& message
) {
202 scoped_ptr
<base::Value
> parsed_message(base::JSONReader::Read(message
));
203 base::ListValue
* shortcut_list
;
204 if (!parsed_message
->GetAsList(&shortcut_list
))
206 base::ListValue::iterator it
= shortcut_list
->begin();
207 for (; it
!= shortcut_list
->end(); ++it
) {
208 base::DictionaryValue
* dictionary
;
209 if (!(*it
)->GetAsDictionary(&dictionary
))
212 dictionary
->GetInteger("keyCode", &key_code
);
216 dictionary
->GetInteger("modifiers", &modifiers
);
217 if (!KeyWhitelistingAllowed(key_code
, modifiers
)) {
218 LOG(WARNING
) << "Key whitelisting forbidden: "
219 << "(" << key_code
<< "," << modifiers
<< ")";
222 whitelisted_keys_
.insert(CombineKeyCodeAndModifiers(key_code
, modifiers
));
226 bool DevToolsEventForwarder::ForwardEvent(
227 const content::NativeWebKeyboardEvent
& event
) {
228 std::string event_type
;
229 switch (event
.type
) {
230 case WebInputEvent::KeyDown
:
231 case WebInputEvent::RawKeyDown
:
232 event_type
= kKeyDownEventName
;
234 case WebInputEvent::KeyUp
:
235 event_type
= kKeyUpEventName
;
241 int key_code
= VirtualKeyCodeWithoutLocation(event
.windowsKeyCode
);
242 int key
= CombineKeyCodeAndModifiers(key_code
, event
.modifiers
);
243 if (whitelisted_keys_
.find(key
) == whitelisted_keys_
.end())
246 base::DictionaryValue event_data
;
247 event_data
.SetString("type", event_type
);
248 event_data
.SetString("keyIdentifier", event
.keyIdentifier
);
249 event_data
.SetInteger("keyCode", key_code
);
250 event_data
.SetInteger("modifiers", event
.modifiers
);
251 devtools_window_
->bindings_
->CallClientFunction(
252 "InspectorFrontendAPI.keyEventUnhandled", &event_data
, NULL
, NULL
);
256 int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code
,
258 return key_code
| (modifiers
<< 16);
261 bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code
,
263 return (ui::VKEY_F1
<= key_code
&& key_code
<= ui::VKEY_F12
) ||
267 // Mapping copied from Blink's KeyboardEvent.cpp.
268 int DevToolsEventForwarder::VirtualKeyCodeWithoutLocation(int key_code
)
271 case ui::VKEY_LCONTROL
:
272 case ui::VKEY_RCONTROL
:
273 return ui::VKEY_CONTROL
;
274 case ui::VKEY_LSHIFT
:
275 case ui::VKEY_RSHIFT
:
276 return ui::VKEY_SHIFT
;
279 return ui::VKEY_MENU
;
285 // DevToolsWindow::ObserverWithAccessor -------------------------------
287 DevToolsWindow::ObserverWithAccessor::ObserverWithAccessor(
288 WebContents
* web_contents
)
289 : WebContentsObserver(web_contents
) {
292 DevToolsWindow::ObserverWithAccessor::~ObserverWithAccessor() {
295 // DevToolsWindow -------------------------------------------------------------
297 const char DevToolsWindow::kDevToolsApp
[] = "DevToolsApp";
299 DevToolsWindow::~DevToolsWindow() {
300 life_stage_
= kClosing
;
302 UpdateBrowserWindow();
303 UpdateBrowserToolbar();
305 if (toolbox_web_contents_
)
306 delete toolbox_web_contents_
;
308 DevToolsWindows
* instances
= g_instances
.Pointer();
309 DevToolsWindows::iterator
it(
310 std::find(instances
->begin(), instances
->end(), this));
311 DCHECK(it
!= instances
->end());
312 instances
->erase(it
);
314 if (!close_callback_
.is_null()) {
315 close_callback_
.Run();
316 close_callback_
= base::Closure();
321 void DevToolsWindow::RegisterProfilePrefs(
322 user_prefs::PrefRegistrySyncable
* registry
) {
323 registry
->RegisterDictionaryPref(
324 prefs::kDevToolsEditedFiles
,
325 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
326 registry
->RegisterDictionaryPref(
327 prefs::kDevToolsFileSystemPaths
,
328 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
329 registry
->RegisterStringPref(
330 prefs::kDevToolsAdbKey
, std::string(),
331 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
333 registry
->RegisterBooleanPref(
334 prefs::kDevToolsDiscoverUsbDevicesEnabled
,
336 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
337 registry
->RegisterBooleanPref(
338 prefs::kDevToolsPortForwardingEnabled
,
340 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
341 registry
->RegisterBooleanPref(
342 prefs::kDevToolsPortForwardingDefaultSet
,
344 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
345 registry
->RegisterDictionaryPref(
346 prefs::kDevToolsPortForwardingConfig
,
347 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
351 content::WebContents
* DevToolsWindow::GetInTabWebContents(
352 WebContents
* inspected_web_contents
,
353 DevToolsContentsResizingStrategy
* out_strategy
) {
354 DevToolsWindow
* window
= GetInstanceForInspectedWebContents(
355 inspected_web_contents
);
356 if (!window
|| window
->life_stage_
== kClosing
)
359 // Not yet loaded window is treated as docked, but we should not present it
360 // until we decided on docking.
361 bool is_docked_set
= window
->life_stage_
== kLoadCompleted
||
362 window
->life_stage_
== kIsDockedSet
;
366 // Undocked window should have toolbox web contents.
367 if (!window
->is_docked_
&& !window
->toolbox_web_contents_
)
371 out_strategy
->CopyFrom(window
->contents_resizing_strategy_
);
373 return window
->is_docked_
? window
->main_web_contents_
:
374 window
->toolbox_web_contents_
;
378 DevToolsWindow
* DevToolsWindow::GetInstanceForInspectedWebContents(
379 WebContents
* inspected_web_contents
) {
380 if (!inspected_web_contents
|| g_instances
== NULL
)
382 DevToolsWindows
* instances
= g_instances
.Pointer();
383 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
385 if ((*it
)->GetInspectedWebContents() == inspected_web_contents
)
392 bool DevToolsWindow::IsDevToolsWindow(content::WebContents
* web_contents
) {
393 if (!web_contents
|| g_instances
== NULL
)
395 DevToolsWindows
* instances
= g_instances
.Pointer();
396 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
398 if ((*it
)->main_web_contents_
== web_contents
||
399 (*it
)->toolbox_web_contents_
== web_contents
)
406 DevToolsWindow
* DevToolsWindow::OpenDevToolsWindowForWorker(
408 const scoped_refptr
<DevToolsAgentHost
>& worker_agent
) {
409 DevToolsWindow
* window
= FindDevToolsWindow(worker_agent
.get());
411 window
= DevToolsWindow::CreateDevToolsWindowForWorker(profile
);
412 window
->bindings_
->AttachTo(worker_agent
);
414 window
->ScheduleShow(DevToolsToggleAction::Show());
419 DevToolsWindow
* DevToolsWindow::CreateDevToolsWindowForWorker(
421 content::RecordAction(base::UserMetricsAction("DevTools_InspectWorker"));
422 return Create(profile
, GURL(), NULL
, true, false, false, "");
426 DevToolsWindow
* DevToolsWindow::OpenDevToolsWindow(
427 content::WebContents
* inspected_web_contents
) {
428 return ToggleDevToolsWindow(
429 inspected_web_contents
, true, DevToolsToggleAction::Show(), "");
433 DevToolsWindow
* DevToolsWindow::OpenDevToolsWindow(
434 content::WebContents
* inspected_web_contents
,
435 const DevToolsToggleAction
& action
) {
436 return ToggleDevToolsWindow(inspected_web_contents
, true, action
, "");
440 DevToolsWindow
* DevToolsWindow::ToggleDevToolsWindow(
442 const DevToolsToggleAction
& action
) {
443 if (action
.type() == DevToolsToggleAction::kToggle
&&
444 browser
->is_devtools()) {
445 browser
->tab_strip_model()->CloseAllTabs();
449 return ToggleDevToolsWindow(
450 browser
->tab_strip_model()->GetActiveWebContents(),
451 action
.type() == DevToolsToggleAction::kInspect
,
456 void DevToolsWindow::OpenExternalFrontend(
458 const std::string
& frontend_url
,
459 const scoped_refptr
<content::DevToolsAgentHost
>& agent_host
,
461 DevToolsWindow
* window
= FindDevToolsWindow(agent_host
.get());
463 window
= Create(profile
, DevToolsUI::GetProxyURL(frontend_url
), NULL
,
464 isWorker
, true, false, "");
465 window
->bindings_
->AttachTo(agent_host
);
467 window
->ScheduleShow(DevToolsToggleAction::Show());
471 DevToolsWindow
* DevToolsWindow::ToggleDevToolsWindow(
472 content::WebContents
* inspected_web_contents
,
474 const DevToolsToggleAction
& action
,
475 const std::string
& settings
) {
476 scoped_refptr
<DevToolsAgentHost
> agent(
477 DevToolsAgentHost::GetOrCreateFor(inspected_web_contents
));
478 DevToolsWindow
* window
= FindDevToolsWindow(agent
.get());
479 bool do_open
= force_open
;
481 Profile
* profile
= Profile::FromBrowserContext(
482 inspected_web_contents
->GetBrowserContext());
483 content::RecordAction(
484 base::UserMetricsAction("DevTools_InspectRenderer"));
486 profile
, GURL(), inspected_web_contents
, false, false, true, settings
);
487 window
->bindings_
->AttachTo(agent
.get());
491 // Update toolbar to reflect DevTools changes.
492 window
->UpdateBrowserToolbar();
494 // If window is docked and visible, we hide it on toggle. If window is
495 // undocked, we show (activate) it.
496 if (!window
->is_docked_
|| do_open
)
497 window
->ScheduleShow(action
);
499 window
->CloseWindow();
505 void DevToolsWindow::InspectElement(
506 content::WebContents
* inspected_web_contents
,
509 scoped_refptr
<DevToolsAgentHost
> agent(
510 DevToolsAgentHost::GetOrCreateFor(inspected_web_contents
));
511 agent
->InspectElement(x
, y
);
512 bool should_measure_time
= FindDevToolsWindow(agent
.get()) == NULL
;
513 base::TimeTicks start_time
= base::TimeTicks::Now();
514 // TODO(loislo): we should initiate DevTools window opening from within
515 // renderer. Otherwise, we still can hit a race condition here.
516 DevToolsWindow
* window
= OpenDevToolsWindow(inspected_web_contents
);
517 if (should_measure_time
)
518 window
->inspect_element_start_time_
= start_time
;
521 void DevToolsWindow::ScheduleShow(const DevToolsToggleAction
& action
) {
522 if (life_stage_
== kLoadCompleted
) {
527 // Action will be done only after load completed.
528 action_on_load_
= action
;
531 // No harm to show always-undocked window right away.
533 Show(DevToolsToggleAction::Show());
537 void DevToolsWindow::Show(const DevToolsToggleAction
& action
) {
538 if (life_stage_
== kClosing
)
541 if (action
.type() == DevToolsToggleAction::kNoOp
)
546 Browser
* inspected_browser
= NULL
;
547 int inspected_tab_index
= -1;
548 FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
550 &inspected_tab_index
);
551 DCHECK(inspected_browser
);
552 DCHECK(inspected_tab_index
!= -1);
554 // Tell inspected browser to update splitter and switch to inspected panel.
555 BrowserWindow
* inspected_window
= inspected_browser
->window();
556 main_web_contents_
->SetDelegate(this);
558 TabStripModel
* tab_strip_model
= inspected_browser
->tab_strip_model();
559 tab_strip_model
->ActivateTabAt(inspected_tab_index
, true);
561 inspected_window
->UpdateDevTools();
562 main_web_contents_
->SetInitialFocus();
563 inspected_window
->Show();
564 // On Aura, focusing once is not enough. Do it again.
565 // Note that focusing only here but not before isn't enough either. We just
566 // need to focus twice.
567 main_web_contents_
->SetInitialFocus();
569 PrefsTabHelper::CreateForWebContents(main_web_contents_
);
570 main_web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
576 // Avoid consecutive window switching if the devtools window has been opened
577 // and the Inspect Element shortcut is pressed in the inspected tab.
578 bool should_show_window
=
579 !browser_
|| (action
.type() != DevToolsToggleAction::kInspect
);
582 CreateDevToolsBrowser();
584 if (should_show_window
) {
585 browser_
->window()->Show();
586 main_web_contents_
->SetInitialFocus();
588 if (toolbox_web_contents_
)
589 UpdateBrowserWindow();
595 bool DevToolsWindow::HandleBeforeUnload(WebContents
* frontend_contents
,
596 bool proceed
, bool* proceed_to_fire_unload
) {
597 DevToolsWindow
* window
= AsDevToolsWindow(frontend_contents
);
600 if (!window
->intercepted_page_beforeunload_
)
602 window
->BeforeUnloadFired(frontend_contents
, proceed
,
603 proceed_to_fire_unload
);
608 bool DevToolsWindow::InterceptPageBeforeUnload(WebContents
* contents
) {
609 DevToolsWindow
* window
=
610 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
611 if (!window
|| window
->intercepted_page_beforeunload_
)
614 // Not yet loaded frontend will not handle beforeunload.
615 if (window
->life_stage_
!= kLoadCompleted
)
618 window
->intercepted_page_beforeunload_
= true;
619 // Handle case of devtools inspecting another devtools instance by passing
620 // the call up to the inspecting devtools instance.
621 if (!DevToolsWindow::InterceptPageBeforeUnload(window
->main_web_contents_
)) {
622 window
->main_web_contents_
->DispatchBeforeUnload(false);
628 bool DevToolsWindow::NeedsToInterceptBeforeUnload(
629 WebContents
* contents
) {
630 DevToolsWindow
* window
=
631 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
632 return window
&& !window
->intercepted_page_beforeunload_
;
636 bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(
638 DCHECK(browser
->is_devtools());
639 // When FastUnloadController is used, devtools frontend will be detached
640 // from the browser window at this point which means we've already fired
642 if (browser
->tab_strip_model()->empty())
644 WebContents
* contents
=
645 browser
->tab_strip_model()->GetWebContentsAt(0);
646 DevToolsWindow
* window
= AsDevToolsWindow(contents
);
649 return window
->intercepted_page_beforeunload_
;
653 void DevToolsWindow::OnPageCloseCanceled(WebContents
* contents
) {
654 DevToolsWindow
*window
=
655 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
658 window
->intercepted_page_beforeunload_
= false;
659 // Propagate to devtools opened on devtools if any.
660 DevToolsWindow::OnPageCloseCanceled(window
->main_web_contents_
);
663 DevToolsWindow::DevToolsWindow(Profile
* profile
,
665 content::WebContents
* inspected_web_contents
,
669 WebContents::Create(WebContents::CreateParams(profile
))),
670 toolbox_web_contents_(NULL
),
675 // This initialization allows external front-end to work without changes.
676 // We don't wait for docking call, but instead immediately show undocked.
677 // Passing "dockSide=undocked" parameter ensures proper UI.
678 life_stage_(can_dock
? kNotLoaded
: kIsDockedSet
),
679 action_on_load_(DevToolsToggleAction::NoOp()),
680 intercepted_page_beforeunload_(false) {
681 // Set up delegate, so we get fully-functional window immediately.
682 // It will not appear in UI though until |life_stage_ == kLoadCompleted|.
683 main_web_contents_
->SetDelegate(this);
685 main_web_contents_
->GetController().LoadURL(
686 DevToolsUIBindings::ApplyThemeToURL(profile
, url
), content::Referrer(),
687 content::PAGE_TRANSITION_AUTO_TOPLEVEL
, std::string());
689 bindings_
= DevToolsUIBindings::ForWebContents(main_web_contents_
);
692 // Bindings take ownership over devtools as its delegate.
693 bindings_
->SetDelegate(this);
694 // DevTools uses chrome_page_zoom::Zoom(), so main_web_contents_ requires a
696 ZoomController::CreateForWebContents(main_web_contents_
);
697 ZoomController::FromWebContents(main_web_contents_
)
698 ->SetShowsNotificationBubble(false);
700 g_instances
.Get().push_back(this);
702 // There is no inspected_web_contents in case of various workers.
703 if (inspected_web_contents
)
704 inspected_contents_observer_
.reset(
705 new ObserverWithAccessor(inspected_web_contents
));
707 // Initialize docked page to be of the right size.
708 if (can_dock_
&& inspected_web_contents
) {
709 content::RenderWidgetHostView
* inspected_view
=
710 inspected_web_contents
->GetRenderWidgetHostView();
711 if (inspected_view
&& main_web_contents_
->GetRenderWidgetHostView()) {
712 gfx::Size size
= inspected_view
->GetViewBounds().size();
713 main_web_contents_
->GetRenderWidgetHostView()->SetSize(size
);
717 event_forwarder_
.reset(new DevToolsEventForwarder(this));
721 DevToolsWindow
* DevToolsWindow::Create(
723 const GURL
& frontend_url
,
724 content::WebContents
* inspected_web_contents
,
725 bool shared_worker_frontend
,
726 bool external_frontend
,
728 const std::string
& settings
) {
729 if (inspected_web_contents
) {
730 // Check for a place to dock.
731 Browser
* browser
= NULL
;
733 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents
,
735 browser
->is_type_popup()) {
740 // Create WebContents with devtools.
741 GURL
url(GetDevToolsURL(profile
, frontend_url
,
742 shared_worker_frontend
,
744 can_dock
, settings
));
745 return new DevToolsWindow(profile
, url
, inspected_web_contents
, can_dock
);
749 GURL
DevToolsWindow::GetDevToolsURL(Profile
* profile
,
750 const GURL
& base_url
,
751 bool shared_worker_frontend
,
752 bool external_frontend
,
754 const std::string
& settings
) {
755 // Compatibility errors are encoded with data urls, pass them
756 // through with no decoration.
757 if (base_url
.SchemeIs("data"))
760 std::string
frontend_url(
761 base_url
.is_empty() ? chrome::kChromeUIDevToolsURL
: base_url
.spec());
762 std::string
url_string(
764 ((frontend_url
.find("?") == std::string::npos
) ? "?" : "&"));
765 if (shared_worker_frontend
)
766 url_string
+= "&isSharedWorker=true";
767 if (external_frontend
)
768 url_string
+= "&remoteFrontend=true";
770 url_string
+= "&can_dock=true";
772 url_string
+= "&settings=" + settings
;
773 return GURL(url_string
);
777 DevToolsWindow
* DevToolsWindow::FindDevToolsWindow(
778 DevToolsAgentHost
* agent_host
) {
779 if (!agent_host
|| g_instances
== NULL
)
781 DevToolsWindows
* instances
= g_instances
.Pointer();
782 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
784 if ((*it
)->bindings_
->IsAttachedTo(agent_host
))
791 DevToolsWindow
* DevToolsWindow::AsDevToolsWindow(
792 content::WebContents
* web_contents
) {
793 if (!web_contents
|| g_instances
== NULL
)
795 DevToolsWindows
* instances
= g_instances
.Pointer();
796 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
798 if ((*it
)->main_web_contents_
== web_contents
)
804 WebContents
* DevToolsWindow::OpenURLFromTab(
806 const content::OpenURLParams
& params
) {
807 DCHECK(source
== main_web_contents_
);
808 if (!params
.url
.SchemeIs(content::kChromeDevToolsScheme
)) {
809 WebContents
* inspected_web_contents
= GetInspectedWebContents();
810 return inspected_web_contents
?
811 inspected_web_contents
->OpenURL(params
) : NULL
;
814 bindings_
->Reattach();
816 content::NavigationController::LoadURLParams
load_url_params(params
.url
);
817 main_web_contents_
->GetController().LoadURLWithParams(load_url_params
);
818 return main_web_contents_
;
821 void DevToolsWindow::ActivateContents(WebContents
* contents
) {
823 WebContents
* inspected_tab
= GetInspectedWebContents();
824 inspected_tab
->GetDelegate()->ActivateContents(inspected_tab
);
826 browser_
->window()->Activate();
830 void DevToolsWindow::AddNewContents(WebContents
* source
,
831 WebContents
* new_contents
,
832 WindowOpenDisposition disposition
,
833 const gfx::Rect
& initial_pos
,
836 if (new_contents
== toolbox_web_contents_
) {
837 toolbox_web_contents_
->SetDelegate(
838 new DevToolsToolboxDelegate(toolbox_web_contents_
,
839 inspected_contents_observer_
.get()));
840 if (main_web_contents_
->GetRenderWidgetHostView() &&
841 toolbox_web_contents_
->GetRenderWidgetHostView()) {
843 main_web_contents_
->GetRenderWidgetHostView()->GetViewBounds().size();
844 toolbox_web_contents_
->GetRenderWidgetHostView()->SetSize(size
);
846 UpdateBrowserWindow();
850 WebContents
* inspected_web_contents
= GetInspectedWebContents();
851 if (inspected_web_contents
) {
852 inspected_web_contents
->GetDelegate()->AddNewContents(
853 source
, new_contents
, disposition
, initial_pos
, user_gesture
,
858 void DevToolsWindow::WebContentsCreated(WebContents
* source_contents
,
859 int opener_render_frame_id
,
860 const base::string16
& frame_name
,
861 const GURL
& target_url
,
862 WebContents
* new_contents
) {
863 if (target_url
.SchemeIs(content::kChromeDevToolsScheme
) &&
864 target_url
.query().find("toolbox=true") != std::string::npos
) {
866 toolbox_web_contents_
= new_contents
;
870 void DevToolsWindow::CloseContents(WebContents
* source
) {
872 life_stage_
= kClosing
;
873 UpdateBrowserWindow();
874 // In case of docked main_web_contents_, we own it so delete here.
875 // Embedding DevTools window will be deleted as a result of
876 // DevToolsUIBindings destruction.
877 delete main_web_contents_
;
880 void DevToolsWindow::ContentsZoomChange(bool zoom_in
) {
882 chrome_page_zoom::Zoom(main_web_contents_
,
883 zoom_in
? content::PAGE_ZOOM_IN
: content::PAGE_ZOOM_OUT
);
886 void DevToolsWindow::BeforeUnloadFired(WebContents
* tab
,
888 bool* proceed_to_fire_unload
) {
889 if (!intercepted_page_beforeunload_
) {
890 // Docked devtools window closed directly.
893 *proceed_to_fire_unload
= proceed
;
895 // Inspected page is attempting to close.
896 WebContents
* inspected_web_contents
= GetInspectedWebContents();
898 inspected_web_contents
->DispatchBeforeUnload(false);
901 inspected_web_contents
->GetDelegate()->BeforeUnloadFired(
902 inspected_web_contents
, false, &should_proceed
);
903 DCHECK(!should_proceed
);
905 *proceed_to_fire_unload
= false;
909 bool DevToolsWindow::PreHandleKeyboardEvent(
911 const content::NativeWebKeyboardEvent
& event
,
912 bool* is_keyboard_shortcut
) {
913 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
914 if (inspected_window
) {
915 return inspected_window
->PreHandleKeyboardEvent(event
,
916 is_keyboard_shortcut
);
921 void DevToolsWindow::HandleKeyboardEvent(
923 const content::NativeWebKeyboardEvent
& event
) {
924 if (event
.windowsKeyCode
== 0x08) {
925 // Do not navigate back in history on Windows (http://crbug.com/74156).
928 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
929 if (inspected_window
)
930 inspected_window
->HandleKeyboardEvent(event
);
933 content::JavaScriptDialogManager
* DevToolsWindow::GetJavaScriptDialogManager() {
934 WebContents
* inspected_web_contents
= GetInspectedWebContents();
935 return (inspected_web_contents
&& inspected_web_contents
->GetDelegate()) ?
936 inspected_web_contents
->GetDelegate()->GetJavaScriptDialogManager() :
937 content::WebContentsDelegate::GetJavaScriptDialogManager();
940 content::ColorChooser
* DevToolsWindow::OpenColorChooser(
941 WebContents
* web_contents
,
942 SkColor initial_color
,
943 const std::vector
<content::ColorSuggestion
>& suggestions
) {
944 return chrome::ShowColorChooser(web_contents
, initial_color
);
947 void DevToolsWindow::RunFileChooser(WebContents
* web_contents
,
948 const content::FileChooserParams
& params
) {
949 FileSelectHelper::RunFileChooser(web_contents
, params
);
952 void DevToolsWindow::WebContentsFocused(WebContents
* contents
) {
953 Browser
* inspected_browser
= NULL
;
954 int inspected_tab_index
= -1;
955 if (is_docked_
&& FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
957 &inspected_tab_index
))
958 inspected_browser
->window()->WebContentsFocused(contents
);
961 bool DevToolsWindow::PreHandleGestureEvent(
963 const blink::WebGestureEvent
& event
) {
964 // Disable pinch zooming.
965 return event
.type
== blink::WebGestureEvent::GesturePinchBegin
||
966 event
.type
== blink::WebGestureEvent::GesturePinchUpdate
||
967 event
.type
== blink::WebGestureEvent::GesturePinchEnd
;
970 void DevToolsWindow::ActivateWindow() {
971 if (is_docked_
&& GetInspectedBrowserWindow())
972 main_web_contents_
->Focus();
973 else if (!is_docked_
&& !browser_
->window()->IsActive())
974 browser_
->window()->Activate();
977 void DevToolsWindow::CloseWindow() {
979 life_stage_
= kClosing
;
980 main_web_contents_
->DispatchBeforeUnload(false);
983 void DevToolsWindow::SetInspectedPageBounds(const gfx::Rect
& rect
) {
984 DevToolsContentsResizingStrategy
strategy(rect
);
985 if (contents_resizing_strategy_
.Equals(strategy
))
988 contents_resizing_strategy_
.CopyFrom(strategy
);
989 UpdateBrowserWindow();
992 void DevToolsWindow::InspectElementCompleted() {
993 if (!inspect_element_start_time_
.is_null()) {
994 UMA_HISTOGRAM_TIMES("DevTools.InspectElement",
995 base::TimeTicks::Now() - inspect_element_start_time_
);
996 inspect_element_start_time_
= base::TimeTicks();
1000 void DevToolsWindow::MoveWindow(int x
, int y
) {
1002 gfx::Rect bounds
= browser_
->window()->GetBounds();
1003 bounds
.Offset(x
, y
);
1004 browser_
->window()->SetBounds(bounds
);
1008 void DevToolsWindow::SetIsDocked(bool dock_requested
) {
1009 if (life_stage_
== kClosing
)
1012 DCHECK(can_dock_
|| !dock_requested
);
1014 dock_requested
= false;
1016 bool was_docked
= is_docked_
;
1017 is_docked_
= dock_requested
;
1019 if (life_stage_
!= kLoadCompleted
) {
1020 // This is a first time call we waited for to initialize.
1021 life_stage_
= life_stage_
== kOnLoadFired
? kLoadCompleted
: kIsDockedSet
;
1022 if (life_stage_
== kLoadCompleted
)
1027 if (dock_requested
== was_docked
)
1030 if (dock_requested
&& !was_docked
) {
1031 // Detach window from the external devtools browser. It will lead to
1032 // the browser object's close and delete. Remove observer first.
1033 TabStripModel
* tab_strip_model
= browser_
->tab_strip_model();
1034 tab_strip_model
->DetachWebContentsAt(
1035 tab_strip_model
->GetIndexOfWebContents(main_web_contents_
));
1037 } else if (!dock_requested
&& was_docked
) {
1038 UpdateBrowserWindow();
1041 Show(DevToolsToggleAction::Show());
1044 void DevToolsWindow::OpenInNewTab(const std::string
& url
) {
1045 content::OpenURLParams
params(
1046 GURL(url
), content::Referrer(), NEW_FOREGROUND_TAB
,
1047 content::PAGE_TRANSITION_LINK
, false);
1048 WebContents
* inspected_web_contents
= GetInspectedWebContents();
1049 if (inspected_web_contents
) {
1050 inspected_web_contents
->OpenURL(params
);
1052 chrome::HostDesktopType host_desktop_type
;
1054 host_desktop_type
= browser_
->host_desktop_type();
1056 // There should always be a browser when there are no inspected web
1059 host_desktop_type
= chrome::GetActiveDesktop();
1062 const BrowserList
* browser_list
=
1063 BrowserList::GetInstance(host_desktop_type
);
1064 for (BrowserList::const_iterator it
= browser_list
->begin();
1065 it
!= browser_list
->end(); ++it
) {
1066 if ((*it
)->type() == Browser::TYPE_TABBED
) {
1067 (*it
)->OpenURL(params
);
1074 void DevToolsWindow::SetWhitelistedShortcuts(
1075 const std::string
& message
) {
1076 event_forwarder_
->SetWhitelistedShortcuts(message
);
1079 void DevToolsWindow::InspectedContentsClosing() {
1080 intercepted_page_beforeunload_
= false;
1081 life_stage_
= kClosing
;
1082 main_web_contents_
->GetRenderViewHost()->ClosePage();
1085 InfoBarService
* DevToolsWindow::GetInfoBarService() {
1087 InfoBarService::FromWebContents(GetInspectedWebContents()) :
1088 InfoBarService::FromWebContents(main_web_contents_
);
1091 void DevToolsWindow::RenderProcessGone() {
1092 // Docked DevToolsWindow owns its main_web_contents_ and must delete it.
1093 // Undocked main_web_contents_ are owned and handled by browser.
1094 // see crbug.com/369932
1096 CloseContents(main_web_contents_
);
1099 void DevToolsWindow::OnLoadCompleted() {
1100 // First seed inspected tab id for extension APIs.
1101 WebContents
* inspected_web_contents
= GetInspectedWebContents();
1102 if (inspected_web_contents
) {
1103 SessionTabHelper
* session_tab_helper
=
1104 SessionTabHelper::FromWebContents(inspected_web_contents
);
1105 if (session_tab_helper
) {
1106 base::FundamentalValue
tabId(session_tab_helper
->session_id().id());
1107 bindings_
->CallClientFunction("WebInspector.setInspectedTabId",
1108 &tabId
, NULL
, NULL
);
1112 if (life_stage_
== kClosing
)
1115 // We could be in kLoadCompleted state already if frontend reloads itself.
1116 if (life_stage_
!= kLoadCompleted
) {
1117 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
1118 // Here we set kOnLoadFired.
1119 life_stage_
= life_stage_
== kIsDockedSet
? kLoadCompleted
: kOnLoadFired
;
1121 if (life_stage_
== kLoadCompleted
)
1125 void DevToolsWindow::CreateDevToolsBrowser() {
1126 PrefService
* prefs
= profile_
->GetPrefs();
1127 if (!prefs
->GetDictionary(prefs::kAppWindowPlacement
)->HasKey(kDevToolsApp
)) {
1128 DictionaryPrefUpdate
update(prefs
, prefs::kAppWindowPlacement
);
1129 base::DictionaryValue
* wp_prefs
= update
.Get();
1130 base::DictionaryValue
* dev_tools_defaults
= new base::DictionaryValue
;
1131 wp_prefs
->Set(kDevToolsApp
, dev_tools_defaults
);
1132 dev_tools_defaults
->SetInteger("left", 100);
1133 dev_tools_defaults
->SetInteger("top", 100);
1134 dev_tools_defaults
->SetInteger("right", 740);
1135 dev_tools_defaults
->SetInteger("bottom", 740);
1136 dev_tools_defaults
->SetBoolean("maximized", false);
1137 dev_tools_defaults
->SetBoolean("always_on_top", false);
1140 browser_
= new Browser(Browser::CreateParams::CreateForDevTools(
1142 chrome::GetHostDesktopTypeForNativeView(
1143 main_web_contents_
->GetNativeView())));
1144 browser_
->tab_strip_model()->AddWebContents(
1145 main_web_contents_
, -1, content::PAGE_TRANSITION_AUTO_TOPLEVEL
,
1146 TabStripModel::ADD_ACTIVE
);
1147 main_web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
1150 BrowserWindow
* DevToolsWindow::GetInspectedBrowserWindow() {
1151 Browser
* browser
= NULL
;
1153 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1155 browser
->window() : NULL
;
1158 void DevToolsWindow::DoAction(const DevToolsToggleAction
& action
) {
1159 switch (action
.type()) {
1160 case DevToolsToggleAction::kShowConsole
:
1161 bindings_
->CallClientFunction(
1162 "InspectorFrontendAPI.showConsole", NULL
, NULL
, NULL
);
1165 case DevToolsToggleAction::kInspect
:
1166 bindings_
->CallClientFunction(
1167 "InspectorFrontendAPI.enterInspectElementMode", NULL
, NULL
, NULL
);
1170 case DevToolsToggleAction::kShow
:
1171 case DevToolsToggleAction::kToggle
:
1175 case DevToolsToggleAction::kReveal
: {
1176 const DevToolsToggleAction::RevealParams
* params
=
1179 base::StringValue
url_value(params
->url
);
1180 base::FundamentalValue
line_value(static_cast<int>(params
->line_number
));
1181 base::FundamentalValue
column_value(
1182 static_cast<int>(params
->column_number
));
1183 bindings_
->CallClientFunction("InspectorFrontendAPI.revealSourceLine",
1184 &url_value
, &line_value
, &column_value
);
1193 void DevToolsWindow::UpdateBrowserToolbar() {
1194 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
1195 if (inspected_window
)
1196 inspected_window
->UpdateToolbar(NULL
);
1199 void DevToolsWindow::UpdateBrowserWindow() {
1200 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
1201 if (inspected_window
)
1202 inspected_window
->UpdateDevTools();
1205 WebContents
* DevToolsWindow::GetInspectedWebContents() {
1206 return inspected_contents_observer_
1207 ? inspected_contents_observer_
->web_contents()
1211 void DevToolsWindow::LoadCompleted() {
1212 Show(action_on_load_
);
1213 action_on_load_
= DevToolsToggleAction::NoOp();
1214 if (!load_completed_callback_
.is_null()) {
1215 load_completed_callback_
.Run();
1216 load_completed_callback_
= base::Closure();
1220 void DevToolsWindow::SetLoadCompletedCallback(const base::Closure
& closure
) {
1221 if (life_stage_
== kLoadCompleted
|| life_stage_
== kClosing
) {
1222 if (!closure
.is_null())
1226 load_completed_callback_
= closure
;
1229 bool DevToolsWindow::ForwardKeyboardEvent(
1230 const content::NativeWebKeyboardEvent
& event
) {
1231 return event_forwarder_
->ForwardEvent(event
);