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();
176 // DevToolsEventForwarder -----------------------------------------------------
178 class DevToolsEventForwarder
{
180 explicit DevToolsEventForwarder(DevToolsWindow
* window
)
181 : devtools_window_(window
) {}
183 // Registers whitelisted shortcuts with the forwarder.
184 // Only registered keys will be forwarded to the DevTools frontend.
185 void SetWhitelistedShortcuts(const std::string
& message
);
187 // Forwards a keyboard event to the DevTools frontend if it is whitelisted.
188 // Returns |true| if the event has been forwarded, |false| otherwise.
189 bool ForwardEvent(const content::NativeWebKeyboardEvent
& event
);
192 static bool KeyWhitelistingAllowed(int key_code
, int modifiers
);
193 static int CombineKeyCodeAndModifiers(int key_code
, int modifiers
);
195 DevToolsWindow
* devtools_window_
;
196 std::set
<int> whitelisted_keys_
;
198 DISALLOW_COPY_AND_ASSIGN(DevToolsEventForwarder
);
201 void DevToolsEventForwarder::SetWhitelistedShortcuts(
202 const std::string
& message
) {
203 scoped_ptr
<base::Value
> parsed_message(base::JSONReader::Read(message
));
204 base::ListValue
* shortcut_list
;
205 if (!parsed_message
->GetAsList(&shortcut_list
))
207 base::ListValue::iterator it
= shortcut_list
->begin();
208 for (; it
!= shortcut_list
->end(); ++it
) {
209 base::DictionaryValue
* dictionary
;
210 if (!(*it
)->GetAsDictionary(&dictionary
))
213 dictionary
->GetInteger("keyCode", &key_code
);
217 dictionary
->GetInteger("modifiers", &modifiers
);
218 if (!KeyWhitelistingAllowed(key_code
, modifiers
)) {
219 LOG(WARNING
) << "Key whitelisting forbidden: "
220 << "(" << key_code
<< "," << modifiers
<< ")";
223 whitelisted_keys_
.insert(CombineKeyCodeAndModifiers(key_code
, modifiers
));
227 bool DevToolsEventForwarder::ForwardEvent(
228 const content::NativeWebKeyboardEvent
& event
) {
229 std::string event_type
;
230 switch (event
.type
) {
231 case WebInputEvent::KeyDown
:
232 case WebInputEvent::RawKeyDown
:
233 event_type
= kKeyDownEventName
;
235 case WebInputEvent::KeyUp
:
236 event_type
= kKeyUpEventName
;
242 int key_code
= ui::LocatedToNonLocatedKeyboardCode(
243 static_cast<ui::KeyboardCode
>(event
.windowsKeyCode
));
244 int key
= CombineKeyCodeAndModifiers(key_code
, event
.modifiers
);
245 if (whitelisted_keys_
.find(key
) == whitelisted_keys_
.end())
248 base::DictionaryValue event_data
;
249 event_data
.SetString("type", event_type
);
250 event_data
.SetString("keyIdentifier", event
.keyIdentifier
);
251 event_data
.SetInteger("keyCode", key_code
);
252 event_data
.SetInteger("modifiers", event
.modifiers
);
253 devtools_window_
->bindings_
->CallClientFunction(
254 "DevToolsAPI.keyEventUnhandled", &event_data
, NULL
, NULL
);
258 int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code
,
260 return key_code
| (modifiers
<< 16);
263 bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code
,
265 return (ui::VKEY_F1
<= key_code
&& key_code
<= ui::VKEY_F12
) ||
269 // DevToolsWindow::ObserverWithAccessor -------------------------------
271 DevToolsWindow::ObserverWithAccessor::ObserverWithAccessor(
272 WebContents
* web_contents
)
273 : WebContentsObserver(web_contents
) {
276 DevToolsWindow::ObserverWithAccessor::~ObserverWithAccessor() {
279 // DevToolsWindow -------------------------------------------------------------
281 const char DevToolsWindow::kDevToolsApp
[] = "DevToolsApp";
283 DevToolsWindow::~DevToolsWindow() {
284 life_stage_
= kClosing
;
286 UpdateBrowserWindow();
287 UpdateBrowserToolbar();
289 if (toolbox_web_contents_
)
290 delete toolbox_web_contents_
;
292 DevToolsWindows
* instances
= g_instances
.Pointer();
293 DevToolsWindows::iterator
it(
294 std::find(instances
->begin(), instances
->end(), this));
295 DCHECK(it
!= instances
->end());
296 instances
->erase(it
);
298 if (!close_callback_
.is_null()) {
299 close_callback_
.Run();
300 close_callback_
= base::Closure();
305 void DevToolsWindow::RegisterProfilePrefs(
306 user_prefs::PrefRegistrySyncable
* registry
) {
307 registry
->RegisterDictionaryPref(
308 prefs::kDevToolsEditedFiles
,
309 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
310 registry
->RegisterDictionaryPref(
311 prefs::kDevToolsFileSystemPaths
,
312 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
313 registry
->RegisterStringPref(
314 prefs::kDevToolsAdbKey
, std::string(),
315 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
317 registry
->RegisterBooleanPref(
318 prefs::kDevToolsDiscoverUsbDevicesEnabled
,
320 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
321 registry
->RegisterBooleanPref(
322 prefs::kDevToolsPortForwardingEnabled
,
324 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
325 registry
->RegisterBooleanPref(
326 prefs::kDevToolsPortForwardingDefaultSet
,
328 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
329 registry
->RegisterDictionaryPref(
330 prefs::kDevToolsPortForwardingConfig
,
331 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
335 content::WebContents
* DevToolsWindow::GetInTabWebContents(
336 WebContents
* inspected_web_contents
,
337 DevToolsContentsResizingStrategy
* out_strategy
) {
338 DevToolsWindow
* window
= GetInstanceForInspectedWebContents(
339 inspected_web_contents
);
340 if (!window
|| window
->life_stage_
== kClosing
)
343 // Not yet loaded window is treated as docked, but we should not present it
344 // until we decided on docking.
345 bool is_docked_set
= window
->life_stage_
== kLoadCompleted
||
346 window
->life_stage_
== kIsDockedSet
;
350 // Undocked window should have toolbox web contents.
351 if (!window
->is_docked_
&& !window
->toolbox_web_contents_
)
355 out_strategy
->CopyFrom(window
->contents_resizing_strategy_
);
357 return window
->is_docked_
? window
->main_web_contents_
:
358 window
->toolbox_web_contents_
;
362 DevToolsWindow
* DevToolsWindow::GetInstanceForInspectedWebContents(
363 WebContents
* inspected_web_contents
) {
364 if (!inspected_web_contents
|| g_instances
== NULL
)
366 DevToolsWindows
* instances
= g_instances
.Pointer();
367 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
369 if ((*it
)->GetInspectedWebContents() == inspected_web_contents
)
376 bool DevToolsWindow::IsDevToolsWindow(content::WebContents
* web_contents
) {
377 if (!web_contents
|| g_instances
== NULL
)
379 DevToolsWindows
* instances
= g_instances
.Pointer();
380 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
382 if ((*it
)->main_web_contents_
== web_contents
||
383 (*it
)->toolbox_web_contents_
== web_contents
)
390 void DevToolsWindow::OpenDevToolsWindowForWorker(
392 const scoped_refptr
<DevToolsAgentHost
>& worker_agent
) {
393 DevToolsWindow
* window
= FindDevToolsWindow(worker_agent
.get());
395 window
= DevToolsWindow::CreateDevToolsWindowForWorker(profile
);
398 window
->bindings_
->AttachTo(worker_agent
);
400 window
->ScheduleShow(DevToolsToggleAction::Show());
404 DevToolsWindow
* DevToolsWindow::CreateDevToolsWindowForWorker(
406 content::RecordAction(base::UserMetricsAction("DevTools_InspectWorker"));
407 return Create(profile
, GURL(), NULL
, true, std::string(), false, "");
411 void DevToolsWindow::OpenDevToolsWindow(
412 content::WebContents
* inspected_web_contents
) {
413 ToggleDevToolsWindow(
414 inspected_web_contents
, true, DevToolsToggleAction::Show(), "");
418 void DevToolsWindow::OpenDevToolsWindow(
419 content::WebContents
* inspected_web_contents
,
420 const DevToolsToggleAction
& action
) {
421 ToggleDevToolsWindow(inspected_web_contents
, true, action
, "");
425 void DevToolsWindow::OpenDevToolsWindow(
427 const scoped_refptr
<content::DevToolsAgentHost
>& agent_host
) {
428 DevToolsWindow
* window
= FindDevToolsWindow(agent_host
.get());
430 window
= DevToolsWindow::Create(
431 profile
, GURL(), nullptr, false, std::string(), false, std::string());
434 window
->bindings_
->AttachTo(agent_host
);
436 window
->ScheduleShow(DevToolsToggleAction::Show());
440 void DevToolsWindow::ToggleDevToolsWindow(
442 const DevToolsToggleAction
& action
) {
443 if (action
.type() == DevToolsToggleAction::kToggle
&&
444 browser
->is_devtools()) {
445 browser
->tab_strip_model()->CloseAllTabs();
449 ToggleDevToolsWindow(
450 browser
->tab_strip_model()->GetActiveWebContents(),
451 action
.type() == DevToolsToggleAction::kInspect
,
456 void DevToolsWindow::OpenExternalFrontend(
458 const std::string
& frontend_url
,
459 const scoped_refptr
<content::DevToolsAgentHost
>& agent_host
,
461 DevToolsWindow
* window
= FindDevToolsWindow(agent_host
.get());
463 window
= Create(profile
, GURL(), nullptr, isWorker
,
464 DevToolsUI::GetProxyURL(frontend_url
).spec(), false, std::string());
467 window
->bindings_
->AttachTo(agent_host
);
470 window
->ScheduleShow(DevToolsToggleAction::Show());
474 void DevToolsWindow::ToggleDevToolsWindow(
475 content::WebContents
* inspected_web_contents
,
477 const DevToolsToggleAction
& action
,
478 const std::string
& settings
) {
479 scoped_refptr
<DevToolsAgentHost
> agent(
480 DevToolsAgentHost::GetOrCreateFor(inspected_web_contents
));
481 DevToolsWindow
* window
= FindDevToolsWindow(agent
.get());
482 bool do_open
= force_open
;
484 Profile
* profile
= Profile::FromBrowserContext(
485 inspected_web_contents
->GetBrowserContext());
486 content::RecordAction(
487 base::UserMetricsAction("DevTools_InspectRenderer"));
488 window
= Create(profile
, GURL(), inspected_web_contents
,
489 false, std::string(), true, settings
);
492 window
->bindings_
->AttachTo(agent
.get());
496 // Update toolbar to reflect DevTools changes.
497 window
->UpdateBrowserToolbar();
499 // If window is docked and visible, we hide it on toggle. If window is
500 // undocked, we show (activate) it.
501 if (!window
->is_docked_
|| do_open
)
502 window
->ScheduleShow(action
);
504 window
->CloseWindow();
508 void DevToolsWindow::InspectElement(
509 content::WebContents
* inspected_web_contents
,
512 scoped_refptr
<DevToolsAgentHost
> agent(
513 DevToolsAgentHost::GetOrCreateFor(inspected_web_contents
));
514 agent
->InspectElement(x
, y
);
515 bool should_measure_time
= FindDevToolsWindow(agent
.get()) == NULL
;
516 base::TimeTicks start_time
= base::TimeTicks::Now();
517 // TODO(loislo): we should initiate DevTools window opening from within
518 // renderer. Otherwise, we still can hit a race condition here.
519 OpenDevToolsWindow(inspected_web_contents
);
521 DevToolsWindow
* window
= FindDevToolsWindow(agent
.get());
522 if (should_measure_time
&& window
)
523 window
->inspect_element_start_time_
= start_time
;
526 void DevToolsWindow::ScheduleShow(const DevToolsToggleAction
& action
) {
527 if (life_stage_
== kLoadCompleted
) {
532 // Action will be done only after load completed.
533 action_on_load_
= action
;
536 // No harm to show always-undocked window right away.
538 Show(DevToolsToggleAction::Show());
542 void DevToolsWindow::Show(const DevToolsToggleAction
& action
) {
543 if (life_stage_
== kClosing
)
546 if (action
.type() == DevToolsToggleAction::kNoOp
)
551 Browser
* inspected_browser
= NULL
;
552 int inspected_tab_index
= -1;
553 FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
555 &inspected_tab_index
);
556 DCHECK(inspected_browser
);
557 DCHECK(inspected_tab_index
!= -1);
559 // Tell inspected browser to update splitter and switch to inspected panel.
560 BrowserWindow
* inspected_window
= inspected_browser
->window();
561 main_web_contents_
->SetDelegate(this);
563 TabStripModel
* tab_strip_model
= inspected_browser
->tab_strip_model();
564 tab_strip_model
->ActivateTabAt(inspected_tab_index
, true);
566 inspected_window
->UpdateDevTools();
567 main_web_contents_
->SetInitialFocus();
568 inspected_window
->Show();
569 // On Aura, focusing once is not enough. Do it again.
570 // Note that focusing only here but not before isn't enough either. We just
571 // need to focus twice.
572 main_web_contents_
->SetInitialFocus();
574 PrefsTabHelper::CreateForWebContents(main_web_contents_
);
575 main_web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
581 // Avoid consecutive window switching if the devtools window has been opened
582 // and the Inspect Element shortcut is pressed in the inspected tab.
583 bool should_show_window
=
584 !browser_
|| (action
.type() != DevToolsToggleAction::kInspect
);
587 CreateDevToolsBrowser();
589 if (should_show_window
) {
590 browser_
->window()->Show();
591 main_web_contents_
->SetInitialFocus();
593 if (toolbox_web_contents_
)
594 UpdateBrowserWindow();
600 bool DevToolsWindow::HandleBeforeUnload(WebContents
* frontend_contents
,
601 bool proceed
, bool* proceed_to_fire_unload
) {
602 DevToolsWindow
* window
= AsDevToolsWindow(frontend_contents
);
605 if (!window
->intercepted_page_beforeunload_
)
607 window
->BeforeUnloadFired(frontend_contents
, proceed
,
608 proceed_to_fire_unload
);
613 bool DevToolsWindow::InterceptPageBeforeUnload(WebContents
* contents
) {
614 DevToolsWindow
* window
=
615 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
616 if (!window
|| window
->intercepted_page_beforeunload_
)
619 // Not yet loaded frontend will not handle beforeunload.
620 if (window
->life_stage_
!= kLoadCompleted
)
623 window
->intercepted_page_beforeunload_
= true;
624 // Handle case of devtools inspecting another devtools instance by passing
625 // the call up to the inspecting devtools instance.
626 if (!DevToolsWindow::InterceptPageBeforeUnload(window
->main_web_contents_
)) {
627 window
->main_web_contents_
->DispatchBeforeUnload(false);
633 bool DevToolsWindow::NeedsToInterceptBeforeUnload(
634 WebContents
* contents
) {
635 DevToolsWindow
* window
=
636 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
637 return window
&& !window
->intercepted_page_beforeunload_
;
641 bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(
643 DCHECK(browser
->is_devtools());
644 // When FastUnloadController is used, devtools frontend will be detached
645 // from the browser window at this point which means we've already fired
647 if (browser
->tab_strip_model()->empty())
649 WebContents
* contents
=
650 browser
->tab_strip_model()->GetWebContentsAt(0);
651 DevToolsWindow
* window
= AsDevToolsWindow(contents
);
654 return window
->intercepted_page_beforeunload_
;
658 void DevToolsWindow::OnPageCloseCanceled(WebContents
* contents
) {
659 DevToolsWindow
*window
=
660 DevToolsWindow::GetInstanceForInspectedWebContents(contents
);
663 window
->intercepted_page_beforeunload_
= false;
664 // Propagate to devtools opened on devtools if any.
665 DevToolsWindow::OnPageCloseCanceled(window
->main_web_contents_
);
668 DevToolsWindow::DevToolsWindow(Profile
* profile
,
669 WebContents
* main_web_contents
,
670 DevToolsUIBindings
* bindings
,
671 WebContents
* inspected_web_contents
,
674 main_web_contents_(main_web_contents
),
675 toolbox_web_contents_(nullptr),
680 // This initialization allows external front-end to work without changes.
681 // We don't wait for docking call, but instead immediately show undocked.
682 // Passing "dockSide=undocked" parameter ensures proper UI.
683 life_stage_(can_dock
? kNotLoaded
: kIsDockedSet
),
684 action_on_load_(DevToolsToggleAction::NoOp()),
685 intercepted_page_beforeunload_(false) {
686 // Set up delegate, so we get fully-functional window immediately.
687 // It will not appear in UI though until |life_stage_ == kLoadCompleted|.
688 main_web_contents_
->SetDelegate(this);
689 // Bindings take ownership over devtools as its delegate.
690 bindings_
->SetDelegate(this);
691 // DevTools uses PageZoom::Zoom(), so main_web_contents_ requires a
693 ui_zoom::ZoomController::CreateForWebContents(main_web_contents_
);
694 ui_zoom::ZoomController::FromWebContents(main_web_contents_
)
695 ->SetShowsNotificationBubble(false);
697 g_instances
.Get().push_back(this);
699 // There is no inspected_web_contents in case of various workers.
700 if (inspected_web_contents
)
701 inspected_contents_observer_
.reset(
702 new ObserverWithAccessor(inspected_web_contents
));
704 // Initialize docked page to be of the right size.
705 if (can_dock_
&& inspected_web_contents
) {
706 content::RenderWidgetHostView
* inspected_view
=
707 inspected_web_contents
->GetRenderWidgetHostView();
708 if (inspected_view
&& main_web_contents_
->GetRenderWidgetHostView()) {
709 gfx::Size size
= inspected_view
->GetViewBounds().size();
710 main_web_contents_
->GetRenderWidgetHostView()->SetSize(size
);
714 event_forwarder_
.reset(new DevToolsEventForwarder(this));
718 DevToolsWindow
* DevToolsWindow::Create(
720 const GURL
& frontend_url
,
721 content::WebContents
* inspected_web_contents
,
722 bool shared_worker_frontend
,
723 const std::string
& remote_frontend
,
725 const std::string
& settings
) {
726 // If developer tools disabled by policy don't open the window.
727 if (profile
->GetPrefs()->GetBoolean(prefs::kDevToolsDisabled
))
730 if (inspected_web_contents
) {
731 // Check for a place to dock.
732 Browser
* browser
= NULL
;
734 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents
,
736 browser
->is_type_popup()) {
741 // Create WebContents with devtools.
742 GURL
url(GetDevToolsURL(profile
, frontend_url
,
743 shared_worker_frontend
,
745 can_dock
, settings
));
746 scoped_ptr
<WebContents
> main_web_contents(
747 WebContents::Create(WebContents::CreateParams(profile
)));
748 main_web_contents
->GetController().LoadURL(
749 DevToolsUIBindings::ApplyThemeToURL(profile
, url
), content::Referrer(),
750 ui::PAGE_TRANSITION_AUTO_TOPLEVEL
, std::string());
751 DevToolsUIBindings
* bindings
=
752 DevToolsUIBindings::ForWebContents(main_web_contents
.get());
756 return new DevToolsWindow(profile
, main_web_contents
.release(), bindings
,
757 inspected_web_contents
, can_dock
);
761 GURL
DevToolsWindow::GetDevToolsURL(Profile
* profile
,
762 const GURL
& base_url
,
763 bool shared_worker_frontend
,
764 const std::string
& remote_frontend
,
766 const std::string
& settings
) {
767 // Compatibility errors are encoded with data urls, pass them
768 // through with no decoration.
769 if (base_url
.SchemeIs("data"))
772 std::string
frontend_url(
773 base_url
.is_empty() ? chrome::kChromeUIDevToolsURL
: base_url
.spec());
774 std::string
url_string(
776 ((frontend_url
.find("?") == std::string::npos
) ? "?" : "&"));
777 if (shared_worker_frontend
)
778 url_string
+= "&isSharedWorker=true";
779 if (remote_frontend
.size()) {
780 url_string
+= "&remoteFrontend=true";
781 url_string
+= "&remoteFrontendUrl=" + net::EscapePath(remote_frontend
);
783 url_string
+= "&remoteBase=" + DevToolsUI::GetRemoteBaseURL().spec();
786 url_string
+= "&can_dock=true";
788 url_string
+= "&settings=" + settings
;
789 return GURL(url_string
);
793 DevToolsWindow
* DevToolsWindow::FindDevToolsWindow(
794 DevToolsAgentHost
* agent_host
) {
795 if (!agent_host
|| g_instances
== NULL
)
797 DevToolsWindows
* instances
= g_instances
.Pointer();
798 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
800 if ((*it
)->bindings_
->IsAttachedTo(agent_host
))
807 DevToolsWindow
* DevToolsWindow::AsDevToolsWindow(
808 content::WebContents
* web_contents
) {
809 if (!web_contents
|| g_instances
== NULL
)
811 DevToolsWindows
* instances
= g_instances
.Pointer();
812 for (DevToolsWindows::iterator
it(instances
->begin()); it
!= instances
->end();
814 if ((*it
)->main_web_contents_
== web_contents
)
820 WebContents
* DevToolsWindow::OpenURLFromTab(
822 const content::OpenURLParams
& params
) {
823 DCHECK(source
== main_web_contents_
);
824 if (!params
.url
.SchemeIs(content::kChromeDevToolsScheme
)) {
825 WebContents
* inspected_web_contents
= GetInspectedWebContents();
826 return inspected_web_contents
?
827 inspected_web_contents
->OpenURL(params
) : NULL
;
830 bindings_
->Reattach();
832 content::NavigationController::LoadURLParams
load_url_params(params
.url
);
833 main_web_contents_
->GetController().LoadURLWithParams(load_url_params
);
834 return main_web_contents_
;
837 void DevToolsWindow::ActivateContents(WebContents
* contents
) {
839 WebContents
* inspected_tab
= GetInspectedWebContents();
840 inspected_tab
->GetDelegate()->ActivateContents(inspected_tab
);
841 } else if (browser_
) {
842 browser_
->window()->Activate();
846 void DevToolsWindow::AddNewContents(WebContents
* source
,
847 WebContents
* new_contents
,
848 WindowOpenDisposition disposition
,
849 const gfx::Rect
& initial_rect
,
852 if (new_contents
== toolbox_web_contents_
) {
853 toolbox_web_contents_
->SetDelegate(
854 new DevToolsToolboxDelegate(toolbox_web_contents_
,
855 inspected_contents_observer_
.get()));
856 if (main_web_contents_
->GetRenderWidgetHostView() &&
857 toolbox_web_contents_
->GetRenderWidgetHostView()) {
859 main_web_contents_
->GetRenderWidgetHostView()->GetViewBounds().size();
860 toolbox_web_contents_
->GetRenderWidgetHostView()->SetSize(size
);
862 UpdateBrowserWindow();
866 WebContents
* inspected_web_contents
= GetInspectedWebContents();
867 if (inspected_web_contents
) {
868 inspected_web_contents
->GetDelegate()->AddNewContents(
869 source
, new_contents
, disposition
, initial_rect
, user_gesture
,
874 void DevToolsWindow::WebContentsCreated(WebContents
* source_contents
,
875 int opener_render_frame_id
,
876 const base::string16
& frame_name
,
877 const GURL
& target_url
,
878 WebContents
* new_contents
) {
879 if (target_url
.SchemeIs(content::kChromeDevToolsScheme
) &&
880 target_url
.path().rfind("toolbox.html") != std::string::npos
) {
882 if (toolbox_web_contents_
)
883 delete toolbox_web_contents_
;
884 toolbox_web_contents_
= new_contents
;
888 void DevToolsWindow::CloseContents(WebContents
* source
) {
890 life_stage_
= kClosing
;
891 UpdateBrowserWindow();
892 // In case of docked main_web_contents_, we own it so delete here.
893 // Embedding DevTools window will be deleted as a result of
894 // DevToolsUIBindings destruction.
895 delete main_web_contents_
;
898 void DevToolsWindow::ContentsZoomChange(bool zoom_in
) {
900 ui_zoom::PageZoom::Zoom(main_web_contents_
, zoom_in
? content::PAGE_ZOOM_IN
901 : content::PAGE_ZOOM_OUT
);
904 void DevToolsWindow::BeforeUnloadFired(WebContents
* tab
,
906 bool* proceed_to_fire_unload
) {
907 if (!intercepted_page_beforeunload_
) {
908 // Docked devtools window closed directly.
911 *proceed_to_fire_unload
= proceed
;
913 // Inspected page is attempting to close.
914 WebContents
* inspected_web_contents
= GetInspectedWebContents();
916 inspected_web_contents
->DispatchBeforeUnload(false);
919 inspected_web_contents
->GetDelegate()->BeforeUnloadFired(
920 inspected_web_contents
, false, &should_proceed
);
921 DCHECK(!should_proceed
);
923 *proceed_to_fire_unload
= false;
927 bool DevToolsWindow::PreHandleKeyboardEvent(
929 const content::NativeWebKeyboardEvent
& event
,
930 bool* is_keyboard_shortcut
) {
931 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
932 if (inspected_window
) {
933 return inspected_window
->PreHandleKeyboardEvent(event
,
934 is_keyboard_shortcut
);
939 void DevToolsWindow::HandleKeyboardEvent(
941 const content::NativeWebKeyboardEvent
& event
) {
942 if (event
.windowsKeyCode
== 0x08) {
943 // Do not navigate back in history on Windows (http://crbug.com/74156).
946 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
947 if (inspected_window
)
948 inspected_window
->HandleKeyboardEvent(event
);
951 content::JavaScriptDialogManager
* DevToolsWindow::GetJavaScriptDialogManager(
952 WebContents
* source
) {
953 WebContents
* inspected_web_contents
= GetInspectedWebContents();
954 return (inspected_web_contents
&& inspected_web_contents
->GetDelegate())
955 ? inspected_web_contents
->GetDelegate()
956 ->GetJavaScriptDialogManager(inspected_web_contents
)
957 : content::WebContentsDelegate::GetJavaScriptDialogManager(source
);
960 content::ColorChooser
* DevToolsWindow::OpenColorChooser(
961 WebContents
* web_contents
,
962 SkColor initial_color
,
963 const std::vector
<content::ColorSuggestion
>& suggestions
) {
964 return chrome::ShowColorChooser(web_contents
, initial_color
);
967 void DevToolsWindow::RunFileChooser(WebContents
* web_contents
,
968 const content::FileChooserParams
& params
) {
969 FileSelectHelper::RunFileChooser(web_contents
, params
);
972 void DevToolsWindow::WebContentsFocused(WebContents
* contents
) {
973 Browser
* inspected_browser
= NULL
;
974 int inspected_tab_index
= -1;
975 if (is_docked_
&& FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
977 &inspected_tab_index
))
978 inspected_browser
->window()->WebContentsFocused(contents
);
981 bool DevToolsWindow::PreHandleGestureEvent(
983 const blink::WebGestureEvent
& event
) {
984 // Disable pinch zooming.
985 return event
.type
== blink::WebGestureEvent::GesturePinchBegin
||
986 event
.type
== blink::WebGestureEvent::GesturePinchUpdate
||
987 event
.type
== blink::WebGestureEvent::GesturePinchEnd
;
990 void DevToolsWindow::ActivateWindow() {
991 if (life_stage_
!= kLoadCompleted
)
993 if (is_docked_
&& GetInspectedBrowserWindow())
994 main_web_contents_
->Focus();
995 else if (!is_docked_
&& !browser_
->window()->IsActive())
996 browser_
->window()->Activate();
999 void DevToolsWindow::CloseWindow() {
1001 life_stage_
= kClosing
;
1002 main_web_contents_
->DispatchBeforeUnload(false);
1005 void DevToolsWindow::SetInspectedPageBounds(const gfx::Rect
& rect
) {
1006 DevToolsContentsResizingStrategy
strategy(rect
);
1007 if (contents_resizing_strategy_
.Equals(strategy
))
1010 contents_resizing_strategy_
.CopyFrom(strategy
);
1011 UpdateBrowserWindow();
1014 void DevToolsWindow::InspectElementCompleted() {
1015 if (!inspect_element_start_time_
.is_null()) {
1016 UMA_HISTOGRAM_TIMES("DevTools.InspectElement",
1017 base::TimeTicks::Now() - inspect_element_start_time_
);
1018 inspect_element_start_time_
= base::TimeTicks();
1022 void DevToolsWindow::SetIsDocked(bool dock_requested
) {
1023 if (life_stage_
== kClosing
)
1026 DCHECK(can_dock_
|| !dock_requested
);
1028 dock_requested
= false;
1030 bool was_docked
= is_docked_
;
1031 is_docked_
= dock_requested
;
1033 if (life_stage_
!= kLoadCompleted
) {
1034 // This is a first time call we waited for to initialize.
1035 life_stage_
= life_stage_
== kOnLoadFired
? kLoadCompleted
: kIsDockedSet
;
1036 if (life_stage_
== kLoadCompleted
)
1041 if (dock_requested
== was_docked
)
1044 if (dock_requested
&& !was_docked
) {
1045 // Detach window from the external devtools browser. It will lead to
1046 // the browser object's close and delete. Remove observer first.
1047 TabStripModel
* tab_strip_model
= browser_
->tab_strip_model();
1048 tab_strip_model
->DetachWebContentsAt(
1049 tab_strip_model
->GetIndexOfWebContents(main_web_contents_
));
1051 } else if (!dock_requested
&& was_docked
) {
1052 UpdateBrowserWindow();
1055 Show(DevToolsToggleAction::Show());
1058 void DevToolsWindow::OpenInNewTab(const std::string
& url
) {
1059 content::OpenURLParams
params(
1060 GURL(url
), content::Referrer(), NEW_FOREGROUND_TAB
,
1061 ui::PAGE_TRANSITION_LINK
, false);
1062 WebContents
* inspected_web_contents
= GetInspectedWebContents();
1063 if (!inspected_web_contents
|| !inspected_web_contents
->OpenURL(params
)) {
1064 chrome::HostDesktopType host_desktop_type
=
1065 browser_
? browser_
->host_desktop_type() : chrome::GetActiveDesktop();
1067 chrome::ScopedTabbedBrowserDisplayer
displayer(profile_
, host_desktop_type
);
1068 chrome::AddSelectedTabWithURL(displayer
.browser(), GURL(url
),
1069 ui::PAGE_TRANSITION_LINK
);
1073 void DevToolsWindow::SetWhitelistedShortcuts(
1074 const std::string
& message
) {
1075 event_forwarder_
->SetWhitelistedShortcuts(message
);
1078 void DevToolsWindow::InspectedContentsClosing() {
1079 intercepted_page_beforeunload_
= false;
1080 life_stage_
= kClosing
;
1081 main_web_contents_
->GetRenderViewHost()->ClosePage();
1084 InfoBarService
* DevToolsWindow::GetInfoBarService() {
1086 InfoBarService::FromWebContents(GetInspectedWebContents()) :
1087 InfoBarService::FromWebContents(main_web_contents_
);
1090 void DevToolsWindow::RenderProcessGone(bool crashed
) {
1091 // Docked DevToolsWindow owns its main_web_contents_ and must delete it.
1092 // Undocked main_web_contents_ are owned and handled by browser.
1093 // see crbug.com/369932
1095 CloseContents(main_web_contents_
);
1096 } else if (browser_
&& crashed
) {
1097 browser_
->window()->Close();
1101 void DevToolsWindow::OnLoadCompleted() {
1102 // First seed inspected tab id for extension APIs.
1103 WebContents
* inspected_web_contents
= GetInspectedWebContents();
1104 if (inspected_web_contents
) {
1105 SessionTabHelper
* session_tab_helper
=
1106 SessionTabHelper::FromWebContents(inspected_web_contents
);
1107 if (session_tab_helper
) {
1108 base::FundamentalValue
tabId(session_tab_helper
->session_id().id());
1109 bindings_
->CallClientFunction("DevToolsAPI.setInspectedTabId",
1110 &tabId
, NULL
, NULL
);
1114 if (life_stage_
== kClosing
)
1117 // We could be in kLoadCompleted state already if frontend reloads itself.
1118 if (life_stage_
!= kLoadCompleted
) {
1119 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
1120 // Here we set kOnLoadFired.
1121 life_stage_
= life_stage_
== kIsDockedSet
? kLoadCompleted
: kOnLoadFired
;
1123 if (life_stage_
== kLoadCompleted
)
1127 void DevToolsWindow::CreateDevToolsBrowser() {
1128 PrefService
* prefs
= profile_
->GetPrefs();
1129 if (!prefs
->GetDictionary(prefs::kAppWindowPlacement
)->HasKey(kDevToolsApp
)) {
1130 DictionaryPrefUpdate
update(prefs
, prefs::kAppWindowPlacement
);
1131 base::DictionaryValue
* wp_prefs
= update
.Get();
1132 base::DictionaryValue
* dev_tools_defaults
= new base::DictionaryValue
;
1133 wp_prefs
->Set(kDevToolsApp
, dev_tools_defaults
);
1134 dev_tools_defaults
->SetInteger("left", 100);
1135 dev_tools_defaults
->SetInteger("top", 100);
1136 dev_tools_defaults
->SetInteger("right", 740);
1137 dev_tools_defaults
->SetInteger("bottom", 740);
1138 dev_tools_defaults
->SetBoolean("maximized", false);
1139 dev_tools_defaults
->SetBoolean("always_on_top", false);
1142 browser_
= new Browser(Browser::CreateParams::CreateForDevTools(
1144 chrome::GetHostDesktopTypeForNativeView(
1145 main_web_contents_
->GetNativeView())));
1146 browser_
->tab_strip_model()->AddWebContents(
1147 main_web_contents_
, -1, ui::PAGE_TRANSITION_AUTO_TOPLEVEL
,
1148 TabStripModel::ADD_ACTIVE
);
1149 main_web_contents_
->GetRenderViewHost()->SyncRendererPrefs();
1152 BrowserWindow
* DevToolsWindow::GetInspectedBrowserWindow() {
1153 Browser
* browser
= NULL
;
1155 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1157 browser
->window() : NULL
;
1160 void DevToolsWindow::DoAction(const DevToolsToggleAction
& action
) {
1161 switch (action
.type()) {
1162 case DevToolsToggleAction::kShowConsole
:
1163 bindings_
->CallClientFunction(
1164 "DevToolsAPI.showConsole", NULL
, NULL
, NULL
);
1167 case DevToolsToggleAction::kInspect
:
1168 bindings_
->CallClientFunction(
1169 "DevToolsAPI.enterInspectElementMode", NULL
, NULL
, NULL
);
1172 case DevToolsToggleAction::kShow
:
1173 case DevToolsToggleAction::kToggle
:
1177 case DevToolsToggleAction::kReveal
: {
1178 const DevToolsToggleAction::RevealParams
* params
=
1181 base::StringValue
url_value(params
->url
);
1182 base::FundamentalValue
line_value(static_cast<int>(params
->line_number
));
1183 base::FundamentalValue
column_value(
1184 static_cast<int>(params
->column_number
));
1185 bindings_
->CallClientFunction("DevToolsAPI.revealSourceLine",
1186 &url_value
, &line_value
, &column_value
);
1195 void DevToolsWindow::UpdateBrowserToolbar() {
1196 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
1197 if (inspected_window
)
1198 inspected_window
->UpdateToolbar(NULL
);
1201 void DevToolsWindow::UpdateBrowserWindow() {
1202 BrowserWindow
* inspected_window
= GetInspectedBrowserWindow();
1203 if (inspected_window
)
1204 inspected_window
->UpdateDevTools();
1207 WebContents
* DevToolsWindow::GetInspectedWebContents() {
1208 return inspected_contents_observer_
1209 ? inspected_contents_observer_
->web_contents()
1213 void DevToolsWindow::LoadCompleted() {
1214 Show(action_on_load_
);
1215 action_on_load_
= DevToolsToggleAction::NoOp();
1216 if (!load_completed_callback_
.is_null()) {
1217 load_completed_callback_
.Run();
1218 load_completed_callback_
= base::Closure();
1222 void DevToolsWindow::SetLoadCompletedCallback(const base::Closure
& closure
) {
1223 if (life_stage_
== kLoadCompleted
|| life_stage_
== kClosing
) {
1224 if (!closure
.is_null())
1228 load_completed_callback_
= closure
;
1231 bool DevToolsWindow::ForwardKeyboardEvent(
1232 const content::NativeWebKeyboardEvent
& event
) {
1233 return event_forwarder_
->ForwardEvent(event
);