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/common/chrome_switches.h"
30 #include "chrome/common/pref_names.h"
31 #include "chrome/common/render_messages.h"
32 #include "chrome/common/url_constants.h"
33 #include "components/user_prefs/pref_registry_syncable.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/devtools_agent_host.h"
36 #include "content/public/browser/devtools_client_host.h"
37 #include "content/public/browser/devtools_manager.h"
38 #include "content/public/browser/native_web_keyboard_event.h"
39 #include "content/public/browser/navigation_controller.h"
40 #include "content/public/browser/navigation_entry.h"
41 #include "content/public/browser/notification_source.h"
42 #include "content/public/browser/render_frame_host.h"
43 #include "content/public/browser/render_process_host.h"
44 #include "content/public/browser/render_view_host.h"
45 #include "content/public/browser/user_metrics.h"
46 #include "content/public/browser/web_contents.h"
47 #include "content/public/browser/web_contents_observer.h"
48 #include "content/public/common/content_client.h"
49 #include "content/public/common/page_transition_types.h"
50 #include "content/public/common/url_constants.h"
51 #include "content/public/test/test_utils.h"
52 #include "third_party/WebKit/public/web/WebInputEvent.h"
53 #include "ui/events/keycodes/keyboard_codes.h"
55 using base::DictionaryValue
;
56 using blink::WebInputEvent
;
57 using content::BrowserThread
;
58 using content::DevToolsAgentHost
;
59 using content::WebContents
;
63 typedef std::vector
<DevToolsWindow
*> DevToolsWindows
;
64 base::LazyInstance
<DevToolsWindows
>::Leaky g_instances
=
65 LAZY_INSTANCE_INITIALIZER
;
67 static const char kKeyUpEventName
[] = "keyup";
68 static const char kKeyDownEventName
[] = "keydown";
72 // DevToolsEventForwarder -----------------------------------------------------
74 class DevToolsEventForwarder
{
76 explicit DevToolsEventForwarder(DevToolsWindow
* window
)
77 : devtools_window_(window
) {}
79 // Registers whitelisted shortcuts with the forwarder.
80 // Only registered keys will be forwarded to the DevTools frontend.
81 void SetWhitelistedShortcuts(const std::string
& message
);
83 // Forwards a keyboard event to the DevTools frontend if it is whitelisted.
84 // Returns |true| if the event has been forwarded, |false| otherwise.
85 bool ForwardEvent(const content::NativeWebKeyboardEvent
& event
);
88 static int VirtualKeyCodeWithoutLocation(int key_code
);
89 static bool KeyWhitelistingAllowed(int key_code
, int modifiers
);
90 static int CombineKeyCodeAndModifiers(int key_code
, int modifiers
);
92 DevToolsWindow
* devtools_window_
;
93 std::set
<int> whitelisted_keys_
;
95 DISALLOW_COPY_AND_ASSIGN(DevToolsEventForwarder
);
98 void DevToolsEventForwarder::SetWhitelistedShortcuts(
99 const std::string
& message
) {
100 scoped_ptr
<base::Value
> parsed_message(base::JSONReader::Read(message
));
101 base::ListValue
* shortcut_list
;
102 if (!parsed_message
->GetAsList(&shortcut_list
))
104 base::ListValue::iterator it
= shortcut_list
->begin();
105 for (; it
!= shortcut_list
->end(); ++it
) {
106 base::DictionaryValue
* dictionary
;
107 if (!(*it
)->GetAsDictionary(&dictionary
))
110 dictionary
->GetInteger("keyCode", &key_code
);
114 dictionary
->GetInteger("modifiers", &modifiers
);
115 if (!KeyWhitelistingAllowed(key_code
, modifiers
)) {
116 LOG(WARNING
) << "Key whitelisting forbidden: "
117 << "(" << key_code
<< "," << modifiers
<< ")";
120 whitelisted_keys_
.insert(CombineKeyCodeAndModifiers(key_code
, modifiers
));
124 bool DevToolsEventForwarder::ForwardEvent(
125 const content::NativeWebKeyboardEvent
& event
) {
126 std::string event_type
;
127 switch (event
.type
) {
128 case WebInputEvent::KeyDown
:
129 case WebInputEvent::RawKeyDown
:
130 event_type
= kKeyDownEventName
;
132 case WebInputEvent::KeyUp
:
133 event_type
= kKeyUpEventName
;
139 int key_code
= VirtualKeyCodeWithoutLocation(event
.windowsKeyCode
);
140 int key
= CombineKeyCodeAndModifiers(key_code
, event
.modifiers
);
141 if (whitelisted_keys_
.find(key
) == whitelisted_keys_
.end())
144 base::DictionaryValue event_data
;
145 event_data
.SetString("type", event_type
);
146 event_data
.SetString("keyIdentifier", event
.keyIdentifier
);
147 event_data
.SetInteger("keyCode", key_code
);
148 event_data
.SetInteger("modifiers", event
.modifiers
);
149 devtools_window_
->bindings_
->CallClientFunction(
150 "InspectorFrontendAPI.keyEventUnhandled", &event_data
, NULL
, NULL
);
154 int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code
,
156 return key_code
| (modifiers
<< 16);
159 bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code
,
161 return (ui::VKEY_F1
<= key_code
&& key_code
<= ui::VKEY_F12
) ||
165 // Mapping copied from Blink's KeyboardEvent.cpp.
166 int DevToolsEventForwarder::VirtualKeyCodeWithoutLocation(int key_code
)
169 case ui::VKEY_LCONTROL
:
170 case ui::VKEY_RCONTROL
:
171 return ui::VKEY_CONTROL
;
172 case ui::VKEY_LSHIFT
:
173 case ui::VKEY_RSHIFT
:
174 return ui::VKEY_SHIFT
;
177 return ui::VKEY_MENU
;
183 // DevToolsWindow::InspectedWebContentsObserver -------------------------------
185 class DevToolsWindow::InspectedWebContentsObserver
186 : public content::WebContentsObserver
{
188 explicit InspectedWebContentsObserver(WebContents
* web_contents
);
189 virtual ~InspectedWebContentsObserver();
191 WebContents
* web_contents() {
192 return WebContentsObserver::web_contents();
196 DISALLOW_COPY_AND_ASSIGN(InspectedWebContentsObserver
);
199 DevToolsWindow::InspectedWebContentsObserver::InspectedWebContentsObserver(
200 WebContents
* web_contents
)
201 : WebContentsObserver(web_contents
) {
204 DevToolsWindow::InspectedWebContentsObserver::~InspectedWebContentsObserver() {
207 // DevToolsWindow -------------------------------------------------------------
209 const char DevToolsWindow::kDevToolsApp
[] = "DevToolsApp";
211 DevToolsWindow::~DevToolsWindow() {
212 UpdateBrowserToolbar();
214 DevToolsWindows
* instances
= g_instances
.Pointer();
215 DevToolsWindows::iterator
it(
216 std::find(instances
->begin(), instances
->end(), this));
217 DCHECK(it
!= instances
->end());
218 instances
->erase(it
);
222 std::string
DevToolsWindow::GetDevToolsWindowPlacementPrefKey() {
223 return std::string(prefs::kBrowserWindowPlacement
) + "_" +
224 std::string(kDevToolsApp
);
228 void DevToolsWindow::RegisterProfilePrefs(
229 user_prefs::PrefRegistrySyncable
* registry
) {
230 registry
->RegisterDictionaryPref(
231 prefs::kDevToolsEditedFiles
,
232 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
233 registry
->RegisterDictionaryPref(
234 prefs::kDevToolsFileSystemPaths
,
235 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
236 registry
->RegisterStringPref(
237 prefs::kDevToolsAdbKey
, std::string(),
238 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
240 registry
->RegisterDictionaryPref(
241 GetDevToolsWindowPlacementPrefKey().c_str(),
242 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
244 registry
->RegisterBooleanPref(
245 prefs::kDevToolsDiscoverUsbDevicesEnabled
,
247 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
248 registry
->RegisterBooleanPref(
249 prefs::kDevToolsPortForwardingEnabled
,
251 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
252 registry
->RegisterBooleanPref(
253 prefs::kDevToolsPortForwardingDefaultSet
,
255 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
256 registry
->RegisterDictionaryPref(
257 prefs::kDevToolsPortForwardingConfig
,
258 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
262 DevToolsWindow
* DevToolsWindow::GetDockedInstanceForInspectedTab(
263 WebContents
* inspected_web_contents
) {
264 DevToolsWindow
* window
= GetInstanceForInspectedRenderViewHost(
265 inspected_web_contents
->GetRenderViewHost());
268 // Not yet loaded window is treated as docked, but we should not present it
269 // until we decided on docking.
270 bool is_docked_set
= window
->load_state_
== kLoadCompleted
||
271 window
->load_state_
== kIsDockedSet
;
272 return window
->is_docked_
&& is_docked_set
? window
: NULL
;
276 DevToolsWindow
* DevToolsWindow::GetInstanceForInspectedRenderViewHost(
277 content::RenderViewHost
* inspected_rvh
) {
278 if (!inspected_rvh
|| !DevToolsAgentHost::HasFor(inspected_rvh
))
281 scoped_refptr
<DevToolsAgentHost
> agent(DevToolsAgentHost::GetOrCreateFor(
283 return FindDevToolsWindow(agent
.get());
287 DevToolsWindow
* DevToolsWindow::GetInstanceForInspectedWebContents(
288 WebContents
* inspected_web_contents
) {
289 if (!inspected_web_contents
)
291 return GetInstanceForInspectedRenderViewHost(
292 inspected_web_contents
->GetRenderViewHost());
296 bool DevToolsWindow::IsDevToolsWindow(content::RenderViewHost
* window_rvh
) {
297 return AsDevToolsWindow(window_rvh
) != NULL
;
301 DevToolsWindow
* DevToolsWindow::OpenDevToolsWindowForWorker(
303 DevToolsAgentHost
* worker_agent
) {
304 DevToolsWindow
* window
= FindDevToolsWindow(worker_agent
);
306 window
= DevToolsWindow::CreateDevToolsWindowForWorker(profile
);
307 // Will disconnect the current client host if there is one.
308 content::DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor(
309 worker_agent
, window
->bindings_
->frontend_host());
311 window
->ScheduleShow(DevToolsToggleAction::Show());
316 DevToolsWindow
* DevToolsWindow::CreateDevToolsWindowForWorker(
318 content::RecordAction(base::UserMetricsAction("DevTools_InspectWorker"));
319 return Create(profile
, GURL(), NULL
, true, false, false);
323 DevToolsWindow
* DevToolsWindow::OpenDevToolsWindow(
324 content::RenderViewHost
* inspected_rvh
) {
325 return ToggleDevToolsWindow(
326 inspected_rvh
, true, DevToolsToggleAction::Show());
330 DevToolsWindow
* DevToolsWindow::OpenDevToolsWindow(
331 content::RenderViewHost
* inspected_rvh
,
332 const DevToolsToggleAction
& action
) {
333 return ToggleDevToolsWindow(
334 inspected_rvh
, true, action
);
338 DevToolsWindow
* DevToolsWindow::OpenDevToolsWindowForTest(
339 content::RenderViewHost
* inspected_rvh
,
341 DevToolsWindow
* window
= OpenDevToolsWindow(inspected_rvh
);
342 window
->SetIsDockedAndShowImmediatelyForTest(is_docked
);
347 DevToolsWindow
* DevToolsWindow::OpenDevToolsWindowForTest(
350 return OpenDevToolsWindowForTest(
351 browser
->tab_strip_model()->GetActiveWebContents()->GetRenderViewHost(),
356 DevToolsWindow
* DevToolsWindow::ToggleDevToolsWindow(
358 const DevToolsToggleAction
& action
) {
359 if (action
.type() == DevToolsToggleAction::kToggle
&&
360 browser
->is_devtools()) {
361 browser
->tab_strip_model()->CloseAllTabs();
365 return ToggleDevToolsWindow(
366 browser
->tab_strip_model()->GetActiveWebContents()->GetRenderViewHost(),
367 action
.type() == DevToolsToggleAction::kInspect
, action
);
371 void DevToolsWindow::OpenExternalFrontend(
373 const std::string
& frontend_url
,
374 content::DevToolsAgentHost
* agent_host
) {
375 DevToolsWindow
* window
= FindDevToolsWindow(agent_host
);
377 window
= Create(profile
, DevToolsUI::GetProxyURL(frontend_url
), NULL
,
379 content::DevToolsManager::GetInstance()->RegisterDevToolsClientHostFor(
380 agent_host
, window
->bindings_
->frontend_host());
382 window
->ScheduleShow(DevToolsToggleAction::Show());
386 DevToolsWindow
* DevToolsWindow::ToggleDevToolsWindow(
387 content::RenderViewHost
* inspected_rvh
,
389 const DevToolsToggleAction
& action
) {
390 scoped_refptr
<DevToolsAgentHost
> agent(
391 DevToolsAgentHost::GetOrCreateFor(inspected_rvh
));
392 content::DevToolsManager
* manager
= content::DevToolsManager::GetInstance();
393 DevToolsWindow
* window
= FindDevToolsWindow(agent
.get());
394 bool do_open
= force_open
;
396 Profile
* profile
= Profile::FromBrowserContext(
397 inspected_rvh
->GetProcess()->GetBrowserContext());
398 content::RecordAction(
399 base::UserMetricsAction("DevTools_InspectRenderer"));
400 window
= Create(profile
, GURL(), inspected_rvh
, false, false, true);
401 manager
->RegisterDevToolsClientHostFor(agent
.get(),
402 window
->bindings_
->frontend_host());
406 // Update toolbar to reflect DevTools changes.
407 window
->UpdateBrowserToolbar();
409 // If window is docked and visible, we hide it on toggle. If window is
410 // undocked, we show (activate) it.
411 if (!window
->is_docked_
|| do_open
)
412 window
->ScheduleShow(action
);
414 window
->CloseWindow();
420 void DevToolsWindow::InspectElement(content::RenderViewHost
* inspected_rvh
,
423 scoped_refptr
<DevToolsAgentHost
> agent(
424 DevToolsAgentHost::GetOrCreateFor(inspected_rvh
));
425 agent
->InspectElement(x
, y
);
426 bool should_measure_time
= FindDevToolsWindow(agent
.get()) == NULL
;
427 base::TimeTicks start_time
= base::TimeTicks::Now();
428 // TODO(loislo): we should initiate DevTools window opening from within
429 // renderer. Otherwise, we still can hit a race condition here.
430 DevToolsWindow
* window
= OpenDevToolsWindow(inspected_rvh
);
431 if (should_measure_time
)
432 window
->inspect_element_start_time_
= start_time
;
435 const DevToolsContentsResizingStrategy
&
436 DevToolsWindow::GetContentsResizingStrategy() const {
437 return contents_resizing_strategy_
;
440 gfx::Size
DevToolsWindow::GetMinimumSize() const {
441 const gfx::Size kMinDevToolsSize
= gfx::Size(230, 100);
442 return kMinDevToolsSize
;
445 void DevToolsWindow::ScheduleShow(const DevToolsToggleAction
& action
) {
446 if (load_state_
== kLoadCompleted
) {
451 // Action will be done only after load completed.
452 action_on_load_
= action
;
455 // No harm to show always-undocked window right away.
457 Show(DevToolsToggleAction::Show());
461 void DevToolsWindow::Show(const DevToolsToggleAction
& action
) {
462 if (action
.type() == DevToolsToggleAction::kNoOp
)
467 Browser
* inspected_browser
= NULL
;
468 int inspected_tab_index
= -1;
469 FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
471 &inspected_tab_index
);
472 DCHECK(inspected_browser
);
473 DCHECK(inspected_tab_index
!= -1);
475 // Tell inspected browser to update splitter and switch to inspected panel.
476 BrowserWindow
* inspected_window
= inspected_browser
->window();
477 web_contents_
->SetDelegate(this);
479 TabStripModel
* tab_strip_model
= inspected_browser
->tab_strip_model();
480 tab_strip_model
->ActivateTabAt(inspected_tab_index
, true);
482 inspected_window
->UpdateDevTools();
483 web_contents_
->SetInitialFocus();
484 inspected_window
->Show();
485 // On Aura, focusing once is not enough. Do it again.
486 // Note that focusing only here but not before isn't enough either. We just
487 // need to focus twice.
488 web_contents_
->SetInitialFocus();
490 PrefsTabHelper::CreateForWebContents(web_contents_
);
491 web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
497 // Avoid consecutive window switching if the devtools window has been opened
498 // and the Inspect Element shortcut is pressed in the inspected tab.
499 bool should_show_window
=
500 !browser_
|| (action
.type() != DevToolsToggleAction::kInspect
);
503 CreateDevToolsBrowser();
505 if (should_show_window
) {
506 browser_
->window()->Show();
507 web_contents_
->SetInitialFocus();
514 bool DevToolsWindow::HandleBeforeUnload(WebContents
* frontend_contents
,
515 bool proceed
, bool* proceed_to_fire_unload
) {
516 DevToolsWindow
* window
= AsDevToolsWindow(
517 frontend_contents
->GetRenderViewHost());
520 if (!window
->intercepted_page_beforeunload_
)
522 window
->BeforeUnloadFired(frontend_contents
, proceed
,
523 proceed_to_fire_unload
);
528 bool DevToolsWindow::InterceptPageBeforeUnload(WebContents
* contents
) {
529 DevToolsWindow
* window
=
530 DevToolsWindow::GetInstanceForInspectedRenderViewHost(
531 contents
->GetRenderViewHost());
532 if (!window
|| window
->intercepted_page_beforeunload_
)
535 // Not yet loaded frontend will not handle beforeunload.
536 if (window
->load_state_
!= kLoadCompleted
)
539 window
->intercepted_page_beforeunload_
= true;
540 // Handle case of devtools inspecting another devtools instance by passing
541 // the call up to the inspecting devtools instance.
542 if (!DevToolsWindow::InterceptPageBeforeUnload(window
->web_contents_
)) {
543 window
->web_contents_
->DispatchBeforeUnload(false);
549 bool DevToolsWindow::NeedsToInterceptBeforeUnload(
550 WebContents
* contents
) {
551 DevToolsWindow
* window
=
552 DevToolsWindow::GetInstanceForInspectedRenderViewHost(
553 contents
->GetRenderViewHost());
554 return window
&& !window
->intercepted_page_beforeunload_
;
558 bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(
560 DCHECK(browser
->is_devtools());
561 // When FastUnloadController is used, devtools frontend will be detached
562 // from the browser window at this point which means we've already fired
564 if (browser
->tab_strip_model()->empty())
566 WebContents
* contents
=
567 browser
->tab_strip_model()->GetWebContentsAt(0);
568 DevToolsWindow
* window
= AsDevToolsWindow(contents
->GetRenderViewHost());
571 return window
->intercepted_page_beforeunload_
;
575 void DevToolsWindow::OnPageCloseCanceled(WebContents
* contents
) {
576 DevToolsWindow
*window
=
577 DevToolsWindow::GetInstanceForInspectedRenderViewHost(
578 contents
->GetRenderViewHost());
581 window
->intercepted_page_beforeunload_
= false;
582 // Propagate to devtools opened on devtools if any.
583 DevToolsWindow::OnPageCloseCanceled(window
->web_contents_
);
586 DevToolsWindow::DevToolsWindow(Profile
* profile
,
588 content::RenderViewHost
* inspected_rvh
,
591 web_contents_(WebContents::Create(WebContents::CreateParams(profile
))),
596 // This initialization allows external front-end to work without changes.
597 // We don't wait for docking call, but instead immediately show undocked.
598 // Passing "dockSide=undocked" parameter ensures proper UI.
599 load_state_(can_dock
? kNotLoaded
: kIsDockedSet
),
600 action_on_load_(DevToolsToggleAction::NoOp()),
601 ignore_set_is_docked_(false),
602 intercepted_page_beforeunload_(false) {
603 // Set up delegate, so we get fully-functional window immediately.
604 // It will not appear in UI though until |load_state_ == kLoadCompleted|.
605 web_contents_
->SetDelegate(this);
606 web_contents_
->GetController().LoadURL(
607 DevToolsUIBindings::ApplyThemeToURL(profile
, url
), content::Referrer(),
608 content::PAGE_TRANSITION_AUTO_TOPLEVEL
, std::string());
610 // Lookup bindings and pass ownership over self into them.
611 bindings_
= DevToolsUIBindings::ForWebContents(web_contents_
);
612 bindings_
->SetDelegate(this);
614 g_instances
.Get().push_back(this);
616 // There is no inspected_rvh in case of shared workers.
618 inspected_contents_observer_
.reset(new InspectedWebContentsObserver(
619 content::WebContents::FromRenderViewHost(inspected_rvh
)));
620 event_forwarder_
.reset(new DevToolsEventForwarder(this));
624 DevToolsWindow
* DevToolsWindow::Create(
626 const GURL
& frontend_url
,
627 content::RenderViewHost
* inspected_rvh
,
628 bool shared_worker_frontend
,
629 bool external_frontend
,
632 // Check for a place to dock.
633 Browser
* browser
= NULL
;
635 WebContents
* inspected_web_contents
=
636 content::WebContents::FromRenderViewHost(inspected_rvh
);
637 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents
,
639 inspected_rvh
->GetMainFrame()->IsCrossProcessSubframe() ||
640 browser
->is_type_popup()) {
645 // Create WebContents with devtools.
646 GURL
url(GetDevToolsURL(profile
, frontend_url
,
647 shared_worker_frontend
,
650 return new DevToolsWindow(profile
, url
, inspected_rvh
, can_dock
);
654 GURL
DevToolsWindow::GetDevToolsURL(Profile
* profile
,
655 const GURL
& base_url
,
656 bool shared_worker_frontend
,
657 bool external_frontend
,
659 // Compatibility errors are encoded with data urls, pass them
660 // through with no decoration.
661 if (base_url
.SchemeIs("data"))
664 std::string
frontend_url(
665 base_url
.is_empty() ? chrome::kChromeUIDevToolsURL
: base_url
.spec());
666 std::string
url_string(
668 ((frontend_url
.find("?") == std::string::npos
) ? "?" : "&"));
669 if (shared_worker_frontend
)
670 url_string
+= "&isSharedWorker=true";
671 if (external_frontend
)
672 url_string
+= "&remoteFrontend=true";
674 url_string
+= "&can_dock=true";
675 return GURL(url_string
);
679 DevToolsWindow
* DevToolsWindow::FindDevToolsWindow(
680 DevToolsAgentHost
* agent_host
) {
681 DevToolsWindows
* instances
= g_instances
.Pointer();
682 content::DevToolsManager
* manager
= content::DevToolsManager::GetInstance();
683 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
685 if (manager
->GetDevToolsAgentHostFor((*it
)->bindings_
->frontend_host()) ==
693 DevToolsWindow
* DevToolsWindow::AsDevToolsWindow(
694 content::RenderViewHost
* window_rvh
) {
695 if (g_instances
== NULL
)
697 DevToolsWindows
* instances
= g_instances
.Pointer();
698 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
700 if ((*it
)->web_contents_
->GetRenderViewHost() == window_rvh
)
706 WebContents
* DevToolsWindow::OpenURLFromTab(
708 const content::OpenURLParams
& params
) {
709 DCHECK(source
== web_contents_
);
710 if (!params
.url
.SchemeIs(content::kChromeDevToolsScheme
)) {
711 WebContents
* inspected_web_contents
= GetInspectedWebContents();
712 return inspected_web_contents
?
713 inspected_web_contents
->OpenURL(params
) : NULL
;
716 content::DevToolsManager
* manager
= content::DevToolsManager::GetInstance();
717 scoped_refptr
<DevToolsAgentHost
> agent_host(
718 manager
->GetDevToolsAgentHostFor(bindings_
->frontend_host()));
719 if (!agent_host
.get())
721 manager
->ClientHostClosing(bindings_
->frontend_host());
722 manager
->RegisterDevToolsClientHostFor(agent_host
.get(),
723 bindings_
->frontend_host());
725 content::NavigationController::LoadURLParams
load_url_params(params
.url
);
726 web_contents_
->GetController().LoadURLWithParams(load_url_params
);
727 return web_contents_
;
730 void DevToolsWindow::ActivateContents(WebContents
* contents
) {
732 WebContents
* inspected_tab
= GetInspectedWebContents();
733 inspected_tab
->GetDelegate()->ActivateContents(inspected_tab
);
735 browser_
->window()->Activate();
739 void DevToolsWindow::AddNewContents(WebContents
* source
,
740 WebContents
* new_contents
,
741 WindowOpenDisposition disposition
,
742 const gfx::Rect
& initial_pos
,
745 WebContents
* inspected_web_contents
= GetInspectedWebContents();
746 if (inspected_web_contents
) {
747 inspected_web_contents
->GetDelegate()->AddNewContents(
748 source
, new_contents
, disposition
, initial_pos
, user_gesture
,
753 void DevToolsWindow::CloseContents(WebContents
* source
) {
755 // This will prevent any activity after frontend is loaded.
756 action_on_load_
= DevToolsToggleAction::NoOp();
757 ignore_set_is_docked_
= true;
758 // Update dev tools to reflect removed dev tools window.
759 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
760 if (inspected_window
)
761 inspected_window
->UpdateDevTools();
762 // In case of docked web_contents_, we own it so delete here.
763 // Embedding DevTools window will be deleted as a result of
764 // WebContentsDestroyed callback.
765 delete web_contents_
;
768 void DevToolsWindow::ContentsZoomChange(bool zoom_in
) {
770 chrome_page_zoom::Zoom(web_contents_
,
771 zoom_in
? content::PAGE_ZOOM_IN
: content::PAGE_ZOOM_OUT
);
774 void DevToolsWindow::BeforeUnloadFired(WebContents
* tab
,
776 bool* proceed_to_fire_unload
) {
777 if (!intercepted_page_beforeunload_
) {
778 // Docked devtools window closed directly.
780 content::DevToolsManager::GetInstance()->ClientHostClosing(
781 bindings_
->frontend_host());
783 *proceed_to_fire_unload
= proceed
;
785 // Inspected page is attempting to close.
786 WebContents
* inspected_web_contents
= GetInspectedWebContents();
788 inspected_web_contents
->DispatchBeforeUnload(false);
791 inspected_web_contents
->GetDelegate()->BeforeUnloadFired(
792 inspected_web_contents
, false, &should_proceed
);
793 DCHECK(!should_proceed
);
795 *proceed_to_fire_unload
= false;
799 bool DevToolsWindow::PreHandleKeyboardEvent(
801 const content::NativeWebKeyboardEvent
& event
,
802 bool* is_keyboard_shortcut
) {
804 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
805 if (inspected_window
) {
806 return inspected_window
->PreHandleKeyboardEvent(event
,
807 is_keyboard_shortcut
);
813 void DevToolsWindow::HandleKeyboardEvent(
815 const content::NativeWebKeyboardEvent
& event
) {
817 if (event
.windowsKeyCode
== 0x08) {
818 // Do not navigate back in history on Windows (http://crbug.com/74156).
821 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
822 if (inspected_window
)
823 inspected_window
->HandleKeyboardEvent(event
);
827 content::JavaScriptDialogManager
* DevToolsWindow::GetJavaScriptDialogManager() {
828 WebContents
* inspected_web_contents
= GetInspectedWebContents();
829 return (inspected_web_contents
&& inspected_web_contents
->GetDelegate()) ?
830 inspected_web_contents
->GetDelegate()->GetJavaScriptDialogManager() :
831 content::WebContentsDelegate::GetJavaScriptDialogManager();
834 content::ColorChooser
* DevToolsWindow::OpenColorChooser(
835 WebContents
* web_contents
,
836 SkColor initial_color
,
837 const std::vector
<content::ColorSuggestion
>& suggestions
) {
838 return chrome::ShowColorChooser(web_contents
, initial_color
);
841 void DevToolsWindow::RunFileChooser(WebContents
* web_contents
,
842 const content::FileChooserParams
& params
) {
843 FileSelectHelper::RunFileChooser(web_contents
, params
);
846 void DevToolsWindow::WebContentsFocused(WebContents
* contents
) {
847 Browser
* inspected_browser
= NULL
;
848 int inspected_tab_index
= -1;
849 if (is_docked_
&& FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
851 &inspected_tab_index
))
852 inspected_browser
->window()->WebContentsFocused(contents
);
855 bool DevToolsWindow::PreHandleGestureEvent(
857 const blink::WebGestureEvent
& event
) {
858 // Disable pinch zooming.
859 return event
.type
== blink::WebGestureEvent::GesturePinchBegin
||
860 event
.type
== blink::WebGestureEvent::GesturePinchUpdate
||
861 event
.type
== blink::WebGestureEvent::GesturePinchEnd
;
864 void DevToolsWindow::ActivateWindow() {
865 if (is_docked_
&& GetInspectedBrowserWindow())
866 web_contents_
->Focus();
867 else if (!is_docked_
&& !browser_
->window()->IsActive())
868 browser_
->window()->Activate();
871 void DevToolsWindow::CloseWindow() {
873 // This will prevent any activity after frontend is loaded.
874 action_on_load_
= DevToolsToggleAction::NoOp();
875 ignore_set_is_docked_
= true;
876 web_contents_
->DispatchBeforeUnload(false);
879 void DevToolsWindow::SetContentsInsets(
880 int top
, int left
, int bottom
, int right
) {
881 gfx::Insets
insets(top
, left
, bottom
, right
);
882 SetContentsResizingStrategy(insets
, contents_resizing_strategy_
.min_size());
885 void DevToolsWindow::SetContentsResizingStrategy(
886 const gfx::Insets
& insets
, const gfx::Size
& min_size
) {
887 DevToolsContentsResizingStrategy
strategy(insets
, min_size
);
888 if (contents_resizing_strategy_
.Equals(strategy
))
891 contents_resizing_strategy_
.CopyFrom(strategy
);
893 // Update inspected window.
894 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
895 if (inspected_window
)
896 inspected_window
->UpdateDevTools();
900 void DevToolsWindow::InspectElementCompleted() {
901 if (!inspect_element_start_time_
.is_null()) {
902 UMA_HISTOGRAM_TIMES("DevTools.InspectElement",
903 base::TimeTicks::Now() - inspect_element_start_time_
);
904 inspect_element_start_time_
= base::TimeTicks();
908 void DevToolsWindow::MoveWindow(int x
, int y
) {
910 gfx::Rect bounds
= browser_
->window()->GetBounds();
912 browser_
->window()->SetBounds(bounds
);
916 void DevToolsWindow::SetIsDockedAndShowImmediatelyForTest(bool is_docked
) {
917 DCHECK(!is_docked
|| can_dock_
);
918 if (load_state_
== kLoadCompleted
) {
919 SetIsDocked(is_docked
);
921 is_docked_
= is_docked
;
922 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
923 // Note that kIsDockedSet may be already set when can_dock_ is false.
924 load_state_
= load_state_
== kOnLoadFired
? kLoadCompleted
: kIsDockedSet
;
925 // Note that action_on_load_ will be performed after the load is actually
926 // completed. For now, just show the window.
927 Show(DevToolsToggleAction::Show());
928 if (load_state_
== kLoadCompleted
)
931 ignore_set_is_docked_
= true;
934 void DevToolsWindow::SetIsDocked(bool dock_requested
) {
935 if (ignore_set_is_docked_
)
938 DCHECK(can_dock_
|| !dock_requested
);
940 dock_requested
= false;
942 bool was_docked
= is_docked_
;
943 is_docked_
= dock_requested
;
945 if (load_state_
!= kLoadCompleted
) {
946 // This is a first time call we waited for to initialize.
947 load_state_
= load_state_
== kOnLoadFired
? kLoadCompleted
: kIsDockedSet
;
948 if (load_state_
== kLoadCompleted
)
953 if (dock_requested
== was_docked
)
956 if (dock_requested
&& !was_docked
) {
957 // Detach window from the external devtools browser. It will lead to
958 // the browser object's close and delete. Remove observer first.
959 TabStripModel
* tab_strip_model
= browser_
->tab_strip_model();
960 tab_strip_model
->DetachWebContentsAt(
961 tab_strip_model
->GetIndexOfWebContents(web_contents_
));
963 } else if (!dock_requested
&& was_docked
) {
964 // Update inspected window to hide split and reset it.
965 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
966 if (inspected_window
)
967 inspected_window
->UpdateDevTools();
970 Show(DevToolsToggleAction::Show());
973 void DevToolsWindow::OpenInNewTab(const std::string
& url
) {
974 content::OpenURLParams
params(
975 GURL(url
), content::Referrer(), NEW_FOREGROUND_TAB
,
976 content::PAGE_TRANSITION_LINK
, false);
977 WebContents
* inspected_web_contents
= GetInspectedWebContents();
978 if (inspected_web_contents
) {
979 inspected_web_contents
->OpenURL(params
);
981 chrome::HostDesktopType host_desktop_type
;
983 host_desktop_type
= browser_
->host_desktop_type();
985 // There should always be a browser when there are no inspected web
988 host_desktop_type
= chrome::GetActiveDesktop();
991 const BrowserList
* browser_list
=
992 BrowserList::GetInstance(host_desktop_type
);
993 for (BrowserList::const_iterator it
= browser_list
->begin();
994 it
!= browser_list
->end(); ++it
) {
995 if ((*it
)->type() == Browser::TYPE_TABBED
) {
996 (*it
)->OpenURL(params
);
1003 void DevToolsWindow::SetWhitelistedShortcuts(
1004 const std::string
& message
) {
1005 event_forwarder_
->SetWhitelistedShortcuts(message
);
1008 void DevToolsWindow::InspectedContentsClosing() {
1009 intercepted_page_beforeunload_
= false;
1010 // This will prevent any activity after frontend is loaded.
1011 action_on_load_
= DevToolsToggleAction::NoOp();
1012 ignore_set_is_docked_
= true;
1013 web_contents_
->GetRenderViewHost()->ClosePage();
1016 InfoBarService
* DevToolsWindow::GetInfoBarService() {
1018 InfoBarService::FromWebContents(GetInspectedWebContents()) :
1019 InfoBarService::FromWebContents(web_contents_
);
1022 void DevToolsWindow::OnLoadCompleted() {
1023 // First seed inspected tab id for extension APIs.
1024 WebContents
* inspected_web_contents
= GetInspectedWebContents();
1025 if (inspected_web_contents
) {
1026 SessionTabHelper
* session_tab_helper
=
1027 SessionTabHelper::FromWebContents(inspected_web_contents
);
1028 if (session_tab_helper
) {
1029 base::FundamentalValue
tabId(session_tab_helper
->session_id().id());
1030 bindings_
->CallClientFunction("WebInspector.setInspectedTabId",
1031 &tabId
, NULL
, NULL
);
1035 // We could be in kLoadCompleted state already if frontend reloads itself.
1036 if (load_state_
!= kLoadCompleted
) {
1037 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
1038 // Here we set kOnLoadFired.
1039 load_state_
= load_state_
== kIsDockedSet
? kLoadCompleted
: kOnLoadFired
;
1041 if (load_state_
== kLoadCompleted
)
1045 void DevToolsWindow::CreateDevToolsBrowser() {
1046 std::string wp_key
= GetDevToolsWindowPlacementPrefKey();
1047 PrefService
* prefs
= profile_
->GetPrefs();
1048 const base::DictionaryValue
* wp_pref
= prefs
->GetDictionary(wp_key
.c_str());
1049 if (!wp_pref
|| wp_pref
->empty()) {
1050 DictionaryPrefUpdate
update(prefs
, wp_key
.c_str());
1051 base::DictionaryValue
* defaults
= update
.Get();
1052 defaults
->SetInteger("left", 100);
1053 defaults
->SetInteger("top", 100);
1054 defaults
->SetInteger("right", 740);
1055 defaults
->SetInteger("bottom", 740);
1056 defaults
->SetBoolean("maximized", false);
1057 defaults
->SetBoolean("always_on_top", false);
1060 browser_
= new Browser(Browser::CreateParams::CreateForDevTools(
1062 chrome::GetHostDesktopTypeForNativeView(
1063 web_contents_
->GetNativeView())));
1064 browser_
->tab_strip_model()->AddWebContents(
1065 web_contents_
, -1, content::PAGE_TRANSITION_AUTO_TOPLEVEL
,
1066 TabStripModel::ADD_ACTIVE
);
1067 web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
1071 bool DevToolsWindow::FindInspectedBrowserAndTabIndex(
1072 WebContents
* inspected_web_contents
, Browser
** browser
, int* tab
) {
1073 if (!inspected_web_contents
)
1076 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
1077 int tab_index
= it
->tab_strip_model()->GetIndexOfWebContents(
1078 inspected_web_contents
);
1079 if (tab_index
!= TabStripModel::kNoTab
) {
1088 BrowserWindow
* DevToolsWindow::GetInspectedBrowserWindow() {
1089 Browser
* browser
= NULL
;
1091 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1093 browser
->window() : NULL
;
1096 void DevToolsWindow::DoAction(const DevToolsToggleAction
& action
) {
1097 switch (action
.type()) {
1098 case DevToolsToggleAction::kShowConsole
:
1099 bindings_
->CallClientFunction(
1100 "InspectorFrontendAPI.showConsole", NULL
, NULL
, NULL
);
1103 case DevToolsToggleAction::kInspect
:
1104 bindings_
->CallClientFunction(
1105 "InspectorFrontendAPI.enterInspectElementMode", NULL
, NULL
, NULL
);
1108 case DevToolsToggleAction::kShow
:
1109 case DevToolsToggleAction::kToggle
:
1113 case DevToolsToggleAction::kReveal
: {
1114 const DevToolsToggleAction::RevealParams
* params
=
1117 base::StringValue
url_value(params
->url
);
1118 base::FundamentalValue
line_value(static_cast<int>(params
->line_number
));
1119 base::FundamentalValue
column_value(
1120 static_cast<int>(params
->column_number
));
1121 bindings_
->CallClientFunction("InspectorFrontendAPI.revealSourceLine",
1122 &url_value
, &line_value
, &column_value
);
1131 void DevToolsWindow::UpdateBrowserToolbar() {
1132 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
1133 if (inspected_window
)
1134 inspected_window
->UpdateToolbar(NULL
);
1137 WebContents
* DevToolsWindow::GetInspectedWebContents() {
1138 return inspected_contents_observer_
?
1139 inspected_contents_observer_
->web_contents() : NULL
;
1142 void DevToolsWindow::LoadCompleted() {
1143 Show(action_on_load_
);
1144 action_on_load_
= DevToolsToggleAction::NoOp();
1145 if (!load_completed_callback_
.is_null()) {
1146 load_completed_callback_
.Run();
1147 load_completed_callback_
= base::Closure();
1151 void DevToolsWindow::SetLoadCompletedCallback(const base::Closure
& closure
) {
1152 if (load_state_
== kLoadCompleted
) {
1153 if (!closure
.is_null())
1157 load_completed_callback_
= closure
;
1160 bool DevToolsWindow::ForwardKeyboardEvent(
1161 const content::NativeWebKeyboardEvent
& event
) {
1162 return event_forwarder_
->ForwardEvent(event
);