1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/devtools/devtools_window.h"
9 #include "base/command_line.h"
10 #include "base/json/json_reader.h"
11 #include "base/metrics/histogram.h"
12 #include "base/prefs/scoped_user_pref_update.h"
13 #include "base/time/time.h"
14 #include "base/values.h"
15 #include "chrome/browser/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/task_management/web_contents_tags.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/browser_dialogs.h"
23 #include "chrome/browser/ui/browser_iterator.h"
24 #include "chrome/browser/ui/browser_list.h"
25 #include "chrome/browser/ui/browser_tabstrip.h"
26 #include "chrome/browser/ui/browser_window.h"
27 #include "chrome/browser/ui/host_desktop.h"
28 #include "chrome/browser/ui/prefs/prefs_tab_helper.h"
29 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
30 #include "chrome/browser/ui/tabs/tab_strip_model.h"
31 #include "chrome/browser/ui/webui/devtools_ui.h"
32 #include "chrome/common/chrome_switches.h"
33 #include "chrome/common/pref_names.h"
34 #include "chrome/common/url_constants.h"
35 #include "components/pref_registry/pref_registry_syncable.h"
36 #include "components/ui/zoom/page_zoom.h"
37 #include "components/ui/zoom/zoom_controller.h"
38 #include "content/public/browser/browser_thread.h"
39 #include "content/public/browser/devtools_agent_host.h"
40 #include "content/public/browser/native_web_keyboard_event.h"
41 #include "content/public/browser/navigation_controller.h"
42 #include "content/public/browser/navigation_entry.h"
43 #include "content/public/browser/render_frame_host.h"
44 #include "content/public/browser/render_process_host.h"
45 #include "content/public/browser/render_view_host.h"
46 #include "content/public/browser/render_widget_host_view.h"
47 #include "content/public/browser/user_metrics.h"
48 #include "content/public/browser/web_contents.h"
49 #include "content/public/common/content_client.h"
50 #include "content/public/common/url_constants.h"
51 #include "net/base/escape.h"
52 #include "third_party/WebKit/public/web/WebInputEvent.h"
53 #include "ui/base/page_transition_types.h"
54 #include "ui/events/keycodes/keyboard_code_conversion.h"
55 #include "ui/events/keycodes/keyboard_codes.h"
57 using base::DictionaryValue
;
58 using blink::WebInputEvent
;
59 using content::BrowserThread
;
60 using content::DevToolsAgentHost
;
61 using content::WebContents
;
65 typedef std::vector
<DevToolsWindow
*> DevToolsWindows
;
66 base::LazyInstance
<DevToolsWindows
>::Leaky g_instances
=
67 LAZY_INSTANCE_INITIALIZER
;
69 static const char kKeyUpEventName
[] = "keyup";
70 static const char kKeyDownEventName
[] = "keydown";
72 bool FindInspectedBrowserAndTabIndex(
73 WebContents
* inspected_web_contents
, Browser
** browser
, int* tab
) {
74 if (!inspected_web_contents
)
77 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
78 int tab_index
= it
->tab_strip_model()->GetIndexOfWebContents(
79 inspected_web_contents
);
80 if (tab_index
!= TabStripModel::kNoTab
) {
89 // DevToolsToolboxDelegate ----------------------------------------------------
91 class DevToolsToolboxDelegate
92 : public content::WebContentsObserver
,
93 public content::WebContentsDelegate
{
95 DevToolsToolboxDelegate(
96 WebContents
* toolbox_contents
,
97 DevToolsWindow::ObserverWithAccessor
* web_contents_observer
);
98 ~DevToolsToolboxDelegate() override
;
100 content::WebContents
* OpenURLFromTab(
101 content::WebContents
* source
,
102 const content::OpenURLParams
& params
) override
;
103 bool PreHandleKeyboardEvent(content::WebContents
* source
,
104 const content::NativeWebKeyboardEvent
& event
,
105 bool* is_keyboard_shortcut
) override
;
106 void HandleKeyboardEvent(
107 content::WebContents
* source
,
108 const content::NativeWebKeyboardEvent
& event
) override
;
109 void WebContentsDestroyed() override
;
112 BrowserWindow
* GetInspectedBrowserWindow();
113 DevToolsWindow::ObserverWithAccessor
* inspected_contents_observer_
;
114 DISALLOW_COPY_AND_ASSIGN(DevToolsToolboxDelegate
);
117 DevToolsToolboxDelegate::DevToolsToolboxDelegate(
118 WebContents
* toolbox_contents
,
119 DevToolsWindow::ObserverWithAccessor
* web_contents_observer
)
120 : WebContentsObserver(toolbox_contents
),
121 inspected_contents_observer_(web_contents_observer
) {
124 DevToolsToolboxDelegate::~DevToolsToolboxDelegate() {
127 content::WebContents
* DevToolsToolboxDelegate::OpenURLFromTab(
128 content::WebContents
* source
,
129 const content::OpenURLParams
& params
) {
130 DCHECK(source
== web_contents());
131 if (!params
.url
.SchemeIs(content::kChromeDevToolsScheme
))
133 content::NavigationController::LoadURLParams
load_url_params(params
.url
);
134 source
->GetController().LoadURLWithParams(load_url_params
);
138 bool DevToolsToolboxDelegate::PreHandleKeyboardEvent(
139 content::WebContents
* source
,
140 const content::NativeWebKeyboardEvent
& event
,
141 bool* is_keyboard_shortcut
) {
142 BrowserWindow
* window
= GetInspectedBrowserWindow();
144 return window
->PreHandleKeyboardEvent(event
, is_keyboard_shortcut
);
148 void DevToolsToolboxDelegate::HandleKeyboardEvent(
149 content::WebContents
* source
,
150 const content::NativeWebKeyboardEvent
& event
) {
151 if (event
.windowsKeyCode
== 0x08) {
152 // Do not navigate back in history on Windows (http://crbug.com/74156).
155 BrowserWindow
* window
= GetInspectedBrowserWindow();
157 window
->HandleKeyboardEvent(event
);
160 void DevToolsToolboxDelegate::WebContentsDestroyed() {
164 BrowserWindow
* DevToolsToolboxDelegate::GetInspectedBrowserWindow() {
165 WebContents
* inspected_contents
=
166 inspected_contents_observer_
->web_contents();
167 if (!inspected_contents
)
169 Browser
* browser
= NULL
;
171 if (FindInspectedBrowserAndTabIndex(inspected_contents
, &browser
, &tab
))
172 return browser
->window();
177 GURL
DecorateFrontendURL(const GURL
& base_url
) {
178 std::string frontend_url
= base_url
.spec();
179 std::string
url_string(
181 ((frontend_url
.find("?") == std::string::npos
) ? "?" : "&") +
182 "dockSide=undocked"); // TODO(dgozman): remove this support in M38.
183 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
184 switches::kEnableDevToolsExperiments
))
185 url_string
+= "&experiments=true";
186 #if defined(DEBUG_DEVTOOLS)
187 url_string
+= "&debugFrontend=true";
188 #endif // defined(DEBUG_DEVTOOLS)
189 return GURL(url_string
);
194 // DevToolsEventForwarder -----------------------------------------------------
196 class DevToolsEventForwarder
{
198 explicit DevToolsEventForwarder(DevToolsWindow
* window
)
199 : devtools_window_(window
) {}
201 // Registers whitelisted shortcuts with the forwarder.
202 // Only registered keys will be forwarded to the DevTools frontend.
203 void SetWhitelistedShortcuts(const std::string
& message
);
205 // Forwards a keyboard event to the DevTools frontend if it is whitelisted.
206 // Returns |true| if the event has been forwarded, |false| otherwise.
207 bool ForwardEvent(const content::NativeWebKeyboardEvent
& event
);
210 static bool KeyWhitelistingAllowed(int key_code
, int modifiers
);
211 static int CombineKeyCodeAndModifiers(int key_code
, int modifiers
);
213 DevToolsWindow
* devtools_window_
;
214 std::set
<int> whitelisted_keys_
;
216 DISALLOW_COPY_AND_ASSIGN(DevToolsEventForwarder
);
219 void DevToolsEventForwarder::SetWhitelistedShortcuts(
220 const std::string
& message
) {
221 scoped_ptr
<base::Value
> parsed_message
= base::JSONReader::Read(message
);
222 base::ListValue
* shortcut_list
;
223 if (!parsed_message
->GetAsList(&shortcut_list
))
225 base::ListValue::iterator it
= shortcut_list
->begin();
226 for (; it
!= shortcut_list
->end(); ++it
) {
227 base::DictionaryValue
* dictionary
;
228 if (!(*it
)->GetAsDictionary(&dictionary
))
231 dictionary
->GetInteger("keyCode", &key_code
);
235 dictionary
->GetInteger("modifiers", &modifiers
);
236 if (!KeyWhitelistingAllowed(key_code
, modifiers
)) {
237 LOG(WARNING
) << "Key whitelisting forbidden: "
238 << "(" << key_code
<< "," << modifiers
<< ")";
241 whitelisted_keys_
.insert(CombineKeyCodeAndModifiers(key_code
, modifiers
));
245 bool DevToolsEventForwarder::ForwardEvent(
246 const content::NativeWebKeyboardEvent
& event
) {
247 std::string event_type
;
248 switch (event
.type
) {
249 case WebInputEvent::KeyDown
:
250 case WebInputEvent::RawKeyDown
:
251 event_type
= kKeyDownEventName
;
253 case WebInputEvent::KeyUp
:
254 event_type
= kKeyUpEventName
;
260 int key_code
= ui::LocatedToNonLocatedKeyboardCode(
261 static_cast<ui::KeyboardCode
>(event
.windowsKeyCode
));
262 int key
= CombineKeyCodeAndModifiers(key_code
, event
.modifiers
);
263 if (whitelisted_keys_
.find(key
) == whitelisted_keys_
.end())
266 base::DictionaryValue event_data
;
267 event_data
.SetString("type", event_type
);
268 event_data
.SetString("keyIdentifier", event
.keyIdentifier
);
269 event_data
.SetInteger("keyCode", key_code
);
270 event_data
.SetInteger("modifiers", event
.modifiers
);
271 devtools_window_
->bindings_
->CallClientFunction(
272 "DevToolsAPI.keyEventUnhandled", &event_data
, NULL
, NULL
);
276 int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code
,
278 return key_code
| (modifiers
<< 16);
281 bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code
,
283 return (ui::VKEY_F1
<= key_code
&& key_code
<= ui::VKEY_F12
) ||
287 // DevToolsWindow::ObserverWithAccessor -------------------------------
289 DevToolsWindow::ObserverWithAccessor::ObserverWithAccessor(
290 WebContents
* web_contents
)
291 : WebContentsObserver(web_contents
) {
294 DevToolsWindow::ObserverWithAccessor::~ObserverWithAccessor() {
297 // DevToolsWindow -------------------------------------------------------------
299 const char DevToolsWindow::kDevToolsApp
[] = "DevToolsApp";
301 DevToolsWindow::~DevToolsWindow() {
302 life_stage_
= kClosing
;
304 UpdateBrowserWindow();
305 UpdateBrowserToolbar();
307 if (toolbox_web_contents_
)
308 delete toolbox_web_contents_
;
310 DevToolsWindows
* instances
= g_instances
.Pointer();
311 DevToolsWindows::iterator
it(
312 std::find(instances
->begin(), instances
->end(), this));
313 DCHECK(it
!= instances
->end());
314 instances
->erase(it
);
316 if (!close_callback_
.is_null()) {
317 close_callback_
.Run();
318 close_callback_
= base::Closure();
323 void DevToolsWindow::RegisterProfilePrefs(
324 user_prefs::PrefRegistrySyncable
* registry
) {
325 registry
->RegisterDictionaryPref(prefs::kDevToolsEditedFiles
);
326 registry
->RegisterDictionaryPref(prefs::kDevToolsFileSystemPaths
);
327 registry
->RegisterStringPref(prefs::kDevToolsAdbKey
, std::string());
329 registry
->RegisterBooleanPref(prefs::kDevToolsDiscoverUsbDevicesEnabled
,
331 registry
->RegisterBooleanPref(prefs::kDevToolsPortForwardingEnabled
, false);
332 registry
->RegisterBooleanPref(prefs::kDevToolsPortForwardingDefaultSet
,
334 registry
->RegisterDictionaryPref(prefs::kDevToolsPortForwardingConfig
);
335 registry
->RegisterDictionaryPref(prefs::kDevToolsPreferences
);
339 content::WebContents
* DevToolsWindow::GetInTabWebContents(
340 WebContents
* inspected_web_contents
,
341 DevToolsContentsResizingStrategy
* out_strategy
) {
342 DevToolsWindow
* window
= GetInstanceForInspectedWebContents(
343 inspected_web_contents
);
344 if (!window
|| window
->life_stage_
== kClosing
)
347 // Not yet loaded window is treated as docked, but we should not present it
348 // until we decided on docking.
349 bool is_docked_set
= window
->life_stage_
== kLoadCompleted
||
350 window
->life_stage_
== kIsDockedSet
;
354 // Undocked window should have toolbox web contents.
355 if (!window
->is_docked_
&& !window
->toolbox_web_contents_
)
359 out_strategy
->CopyFrom(window
->contents_resizing_strategy_
);
361 return window
->is_docked_
? window
->main_web_contents_
:
362 window
->toolbox_web_contents_
;
366 DevToolsWindow
* DevToolsWindow::GetInstanceForInspectedWebContents(
367 WebContents
* inspected_web_contents
) {
368 if (!inspected_web_contents
|| g_instances
== NULL
)
370 DevToolsWindows
* instances
= g_instances
.Pointer();
371 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
373 if ((*it
)->GetInspectedWebContents() == inspected_web_contents
)
380 bool DevToolsWindow::IsDevToolsWindow(content::WebContents
* web_contents
) {
381 if (!web_contents
|| g_instances
== NULL
)
383 DevToolsWindows
* instances
= g_instances
.Pointer();
384 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
386 if ((*it
)->main_web_contents_
== web_contents
||
387 (*it
)->toolbox_web_contents_
== web_contents
)
394 void DevToolsWindow::OpenDevToolsWindowForWorker(
396 const scoped_refptr
<DevToolsAgentHost
>& worker_agent
) {
397 DevToolsWindow
* window
= FindDevToolsWindow(worker_agent
.get());
399 window
= DevToolsWindow::CreateDevToolsWindowForWorker(profile
);
402 window
->bindings_
->AttachTo(worker_agent
);
404 window
->ScheduleShow(DevToolsToggleAction::Show());
408 DevToolsWindow
* DevToolsWindow::CreateDevToolsWindowForWorker(
410 content::RecordAction(base::UserMetricsAction("DevTools_InspectWorker"));
411 return Create(profile
, GURL(), NULL
, true, std::string(), false, "");
415 void DevToolsWindow::OpenDevToolsWindow(
416 content::WebContents
* inspected_web_contents
) {
417 ToggleDevToolsWindow(
418 inspected_web_contents
, true, DevToolsToggleAction::Show(), "");
422 void DevToolsWindow::OpenDevToolsWindow(
423 content::WebContents
* inspected_web_contents
,
424 const DevToolsToggleAction
& action
) {
425 ToggleDevToolsWindow(inspected_web_contents
, true, action
, "");
429 void DevToolsWindow::OpenDevToolsWindow(
431 const scoped_refptr
<content::DevToolsAgentHost
>& agent_host
) {
432 DevToolsWindow
* window
= FindDevToolsWindow(agent_host
.get());
434 window
= DevToolsWindow::Create(
435 profile
, GURL(), nullptr, false, std::string(), false, std::string());
438 window
->bindings_
->AttachTo(agent_host
);
440 window
->ScheduleShow(DevToolsToggleAction::Show());
444 void DevToolsWindow::ToggleDevToolsWindow(
446 const DevToolsToggleAction
& action
) {
447 if (action
.type() == DevToolsToggleAction::kToggle
&&
448 browser
->is_devtools()) {
449 browser
->tab_strip_model()->CloseAllTabs();
453 ToggleDevToolsWindow(
454 browser
->tab_strip_model()->GetActiveWebContents(),
455 action
.type() == DevToolsToggleAction::kInspect
,
460 void DevToolsWindow::OpenExternalFrontend(
462 const std::string
& frontend_url
,
463 const scoped_refptr
<content::DevToolsAgentHost
>& agent_host
,
465 DevToolsWindow
* window
= FindDevToolsWindow(agent_host
.get());
467 window
= Create(profile
, GURL(), nullptr, isWorker
,
468 DevToolsUI::GetProxyURL(frontend_url
).spec(), false, std::string());
471 window
->bindings_
->AttachTo(agent_host
);
474 window
->ScheduleShow(DevToolsToggleAction::Show());
478 void DevToolsWindow::ToggleDevToolsWindow(
479 content::WebContents
* inspected_web_contents
,
481 const DevToolsToggleAction
& action
,
482 const std::string
& settings
) {
483 scoped_refptr
<DevToolsAgentHost
> agent(
484 DevToolsAgentHost::GetOrCreateFor(inspected_web_contents
));
485 DevToolsWindow
* window
= FindDevToolsWindow(agent
.get());
486 bool do_open
= force_open
;
488 Profile
* profile
= Profile::FromBrowserContext(
489 inspected_web_contents
->GetBrowserContext());
490 content::RecordAction(
491 base::UserMetricsAction("DevTools_InspectRenderer"));
492 window
= Create(profile
, GURL(), inspected_web_contents
,
493 false, std::string(), true, settings
);
496 window
->bindings_
->AttachTo(agent
.get());
500 // Update toolbar to reflect DevTools changes.
501 window
->UpdateBrowserToolbar();
503 // If window is docked and visible, we hide it on toggle. If window is
504 // undocked, we show (activate) it.
505 if (!window
->is_docked_
|| do_open
)
506 window
->ScheduleShow(action
);
508 window
->CloseWindow();
512 void DevToolsWindow::InspectElement(
513 content::WebContents
* inspected_web_contents
,
516 scoped_refptr
<DevToolsAgentHost
> agent(
517 DevToolsAgentHost::GetOrCreateFor(inspected_web_contents
));
518 agent
->InspectElement(x
, y
);
519 bool should_measure_time
= FindDevToolsWindow(agent
.get()) == NULL
;
520 base::TimeTicks start_time
= base::TimeTicks::Now();
521 // TODO(loislo): we should initiate DevTools window opening from within
522 // renderer. Otherwise, we still can hit a race condition here.
523 OpenDevToolsWindow(inspected_web_contents
);
525 DevToolsWindow
* window
= FindDevToolsWindow(agent
.get());
526 if (should_measure_time
&& window
)
527 window
->inspect_element_start_time_
= start_time
;
530 void DevToolsWindow::ScheduleShow(const DevToolsToggleAction
& action
) {
531 if (life_stage_
== kLoadCompleted
) {
536 // Action will be done only after load completed.
537 action_on_load_
= action
;
540 // No harm to show always-undocked window right away.
542 Show(DevToolsToggleAction::Show());
546 void DevToolsWindow::Show(const DevToolsToggleAction
& action
) {
547 if (life_stage_
== kClosing
)
550 if (action
.type() == DevToolsToggleAction::kNoOp
)
555 Browser
* inspected_browser
= NULL
;
556 int inspected_tab_index
= -1;
557 FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
559 &inspected_tab_index
);
560 DCHECK(inspected_browser
);
561 DCHECK(inspected_tab_index
!= -1);
563 // Tell inspected browser to update splitter and switch to inspected panel.
564 BrowserWindow
* inspected_window
= inspected_browser
->window();
565 main_web_contents_
->SetDelegate(this);
567 TabStripModel
* tab_strip_model
= inspected_browser
->tab_strip_model();
568 tab_strip_model
->ActivateTabAt(inspected_tab_index
, true);
570 inspected_window
->UpdateDevTools();
571 main_web_contents_
->SetInitialFocus();
572 inspected_window
->Show();
573 // On Aura, focusing once is not enough. Do it again.
574 // Note that focusing only here but not before isn't enough either. We just
575 // need to focus twice.
576 main_web_contents_
->SetInitialFocus();
578 PrefsTabHelper::CreateForWebContents(main_web_contents_
);
579 main_web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
585 // Avoid consecutive window switching if the devtools window has been opened
586 // and the Inspect Element shortcut is pressed in the inspected tab.
587 bool should_show_window
=
588 !browser_
|| (action
.type() != DevToolsToggleAction::kInspect
);
591 CreateDevToolsBrowser();
593 if (should_show_window
) {
594 browser_
->window()->Show();
595 main_web_contents_
->SetInitialFocus();
597 if (toolbox_web_contents_
)
598 UpdateBrowserWindow();
604 bool DevToolsWindow::HandleBeforeUnload(WebContents
* frontend_contents
,
605 bool proceed
, bool* proceed_to_fire_unload
) {
606 DevToolsWindow
* window
= AsDevToolsWindow(frontend_contents
);
609 if (!window
->intercepted_page_beforeunload_
)
611 window
->BeforeUnloadFired(frontend_contents
, proceed
,
612 proceed_to_fire_unload
);
617 bool DevToolsWindow::InterceptPageBeforeUnload(WebContents
* contents
) {
618 DevToolsWindow
* window
=
619 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
620 if (!window
|| window
->intercepted_page_beforeunload_
)
623 // Not yet loaded frontend will not handle beforeunload.
624 if (window
->life_stage_
!= kLoadCompleted
)
627 window
->intercepted_page_beforeunload_
= true;
628 // Handle case of devtools inspecting another devtools instance by passing
629 // the call up to the inspecting devtools instance.
630 if (!DevToolsWindow::InterceptPageBeforeUnload(window
->main_web_contents_
)) {
631 window
->main_web_contents_
->DispatchBeforeUnload(false);
637 bool DevToolsWindow::NeedsToInterceptBeforeUnload(
638 WebContents
* contents
) {
639 DevToolsWindow
* window
=
640 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
641 return window
&& !window
->intercepted_page_beforeunload_
;
645 bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(
647 DCHECK(browser
->is_devtools());
648 // When FastUnloadController is used, devtools frontend will be detached
649 // from the browser window at this point which means we've already fired
651 if (browser
->tab_strip_model()->empty())
653 WebContents
* contents
=
654 browser
->tab_strip_model()->GetWebContentsAt(0);
655 DevToolsWindow
* window
= AsDevToolsWindow(contents
);
658 return window
->intercepted_page_beforeunload_
;
662 void DevToolsWindow::OnPageCloseCanceled(WebContents
* contents
) {
663 DevToolsWindow
* window
=
664 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
667 window
->intercepted_page_beforeunload_
= false;
668 // Propagate to devtools opened on devtools if any.
669 DevToolsWindow::OnPageCloseCanceled(window
->main_web_contents_
);
672 DevToolsWindow::DevToolsWindow(Profile
* profile
,
673 WebContents
* main_web_contents
,
674 DevToolsUIBindings
* bindings
,
675 WebContents
* inspected_web_contents
,
678 main_web_contents_(main_web_contents
),
679 toolbox_web_contents_(nullptr),
684 // This initialization allows external front-end to work without changes.
685 // We don't wait for docking call, but instead immediately show undocked.
686 // Passing "dockSide=undocked" parameter ensures proper UI.
687 life_stage_(can_dock
? kNotLoaded
: kIsDockedSet
),
688 action_on_load_(DevToolsToggleAction::NoOp()),
689 intercepted_page_beforeunload_(false) {
690 // Set up delegate, so we get fully-functional window immediately.
691 // It will not appear in UI though until |life_stage_ == kLoadCompleted|.
692 main_web_contents_
->SetDelegate(this);
693 // Bindings take ownership over devtools as its delegate.
694 bindings_
->SetDelegate(this);
695 // DevTools uses PageZoom::Zoom(), so main_web_contents_ requires a
697 ui_zoom::ZoomController::CreateForWebContents(main_web_contents_
);
698 ui_zoom::ZoomController::FromWebContents(main_web_contents_
)
699 ->SetShowsNotificationBubble(false);
701 g_instances
.Get().push_back(this);
703 // There is no inspected_web_contents in case of various workers.
704 if (inspected_web_contents
)
705 inspected_contents_observer_
.reset(
706 new ObserverWithAccessor(inspected_web_contents
));
708 // Initialize docked page to be of the right size.
709 if (can_dock_
&& inspected_web_contents
) {
710 content::RenderWidgetHostView
* inspected_view
=
711 inspected_web_contents
->GetRenderWidgetHostView();
712 if (inspected_view
&& main_web_contents_
->GetRenderWidgetHostView()) {
713 gfx::Size size
= inspected_view
->GetViewBounds().size();
714 main_web_contents_
->GetRenderWidgetHostView()->SetSize(size
);
718 event_forwarder_
.reset(new DevToolsEventForwarder(this));
720 // Tag the DevTools main WebContents with its TaskManager specific UserData
721 // so that it shows up in the task manager.
722 task_management::WebContentsTags::CreateForDevToolsContents(
727 DevToolsWindow
* DevToolsWindow::Create(
729 const GURL
& frontend_url
,
730 content::WebContents
* inspected_web_contents
,
731 bool shared_worker_frontend
,
732 const std::string
& remote_frontend
,
734 const std::string
& settings
) {
735 if (profile
->GetPrefs()->GetBoolean(prefs::kDevToolsDisabled
) ||
736 base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode
))
739 if (inspected_web_contents
) {
740 // Check for a place to dock.
741 Browser
* browser
= NULL
;
743 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents
,
745 browser
->is_type_popup()) {
750 // Create WebContents with devtools.
751 GURL
url(GetDevToolsURL(profile
, frontend_url
,
752 shared_worker_frontend
,
754 can_dock
, settings
));
755 scoped_ptr
<WebContents
> main_web_contents(
756 WebContents::Create(WebContents::CreateParams(profile
)));
757 main_web_contents
->GetController().LoadURL(
758 DecorateFrontendURL(url
), content::Referrer(),
759 ui::PAGE_TRANSITION_AUTO_TOPLEVEL
, std::string());
760 DevToolsUIBindings
* bindings
=
761 DevToolsUIBindings::ForWebContents(main_web_contents
.get());
765 return new DevToolsWindow(profile
, main_web_contents
.release(), bindings
,
766 inspected_web_contents
, can_dock
);
770 GURL
DevToolsWindow::GetDevToolsURL(Profile
* profile
,
771 const GURL
& base_url
,
772 bool shared_worker_frontend
,
773 const std::string
& remote_frontend
,
775 const std::string
& settings
) {
776 // Compatibility errors are encoded with data urls, pass them
777 // through with no decoration.
778 if (base_url
.SchemeIs("data"))
781 std::string
frontend_url(
782 !remote_frontend
.empty() ?
784 base_url
.is_empty() ? chrome::kChromeUIDevToolsURL
: base_url
.spec());
785 std::string
url_string(
787 ((frontend_url
.find("?") == std::string::npos
) ? "?" : "&"));
788 if (shared_worker_frontend
)
789 url_string
+= "&isSharedWorker=true";
790 if (remote_frontend
.size()) {
791 url_string
+= "&remoteFrontend=true";
793 url_string
+= "&remoteBase=" + DevToolsUI::GetRemoteBaseURL().spec();
796 url_string
+= "&can_dock=true";
798 url_string
+= "&settings=" + settings
;
799 return GURL(url_string
);
803 DevToolsWindow
* DevToolsWindow::FindDevToolsWindow(
804 DevToolsAgentHost
* agent_host
) {
805 if (!agent_host
|| g_instances
== NULL
)
807 DevToolsWindows
* instances
= g_instances
.Pointer();
808 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
810 if ((*it
)->bindings_
->IsAttachedTo(agent_host
))
817 DevToolsWindow
* DevToolsWindow::AsDevToolsWindow(
818 content::WebContents
* web_contents
) {
819 if (!web_contents
|| g_instances
== NULL
)
821 DevToolsWindows
* instances
= g_instances
.Pointer();
822 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
824 if ((*it
)->main_web_contents_
== web_contents
)
830 WebContents
* DevToolsWindow::OpenURLFromTab(
832 const content::OpenURLParams
& params
) {
833 DCHECK(source
== main_web_contents_
);
834 if (!params
.url
.SchemeIs(content::kChromeDevToolsScheme
)) {
835 WebContents
* inspected_web_contents
= GetInspectedWebContents();
836 return inspected_web_contents
?
837 inspected_web_contents
->OpenURL(params
) : NULL
;
840 bindings_
->Reattach();
842 content::NavigationController::LoadURLParams
load_url_params(params
.url
);
843 main_web_contents_
->GetController().LoadURLWithParams(load_url_params
);
844 return main_web_contents_
;
847 void DevToolsWindow::ActivateContents(WebContents
* contents
) {
849 WebContents
* inspected_tab
= GetInspectedWebContents();
851 inspected_tab
->GetDelegate()->ActivateContents(inspected_tab
);
852 } else if (browser_
) {
853 browser_
->window()->Activate();
857 void DevToolsWindow::AddNewContents(WebContents
* source
,
858 WebContents
* new_contents
,
859 WindowOpenDisposition disposition
,
860 const gfx::Rect
& initial_rect
,
863 if (new_contents
== toolbox_web_contents_
) {
864 toolbox_web_contents_
->SetDelegate(
865 new DevToolsToolboxDelegate(toolbox_web_contents_
,
866 inspected_contents_observer_
.get()));
867 if (main_web_contents_
->GetRenderWidgetHostView() &&
868 toolbox_web_contents_
->GetRenderWidgetHostView()) {
870 main_web_contents_
->GetRenderWidgetHostView()->GetViewBounds().size();
871 toolbox_web_contents_
->GetRenderWidgetHostView()->SetSize(size
);
873 UpdateBrowserWindow();
877 WebContents
* inspected_web_contents
= GetInspectedWebContents();
878 if (inspected_web_contents
) {
879 inspected_web_contents
->GetDelegate()->AddNewContents(
880 source
, new_contents
, disposition
, initial_rect
, user_gesture
,
885 void DevToolsWindow::WebContentsCreated(WebContents
* source_contents
,
886 int opener_render_frame_id
,
887 const std::string
& frame_name
,
888 const GURL
& target_url
,
889 WebContents
* new_contents
) {
890 if (target_url
.SchemeIs(content::kChromeDevToolsScheme
) &&
891 target_url
.path().rfind("toolbox.html") != std::string::npos
) {
893 if (toolbox_web_contents_
)
894 delete toolbox_web_contents_
;
895 toolbox_web_contents_
= new_contents
;
897 // Tag the DevTools toolbox WebContents with its TaskManager specific
898 // UserData so that it shows up in the task manager.
899 task_management::WebContentsTags::CreateForDevToolsContents(
900 toolbox_web_contents_
);
904 void DevToolsWindow::CloseContents(WebContents
* source
) {
906 life_stage_
= kClosing
;
907 UpdateBrowserWindow();
908 // In case of docked main_web_contents_, we own it so delete here.
909 // Embedding DevTools window will be deleted as a result of
910 // DevToolsUIBindings destruction.
911 delete main_web_contents_
;
914 void DevToolsWindow::ContentsZoomChange(bool zoom_in
) {
916 ui_zoom::PageZoom::Zoom(main_web_contents_
, zoom_in
? content::PAGE_ZOOM_IN
917 : content::PAGE_ZOOM_OUT
);
920 void DevToolsWindow::BeforeUnloadFired(WebContents
* tab
,
922 bool* proceed_to_fire_unload
) {
923 if (!intercepted_page_beforeunload_
) {
924 // Docked devtools window closed directly.
927 *proceed_to_fire_unload
= proceed
;
929 // Inspected page is attempting to close.
930 WebContents
* inspected_web_contents
= GetInspectedWebContents();
932 inspected_web_contents
->DispatchBeforeUnload(false);
935 inspected_web_contents
->GetDelegate()->BeforeUnloadFired(
936 inspected_web_contents
, false, &should_proceed
);
937 DCHECK(!should_proceed
);
939 *proceed_to_fire_unload
= false;
943 bool DevToolsWindow::PreHandleKeyboardEvent(
945 const content::NativeWebKeyboardEvent
& event
,
946 bool* is_keyboard_shortcut
) {
947 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
948 if (inspected_window
) {
949 return inspected_window
->PreHandleKeyboardEvent(event
,
950 is_keyboard_shortcut
);
955 void DevToolsWindow::HandleKeyboardEvent(
957 const content::NativeWebKeyboardEvent
& event
) {
958 if (event
.windowsKeyCode
== 0x08) {
959 // Do not navigate back in history on Windows (http://crbug.com/74156).
962 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
963 if (inspected_window
)
964 inspected_window
->HandleKeyboardEvent(event
);
967 content::JavaScriptDialogManager
* DevToolsWindow::GetJavaScriptDialogManager(
968 WebContents
* source
) {
969 WebContents
* inspected_web_contents
= GetInspectedWebContents();
970 return (inspected_web_contents
&& inspected_web_contents
->GetDelegate())
971 ? inspected_web_contents
->GetDelegate()
972 ->GetJavaScriptDialogManager(inspected_web_contents
)
973 : content::WebContentsDelegate::GetJavaScriptDialogManager(source
);
976 content::ColorChooser
* DevToolsWindow::OpenColorChooser(
977 WebContents
* web_contents
,
978 SkColor initial_color
,
979 const std::vector
<content::ColorSuggestion
>& suggestions
) {
980 return chrome::ShowColorChooser(web_contents
, initial_color
);
983 void DevToolsWindow::RunFileChooser(WebContents
* web_contents
,
984 const content::FileChooserParams
& params
) {
985 FileSelectHelper::RunFileChooser(web_contents
, params
);
988 bool DevToolsWindow::PreHandleGestureEvent(
990 const blink::WebGestureEvent
& event
) {
991 // Disable pinch zooming.
992 return event
.type
== blink::WebGestureEvent::GesturePinchBegin
||
993 event
.type
== blink::WebGestureEvent::GesturePinchUpdate
||
994 event
.type
== blink::WebGestureEvent::GesturePinchEnd
;
997 void DevToolsWindow::ActivateWindow() {
998 if (life_stage_
!= kLoadCompleted
)
1000 if (is_docked_
&& GetInspectedBrowserWindow())
1001 main_web_contents_
->Focus();
1002 else if (!is_docked_
&& !browser_
->window()->IsActive())
1003 browser_
->window()->Activate();
1006 void DevToolsWindow::CloseWindow() {
1008 life_stage_
= kClosing
;
1009 main_web_contents_
->DispatchBeforeUnload(false);
1012 void DevToolsWindow::SetInspectedPageBounds(const gfx::Rect
& rect
) {
1013 DevToolsContentsResizingStrategy
strategy(rect
);
1014 if (contents_resizing_strategy_
.Equals(strategy
))
1017 contents_resizing_strategy_
.CopyFrom(strategy
);
1018 UpdateBrowserWindow();
1021 void DevToolsWindow::InspectElementCompleted() {
1022 if (!inspect_element_start_time_
.is_null()) {
1023 UMA_HISTOGRAM_TIMES("DevTools.InspectElement",
1024 base::TimeTicks::Now() - inspect_element_start_time_
);
1025 inspect_element_start_time_
= base::TimeTicks();
1029 void DevToolsWindow::SetIsDocked(bool dock_requested
) {
1030 if (life_stage_
== kClosing
)
1033 DCHECK(can_dock_
|| !dock_requested
);
1035 dock_requested
= false;
1037 bool was_docked
= is_docked_
;
1038 is_docked_
= dock_requested
;
1040 if (life_stage_
!= kLoadCompleted
) {
1041 // This is a first time call we waited for to initialize.
1042 life_stage_
= life_stage_
== kOnLoadFired
? kLoadCompleted
: kIsDockedSet
;
1043 if (life_stage_
== kLoadCompleted
)
1048 if (dock_requested
== was_docked
)
1051 if (dock_requested
&& !was_docked
) {
1052 // Detach window from the external devtools browser. It will lead to
1053 // the browser object's close and delete. Remove observer first.
1054 TabStripModel
* tab_strip_model
= browser_
->tab_strip_model();
1055 tab_strip_model
->DetachWebContentsAt(
1056 tab_strip_model
->GetIndexOfWebContents(main_web_contents_
));
1058 } else if (!dock_requested
&& was_docked
) {
1059 UpdateBrowserWindow();
1062 Show(DevToolsToggleAction::Show());
1065 void DevToolsWindow::OpenInNewTab(const std::string
& url
) {
1066 content::OpenURLParams
params(
1067 GURL(url
), content::Referrer(), NEW_FOREGROUND_TAB
,
1068 ui::PAGE_TRANSITION_LINK
, false);
1069 WebContents
* inspected_web_contents
= GetInspectedWebContents();
1070 if (!inspected_web_contents
|| !inspected_web_contents
->OpenURL(params
)) {
1071 chrome::HostDesktopType host_desktop_type
=
1072 browser_
? browser_
->host_desktop_type() : chrome::GetActiveDesktop();
1074 chrome::ScopedTabbedBrowserDisplayer
displayer(profile_
, host_desktop_type
);
1075 chrome::AddSelectedTabWithURL(displayer
.browser(), GURL(url
),
1076 ui::PAGE_TRANSITION_LINK
);
1080 void DevToolsWindow::SetWhitelistedShortcuts(
1081 const std::string
& message
) {
1082 event_forwarder_
->SetWhitelistedShortcuts(message
);
1085 void DevToolsWindow::InspectedContentsClosing() {
1086 intercepted_page_beforeunload_
= false;
1087 life_stage_
= kClosing
;
1088 main_web_contents_
->ClosePage();
1091 InfoBarService
* DevToolsWindow::GetInfoBarService() {
1093 InfoBarService::FromWebContents(GetInspectedWebContents()) :
1094 InfoBarService::FromWebContents(main_web_contents_
);
1097 void DevToolsWindow::RenderProcessGone(bool crashed
) {
1098 // Docked DevToolsWindow owns its main_web_contents_ and must delete it.
1099 // Undocked main_web_contents_ are owned and handled by browser.
1100 // see crbug.com/369932
1102 CloseContents(main_web_contents_
);
1103 } else if (browser_
&& crashed
) {
1104 browser_
->window()->Close();
1108 void DevToolsWindow::OnLoadCompleted() {
1109 // First seed inspected tab id for extension APIs.
1110 WebContents
* inspected_web_contents
= GetInspectedWebContents();
1111 if (inspected_web_contents
) {
1112 SessionTabHelper
* session_tab_helper
=
1113 SessionTabHelper::FromWebContents(inspected_web_contents
);
1114 if (session_tab_helper
) {
1115 base::FundamentalValue
tabId(session_tab_helper
->session_id().id());
1116 bindings_
->CallClientFunction("DevToolsAPI.setInspectedTabId",
1117 &tabId
, NULL
, NULL
);
1121 if (life_stage_
== kClosing
)
1124 // We could be in kLoadCompleted state already if frontend reloads itself.
1125 if (life_stage_
!= kLoadCompleted
) {
1126 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
1127 // Here we set kOnLoadFired.
1128 life_stage_
= life_stage_
== kIsDockedSet
? kLoadCompleted
: kOnLoadFired
;
1130 if (life_stage_
== kLoadCompleted
)
1134 void DevToolsWindow::CreateDevToolsBrowser() {
1135 PrefService
* prefs
= profile_
->GetPrefs();
1136 if (!prefs
->GetDictionary(prefs::kAppWindowPlacement
)->HasKey(kDevToolsApp
)) {
1137 DictionaryPrefUpdate
update(prefs
, prefs::kAppWindowPlacement
);
1138 base::DictionaryValue
* wp_prefs
= update
.Get();
1139 base::DictionaryValue
* dev_tools_defaults
= new base::DictionaryValue
;
1140 wp_prefs
->Set(kDevToolsApp
, dev_tools_defaults
);
1141 dev_tools_defaults
->SetInteger("left", 100);
1142 dev_tools_defaults
->SetInteger("top", 100);
1143 dev_tools_defaults
->SetInteger("right", 740);
1144 dev_tools_defaults
->SetInteger("bottom", 740);
1145 dev_tools_defaults
->SetBoolean("maximized", false);
1146 dev_tools_defaults
->SetBoolean("always_on_top", false);
1149 browser_
= new Browser(Browser::CreateParams::CreateForDevTools(
1151 chrome::GetHostDesktopTypeForNativeView(
1152 main_web_contents_
->GetNativeView())));
1153 browser_
->tab_strip_model()->AddWebContents(
1154 main_web_contents_
, -1, ui::PAGE_TRANSITION_AUTO_TOPLEVEL
,
1155 TabStripModel::ADD_ACTIVE
);
1156 main_web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
1159 BrowserWindow
* DevToolsWindow::GetInspectedBrowserWindow() {
1160 Browser
* browser
= NULL
;
1162 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1164 browser
->window() : NULL
;
1167 void DevToolsWindow::DoAction(const DevToolsToggleAction
& action
) {
1168 switch (action
.type()) {
1169 case DevToolsToggleAction::kShowConsole
:
1170 bindings_
->CallClientFunction(
1171 "DevToolsAPI.showConsole", NULL
, NULL
, NULL
);
1174 case DevToolsToggleAction::kInspect
:
1175 bindings_
->CallClientFunction(
1176 "DevToolsAPI.enterInspectElementMode", NULL
, NULL
, NULL
);
1179 case DevToolsToggleAction::kShow
:
1180 case DevToolsToggleAction::kToggle
:
1184 case DevToolsToggleAction::kReveal
: {
1185 const DevToolsToggleAction::RevealParams
* params
=
1188 base::StringValue
url_value(params
->url
);
1189 base::FundamentalValue
line_value(static_cast<int>(params
->line_number
));
1190 base::FundamentalValue
column_value(
1191 static_cast<int>(params
->column_number
));
1192 bindings_
->CallClientFunction("DevToolsAPI.revealSourceLine",
1193 &url_value
, &line_value
, &column_value
);
1202 void DevToolsWindow::UpdateBrowserToolbar() {
1203 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
1204 if (inspected_window
)
1205 inspected_window
->UpdateToolbar(NULL
);
1208 void DevToolsWindow::UpdateBrowserWindow() {
1209 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
1210 if (inspected_window
)
1211 inspected_window
->UpdateDevTools();
1214 WebContents
* DevToolsWindow::GetInspectedWebContents() {
1215 return inspected_contents_observer_
1216 ? inspected_contents_observer_
->web_contents()
1220 void DevToolsWindow::LoadCompleted() {
1221 Show(action_on_load_
);
1222 action_on_load_
= DevToolsToggleAction::NoOp();
1223 if (!load_completed_callback_
.is_null()) {
1224 load_completed_callback_
.Run();
1225 load_completed_callback_
= base::Closure();
1229 void DevToolsWindow::SetLoadCompletedCallback(const base::Closure
& closure
) {
1230 if (life_stage_
== kLoadCompleted
|| life_stage_
== kClosing
) {
1231 if (!closure
.is_null())
1235 load_completed_callback_
= closure
;
1238 bool DevToolsWindow::ForwardKeyboardEvent(
1239 const content::NativeWebKeyboardEvent
& event
) {
1240 return event_forwarder_
->ForwardEvent(event
);