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/pref_registry/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::GetOrCreateFor(web_contents_
);
612 // Bindings take ownership over devtools as its delegate.
613 bindings_
->SetDelegate(this);
615 g_instances
.Get().push_back(this);
617 // There is no inspected_rvh in case of shared workers.
619 inspected_contents_observer_
.reset(new InspectedWebContentsObserver(
620 content::WebContents::FromRenderViewHost(inspected_rvh
)));
621 event_forwarder_
.reset(new DevToolsEventForwarder(this));
625 DevToolsWindow
* DevToolsWindow::Create(
627 const GURL
& frontend_url
,
628 content::RenderViewHost
* inspected_rvh
,
629 bool shared_worker_frontend
,
630 bool external_frontend
,
633 // Check for a place to dock.
634 Browser
* browser
= NULL
;
636 WebContents
* inspected_web_contents
=
637 content::WebContents::FromRenderViewHost(inspected_rvh
);
638 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents
,
640 inspected_rvh
->GetMainFrame()->IsCrossProcessSubframe() ||
641 browser
->is_type_popup()) {
646 // Create WebContents with devtools.
647 GURL
url(GetDevToolsURL(profile
, frontend_url
,
648 shared_worker_frontend
,
651 return new DevToolsWindow(profile
, url
, inspected_rvh
, can_dock
);
655 GURL
DevToolsWindow::GetDevToolsURL(Profile
* profile
,
656 const GURL
& base_url
,
657 bool shared_worker_frontend
,
658 bool external_frontend
,
660 // Compatibility errors are encoded with data urls, pass them
661 // through with no decoration.
662 if (base_url
.SchemeIs("data"))
665 std::string
frontend_url(
666 base_url
.is_empty() ? chrome::kChromeUIDevToolsURL
: base_url
.spec());
667 std::string
url_string(
669 ((frontend_url
.find("?") == std::string::npos
) ? "?" : "&"));
670 if (shared_worker_frontend
)
671 url_string
+= "&isSharedWorker=true";
672 if (external_frontend
)
673 url_string
+= "&remoteFrontend=true";
675 url_string
+= "&can_dock=true";
676 return GURL(url_string
);
680 DevToolsWindow
* DevToolsWindow::FindDevToolsWindow(
681 DevToolsAgentHost
* agent_host
) {
682 DevToolsWindows
* instances
= g_instances
.Pointer();
683 content::DevToolsManager
* manager
= content::DevToolsManager::GetInstance();
684 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
686 if (manager
->GetDevToolsAgentHostFor((*it
)->bindings_
->frontend_host()) ==
694 DevToolsWindow
* DevToolsWindow::AsDevToolsWindow(
695 content::RenderViewHost
* window_rvh
) {
696 if (g_instances
== NULL
)
698 DevToolsWindows
* instances
= g_instances
.Pointer();
699 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
701 if ((*it
)->web_contents_
->GetRenderViewHost() == window_rvh
)
707 WebContents
* DevToolsWindow::OpenURLFromTab(
709 const content::OpenURLParams
& params
) {
710 DCHECK(source
== web_contents_
);
711 if (!params
.url
.SchemeIs(content::kChromeDevToolsScheme
)) {
712 WebContents
* inspected_web_contents
= GetInspectedWebContents();
713 return inspected_web_contents
?
714 inspected_web_contents
->OpenURL(params
) : NULL
;
717 content::DevToolsManager
* manager
= content::DevToolsManager::GetInstance();
718 scoped_refptr
<DevToolsAgentHost
> agent_host(
719 manager
->GetDevToolsAgentHostFor(bindings_
->frontend_host()));
720 if (!agent_host
.get())
722 manager
->ClientHostClosing(bindings_
->frontend_host());
723 manager
->RegisterDevToolsClientHostFor(agent_host
.get(),
724 bindings_
->frontend_host());
726 content::NavigationController::LoadURLParams
load_url_params(params
.url
);
727 web_contents_
->GetController().LoadURLWithParams(load_url_params
);
728 return web_contents_
;
731 void DevToolsWindow::ActivateContents(WebContents
* contents
) {
733 WebContents
* inspected_tab
= GetInspectedWebContents();
734 inspected_tab
->GetDelegate()->ActivateContents(inspected_tab
);
736 browser_
->window()->Activate();
740 void DevToolsWindow::AddNewContents(WebContents
* source
,
741 WebContents
* new_contents
,
742 WindowOpenDisposition disposition
,
743 const gfx::Rect
& initial_pos
,
746 WebContents
* inspected_web_contents
= GetInspectedWebContents();
747 if (inspected_web_contents
) {
748 inspected_web_contents
->GetDelegate()->AddNewContents(
749 source
, new_contents
, disposition
, initial_pos
, user_gesture
,
754 void DevToolsWindow::CloseContents(WebContents
* source
) {
756 // Do this first so that when GetDockedInstanceForInspectedTab is called
757 // from UpdateDevTools it won't return this instance
758 // see crbug.com/372504
759 content::DevToolsManager::GetInstance()->ClientHostClosing(
760 bindings_
->frontend_host());
761 // This will prevent any activity after frontend is loaded.
762 action_on_load_
= DevToolsToggleAction::NoOp();
763 ignore_set_is_docked_
= true;
764 // Update dev tools to reflect removed dev tools window.
765 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
766 if (inspected_window
)
767 inspected_window
->UpdateDevTools();
768 // In case of docked web_contents_, we own it so delete here.
769 // Embedding DevTools window will be deleted as a result of
770 // DevToolsUIBindings destruction.
771 delete web_contents_
;
774 void DevToolsWindow::ContentsZoomChange(bool zoom_in
) {
776 chrome_page_zoom::Zoom(web_contents_
,
777 zoom_in
? content::PAGE_ZOOM_IN
: content::PAGE_ZOOM_OUT
);
780 void DevToolsWindow::BeforeUnloadFired(WebContents
* tab
,
782 bool* proceed_to_fire_unload
) {
783 if (!intercepted_page_beforeunload_
) {
784 // Docked devtools window closed directly.
786 content::DevToolsManager::GetInstance()->ClientHostClosing(
787 bindings_
->frontend_host());
789 *proceed_to_fire_unload
= proceed
;
791 // Inspected page is attempting to close.
792 WebContents
* inspected_web_contents
= GetInspectedWebContents();
794 inspected_web_contents
->DispatchBeforeUnload(false);
797 inspected_web_contents
->GetDelegate()->BeforeUnloadFired(
798 inspected_web_contents
, false, &should_proceed
);
799 DCHECK(!should_proceed
);
801 *proceed_to_fire_unload
= false;
805 bool DevToolsWindow::PreHandleKeyboardEvent(
807 const content::NativeWebKeyboardEvent
& event
,
808 bool* is_keyboard_shortcut
) {
810 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
811 if (inspected_window
) {
812 return inspected_window
->PreHandleKeyboardEvent(event
,
813 is_keyboard_shortcut
);
819 void DevToolsWindow::HandleKeyboardEvent(
821 const content::NativeWebKeyboardEvent
& event
) {
823 if (event
.windowsKeyCode
== 0x08) {
824 // Do not navigate back in history on Windows (http://crbug.com/74156).
827 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
828 if (inspected_window
)
829 inspected_window
->HandleKeyboardEvent(event
);
833 content::JavaScriptDialogManager
* DevToolsWindow::GetJavaScriptDialogManager() {
834 WebContents
* inspected_web_contents
= GetInspectedWebContents();
835 return (inspected_web_contents
&& inspected_web_contents
->GetDelegate()) ?
836 inspected_web_contents
->GetDelegate()->GetJavaScriptDialogManager() :
837 content::WebContentsDelegate::GetJavaScriptDialogManager();
840 content::ColorChooser
* DevToolsWindow::OpenColorChooser(
841 WebContents
* web_contents
,
842 SkColor initial_color
,
843 const std::vector
<content::ColorSuggestion
>& suggestions
) {
844 return chrome::ShowColorChooser(web_contents
, initial_color
);
847 void DevToolsWindow::RunFileChooser(WebContents
* web_contents
,
848 const content::FileChooserParams
& params
) {
849 FileSelectHelper::RunFileChooser(web_contents
, params
);
852 void DevToolsWindow::WebContentsFocused(WebContents
* contents
) {
853 Browser
* inspected_browser
= NULL
;
854 int inspected_tab_index
= -1;
855 if (is_docked_
&& FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
857 &inspected_tab_index
))
858 inspected_browser
->window()->WebContentsFocused(contents
);
861 bool DevToolsWindow::PreHandleGestureEvent(
863 const blink::WebGestureEvent
& event
) {
864 // Disable pinch zooming.
865 return event
.type
== blink::WebGestureEvent::GesturePinchBegin
||
866 event
.type
== blink::WebGestureEvent::GesturePinchUpdate
||
867 event
.type
== blink::WebGestureEvent::GesturePinchEnd
;
870 void DevToolsWindow::ActivateWindow() {
871 if (is_docked_
&& GetInspectedBrowserWindow())
872 web_contents_
->Focus();
873 else if (!is_docked_
&& !browser_
->window()->IsActive())
874 browser_
->window()->Activate();
877 void DevToolsWindow::CloseWindow() {
879 // This will prevent any activity after frontend is loaded.
880 action_on_load_
= DevToolsToggleAction::NoOp();
881 ignore_set_is_docked_
= true;
882 web_contents_
->DispatchBeforeUnload(false);
885 void DevToolsWindow::SetInspectedPageBounds(const gfx::Rect
& rect
) {
886 DevToolsContentsResizingStrategy
strategy(rect
);
887 if (contents_resizing_strategy_
.Equals(strategy
))
890 contents_resizing_strategy_
.CopyFrom(strategy
);
892 // Update inspected window.
893 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
894 if (inspected_window
)
895 inspected_window
->UpdateDevTools();
899 void DevToolsWindow::SetContentsResizingStrategy(
900 const gfx::Insets
& insets
, const gfx::Size
& min_size
) {
901 DevToolsContentsResizingStrategy
strategy(insets
, min_size
);
902 if (contents_resizing_strategy_
.Equals(strategy
))
905 contents_resizing_strategy_
.CopyFrom(strategy
);
907 // Update inspected window.
908 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
909 if (inspected_window
)
910 inspected_window
->UpdateDevTools();
914 void DevToolsWindow::InspectElementCompleted() {
915 if (!inspect_element_start_time_
.is_null()) {
916 UMA_HISTOGRAM_TIMES("DevTools.InspectElement",
917 base::TimeTicks::Now() - inspect_element_start_time_
);
918 inspect_element_start_time_
= base::TimeTicks();
922 void DevToolsWindow::MoveWindow(int x
, int y
) {
924 gfx::Rect bounds
= browser_
->window()->GetBounds();
926 browser_
->window()->SetBounds(bounds
);
930 void DevToolsWindow::SetIsDockedAndShowImmediatelyForTest(bool is_docked
) {
931 DCHECK(!is_docked
|| can_dock_
);
932 if (load_state_
== kLoadCompleted
) {
933 SetIsDocked(is_docked
);
935 is_docked_
= is_docked
;
936 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
937 // Note that kIsDockedSet may be already set when can_dock_ is false.
938 load_state_
= load_state_
== kOnLoadFired
? kLoadCompleted
: kIsDockedSet
;
939 // Note that action_on_load_ will be performed after the load is actually
940 // completed. For now, just show the window.
941 Show(DevToolsToggleAction::Show());
942 if (load_state_
== kLoadCompleted
)
945 ignore_set_is_docked_
= true;
948 void DevToolsWindow::SetIsDocked(bool dock_requested
) {
949 if (ignore_set_is_docked_
)
952 DCHECK(can_dock_
|| !dock_requested
);
954 dock_requested
= false;
956 bool was_docked
= is_docked_
;
957 is_docked_
= dock_requested
;
959 if (load_state_
!= kLoadCompleted
) {
960 // This is a first time call we waited for to initialize.
961 load_state_
= load_state_
== kOnLoadFired
? kLoadCompleted
: kIsDockedSet
;
962 if (load_state_
== kLoadCompleted
)
967 if (dock_requested
== was_docked
)
970 if (dock_requested
&& !was_docked
) {
971 // Detach window from the external devtools browser. It will lead to
972 // the browser object's close and delete. Remove observer first.
973 TabStripModel
* tab_strip_model
= browser_
->tab_strip_model();
974 tab_strip_model
->DetachWebContentsAt(
975 tab_strip_model
->GetIndexOfWebContents(web_contents_
));
977 } else if (!dock_requested
&& was_docked
) {
978 // Update inspected window to hide split and reset it.
979 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
980 if (inspected_window
)
981 inspected_window
->UpdateDevTools();
984 Show(DevToolsToggleAction::Show());
987 void DevToolsWindow::OpenInNewTab(const std::string
& url
) {
988 content::OpenURLParams
params(
989 GURL(url
), content::Referrer(), NEW_FOREGROUND_TAB
,
990 content::PAGE_TRANSITION_LINK
, false);
991 WebContents
* inspected_web_contents
= GetInspectedWebContents();
992 if (inspected_web_contents
) {
993 inspected_web_contents
->OpenURL(params
);
995 chrome::HostDesktopType host_desktop_type
;
997 host_desktop_type
= browser_
->host_desktop_type();
999 // There should always be a browser when there are no inspected web
1002 host_desktop_type
= chrome::GetActiveDesktop();
1005 const BrowserList
* browser_list
=
1006 BrowserList::GetInstance(host_desktop_type
);
1007 for (BrowserList::const_iterator it
= browser_list
->begin();
1008 it
!= browser_list
->end(); ++it
) {
1009 if ((*it
)->type() == Browser::TYPE_TABBED
) {
1010 (*it
)->OpenURL(params
);
1017 void DevToolsWindow::SetWhitelistedShortcuts(
1018 const std::string
& message
) {
1019 event_forwarder_
->SetWhitelistedShortcuts(message
);
1022 void DevToolsWindow::InspectedContentsClosing() {
1023 intercepted_page_beforeunload_
= false;
1024 // This will prevent any activity after frontend is loaded.
1025 action_on_load_
= DevToolsToggleAction::NoOp();
1026 ignore_set_is_docked_
= true;
1027 web_contents_
->GetRenderViewHost()->ClosePage();
1030 InfoBarService
* DevToolsWindow::GetInfoBarService() {
1032 InfoBarService::FromWebContents(GetInspectedWebContents()) :
1033 InfoBarService::FromWebContents(web_contents_
);
1036 void DevToolsWindow::RenderProcessGone() {
1037 // Docked DevToolsWindow owns its web_contents_ and must delete it.
1038 // Undocked web_contents_ are owned and handled by browser.
1039 // see crbug.com/369932
1041 CloseContents(web_contents_
);
1044 void DevToolsWindow::OnLoadCompleted() {
1045 // First seed inspected tab id for extension APIs.
1046 WebContents
* inspected_web_contents
= GetInspectedWebContents();
1047 if (inspected_web_contents
) {
1048 SessionTabHelper
* session_tab_helper
=
1049 SessionTabHelper::FromWebContents(inspected_web_contents
);
1050 if (session_tab_helper
) {
1051 base::FundamentalValue
tabId(session_tab_helper
->session_id().id());
1052 bindings_
->CallClientFunction("WebInspector.setInspectedTabId",
1053 &tabId
, NULL
, NULL
);
1057 // We could be in kLoadCompleted state already if frontend reloads itself.
1058 if (load_state_
!= kLoadCompleted
) {
1059 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
1060 // Here we set kOnLoadFired.
1061 load_state_
= load_state_
== kIsDockedSet
? kLoadCompleted
: kOnLoadFired
;
1063 if (load_state_
== kLoadCompleted
)
1067 void DevToolsWindow::CreateDevToolsBrowser() {
1068 std::string wp_key
= GetDevToolsWindowPlacementPrefKey();
1069 PrefService
* prefs
= profile_
->GetPrefs();
1070 const base::DictionaryValue
* wp_pref
= prefs
->GetDictionary(wp_key
.c_str());
1071 if (!wp_pref
|| wp_pref
->empty()) {
1072 DictionaryPrefUpdate
update(prefs
, wp_key
.c_str());
1073 base::DictionaryValue
* defaults
= update
.Get();
1074 defaults
->SetInteger("left", 100);
1075 defaults
->SetInteger("top", 100);
1076 defaults
->SetInteger("right", 740);
1077 defaults
->SetInteger("bottom", 740);
1078 defaults
->SetBoolean("maximized", false);
1079 defaults
->SetBoolean("always_on_top", false);
1082 browser_
= new Browser(Browser::CreateParams::CreateForDevTools(
1084 chrome::GetHostDesktopTypeForNativeView(
1085 web_contents_
->GetNativeView())));
1086 browser_
->tab_strip_model()->AddWebContents(
1087 web_contents_
, -1, content::PAGE_TRANSITION_AUTO_TOPLEVEL
,
1088 TabStripModel::ADD_ACTIVE
);
1089 web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
1093 bool DevToolsWindow::FindInspectedBrowserAndTabIndex(
1094 WebContents
* inspected_web_contents
, Browser
** browser
, int* tab
) {
1095 if (!inspected_web_contents
)
1098 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
1099 int tab_index
= it
->tab_strip_model()->GetIndexOfWebContents(
1100 inspected_web_contents
);
1101 if (tab_index
!= TabStripModel::kNoTab
) {
1110 BrowserWindow
* DevToolsWindow::GetInspectedBrowserWindow() {
1111 Browser
* browser
= NULL
;
1113 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1115 browser
->window() : NULL
;
1118 void DevToolsWindow::DoAction(const DevToolsToggleAction
& action
) {
1119 switch (action
.type()) {
1120 case DevToolsToggleAction::kShowConsole
:
1121 bindings_
->CallClientFunction(
1122 "InspectorFrontendAPI.showConsole", NULL
, NULL
, NULL
);
1125 case DevToolsToggleAction::kInspect
:
1126 bindings_
->CallClientFunction(
1127 "InspectorFrontendAPI.enterInspectElementMode", NULL
, NULL
, NULL
);
1130 case DevToolsToggleAction::kShow
:
1131 case DevToolsToggleAction::kToggle
:
1135 case DevToolsToggleAction::kReveal
: {
1136 const DevToolsToggleAction::RevealParams
* params
=
1139 base::StringValue
url_value(params
->url
);
1140 base::FundamentalValue
line_value(static_cast<int>(params
->line_number
));
1141 base::FundamentalValue
column_value(
1142 static_cast<int>(params
->column_number
));
1143 bindings_
->CallClientFunction("InspectorFrontendAPI.revealSourceLine",
1144 &url_value
, &line_value
, &column_value
);
1153 void DevToolsWindow::UpdateBrowserToolbar() {
1154 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
1155 if (inspected_window
)
1156 inspected_window
->UpdateToolbar(NULL
);
1159 WebContents
* DevToolsWindow::GetInspectedWebContents() {
1160 return inspected_contents_observer_
?
1161 inspected_contents_observer_
->web_contents() : NULL
;
1164 void DevToolsWindow::LoadCompleted() {
1165 Show(action_on_load_
);
1166 action_on_load_
= DevToolsToggleAction::NoOp();
1167 if (!load_completed_callback_
.is_null()) {
1168 load_completed_callback_
.Run();
1169 load_completed_callback_
= base::Closure();
1173 void DevToolsWindow::SetLoadCompletedCallback(const base::Closure
& closure
) {
1174 if (load_state_
== kLoadCompleted
) {
1175 if (!closure
.is_null())
1179 load_completed_callback_
= closure
;
1182 bool DevToolsWindow::ForwardKeyboardEvent(
1183 const content::NativeWebKeyboardEvent
& event
) {
1184 return event_forwarder_
->ForwardEvent(event
);