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/file_select_helper.h"
15 #include "chrome/browser/infobars/infobar_service.h"
16 #include "chrome/browser/prefs/pref_service_syncable.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/sessions/session_tab_helper.h"
19 #include "chrome/browser/ui/browser.h"
20 #include "chrome/browser/ui/browser_dialogs.h"
21 #include "chrome/browser/ui/browser_iterator.h"
22 #include "chrome/browser/ui/browser_list.h"
23 #include "chrome/browser/ui/browser_tabstrip.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/scoped_tabbed_browser_displayer.h"
28 #include "chrome/browser/ui/tabs/tab_strip_model.h"
29 #include "chrome/browser/ui/webui/devtools_ui.h"
30 #include "chrome/common/chrome_switches.h"
31 #include "chrome/common/pref_names.h"
32 #include "chrome/common/render_messages.h"
33 #include "chrome/common/url_constants.h"
34 #include "components/pref_registry/pref_registry_syncable.h"
35 #include "components/ui/zoom/page_zoom.h"
36 #include "components/ui/zoom/zoom_controller.h"
37 #include "content/public/browser/browser_thread.h"
38 #include "content/public/browser/devtools_agent_host.h"
39 #include "content/public/browser/native_web_keyboard_event.h"
40 #include "content/public/browser/navigation_controller.h"
41 #include "content/public/browser/navigation_entry.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/render_widget_host_view.h"
46 #include "content/public/browser/user_metrics.h"
47 #include "content/public/browser/web_contents.h"
48 #include "content/public/common/content_client.h"
49 #include "content/public/common/url_constants.h"
50 #include "net/base/escape.h"
51 #include "third_party/WebKit/public/web/WebInputEvent.h"
52 #include "ui/base/page_transition_types.h"
53 #include "ui/events/keycodes/keyboard_code_conversion.h"
54 #include "ui/events/keycodes/keyboard_codes.h"
56 using base::DictionaryValue
;
57 using blink::WebInputEvent
;
58 using content::BrowserThread
;
59 using content::DevToolsAgentHost
;
60 using content::WebContents
;
64 typedef std::vector
<DevToolsWindow
*> DevToolsWindows
;
65 base::LazyInstance
<DevToolsWindows
>::Leaky g_instances
=
66 LAZY_INSTANCE_INITIALIZER
;
68 static const char kKeyUpEventName
[] = "keyup";
69 static const char kKeyDownEventName
[] = "keydown";
71 bool FindInspectedBrowserAndTabIndex(
72 WebContents
* inspected_web_contents
, Browser
** browser
, int* tab
) {
73 if (!inspected_web_contents
)
76 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
77 int tab_index
= it
->tab_strip_model()->GetIndexOfWebContents(
78 inspected_web_contents
);
79 if (tab_index
!= TabStripModel::kNoTab
) {
88 // DevToolsToolboxDelegate ----------------------------------------------------
90 class DevToolsToolboxDelegate
91 : public content::WebContentsObserver
,
92 public content::WebContentsDelegate
{
94 DevToolsToolboxDelegate(
95 WebContents
* toolbox_contents
,
96 DevToolsWindow::ObserverWithAccessor
* web_contents_observer
);
97 ~DevToolsToolboxDelegate() override
;
99 content::WebContents
* OpenURLFromTab(
100 content::WebContents
* source
,
101 const content::OpenURLParams
& params
) override
;
102 bool PreHandleKeyboardEvent(content::WebContents
* source
,
103 const content::NativeWebKeyboardEvent
& event
,
104 bool* is_keyboard_shortcut
) override
;
105 void HandleKeyboardEvent(
106 content::WebContents
* source
,
107 const content::NativeWebKeyboardEvent
& event
) override
;
108 void WebContentsDestroyed() override
;
111 BrowserWindow
* GetInspectedBrowserWindow();
112 DevToolsWindow::ObserverWithAccessor
* inspected_contents_observer_
;
113 DISALLOW_COPY_AND_ASSIGN(DevToolsToolboxDelegate
);
116 DevToolsToolboxDelegate::DevToolsToolboxDelegate(
117 WebContents
* toolbox_contents
,
118 DevToolsWindow::ObserverWithAccessor
* web_contents_observer
)
119 : WebContentsObserver(toolbox_contents
),
120 inspected_contents_observer_(web_contents_observer
) {
123 DevToolsToolboxDelegate::~DevToolsToolboxDelegate() {
126 content::WebContents
* DevToolsToolboxDelegate::OpenURLFromTab(
127 content::WebContents
* source
,
128 const content::OpenURLParams
& params
) {
129 DCHECK(source
== web_contents());
130 if (!params
.url
.SchemeIs(content::kChromeDevToolsScheme
))
132 content::NavigationController::LoadURLParams
load_url_params(params
.url
);
133 source
->GetController().LoadURLWithParams(load_url_params
);
137 bool DevToolsToolboxDelegate::PreHandleKeyboardEvent(
138 content::WebContents
* source
,
139 const content::NativeWebKeyboardEvent
& event
,
140 bool* is_keyboard_shortcut
) {
141 BrowserWindow
* window
= GetInspectedBrowserWindow();
143 return window
->PreHandleKeyboardEvent(event
, is_keyboard_shortcut
);
147 void DevToolsToolboxDelegate::HandleKeyboardEvent(
148 content::WebContents
* source
,
149 const content::NativeWebKeyboardEvent
& event
) {
150 if (event
.windowsKeyCode
== 0x08) {
151 // Do not navigate back in history on Windows (http://crbug.com/74156).
154 BrowserWindow
* window
= GetInspectedBrowserWindow();
156 window
->HandleKeyboardEvent(event
);
159 void DevToolsToolboxDelegate::WebContentsDestroyed() {
163 BrowserWindow
* DevToolsToolboxDelegate::GetInspectedBrowserWindow() {
164 WebContents
* inspected_contents
=
165 inspected_contents_observer_
->web_contents();
166 if (!inspected_contents
)
168 Browser
* browser
= NULL
;
170 if (FindInspectedBrowserAndTabIndex(inspected_contents
, &browser
, &tab
))
171 return browser
->window();
177 // DevToolsEventForwarder -----------------------------------------------------
179 class DevToolsEventForwarder
{
181 explicit DevToolsEventForwarder(DevToolsWindow
* window
)
182 : devtools_window_(window
) {}
184 // Registers whitelisted shortcuts with the forwarder.
185 // Only registered keys will be forwarded to the DevTools frontend.
186 void SetWhitelistedShortcuts(const std::string
& message
);
188 // Forwards a keyboard event to the DevTools frontend if it is whitelisted.
189 // Returns |true| if the event has been forwarded, |false| otherwise.
190 bool ForwardEvent(const content::NativeWebKeyboardEvent
& event
);
193 static bool KeyWhitelistingAllowed(int key_code
, int modifiers
);
194 static int CombineKeyCodeAndModifiers(int key_code
, int modifiers
);
196 DevToolsWindow
* devtools_window_
;
197 std::set
<int> whitelisted_keys_
;
199 DISALLOW_COPY_AND_ASSIGN(DevToolsEventForwarder
);
202 void DevToolsEventForwarder::SetWhitelistedShortcuts(
203 const std::string
& message
) {
204 scoped_ptr
<base::Value
> parsed_message(base::JSONReader::Read(message
));
205 base::ListValue
* shortcut_list
;
206 if (!parsed_message
->GetAsList(&shortcut_list
))
208 base::ListValue::iterator it
= shortcut_list
->begin();
209 for (; it
!= shortcut_list
->end(); ++it
) {
210 base::DictionaryValue
* dictionary
;
211 if (!(*it
)->GetAsDictionary(&dictionary
))
214 dictionary
->GetInteger("keyCode", &key_code
);
218 dictionary
->GetInteger("modifiers", &modifiers
);
219 if (!KeyWhitelistingAllowed(key_code
, modifiers
)) {
220 LOG(WARNING
) << "Key whitelisting forbidden: "
221 << "(" << key_code
<< "," << modifiers
<< ")";
224 whitelisted_keys_
.insert(CombineKeyCodeAndModifiers(key_code
, modifiers
));
228 bool DevToolsEventForwarder::ForwardEvent(
229 const content::NativeWebKeyboardEvent
& event
) {
230 std::string event_type
;
231 switch (event
.type
) {
232 case WebInputEvent::KeyDown
:
233 case WebInputEvent::RawKeyDown
:
234 event_type
= kKeyDownEventName
;
236 case WebInputEvent::KeyUp
:
237 event_type
= kKeyUpEventName
;
243 int key_code
= ui::LocatedToNonLocatedKeyboardCode(
244 static_cast<ui::KeyboardCode
>(event
.windowsKeyCode
));
245 int key
= CombineKeyCodeAndModifiers(key_code
, event
.modifiers
);
246 if (whitelisted_keys_
.find(key
) == whitelisted_keys_
.end())
249 base::DictionaryValue event_data
;
250 event_data
.SetString("type", event_type
);
251 event_data
.SetString("keyIdentifier", event
.keyIdentifier
);
252 event_data
.SetInteger("keyCode", key_code
);
253 event_data
.SetInteger("modifiers", event
.modifiers
);
254 devtools_window_
->bindings_
->CallClientFunction(
255 "DevToolsAPI.keyEventUnhandled", &event_data
, NULL
, NULL
);
259 int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code
,
261 return key_code
| (modifiers
<< 16);
264 bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code
,
266 return (ui::VKEY_F1
<= key_code
&& key_code
<= ui::VKEY_F12
) ||
270 // DevToolsWindow::ObserverWithAccessor -------------------------------
272 DevToolsWindow::ObserverWithAccessor::ObserverWithAccessor(
273 WebContents
* web_contents
)
274 : WebContentsObserver(web_contents
) {
277 DevToolsWindow::ObserverWithAccessor::~ObserverWithAccessor() {
280 // DevToolsWindow -------------------------------------------------------------
282 const char DevToolsWindow::kDevToolsApp
[] = "DevToolsApp";
284 DevToolsWindow::~DevToolsWindow() {
285 life_stage_
= kClosing
;
287 UpdateBrowserWindow();
288 UpdateBrowserToolbar();
290 if (toolbox_web_contents_
)
291 delete toolbox_web_contents_
;
293 DevToolsWindows
* instances
= g_instances
.Pointer();
294 DevToolsWindows::iterator
it(
295 std::find(instances
->begin(), instances
->end(), this));
296 DCHECK(it
!= instances
->end());
297 instances
->erase(it
);
299 if (!close_callback_
.is_null()) {
300 close_callback_
.Run();
301 close_callback_
= base::Closure();
306 void DevToolsWindow::RegisterProfilePrefs(
307 user_prefs::PrefRegistrySyncable
* registry
) {
308 registry
->RegisterDictionaryPref(
309 prefs::kDevToolsEditedFiles
,
310 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
311 registry
->RegisterDictionaryPref(
312 prefs::kDevToolsFileSystemPaths
,
313 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
314 registry
->RegisterStringPref(
315 prefs::kDevToolsAdbKey
, std::string(),
316 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
318 registry
->RegisterBooleanPref(
319 prefs::kDevToolsDiscoverUsbDevicesEnabled
,
321 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
322 registry
->RegisterBooleanPref(
323 prefs::kDevToolsPortForwardingEnabled
,
325 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
326 registry
->RegisterBooleanPref(
327 prefs::kDevToolsPortForwardingDefaultSet
,
329 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
330 registry
->RegisterDictionaryPref(
331 prefs::kDevToolsPortForwardingConfig
,
332 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
336 content::WebContents
* DevToolsWindow::GetInTabWebContents(
337 WebContents
* inspected_web_contents
,
338 DevToolsContentsResizingStrategy
* out_strategy
) {
339 DevToolsWindow
* window
= GetInstanceForInspectedWebContents(
340 inspected_web_contents
);
341 if (!window
|| window
->life_stage_
== kClosing
)
344 // Not yet loaded window is treated as docked, but we should not present it
345 // until we decided on docking.
346 bool is_docked_set
= window
->life_stage_
== kLoadCompleted
||
347 window
->life_stage_
== kIsDockedSet
;
351 // Undocked window should have toolbox web contents.
352 if (!window
->is_docked_
&& !window
->toolbox_web_contents_
)
356 out_strategy
->CopyFrom(window
->contents_resizing_strategy_
);
358 return window
->is_docked_
? window
->main_web_contents_
:
359 window
->toolbox_web_contents_
;
363 DevToolsWindow
* DevToolsWindow::GetInstanceForInspectedWebContents(
364 WebContents
* inspected_web_contents
) {
365 if (!inspected_web_contents
|| g_instances
== NULL
)
367 DevToolsWindows
* instances
= g_instances
.Pointer();
368 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
370 if ((*it
)->GetInspectedWebContents() == inspected_web_contents
)
377 bool DevToolsWindow::IsDevToolsWindow(content::WebContents
* web_contents
) {
378 if (!web_contents
|| g_instances
== NULL
)
380 DevToolsWindows
* instances
= g_instances
.Pointer();
381 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
383 if ((*it
)->main_web_contents_
== web_contents
||
384 (*it
)->toolbox_web_contents_
== web_contents
)
391 DevToolsWindow
* DevToolsWindow::OpenDevToolsWindowForWorker(
393 const scoped_refptr
<DevToolsAgentHost
>& worker_agent
) {
394 DevToolsWindow
* window
= FindDevToolsWindow(worker_agent
.get());
396 window
= DevToolsWindow::CreateDevToolsWindowForWorker(profile
);
398 window
->bindings_
->AttachTo(worker_agent
);
400 window
->ScheduleShow(DevToolsToggleAction::Show());
405 DevToolsWindow
* DevToolsWindow::CreateDevToolsWindowForWorker(
407 content::RecordAction(base::UserMetricsAction("DevTools_InspectWorker"));
408 return Create(profile
, GURL(), NULL
, true, std::string(), false, "");
412 DevToolsWindow
* DevToolsWindow::OpenDevToolsWindow(
413 content::WebContents
* inspected_web_contents
) {
414 return ToggleDevToolsWindow(
415 inspected_web_contents
, true, DevToolsToggleAction::Show(), "");
419 DevToolsWindow
* DevToolsWindow::OpenDevToolsWindow(
420 content::WebContents
* inspected_web_contents
,
421 const DevToolsToggleAction
& action
) {
422 return ToggleDevToolsWindow(inspected_web_contents
, true, action
, "");
426 DevToolsWindow
* DevToolsWindow::ToggleDevToolsWindow(
428 const DevToolsToggleAction
& action
) {
429 if (action
.type() == DevToolsToggleAction::kToggle
&&
430 browser
->is_devtools()) {
431 browser
->tab_strip_model()->CloseAllTabs();
435 return ToggleDevToolsWindow(
436 browser
->tab_strip_model()->GetActiveWebContents(),
437 action
.type() == DevToolsToggleAction::kInspect
,
442 void DevToolsWindow::OpenExternalFrontend(
444 const std::string
& frontend_url
,
445 const scoped_refptr
<content::DevToolsAgentHost
>& agent_host
,
447 DevToolsWindow
* window
= FindDevToolsWindow(agent_host
.get());
449 window
= Create(profile
, GURL(), nullptr, isWorker
,
450 DevToolsUI::GetProxyURL(frontend_url
).spec(), false, std::string());
452 window
->bindings_
->AttachTo(agent_host
);
455 window
->ScheduleShow(DevToolsToggleAction::Show());
459 DevToolsWindow
* DevToolsWindow::ToggleDevToolsWindow(
460 content::WebContents
* inspected_web_contents
,
462 const DevToolsToggleAction
& action
,
463 const std::string
& settings
) {
464 scoped_refptr
<DevToolsAgentHost
> agent(
465 DevToolsAgentHost::GetOrCreateFor(inspected_web_contents
));
466 DevToolsWindow
* window
= FindDevToolsWindow(agent
.get());
467 bool do_open
= force_open
;
469 Profile
* profile
= Profile::FromBrowserContext(
470 inspected_web_contents
->GetBrowserContext());
471 content::RecordAction(
472 base::UserMetricsAction("DevTools_InspectRenderer"));
473 window
= Create(profile
, GURL(), inspected_web_contents
,
474 false, std::string(), true, settings
);
476 window
->bindings_
->AttachTo(agent
.get());
480 // Update toolbar to reflect DevTools changes.
481 window
->UpdateBrowserToolbar();
483 // If window is docked and visible, we hide it on toggle. If window is
484 // undocked, we show (activate) it.
485 if (!window
->is_docked_
|| do_open
)
486 window
->ScheduleShow(action
);
488 window
->CloseWindow();
494 void DevToolsWindow::InspectElement(
495 content::WebContents
* inspected_web_contents
,
498 scoped_refptr
<DevToolsAgentHost
> agent(
499 DevToolsAgentHost::GetOrCreateFor(inspected_web_contents
));
500 agent
->InspectElement(x
, y
);
501 bool should_measure_time
= FindDevToolsWindow(agent
.get()) == NULL
;
502 base::TimeTicks start_time
= base::TimeTicks::Now();
503 // TODO(loislo): we should initiate DevTools window opening from within
504 // renderer. Otherwise, we still can hit a race condition here.
505 DevToolsWindow
* window
= OpenDevToolsWindow(inspected_web_contents
);
506 if (should_measure_time
)
507 window
->inspect_element_start_time_
= start_time
;
510 void DevToolsWindow::ScheduleShow(const DevToolsToggleAction
& action
) {
511 if (life_stage_
== kLoadCompleted
) {
516 // Action will be done only after load completed.
517 action_on_load_
= action
;
520 // No harm to show always-undocked window right away.
522 Show(DevToolsToggleAction::Show());
526 void DevToolsWindow::Show(const DevToolsToggleAction
& action
) {
527 if (life_stage_
== kClosing
)
530 if (action
.type() == DevToolsToggleAction::kNoOp
)
535 Browser
* inspected_browser
= NULL
;
536 int inspected_tab_index
= -1;
537 FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
539 &inspected_tab_index
);
540 DCHECK(inspected_browser
);
541 DCHECK(inspected_tab_index
!= -1);
543 // Tell inspected browser to update splitter and switch to inspected panel.
544 BrowserWindow
* inspected_window
= inspected_browser
->window();
545 main_web_contents_
->SetDelegate(this);
547 TabStripModel
* tab_strip_model
= inspected_browser
->tab_strip_model();
548 tab_strip_model
->ActivateTabAt(inspected_tab_index
, true);
550 inspected_window
->UpdateDevTools();
551 main_web_contents_
->SetInitialFocus();
552 inspected_window
->Show();
553 // On Aura, focusing once is not enough. Do it again.
554 // Note that focusing only here but not before isn't enough either. We just
555 // need to focus twice.
556 main_web_contents_
->SetInitialFocus();
558 PrefsTabHelper::CreateForWebContents(main_web_contents_
);
559 main_web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
565 // Avoid consecutive window switching if the devtools window has been opened
566 // and the Inspect Element shortcut is pressed in the inspected tab.
567 bool should_show_window
=
568 !browser_
|| (action
.type() != DevToolsToggleAction::kInspect
);
571 CreateDevToolsBrowser();
573 if (should_show_window
) {
574 browser_
->window()->Show();
575 main_web_contents_
->SetInitialFocus();
577 if (toolbox_web_contents_
)
578 UpdateBrowserWindow();
584 bool DevToolsWindow::HandleBeforeUnload(WebContents
* frontend_contents
,
585 bool proceed
, bool* proceed_to_fire_unload
) {
586 DevToolsWindow
* window
= AsDevToolsWindow(frontend_contents
);
589 if (!window
->intercepted_page_beforeunload_
)
591 window
->BeforeUnloadFired(frontend_contents
, proceed
,
592 proceed_to_fire_unload
);
597 bool DevToolsWindow::InterceptPageBeforeUnload(WebContents
* contents
) {
598 DevToolsWindow
* window
=
599 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
600 if (!window
|| window
->intercepted_page_beforeunload_
)
603 // Not yet loaded frontend will not handle beforeunload.
604 if (window
->life_stage_
!= kLoadCompleted
)
607 window
->intercepted_page_beforeunload_
= true;
608 // Handle case of devtools inspecting another devtools instance by passing
609 // the call up to the inspecting devtools instance.
610 if (!DevToolsWindow::InterceptPageBeforeUnload(window
->main_web_contents_
)) {
611 window
->main_web_contents_
->DispatchBeforeUnload(false);
617 bool DevToolsWindow::NeedsToInterceptBeforeUnload(
618 WebContents
* contents
) {
619 DevToolsWindow
* window
=
620 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
621 return window
&& !window
->intercepted_page_beforeunload_
;
625 bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(
627 DCHECK(browser
->is_devtools());
628 // When FastUnloadController is used, devtools frontend will be detached
629 // from the browser window at this point which means we've already fired
631 if (browser
->tab_strip_model()->empty())
633 WebContents
* contents
=
634 browser
->tab_strip_model()->GetWebContentsAt(0);
635 DevToolsWindow
* window
= AsDevToolsWindow(contents
);
638 return window
->intercepted_page_beforeunload_
;
642 void DevToolsWindow::OnPageCloseCanceled(WebContents
* contents
) {
643 DevToolsWindow
*window
=
644 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
647 window
->intercepted_page_beforeunload_
= false;
648 // Propagate to devtools opened on devtools if any.
649 DevToolsWindow::OnPageCloseCanceled(window
->main_web_contents_
);
652 DevToolsWindow::DevToolsWindow(Profile
* profile
,
653 WebContents
* main_web_contents
,
654 DevToolsUIBindings
* bindings
,
655 WebContents
* inspected_web_contents
,
658 main_web_contents_(main_web_contents
),
659 toolbox_web_contents_(nullptr),
664 // This initialization allows external front-end to work without changes.
665 // We don't wait for docking call, but instead immediately show undocked.
666 // Passing "dockSide=undocked" parameter ensures proper UI.
667 life_stage_(can_dock
? kNotLoaded
: kIsDockedSet
),
668 action_on_load_(DevToolsToggleAction::NoOp()),
669 intercepted_page_beforeunload_(false) {
670 // Set up delegate, so we get fully-functional window immediately.
671 // It will not appear in UI though until |life_stage_ == kLoadCompleted|.
672 main_web_contents_
->SetDelegate(this);
673 // Bindings take ownership over devtools as its delegate.
674 bindings_
->SetDelegate(this);
675 // DevTools uses PageZoom::Zoom(), so main_web_contents_ requires a
677 ui_zoom::ZoomController::CreateForWebContents(main_web_contents_
);
678 ui_zoom::ZoomController::FromWebContents(main_web_contents_
)
679 ->SetShowsNotificationBubble(false);
681 g_instances
.Get().push_back(this);
683 // There is no inspected_web_contents in case of various workers.
684 if (inspected_web_contents
)
685 inspected_contents_observer_
.reset(
686 new ObserverWithAccessor(inspected_web_contents
));
688 // Initialize docked page to be of the right size.
689 if (can_dock_
&& inspected_web_contents
) {
690 content::RenderWidgetHostView
* inspected_view
=
691 inspected_web_contents
->GetRenderWidgetHostView();
692 if (inspected_view
&& main_web_contents_
->GetRenderWidgetHostView()) {
693 gfx::Size size
= inspected_view
->GetViewBounds().size();
694 main_web_contents_
->GetRenderWidgetHostView()->SetSize(size
);
698 event_forwarder_
.reset(new DevToolsEventForwarder(this));
702 DevToolsWindow
* DevToolsWindow::Create(
704 const GURL
& frontend_url
,
705 content::WebContents
* inspected_web_contents
,
706 bool shared_worker_frontend
,
707 const std::string
& remote_frontend
,
709 const std::string
& settings
) {
710 if (inspected_web_contents
) {
711 // Check for a place to dock.
712 Browser
* browser
= NULL
;
714 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents
,
716 browser
->is_type_popup()) {
721 // Create WebContents with devtools.
722 GURL
url(GetDevToolsURL(profile
, frontend_url
,
723 shared_worker_frontend
,
725 can_dock
, settings
));
726 scoped_ptr
<WebContents
> main_web_contents(
727 WebContents::Create(WebContents::CreateParams(profile
)));
728 main_web_contents
->GetController().LoadURL(
729 DevToolsUIBindings::ApplyThemeToURL(profile
, url
), content::Referrer(),
730 ui::PAGE_TRANSITION_AUTO_TOPLEVEL
, std::string());
731 DevToolsUIBindings
* bindings
=
732 DevToolsUIBindings::ForWebContents(main_web_contents
.get());
736 return new DevToolsWindow(profile
, main_web_contents
.release(), bindings
,
737 inspected_web_contents
, can_dock
);
741 GURL
DevToolsWindow::GetDevToolsURL(Profile
* profile
,
742 const GURL
& base_url
,
743 bool shared_worker_frontend
,
744 const std::string
& remote_frontend
,
746 const std::string
& settings
) {
747 // Compatibility errors are encoded with data urls, pass them
748 // through with no decoration.
749 if (base_url
.SchemeIs("data"))
752 std::string
frontend_url(
753 base_url
.is_empty() ? chrome::kChromeUIDevToolsURL
: base_url
.spec());
754 std::string
url_string(
756 ((frontend_url
.find("?") == std::string::npos
) ? "?" : "&"));
757 if (shared_worker_frontend
)
758 url_string
+= "&isSharedWorker=true";
759 if (remote_frontend
.size()) {
760 url_string
+= "&remoteFrontend=true";
761 url_string
+= "&remoteFrontendUrl=" + net::EscapePath(remote_frontend
);
764 url_string
+= "&can_dock=true";
766 url_string
+= "&settings=" + settings
;
767 return GURL(url_string
);
771 DevToolsWindow
* DevToolsWindow::FindDevToolsWindow(
772 DevToolsAgentHost
* agent_host
) {
773 if (!agent_host
|| g_instances
== NULL
)
775 DevToolsWindows
* instances
= g_instances
.Pointer();
776 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
778 if ((*it
)->bindings_
->IsAttachedTo(agent_host
))
785 DevToolsWindow
* DevToolsWindow::AsDevToolsWindow(
786 content::WebContents
* web_contents
) {
787 if (!web_contents
|| g_instances
== NULL
)
789 DevToolsWindows
* instances
= g_instances
.Pointer();
790 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
792 if ((*it
)->main_web_contents_
== web_contents
)
798 WebContents
* DevToolsWindow::OpenURLFromTab(
800 const content::OpenURLParams
& params
) {
801 DCHECK(source
== main_web_contents_
);
802 if (!params
.url
.SchemeIs(content::kChromeDevToolsScheme
)) {
803 WebContents
* inspected_web_contents
= GetInspectedWebContents();
804 return inspected_web_contents
?
805 inspected_web_contents
->OpenURL(params
) : NULL
;
808 bindings_
->Reattach();
810 content::NavigationController::LoadURLParams
load_url_params(params
.url
);
811 main_web_contents_
->GetController().LoadURLWithParams(load_url_params
);
812 return main_web_contents_
;
815 void DevToolsWindow::ActivateContents(WebContents
* contents
) {
817 WebContents
* inspected_tab
= GetInspectedWebContents();
818 inspected_tab
->GetDelegate()->ActivateContents(inspected_tab
);
819 } else if (browser_
) {
820 browser_
->window()->Activate();
824 void DevToolsWindow::AddNewContents(WebContents
* source
,
825 WebContents
* new_contents
,
826 WindowOpenDisposition disposition
,
827 const gfx::Rect
& initial_rect
,
830 if (new_contents
== toolbox_web_contents_
) {
831 toolbox_web_contents_
->SetDelegate(
832 new DevToolsToolboxDelegate(toolbox_web_contents_
,
833 inspected_contents_observer_
.get()));
834 if (main_web_contents_
->GetRenderWidgetHostView() &&
835 toolbox_web_contents_
->GetRenderWidgetHostView()) {
837 main_web_contents_
->GetRenderWidgetHostView()->GetViewBounds().size();
838 toolbox_web_contents_
->GetRenderWidgetHostView()->SetSize(size
);
840 UpdateBrowserWindow();
844 WebContents
* inspected_web_contents
= GetInspectedWebContents();
845 if (inspected_web_contents
) {
846 inspected_web_contents
->GetDelegate()->AddNewContents(
847 source
, new_contents
, disposition
, initial_rect
, user_gesture
,
852 void DevToolsWindow::WebContentsCreated(WebContents
* source_contents
,
853 int opener_render_frame_id
,
854 const base::string16
& frame_name
,
855 const GURL
& target_url
,
856 WebContents
* new_contents
) {
857 if (target_url
.SchemeIs(content::kChromeDevToolsScheme
) &&
858 target_url
.path().rfind("toolbox.html") != std::string::npos
) {
860 toolbox_web_contents_
= new_contents
;
864 void DevToolsWindow::CloseContents(WebContents
* source
) {
866 life_stage_
= kClosing
;
867 UpdateBrowserWindow();
868 // In case of docked main_web_contents_, we own it so delete here.
869 // Embedding DevTools window will be deleted as a result of
870 // DevToolsUIBindings destruction.
871 delete main_web_contents_
;
874 void DevToolsWindow::ContentsZoomChange(bool zoom_in
) {
876 ui_zoom::PageZoom::Zoom(main_web_contents_
, zoom_in
? content::PAGE_ZOOM_IN
877 : content::PAGE_ZOOM_OUT
);
880 void DevToolsWindow::BeforeUnloadFired(WebContents
* tab
,
882 bool* proceed_to_fire_unload
) {
883 if (!intercepted_page_beforeunload_
) {
884 // Docked devtools window closed directly.
887 *proceed_to_fire_unload
= proceed
;
889 // Inspected page is attempting to close.
890 WebContents
* inspected_web_contents
= GetInspectedWebContents();
892 inspected_web_contents
->DispatchBeforeUnload(false);
895 inspected_web_contents
->GetDelegate()->BeforeUnloadFired(
896 inspected_web_contents
, false, &should_proceed
);
897 DCHECK(!should_proceed
);
899 *proceed_to_fire_unload
= false;
903 bool DevToolsWindow::PreHandleKeyboardEvent(
905 const content::NativeWebKeyboardEvent
& event
,
906 bool* is_keyboard_shortcut
) {
907 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
908 if (inspected_window
) {
909 return inspected_window
->PreHandleKeyboardEvent(event
,
910 is_keyboard_shortcut
);
915 void DevToolsWindow::HandleKeyboardEvent(
917 const content::NativeWebKeyboardEvent
& event
) {
918 if (event
.windowsKeyCode
== 0x08) {
919 // Do not navigate back in history on Windows (http://crbug.com/74156).
922 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
923 if (inspected_window
)
924 inspected_window
->HandleKeyboardEvent(event
);
927 content::JavaScriptDialogManager
* DevToolsWindow::GetJavaScriptDialogManager(
928 WebContents
* source
) {
929 WebContents
* inspected_web_contents
= GetInspectedWebContents();
930 return (inspected_web_contents
&& inspected_web_contents
->GetDelegate())
931 ? inspected_web_contents
->GetDelegate()
932 ->GetJavaScriptDialogManager(inspected_web_contents
)
933 : content::WebContentsDelegate::GetJavaScriptDialogManager(source
);
936 content::ColorChooser
* DevToolsWindow::OpenColorChooser(
937 WebContents
* web_contents
,
938 SkColor initial_color
,
939 const std::vector
<content::ColorSuggestion
>& suggestions
) {
940 return chrome::ShowColorChooser(web_contents
, initial_color
);
943 void DevToolsWindow::RunFileChooser(WebContents
* web_contents
,
944 const content::FileChooserParams
& params
) {
945 FileSelectHelper::RunFileChooser(web_contents
, params
);
948 void DevToolsWindow::WebContentsFocused(WebContents
* contents
) {
949 Browser
* inspected_browser
= NULL
;
950 int inspected_tab_index
= -1;
951 if (is_docked_
&& FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
953 &inspected_tab_index
))
954 inspected_browser
->window()->WebContentsFocused(contents
);
957 bool DevToolsWindow::PreHandleGestureEvent(
959 const blink::WebGestureEvent
& event
) {
960 // Disable pinch zooming.
961 return event
.type
== blink::WebGestureEvent::GesturePinchBegin
||
962 event
.type
== blink::WebGestureEvent::GesturePinchUpdate
||
963 event
.type
== blink::WebGestureEvent::GesturePinchEnd
;
966 void DevToolsWindow::ActivateWindow() {
967 if (life_stage_
!= kLoadCompleted
)
969 if (is_docked_
&& GetInspectedBrowserWindow())
970 main_web_contents_
->Focus();
971 else if (!is_docked_
&& !browser_
->window()->IsActive())
972 browser_
->window()->Activate();
975 void DevToolsWindow::CloseWindow() {
977 life_stage_
= kClosing
;
978 main_web_contents_
->DispatchBeforeUnload(false);
981 void DevToolsWindow::SetInspectedPageBounds(const gfx::Rect
& rect
) {
982 DevToolsContentsResizingStrategy
strategy(rect
);
983 if (contents_resizing_strategy_
.Equals(strategy
))
986 contents_resizing_strategy_
.CopyFrom(strategy
);
987 UpdateBrowserWindow();
990 void DevToolsWindow::InspectElementCompleted() {
991 if (!inspect_element_start_time_
.is_null()) {
992 UMA_HISTOGRAM_TIMES("DevTools.InspectElement",
993 base::TimeTicks::Now() - inspect_element_start_time_
);
994 inspect_element_start_time_
= base::TimeTicks();
998 void DevToolsWindow::SetIsDocked(bool dock_requested
) {
999 if (life_stage_
== kClosing
)
1002 DCHECK(can_dock_
|| !dock_requested
);
1004 dock_requested
= false;
1006 bool was_docked
= is_docked_
;
1007 is_docked_
= dock_requested
;
1009 if (life_stage_
!= kLoadCompleted
) {
1010 // This is a first time call we waited for to initialize.
1011 life_stage_
= life_stage_
== kOnLoadFired
? kLoadCompleted
: kIsDockedSet
;
1012 if (life_stage_
== kLoadCompleted
)
1017 if (dock_requested
== was_docked
)
1020 if (dock_requested
&& !was_docked
) {
1021 // Detach window from the external devtools browser. It will lead to
1022 // the browser object's close and delete. Remove observer first.
1023 TabStripModel
* tab_strip_model
= browser_
->tab_strip_model();
1024 tab_strip_model
->DetachWebContentsAt(
1025 tab_strip_model
->GetIndexOfWebContents(main_web_contents_
));
1027 } else if (!dock_requested
&& was_docked
) {
1028 UpdateBrowserWindow();
1031 Show(DevToolsToggleAction::Show());
1034 void DevToolsWindow::OpenInNewTab(const std::string
& url
) {
1035 content::OpenURLParams
params(
1036 GURL(url
), content::Referrer(), NEW_FOREGROUND_TAB
,
1037 ui::PAGE_TRANSITION_LINK
, false);
1038 WebContents
* inspected_web_contents
= GetInspectedWebContents();
1039 if (!inspected_web_contents
|| !inspected_web_contents
->OpenURL(params
)) {
1040 chrome::HostDesktopType host_desktop_type
=
1041 browser_
? browser_
->host_desktop_type() : chrome::GetActiveDesktop();
1043 chrome::ScopedTabbedBrowserDisplayer
displayer(profile_
, host_desktop_type
);
1044 chrome::AddSelectedTabWithURL(displayer
.browser(), GURL(url
),
1045 ui::PAGE_TRANSITION_LINK
);
1049 void DevToolsWindow::SetWhitelistedShortcuts(
1050 const std::string
& message
) {
1051 event_forwarder_
->SetWhitelistedShortcuts(message
);
1054 void DevToolsWindow::InspectedContentsClosing() {
1055 intercepted_page_beforeunload_
= false;
1056 life_stage_
= kClosing
;
1057 main_web_contents_
->GetRenderViewHost()->ClosePage();
1060 InfoBarService
* DevToolsWindow::GetInfoBarService() {
1062 InfoBarService::FromWebContents(GetInspectedWebContents()) :
1063 InfoBarService::FromWebContents(main_web_contents_
);
1066 void DevToolsWindow::RenderProcessGone(bool crashed
) {
1067 // Docked DevToolsWindow owns its main_web_contents_ and must delete it.
1068 // Undocked main_web_contents_ are owned and handled by browser.
1069 // see crbug.com/369932
1071 CloseContents(main_web_contents_
);
1072 } else if (browser_
&& crashed
) {
1073 browser_
->window()->Close();
1077 void DevToolsWindow::OnLoadCompleted() {
1078 // First seed inspected tab id for extension APIs.
1079 WebContents
* inspected_web_contents
= GetInspectedWebContents();
1080 if (inspected_web_contents
) {
1081 SessionTabHelper
* session_tab_helper
=
1082 SessionTabHelper::FromWebContents(inspected_web_contents
);
1083 if (session_tab_helper
) {
1084 base::FundamentalValue
tabId(session_tab_helper
->session_id().id());
1085 bindings_
->CallClientFunction("DevToolsAPI.setInspectedTabId",
1086 &tabId
, NULL
, NULL
);
1090 if (life_stage_
== kClosing
)
1093 // We could be in kLoadCompleted state already if frontend reloads itself.
1094 if (life_stage_
!= kLoadCompleted
) {
1095 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
1096 // Here we set kOnLoadFired.
1097 life_stage_
= life_stage_
== kIsDockedSet
? kLoadCompleted
: kOnLoadFired
;
1099 if (life_stage_
== kLoadCompleted
)
1103 void DevToolsWindow::CreateDevToolsBrowser() {
1104 PrefService
* prefs
= profile_
->GetPrefs();
1105 if (!prefs
->GetDictionary(prefs::kAppWindowPlacement
)->HasKey(kDevToolsApp
)) {
1106 DictionaryPrefUpdate
update(prefs
, prefs::kAppWindowPlacement
);
1107 base::DictionaryValue
* wp_prefs
= update
.Get();
1108 base::DictionaryValue
* dev_tools_defaults
= new base::DictionaryValue
;
1109 wp_prefs
->Set(kDevToolsApp
, dev_tools_defaults
);
1110 dev_tools_defaults
->SetInteger("left", 100);
1111 dev_tools_defaults
->SetInteger("top", 100);
1112 dev_tools_defaults
->SetInteger("right", 740);
1113 dev_tools_defaults
->SetInteger("bottom", 740);
1114 dev_tools_defaults
->SetBoolean("maximized", false);
1115 dev_tools_defaults
->SetBoolean("always_on_top", false);
1118 browser_
= new Browser(Browser::CreateParams::CreateForDevTools(
1120 chrome::GetHostDesktopTypeForNativeView(
1121 main_web_contents_
->GetNativeView())));
1122 browser_
->tab_strip_model()->AddWebContents(
1123 main_web_contents_
, -1, ui::PAGE_TRANSITION_AUTO_TOPLEVEL
,
1124 TabStripModel::ADD_ACTIVE
);
1125 main_web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
1128 BrowserWindow
* DevToolsWindow::GetInspectedBrowserWindow() {
1129 Browser
* browser
= NULL
;
1131 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1133 browser
->window() : NULL
;
1136 void DevToolsWindow::DoAction(const DevToolsToggleAction
& action
) {
1137 switch (action
.type()) {
1138 case DevToolsToggleAction::kShowConsole
:
1139 bindings_
->CallClientFunction(
1140 "DevToolsAPI.showConsole", NULL
, NULL
, NULL
);
1143 case DevToolsToggleAction::kInspect
:
1144 bindings_
->CallClientFunction(
1145 "DevToolsAPI.enterInspectElementMode", NULL
, NULL
, NULL
);
1148 case DevToolsToggleAction::kShow
:
1149 case DevToolsToggleAction::kToggle
:
1153 case DevToolsToggleAction::kReveal
: {
1154 const DevToolsToggleAction::RevealParams
* params
=
1157 base::StringValue
url_value(params
->url
);
1158 base::FundamentalValue
line_value(static_cast<int>(params
->line_number
));
1159 base::FundamentalValue
column_value(
1160 static_cast<int>(params
->column_number
));
1161 bindings_
->CallClientFunction("DevToolsAPI.revealSourceLine",
1162 &url_value
, &line_value
, &column_value
);
1171 void DevToolsWindow::UpdateBrowserToolbar() {
1172 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
1173 if (inspected_window
)
1174 inspected_window
->UpdateToolbar(NULL
);
1177 void DevToolsWindow::UpdateBrowserWindow() {
1178 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
1179 if (inspected_window
)
1180 inspected_window
->UpdateDevTools();
1183 WebContents
* DevToolsWindow::GetInspectedWebContents() {
1184 return inspected_contents_observer_
1185 ? inspected_contents_observer_
->web_contents()
1189 void DevToolsWindow::LoadCompleted() {
1190 Show(action_on_load_
);
1191 action_on_load_
= DevToolsToggleAction::NoOp();
1192 if (!load_completed_callback_
.is_null()) {
1193 load_completed_callback_
.Run();
1194 load_completed_callback_
= base::Closure();
1198 void DevToolsWindow::SetLoadCompletedCallback(const base::Closure
& closure
) {
1199 if (life_stage_
== kLoadCompleted
|| life_stage_
== kClosing
) {
1200 if (!closure
.is_null())
1204 load_completed_callback_
= closure
;
1207 bool DevToolsWindow::ForwardKeyboardEvent(
1208 const content::NativeWebKeyboardEvent
& event
) {
1209 return event_forwarder_
->ForwardEvent(event
);