Add more checks to investigate SupervisedUserPrefStore crash at startup.
[chromium-blink-merge.git] / chrome / browser / devtools / devtools_window.cc
blobb7f0f9dcd6f3ca9de23c95eb4b1559e18a74749f
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;
175 } // namespace
177 // DevToolsEventForwarder -----------------------------------------------------
179 class DevToolsEventForwarder {
180 public:
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);
192 private:
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))
207 return;
208 base::ListValue::iterator it = shortcut_list->begin();
209 for (; it != shortcut_list->end(); ++it) {
210 base::DictionaryValue* dictionary;
211 if (!(*it)->GetAsDictionary(&dictionary))
212 continue;
213 int key_code = 0;
214 dictionary->GetInteger("keyCode", &key_code);
215 if (key_code == 0)
216 continue;
217 int modifiers = 0;
218 dictionary->GetInteger("modifiers", &modifiers);
219 if (!KeyWhitelistingAllowed(key_code, modifiers)) {
220 LOG(WARNING) << "Key whitelisting forbidden: "
221 << "(" << key_code << "," << modifiers << ")";
222 continue;
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;
235 break;
236 case WebInputEvent::KeyUp:
237 event_type = kKeyUpEventName;
238 break;
239 default:
240 return false;
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())
247 return false;
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);
256 return true;
259 int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code,
260 int modifiers) {
261 return key_code | (modifiers << 16);
264 bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code,
265 int modifiers) {
266 return (ui::VKEY_F1 <= key_code && key_code <= ui::VKEY_F12) ||
267 modifiers != 0;
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();
305 // static
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,
320 true,
321 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
322 registry->RegisterBooleanPref(
323 prefs::kDevToolsPortForwardingEnabled,
324 false,
325 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
326 registry->RegisterBooleanPref(
327 prefs::kDevToolsPortForwardingDefaultSet,
328 false,
329 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
330 registry->RegisterDictionaryPref(
331 prefs::kDevToolsPortForwardingConfig,
332 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
335 // static
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)
342 return NULL;
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;
348 if (!is_docked_set)
349 return NULL;
351 // Undocked window should have toolbox web contents.
352 if (!window->is_docked_ && !window->toolbox_web_contents_)
353 return NULL;
355 if (out_strategy)
356 out_strategy->CopyFrom(window->contents_resizing_strategy_);
358 return window->is_docked_ ? window->main_web_contents_ :
359 window->toolbox_web_contents_;
362 // static
363 DevToolsWindow* DevToolsWindow::GetInstanceForInspectedWebContents(
364 WebContents* inspected_web_contents) {
365 if (!inspected_web_contents || g_instances == NULL)
366 return NULL;
367 DevToolsWindows* instances = g_instances.Pointer();
368 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
369 ++it) {
370 if ((*it)->GetInspectedWebContents() == inspected_web_contents)
371 return *it;
373 return NULL;
376 // static
377 bool DevToolsWindow::IsDevToolsWindow(content::WebContents* web_contents) {
378 if (!web_contents || g_instances == NULL)
379 return false;
380 DevToolsWindows* instances = g_instances.Pointer();
381 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
382 ++it) {
383 if ((*it)->main_web_contents_ == web_contents ||
384 (*it)->toolbox_web_contents_ == web_contents)
385 return true;
387 return false;
390 // static
391 DevToolsWindow* DevToolsWindow::OpenDevToolsWindowForWorker(
392 Profile* profile,
393 const scoped_refptr<DevToolsAgentHost>& worker_agent) {
394 DevToolsWindow* window = FindDevToolsWindow(worker_agent.get());
395 if (!window) {
396 window = DevToolsWindow::CreateDevToolsWindowForWorker(profile);
397 DCHECK(window);
398 window->bindings_->AttachTo(worker_agent);
400 window->ScheduleShow(DevToolsToggleAction::Show());
401 return window;
404 // static
405 DevToolsWindow* DevToolsWindow::CreateDevToolsWindowForWorker(
406 Profile* profile) {
407 content::RecordAction(base::UserMetricsAction("DevTools_InspectWorker"));
408 return Create(profile, GURL(), NULL, true, std::string(), false, "");
411 // static
412 DevToolsWindow* DevToolsWindow::OpenDevToolsWindow(
413 content::WebContents* inspected_web_contents) {
414 return ToggleDevToolsWindow(
415 inspected_web_contents, true, DevToolsToggleAction::Show(), "");
418 // static
419 DevToolsWindow* DevToolsWindow::OpenDevToolsWindow(
420 content::WebContents* inspected_web_contents,
421 const DevToolsToggleAction& action) {
422 return ToggleDevToolsWindow(inspected_web_contents, true, action, "");
425 // static
426 DevToolsWindow* DevToolsWindow::ToggleDevToolsWindow(
427 Browser* browser,
428 const DevToolsToggleAction& action) {
429 if (action.type() == DevToolsToggleAction::kToggle &&
430 browser->is_devtools()) {
431 browser->tab_strip_model()->CloseAllTabs();
432 return NULL;
435 return ToggleDevToolsWindow(
436 browser->tab_strip_model()->GetActiveWebContents(),
437 action.type() == DevToolsToggleAction::kInspect,
438 action, "");
441 // static
442 void DevToolsWindow::OpenExternalFrontend(
443 Profile* profile,
444 const std::string& frontend_url,
445 const scoped_refptr<content::DevToolsAgentHost>& agent_host,
446 bool isWorker) {
447 DevToolsWindow* window = FindDevToolsWindow(agent_host.get());
448 if (!window) {
449 window = Create(profile, GURL(), nullptr, isWorker,
450 DevToolsUI::GetProxyURL(frontend_url).spec(), false, std::string());
451 if (window)
452 window->bindings_->AttachTo(agent_host);
454 if (window)
455 window->ScheduleShow(DevToolsToggleAction::Show());
458 // static
459 DevToolsWindow* DevToolsWindow::ToggleDevToolsWindow(
460 content::WebContents* inspected_web_contents,
461 bool force_open,
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;
468 if (!window) {
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);
475 DCHECK(window);
476 window->bindings_->AttachTo(agent.get());
477 do_open = true;
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);
487 else
488 window->CloseWindow();
490 return window;
493 // static
494 void DevToolsWindow::InspectElement(
495 content::WebContents* inspected_web_contents,
496 int x,
497 int y) {
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) {
512 Show(action);
513 return;
516 // Action will be done only after load completed.
517 action_on_load_ = action;
519 if (!can_dock_) {
520 // No harm to show always-undocked window right away.
521 is_docked_ = false;
522 Show(DevToolsToggleAction::Show());
526 void DevToolsWindow::Show(const DevToolsToggleAction& action) {
527 if (life_stage_ == kClosing)
528 return;
530 if (action.type() == DevToolsToggleAction::kNoOp)
531 return;
533 if (is_docked_) {
534 DCHECK(can_dock_);
535 Browser* inspected_browser = NULL;
536 int inspected_tab_index = -1;
537 FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
538 &inspected_browser,
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();
561 DoAction(action);
562 return;
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);
570 if (!browser_)
571 CreateDevToolsBrowser();
573 if (should_show_window) {
574 browser_->window()->Show();
575 main_web_contents_->SetInitialFocus();
577 if (toolbox_web_contents_)
578 UpdateBrowserWindow();
580 DoAction(action);
583 // static
584 bool DevToolsWindow::HandleBeforeUnload(WebContents* frontend_contents,
585 bool proceed, bool* proceed_to_fire_unload) {
586 DevToolsWindow* window = AsDevToolsWindow(frontend_contents);
587 if (!window)
588 return false;
589 if (!window->intercepted_page_beforeunload_)
590 return false;
591 window->BeforeUnloadFired(frontend_contents, proceed,
592 proceed_to_fire_unload);
593 return true;
596 // static
597 bool DevToolsWindow::InterceptPageBeforeUnload(WebContents* contents) {
598 DevToolsWindow* window =
599 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
600 if (!window || window->intercepted_page_beforeunload_)
601 return false;
603 // Not yet loaded frontend will not handle beforeunload.
604 if (window->life_stage_ != kLoadCompleted)
605 return false;
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);
613 return true;
616 // static
617 bool DevToolsWindow::NeedsToInterceptBeforeUnload(
618 WebContents* contents) {
619 DevToolsWindow* window =
620 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
621 return window && !window->intercepted_page_beforeunload_;
624 // static
625 bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(
626 Browser* browser) {
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
630 // beforeunload.
631 if (browser->tab_strip_model()->empty())
632 return true;
633 WebContents* contents =
634 browser->tab_strip_model()->GetWebContentsAt(0);
635 DevToolsWindow* window = AsDevToolsWindow(contents);
636 if (!window)
637 return false;
638 return window->intercepted_page_beforeunload_;
641 // static
642 void DevToolsWindow::OnPageCloseCanceled(WebContents* contents) {
643 DevToolsWindow *window =
644 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
645 if (!window)
646 return;
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,
656 bool can_dock)
657 : profile_(profile),
658 main_web_contents_(main_web_contents),
659 toolbox_web_contents_(nullptr),
660 bindings_(bindings),
661 browser_(nullptr),
662 is_docked_(true),
663 can_dock_(can_dock),
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
676 // ZoomController.
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));
701 // static
702 DevToolsWindow* DevToolsWindow::Create(
703 Profile* profile,
704 const GURL& frontend_url,
705 content::WebContents* inspected_web_contents,
706 bool shared_worker_frontend,
707 const std::string& remote_frontend,
708 bool can_dock,
709 const std::string& settings) {
710 if (inspected_web_contents) {
711 // Check for a place to dock.
712 Browser* browser = NULL;
713 int tab;
714 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents,
715 &browser, &tab) ||
716 browser->is_type_popup()) {
717 can_dock = false;
721 // Create WebContents with devtools.
722 GURL url(GetDevToolsURL(profile, frontend_url,
723 shared_worker_frontend,
724 remote_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());
733 if (!bindings)
734 return nullptr;
736 return new DevToolsWindow(profile, main_web_contents.release(), bindings,
737 inspected_web_contents, can_dock);
740 // static
741 GURL DevToolsWindow::GetDevToolsURL(Profile* profile,
742 const GURL& base_url,
743 bool shared_worker_frontend,
744 const std::string& remote_frontend,
745 bool can_dock,
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"))
750 return base_url;
752 std::string frontend_url(
753 base_url.is_empty() ? chrome::kChromeUIDevToolsURL : base_url.spec());
754 std::string url_string(
755 frontend_url +
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);
763 if (can_dock)
764 url_string += "&can_dock=true";
765 if (settings.size())
766 url_string += "&settings=" + settings;
767 return GURL(url_string);
770 // static
771 DevToolsWindow* DevToolsWindow::FindDevToolsWindow(
772 DevToolsAgentHost* agent_host) {
773 if (!agent_host || g_instances == NULL)
774 return NULL;
775 DevToolsWindows* instances = g_instances.Pointer();
776 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
777 ++it) {
778 if ((*it)->bindings_->IsAttachedTo(agent_host))
779 return *it;
781 return NULL;
784 // static
785 DevToolsWindow* DevToolsWindow::AsDevToolsWindow(
786 content::WebContents* web_contents) {
787 if (!web_contents || g_instances == NULL)
788 return NULL;
789 DevToolsWindows* instances = g_instances.Pointer();
790 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
791 ++it) {
792 if ((*it)->main_web_contents_ == web_contents)
793 return *it;
795 return NULL;
798 WebContents* DevToolsWindow::OpenURLFromTab(
799 WebContents* source,
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) {
816 if (is_docked_) {
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,
828 bool user_gesture,
829 bool* was_blocked) {
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()) {
836 gfx::Size size =
837 main_web_contents_->GetRenderWidgetHostView()->GetViewBounds().size();
838 toolbox_web_contents_->GetRenderWidgetHostView()->SetSize(size);
840 UpdateBrowserWindow();
841 return;
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,
848 was_blocked);
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) {
859 CHECK(can_dock_);
860 toolbox_web_contents_ = new_contents;
864 void DevToolsWindow::CloseContents(WebContents* source) {
865 CHECK(is_docked_);
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) {
875 DCHECK(is_docked_);
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,
881 bool proceed,
882 bool* proceed_to_fire_unload) {
883 if (!intercepted_page_beforeunload_) {
884 // Docked devtools window closed directly.
885 if (proceed)
886 bindings_->Detach();
887 *proceed_to_fire_unload = proceed;
888 } else {
889 // Inspected page is attempting to close.
890 WebContents* inspected_web_contents = GetInspectedWebContents();
891 if (proceed) {
892 inspected_web_contents->DispatchBeforeUnload(false);
893 } else {
894 bool should_proceed;
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(
904 WebContents* source,
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);
912 return false;
915 void DevToolsWindow::HandleKeyboardEvent(
916 WebContents* source,
917 const content::NativeWebKeyboardEvent& event) {
918 if (event.windowsKeyCode == 0x08) {
919 // Do not navigate back in history on Windows (http://crbug.com/74156).
920 return;
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(),
952 &inspected_browser,
953 &inspected_tab_index))
954 inspected_browser->window()->WebContentsFocused(contents);
957 bool DevToolsWindow::PreHandleGestureEvent(
958 WebContents* source,
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)
968 return;
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() {
976 DCHECK(is_docked_);
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))
984 return;
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)
1000 return;
1002 DCHECK(can_dock_ || !dock_requested);
1003 if (!can_dock_)
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)
1013 LoadCompleted();
1014 return;
1017 if (dock_requested == was_docked)
1018 return;
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_));
1026 browser_ = NULL;
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() {
1061 return is_docked_ ?
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
1070 if (is_docked_) {
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)
1091 return;
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)
1100 LoadCompleted();
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(
1119 profile_,
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;
1130 int tab;
1131 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1132 &browser, &tab) ?
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);
1141 break;
1143 case DevToolsToggleAction::kInspect:
1144 bindings_->CallClientFunction(
1145 "DevToolsAPI.enterInspectElementMode", NULL, NULL, NULL);
1146 break;
1148 case DevToolsToggleAction::kShow:
1149 case DevToolsToggleAction::kToggle:
1150 // Do nothing.
1151 break;
1153 case DevToolsToggleAction::kReveal: {
1154 const DevToolsToggleAction::RevealParams* params =
1155 action.params();
1156 CHECK(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);
1163 break;
1165 default:
1166 NOTREACHED();
1167 break;
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()
1186 : NULL;
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())
1201 closure.Run();
1202 return;
1204 load_completed_callback_ = closure;
1207 bool DevToolsWindow::ForwardKeyboardEvent(
1208 const content::NativeWebKeyboardEvent& event) {
1209 return event_forwarder_->ForwardEvent(event);