Check USB device path access when prompting users to select a device.
[chromium-blink-merge.git] / chrome / browser / devtools / devtools_window.cc
blob0d18677c10bc1d84e2cd282bbffe02c78fa3f69a
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"
7 #include <algorithm>
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;
62 namespace {
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)
74 return false;
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) {
80 *browser = *it;
81 *tab = tab_index;
82 return true;
85 return false;
88 // DevToolsToolboxDelegate ----------------------------------------------------
90 class DevToolsToolboxDelegate
91 : public content::WebContentsObserver,
92 public content::WebContentsDelegate {
93 public:
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;
110 private:
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))
131 return NULL;
132 content::NavigationController::LoadURLParams load_url_params(params.url);
133 source->GetController().LoadURLWithParams(load_url_params);
134 return source;
137 bool DevToolsToolboxDelegate::PreHandleKeyboardEvent(
138 content::WebContents* source,
139 const content::NativeWebKeyboardEvent& event,
140 bool* is_keyboard_shortcut) {
141 BrowserWindow* window = GetInspectedBrowserWindow();
142 if (window)
143 return window->PreHandleKeyboardEvent(event, is_keyboard_shortcut);
144 return false;
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).
152 return;
154 BrowserWindow* window = GetInspectedBrowserWindow();
155 if (window)
156 window->HandleKeyboardEvent(event);
159 void DevToolsToolboxDelegate::WebContentsDestroyed() {
160 delete this;
163 BrowserWindow* DevToolsToolboxDelegate::GetInspectedBrowserWindow() {
164 WebContents* inspected_contents =
165 inspected_contents_observer_->web_contents();
166 if (!inspected_contents)
167 return NULL;
168 Browser* browser = NULL;
169 int tab = 0;
170 if (FindInspectedBrowserAndTabIndex(inspected_contents, &browser, &tab))
171 return browser->window();
172 return NULL;
174 } // namespace
176 // DevToolsEventForwarder -----------------------------------------------------
178 class DevToolsEventForwarder {
179 public:
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);
191 private:
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))
206 return;
207 base::ListValue::iterator it = shortcut_list->begin();
208 for (; it != shortcut_list->end(); ++it) {
209 base::DictionaryValue* dictionary;
210 if (!(*it)->GetAsDictionary(&dictionary))
211 continue;
212 int key_code = 0;
213 dictionary->GetInteger("keyCode", &key_code);
214 if (key_code == 0)
215 continue;
216 int modifiers = 0;
217 dictionary->GetInteger("modifiers", &modifiers);
218 if (!KeyWhitelistingAllowed(key_code, modifiers)) {
219 LOG(WARNING) << "Key whitelisting forbidden: "
220 << "(" << key_code << "," << modifiers << ")";
221 continue;
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;
234 break;
235 case WebInputEvent::KeyUp:
236 event_type = kKeyUpEventName;
237 break;
238 default:
239 return false;
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())
246 return false;
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);
255 return true;
258 int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code,
259 int modifiers) {
260 return key_code | (modifiers << 16);
263 bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code,
264 int modifiers) {
265 return (ui::VKEY_F1 <= key_code && key_code <= ui::VKEY_F12) ||
266 modifiers != 0;
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();
304 // static
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,
319 true,
320 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
321 registry->RegisterBooleanPref(
322 prefs::kDevToolsPortForwardingEnabled,
323 false,
324 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
325 registry->RegisterBooleanPref(
326 prefs::kDevToolsPortForwardingDefaultSet,
327 false,
328 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
329 registry->RegisterDictionaryPref(
330 prefs::kDevToolsPortForwardingConfig,
331 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
334 // static
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)
341 return NULL;
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;
347 if (!is_docked_set)
348 return NULL;
350 // Undocked window should have toolbox web contents.
351 if (!window->is_docked_ && !window->toolbox_web_contents_)
352 return NULL;
354 if (out_strategy)
355 out_strategy->CopyFrom(window->contents_resizing_strategy_);
357 return window->is_docked_ ? window->main_web_contents_ :
358 window->toolbox_web_contents_;
361 // static
362 DevToolsWindow* DevToolsWindow::GetInstanceForInspectedWebContents(
363 WebContents* inspected_web_contents) {
364 if (!inspected_web_contents || g_instances == NULL)
365 return NULL;
366 DevToolsWindows* instances = g_instances.Pointer();
367 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
368 ++it) {
369 if ((*it)->GetInspectedWebContents() == inspected_web_contents)
370 return *it;
372 return NULL;
375 // static
376 bool DevToolsWindow::IsDevToolsWindow(content::WebContents* web_contents) {
377 if (!web_contents || g_instances == NULL)
378 return false;
379 DevToolsWindows* instances = g_instances.Pointer();
380 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
381 ++it) {
382 if ((*it)->main_web_contents_ == web_contents ||
383 (*it)->toolbox_web_contents_ == web_contents)
384 return true;
386 return false;
389 // static
390 void DevToolsWindow::OpenDevToolsWindowForWorker(
391 Profile* profile,
392 const scoped_refptr<DevToolsAgentHost>& worker_agent) {
393 DevToolsWindow* window = FindDevToolsWindow(worker_agent.get());
394 if (!window) {
395 window = DevToolsWindow::CreateDevToolsWindowForWorker(profile);
396 if (!window)
397 return;
398 window->bindings_->AttachTo(worker_agent);
400 window->ScheduleShow(DevToolsToggleAction::Show());
403 // static
404 DevToolsWindow* DevToolsWindow::CreateDevToolsWindowForWorker(
405 Profile* profile) {
406 content::RecordAction(base::UserMetricsAction("DevTools_InspectWorker"));
407 return Create(profile, GURL(), NULL, true, std::string(), false, "");
410 // static
411 void DevToolsWindow::OpenDevToolsWindow(
412 content::WebContents* inspected_web_contents) {
413 ToggleDevToolsWindow(
414 inspected_web_contents, true, DevToolsToggleAction::Show(), "");
417 // static
418 void DevToolsWindow::OpenDevToolsWindow(
419 content::WebContents* inspected_web_contents,
420 const DevToolsToggleAction& action) {
421 ToggleDevToolsWindow(inspected_web_contents, true, action, "");
424 // static
425 void DevToolsWindow::OpenDevToolsWindow(
426 Profile* profile,
427 const scoped_refptr<content::DevToolsAgentHost>& agent_host) {
428 DevToolsWindow* window = FindDevToolsWindow(agent_host.get());
429 if (!window) {
430 window = DevToolsWindow::Create(
431 profile, GURL(), nullptr, false, std::string(), false, std::string());
432 if (!window)
433 return;
434 window->bindings_->AttachTo(agent_host);
436 window->ScheduleShow(DevToolsToggleAction::Show());
439 // static
440 void DevToolsWindow::ToggleDevToolsWindow(
441 Browser* browser,
442 const DevToolsToggleAction& action) {
443 if (action.type() == DevToolsToggleAction::kToggle &&
444 browser->is_devtools()) {
445 browser->tab_strip_model()->CloseAllTabs();
446 return;
449 ToggleDevToolsWindow(
450 browser->tab_strip_model()->GetActiveWebContents(),
451 action.type() == DevToolsToggleAction::kInspect,
452 action, "");
455 // static
456 void DevToolsWindow::OpenExternalFrontend(
457 Profile* profile,
458 const std::string& frontend_url,
459 const scoped_refptr<content::DevToolsAgentHost>& agent_host,
460 bool isWorker) {
461 DevToolsWindow* window = FindDevToolsWindow(agent_host.get());
462 if (!window) {
463 window = Create(profile, GURL(), nullptr, isWorker,
464 DevToolsUI::GetProxyURL(frontend_url).spec(), false, std::string());
465 if (!window)
466 return;
467 window->bindings_->AttachTo(agent_host);
470 window->ScheduleShow(DevToolsToggleAction::Show());
473 // static
474 void DevToolsWindow::ToggleDevToolsWindow(
475 content::WebContents* inspected_web_contents,
476 bool force_open,
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;
483 if (!window) {
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);
490 if (!window)
491 return;
492 window->bindings_->AttachTo(agent.get());
493 do_open = true;
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);
503 else
504 window->CloseWindow();
507 // static
508 void DevToolsWindow::InspectElement(
509 content::WebContents* inspected_web_contents,
510 int x,
511 int y) {
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) {
528 Show(action);
529 return;
532 // Action will be done only after load completed.
533 action_on_load_ = action;
535 if (!can_dock_) {
536 // No harm to show always-undocked window right away.
537 is_docked_ = false;
538 Show(DevToolsToggleAction::Show());
542 void DevToolsWindow::Show(const DevToolsToggleAction& action) {
543 if (life_stage_ == kClosing)
544 return;
546 if (action.type() == DevToolsToggleAction::kNoOp)
547 return;
549 if (is_docked_) {
550 DCHECK(can_dock_);
551 Browser* inspected_browser = NULL;
552 int inspected_tab_index = -1;
553 FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
554 &inspected_browser,
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();
577 DoAction(action);
578 return;
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);
586 if (!browser_)
587 CreateDevToolsBrowser();
589 if (should_show_window) {
590 browser_->window()->Show();
591 main_web_contents_->SetInitialFocus();
593 if (toolbox_web_contents_)
594 UpdateBrowserWindow();
596 DoAction(action);
599 // static
600 bool DevToolsWindow::HandleBeforeUnload(WebContents* frontend_contents,
601 bool proceed, bool* proceed_to_fire_unload) {
602 DevToolsWindow* window = AsDevToolsWindow(frontend_contents);
603 if (!window)
604 return false;
605 if (!window->intercepted_page_beforeunload_)
606 return false;
607 window->BeforeUnloadFired(frontend_contents, proceed,
608 proceed_to_fire_unload);
609 return true;
612 // static
613 bool DevToolsWindow::InterceptPageBeforeUnload(WebContents* contents) {
614 DevToolsWindow* window =
615 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
616 if (!window || window->intercepted_page_beforeunload_)
617 return false;
619 // Not yet loaded frontend will not handle beforeunload.
620 if (window->life_stage_ != kLoadCompleted)
621 return false;
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);
629 return true;
632 // static
633 bool DevToolsWindow::NeedsToInterceptBeforeUnload(
634 WebContents* contents) {
635 DevToolsWindow* window =
636 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
637 return window && !window->intercepted_page_beforeunload_;
640 // static
641 bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(
642 Browser* browser) {
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
646 // beforeunload.
647 if (browser->tab_strip_model()->empty())
648 return true;
649 WebContents* contents =
650 browser->tab_strip_model()->GetWebContentsAt(0);
651 DevToolsWindow* window = AsDevToolsWindow(contents);
652 if (!window)
653 return false;
654 return window->intercepted_page_beforeunload_;
657 // static
658 void DevToolsWindow::OnPageCloseCanceled(WebContents* contents) {
659 DevToolsWindow *window =
660 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
661 if (!window)
662 return;
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,
672 bool can_dock)
673 : profile_(profile),
674 main_web_contents_(main_web_contents),
675 toolbox_web_contents_(nullptr),
676 bindings_(bindings),
677 browser_(nullptr),
678 is_docked_(true),
679 can_dock_(can_dock),
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
692 // ZoomController.
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));
717 // static
718 DevToolsWindow* DevToolsWindow::Create(
719 Profile* profile,
720 const GURL& frontend_url,
721 content::WebContents* inspected_web_contents,
722 bool shared_worker_frontend,
723 const std::string& remote_frontend,
724 bool can_dock,
725 const std::string& settings) {
726 // If developer tools disabled by policy don't open the window.
727 if (profile->GetPrefs()->GetBoolean(prefs::kDevToolsDisabled))
728 return nullptr;
730 if (inspected_web_contents) {
731 // Check for a place to dock.
732 Browser* browser = NULL;
733 int tab;
734 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents,
735 &browser, &tab) ||
736 browser->is_type_popup()) {
737 can_dock = false;
741 // Create WebContents with devtools.
742 GURL url(GetDevToolsURL(profile, frontend_url,
743 shared_worker_frontend,
744 remote_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());
753 if (!bindings)
754 return nullptr;
756 return new DevToolsWindow(profile, main_web_contents.release(), bindings,
757 inspected_web_contents, can_dock);
760 // static
761 GURL DevToolsWindow::GetDevToolsURL(Profile* profile,
762 const GURL& base_url,
763 bool shared_worker_frontend,
764 const std::string& remote_frontend,
765 bool can_dock,
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"))
770 return base_url;
772 std::string frontend_url(
773 base_url.is_empty() ? chrome::kChromeUIDevToolsURL : base_url.spec());
774 std::string url_string(
775 frontend_url +
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);
782 } else {
783 url_string += "&remoteBase=" + DevToolsUI::GetRemoteBaseURL().spec();
785 if (can_dock)
786 url_string += "&can_dock=true";
787 if (settings.size())
788 url_string += "&settings=" + settings;
789 return GURL(url_string);
792 // static
793 DevToolsWindow* DevToolsWindow::FindDevToolsWindow(
794 DevToolsAgentHost* agent_host) {
795 if (!agent_host || g_instances == NULL)
796 return NULL;
797 DevToolsWindows* instances = g_instances.Pointer();
798 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
799 ++it) {
800 if ((*it)->bindings_->IsAttachedTo(agent_host))
801 return *it;
803 return NULL;
806 // static
807 DevToolsWindow* DevToolsWindow::AsDevToolsWindow(
808 content::WebContents* web_contents) {
809 if (!web_contents || g_instances == NULL)
810 return NULL;
811 DevToolsWindows* instances = g_instances.Pointer();
812 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
813 ++it) {
814 if ((*it)->main_web_contents_ == web_contents)
815 return *it;
817 return NULL;
820 WebContents* DevToolsWindow::OpenURLFromTab(
821 WebContents* source,
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) {
838 if (is_docked_) {
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,
850 bool user_gesture,
851 bool* was_blocked) {
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()) {
858 gfx::Size size =
859 main_web_contents_->GetRenderWidgetHostView()->GetViewBounds().size();
860 toolbox_web_contents_->GetRenderWidgetHostView()->SetSize(size);
862 UpdateBrowserWindow();
863 return;
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,
870 was_blocked);
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) {
881 CHECK(can_dock_);
882 if (toolbox_web_contents_)
883 delete toolbox_web_contents_;
884 toolbox_web_contents_ = new_contents;
888 void DevToolsWindow::CloseContents(WebContents* source) {
889 CHECK(is_docked_);
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) {
899 DCHECK(is_docked_);
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,
905 bool proceed,
906 bool* proceed_to_fire_unload) {
907 if (!intercepted_page_beforeunload_) {
908 // Docked devtools window closed directly.
909 if (proceed)
910 bindings_->Detach();
911 *proceed_to_fire_unload = proceed;
912 } else {
913 // Inspected page is attempting to close.
914 WebContents* inspected_web_contents = GetInspectedWebContents();
915 if (proceed) {
916 inspected_web_contents->DispatchBeforeUnload(false);
917 } else {
918 bool should_proceed;
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(
928 WebContents* source,
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);
936 return false;
939 void DevToolsWindow::HandleKeyboardEvent(
940 WebContents* source,
941 const content::NativeWebKeyboardEvent& event) {
942 if (event.windowsKeyCode == 0x08) {
943 // Do not navigate back in history on Windows (http://crbug.com/74156).
944 return;
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(),
976 &inspected_browser,
977 &inspected_tab_index))
978 inspected_browser->window()->WebContentsFocused(contents);
981 bool DevToolsWindow::PreHandleGestureEvent(
982 WebContents* source,
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)
992 return;
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() {
1000 DCHECK(is_docked_);
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))
1008 return;
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)
1024 return;
1026 DCHECK(can_dock_ || !dock_requested);
1027 if (!can_dock_)
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)
1037 LoadCompleted();
1038 return;
1041 if (dock_requested == was_docked)
1042 return;
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_));
1050 browser_ = NULL;
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() {
1085 return is_docked_ ?
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
1094 if (is_docked_) {
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)
1115 return;
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)
1124 LoadCompleted();
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(
1143 profile_,
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;
1154 int tab;
1155 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1156 &browser, &tab) ?
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);
1165 break;
1167 case DevToolsToggleAction::kInspect:
1168 bindings_->CallClientFunction(
1169 "DevToolsAPI.enterInspectElementMode", NULL, NULL, NULL);
1170 break;
1172 case DevToolsToggleAction::kShow:
1173 case DevToolsToggleAction::kToggle:
1174 // Do nothing.
1175 break;
1177 case DevToolsToggleAction::kReveal: {
1178 const DevToolsToggleAction::RevealParams* params =
1179 action.params();
1180 CHECK(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);
1187 break;
1189 default:
1190 NOTREACHED();
1191 break;
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()
1210 : NULL;
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())
1225 closure.Run();
1226 return;
1228 load_completed_callback_ = closure;
1231 bool DevToolsWindow::ForwardKeyboardEvent(
1232 const content::NativeWebKeyboardEvent& event) {
1233 return event_forwarder_->ForwardEvent(event);