Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / devtools / devtools_window.cc
blob3a85e71cf2f418bbf9b4150db994e3ddce5ff832
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/command_line.h"
10 #include "base/json/json_reader.h"
11 #include "base/metrics/histogram.h"
12 #include "base/prefs/scoped_user_pref_update.h"
13 #include "base/time/time.h"
14 #include "base/values.h"
15 #include "chrome/browser/certificate_viewer.h"
16 #include "chrome/browser/file_select_helper.h"
17 #include "chrome/browser/infobars/infobar_service.h"
18 #include "chrome/browser/prefs/pref_service_syncable.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/sessions/session_tab_helper.h"
21 #include "chrome/browser/task_management/web_contents_tags.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/browser_dialogs.h"
24 #include "chrome/browser/ui/browser_iterator.h"
25 #include "chrome/browser/ui/browser_list.h"
26 #include "chrome/browser/ui/browser_tabstrip.h"
27 #include "chrome/browser/ui/browser_window.h"
28 #include "chrome/browser/ui/host_desktop.h"
29 #include "chrome/browser/ui/prefs/prefs_tab_helper.h"
30 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
31 #include "chrome/browser/ui/tabs/tab_strip_model.h"
32 #include "chrome/browser/ui/webui/devtools_ui.h"
33 #include "chrome/common/chrome_switches.h"
34 #include "chrome/common/pref_names.h"
35 #include "chrome/common/url_constants.h"
36 #include "components/pref_registry/pref_registry_syncable.h"
37 #include "components/ui/zoom/page_zoom.h"
38 #include "components/ui/zoom/zoom_controller.h"
39 #include "content/public/browser/browser_thread.h"
40 #include "content/public/browser/devtools_agent_host.h"
41 #include "content/public/browser/native_web_keyboard_event.h"
42 #include "content/public/browser/navigation_controller.h"
43 #include "content/public/browser/navigation_entry.h"
44 #include "content/public/browser/render_frame_host.h"
45 #include "content/public/browser/render_process_host.h"
46 #include "content/public/browser/render_view_host.h"
47 #include "content/public/browser/render_widget_host_view.h"
48 #include "content/public/browser/user_metrics.h"
49 #include "content/public/browser/web_contents.h"
50 #include "content/public/common/content_client.h"
51 #include "content/public/common/url_constants.h"
52 #include "net/base/escape.h"
53 #include "third_party/WebKit/public/web/WebInputEvent.h"
54 #include "ui/base/page_transition_types.h"
55 #include "ui/events/keycodes/keyboard_code_conversion.h"
56 #include "ui/events/keycodes/keyboard_codes.h"
58 using base::DictionaryValue;
59 using blink::WebInputEvent;
60 using content::BrowserThread;
61 using content::DevToolsAgentHost;
62 using content::WebContents;
64 namespace {
66 typedef std::vector<DevToolsWindow*> DevToolsWindows;
67 base::LazyInstance<DevToolsWindows>::Leaky g_instances =
68 LAZY_INSTANCE_INITIALIZER;
70 static const char kKeyUpEventName[] = "keyup";
71 static const char kKeyDownEventName[] = "keydown";
73 bool FindInspectedBrowserAndTabIndex(
74 WebContents* inspected_web_contents, Browser** browser, int* tab) {
75 if (!inspected_web_contents)
76 return false;
78 for (chrome::BrowserIterator it; !it.done(); it.Next()) {
79 int tab_index = it->tab_strip_model()->GetIndexOfWebContents(
80 inspected_web_contents);
81 if (tab_index != TabStripModel::kNoTab) {
82 *browser = *it;
83 *tab = tab_index;
84 return true;
87 return false;
90 // DevToolsToolboxDelegate ----------------------------------------------------
92 class DevToolsToolboxDelegate
93 : public content::WebContentsObserver,
94 public content::WebContentsDelegate {
95 public:
96 DevToolsToolboxDelegate(
97 WebContents* toolbox_contents,
98 DevToolsWindow::ObserverWithAccessor* web_contents_observer);
99 ~DevToolsToolboxDelegate() override;
101 content::WebContents* OpenURLFromTab(
102 content::WebContents* source,
103 const content::OpenURLParams& params) override;
104 bool PreHandleKeyboardEvent(content::WebContents* source,
105 const content::NativeWebKeyboardEvent& event,
106 bool* is_keyboard_shortcut) override;
107 void HandleKeyboardEvent(
108 content::WebContents* source,
109 const content::NativeWebKeyboardEvent& event) override;
110 void WebContentsDestroyed() override;
112 private:
113 BrowserWindow* GetInspectedBrowserWindow();
114 DevToolsWindow::ObserverWithAccessor* inspected_contents_observer_;
115 DISALLOW_COPY_AND_ASSIGN(DevToolsToolboxDelegate);
118 DevToolsToolboxDelegate::DevToolsToolboxDelegate(
119 WebContents* toolbox_contents,
120 DevToolsWindow::ObserverWithAccessor* web_contents_observer)
121 : WebContentsObserver(toolbox_contents),
122 inspected_contents_observer_(web_contents_observer) {
125 DevToolsToolboxDelegate::~DevToolsToolboxDelegate() {
128 content::WebContents* DevToolsToolboxDelegate::OpenURLFromTab(
129 content::WebContents* source,
130 const content::OpenURLParams& params) {
131 DCHECK(source == web_contents());
132 if (!params.url.SchemeIs(content::kChromeDevToolsScheme))
133 return NULL;
134 content::NavigationController::LoadURLParams load_url_params(params.url);
135 source->GetController().LoadURLWithParams(load_url_params);
136 return source;
139 bool DevToolsToolboxDelegate::PreHandleKeyboardEvent(
140 content::WebContents* source,
141 const content::NativeWebKeyboardEvent& event,
142 bool* is_keyboard_shortcut) {
143 BrowserWindow* window = GetInspectedBrowserWindow();
144 if (window)
145 return window->PreHandleKeyboardEvent(event, is_keyboard_shortcut);
146 return false;
149 void DevToolsToolboxDelegate::HandleKeyboardEvent(
150 content::WebContents* source,
151 const content::NativeWebKeyboardEvent& event) {
152 if (event.windowsKeyCode == 0x08) {
153 // Do not navigate back in history on Windows (http://crbug.com/74156).
154 return;
156 BrowserWindow* window = GetInspectedBrowserWindow();
157 if (window)
158 window->HandleKeyboardEvent(event);
161 void DevToolsToolboxDelegate::WebContentsDestroyed() {
162 delete this;
165 BrowserWindow* DevToolsToolboxDelegate::GetInspectedBrowserWindow() {
166 WebContents* inspected_contents =
167 inspected_contents_observer_->web_contents();
168 if (!inspected_contents)
169 return NULL;
170 Browser* browser = NULL;
171 int tab = 0;
172 if (FindInspectedBrowserAndTabIndex(inspected_contents, &browser, &tab))
173 return browser->window();
174 return NULL;
177 // static
178 GURL DecorateFrontendURL(const GURL& base_url) {
179 std::string frontend_url = base_url.spec();
180 std::string url_string(
181 frontend_url +
182 ((frontend_url.find("?") == std::string::npos) ? "?" : "&") +
183 "dockSide=undocked"); // TODO(dgozman): remove this support in M38.
184 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
185 switches::kEnableDevToolsExperiments))
186 url_string += "&experiments=true";
187 #if defined(DEBUG_DEVTOOLS)
188 url_string += "&debugFrontend=true";
189 #endif // defined(DEBUG_DEVTOOLS)
190 return GURL(url_string);
193 } // namespace
195 // DevToolsEventForwarder -----------------------------------------------------
197 class DevToolsEventForwarder {
198 public:
199 explicit DevToolsEventForwarder(DevToolsWindow* window)
200 : devtools_window_(window) {}
202 // Registers whitelisted shortcuts with the forwarder.
203 // Only registered keys will be forwarded to the DevTools frontend.
204 void SetWhitelistedShortcuts(const std::string& message);
206 // Forwards a keyboard event to the DevTools frontend if it is whitelisted.
207 // Returns |true| if the event has been forwarded, |false| otherwise.
208 bool ForwardEvent(const content::NativeWebKeyboardEvent& event);
210 private:
211 static bool KeyWhitelistingAllowed(int key_code, int modifiers);
212 static int CombineKeyCodeAndModifiers(int key_code, int modifiers);
214 DevToolsWindow* devtools_window_;
215 std::set<int> whitelisted_keys_;
217 DISALLOW_COPY_AND_ASSIGN(DevToolsEventForwarder);
220 void DevToolsEventForwarder::SetWhitelistedShortcuts(
221 const std::string& message) {
222 scoped_ptr<base::Value> parsed_message = base::JSONReader::Read(message);
223 base::ListValue* shortcut_list;
224 if (!parsed_message->GetAsList(&shortcut_list))
225 return;
226 base::ListValue::iterator it = shortcut_list->begin();
227 for (; it != shortcut_list->end(); ++it) {
228 base::DictionaryValue* dictionary;
229 if (!(*it)->GetAsDictionary(&dictionary))
230 continue;
231 int key_code = 0;
232 dictionary->GetInteger("keyCode", &key_code);
233 if (key_code == 0)
234 continue;
235 int modifiers = 0;
236 dictionary->GetInteger("modifiers", &modifiers);
237 if (!KeyWhitelistingAllowed(key_code, modifiers)) {
238 LOG(WARNING) << "Key whitelisting forbidden: "
239 << "(" << key_code << "," << modifiers << ")";
240 continue;
242 whitelisted_keys_.insert(CombineKeyCodeAndModifiers(key_code, modifiers));
246 bool DevToolsEventForwarder::ForwardEvent(
247 const content::NativeWebKeyboardEvent& event) {
248 std::string event_type;
249 switch (event.type) {
250 case WebInputEvent::KeyDown:
251 case WebInputEvent::RawKeyDown:
252 event_type = kKeyDownEventName;
253 break;
254 case WebInputEvent::KeyUp:
255 event_type = kKeyUpEventName;
256 break;
257 default:
258 return false;
261 int key_code = ui::LocatedToNonLocatedKeyboardCode(
262 static_cast<ui::KeyboardCode>(event.windowsKeyCode));
263 int key = CombineKeyCodeAndModifiers(key_code, event.modifiers);
264 if (whitelisted_keys_.find(key) == whitelisted_keys_.end())
265 return false;
267 base::DictionaryValue event_data;
268 event_data.SetString("type", event_type);
269 event_data.SetString("keyIdentifier", event.keyIdentifier);
270 event_data.SetInteger("keyCode", key_code);
271 event_data.SetInteger("modifiers", event.modifiers);
272 devtools_window_->bindings_->CallClientFunction(
273 "DevToolsAPI.keyEventUnhandled", &event_data, NULL, NULL);
274 return true;
277 int DevToolsEventForwarder::CombineKeyCodeAndModifiers(int key_code,
278 int modifiers) {
279 return key_code | (modifiers << 16);
282 bool DevToolsEventForwarder::KeyWhitelistingAllowed(int key_code,
283 int modifiers) {
284 return (ui::VKEY_F1 <= key_code && key_code <= ui::VKEY_F12) ||
285 modifiers != 0;
288 // DevToolsWindow::ObserverWithAccessor -------------------------------
290 DevToolsWindow::ObserverWithAccessor::ObserverWithAccessor(
291 WebContents* web_contents)
292 : WebContentsObserver(web_contents) {
295 DevToolsWindow::ObserverWithAccessor::~ObserverWithAccessor() {
298 // DevToolsWindow -------------------------------------------------------------
300 const char DevToolsWindow::kDevToolsApp[] = "DevToolsApp";
302 DevToolsWindow::~DevToolsWindow() {
303 life_stage_ = kClosing;
305 UpdateBrowserWindow();
306 UpdateBrowserToolbar();
308 if (toolbox_web_contents_)
309 delete toolbox_web_contents_;
311 DevToolsWindows* instances = g_instances.Pointer();
312 DevToolsWindows::iterator it(
313 std::find(instances->begin(), instances->end(), this));
314 DCHECK(it != instances->end());
315 instances->erase(it);
317 if (!close_callback_.is_null()) {
318 close_callback_.Run();
319 close_callback_ = base::Closure();
323 // static
324 void DevToolsWindow::RegisterProfilePrefs(
325 user_prefs::PrefRegistrySyncable* registry) {
326 registry->RegisterDictionaryPref(prefs::kDevToolsEditedFiles);
327 registry->RegisterDictionaryPref(prefs::kDevToolsFileSystemPaths);
328 registry->RegisterStringPref(prefs::kDevToolsAdbKey, std::string());
330 registry->RegisterBooleanPref(prefs::kDevToolsDiscoverUsbDevicesEnabled,
331 true);
332 registry->RegisterBooleanPref(prefs::kDevToolsPortForwardingEnabled, false);
333 registry->RegisterBooleanPref(prefs::kDevToolsPortForwardingDefaultSet,
334 false);
335 registry->RegisterDictionaryPref(prefs::kDevToolsPortForwardingConfig);
336 registry->RegisterDictionaryPref(prefs::kDevToolsPreferences);
339 // static
340 content::WebContents* DevToolsWindow::GetInTabWebContents(
341 WebContents* inspected_web_contents,
342 DevToolsContentsResizingStrategy* out_strategy) {
343 DevToolsWindow* window = GetInstanceForInspectedWebContents(
344 inspected_web_contents);
345 if (!window || window->life_stage_ == kClosing)
346 return NULL;
348 // Not yet loaded window is treated as docked, but we should not present it
349 // until we decided on docking.
350 bool is_docked_set = window->life_stage_ == kLoadCompleted ||
351 window->life_stage_ == kIsDockedSet;
352 if (!is_docked_set)
353 return NULL;
355 // Undocked window should have toolbox web contents.
356 if (!window->is_docked_ && !window->toolbox_web_contents_)
357 return NULL;
359 if (out_strategy)
360 out_strategy->CopyFrom(window->contents_resizing_strategy_);
362 return window->is_docked_ ? window->main_web_contents_ :
363 window->toolbox_web_contents_;
366 // static
367 DevToolsWindow* DevToolsWindow::GetInstanceForInspectedWebContents(
368 WebContents* inspected_web_contents) {
369 if (!inspected_web_contents || g_instances == NULL)
370 return NULL;
371 DevToolsWindows* instances = g_instances.Pointer();
372 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
373 ++it) {
374 if ((*it)->GetInspectedWebContents() == inspected_web_contents)
375 return *it;
377 return NULL;
380 // static
381 bool DevToolsWindow::IsDevToolsWindow(content::WebContents* web_contents) {
382 if (!web_contents || g_instances == NULL)
383 return false;
384 DevToolsWindows* instances = g_instances.Pointer();
385 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
386 ++it) {
387 if ((*it)->main_web_contents_ == web_contents ||
388 (*it)->toolbox_web_contents_ == web_contents)
389 return true;
391 return false;
394 // static
395 void DevToolsWindow::OpenDevToolsWindowForWorker(
396 Profile* profile,
397 const scoped_refptr<DevToolsAgentHost>& worker_agent) {
398 DevToolsWindow* window = FindDevToolsWindow(worker_agent.get());
399 if (!window) {
400 window = DevToolsWindow::CreateDevToolsWindowForWorker(profile);
401 if (!window)
402 return;
403 window->bindings_->AttachTo(worker_agent);
405 window->ScheduleShow(DevToolsToggleAction::Show());
408 // static
409 DevToolsWindow* DevToolsWindow::CreateDevToolsWindowForWorker(
410 Profile* profile) {
411 content::RecordAction(base::UserMetricsAction("DevTools_InspectWorker"));
412 return Create(profile, GURL(), NULL, true, std::string(), false, "");
415 // static
416 void DevToolsWindow::OpenDevToolsWindow(
417 content::WebContents* inspected_web_contents) {
418 ToggleDevToolsWindow(
419 inspected_web_contents, true, DevToolsToggleAction::Show(), "");
422 // static
423 void DevToolsWindow::OpenDevToolsWindow(
424 content::WebContents* inspected_web_contents,
425 const DevToolsToggleAction& action) {
426 ToggleDevToolsWindow(inspected_web_contents, true, action, "");
429 // static
430 void DevToolsWindow::OpenDevToolsWindow(
431 Profile* profile,
432 const scoped_refptr<content::DevToolsAgentHost>& agent_host) {
433 DevToolsWindow* window = FindDevToolsWindow(agent_host.get());
434 if (!window) {
435 window = DevToolsWindow::Create(
436 profile, GURL(), nullptr, false, std::string(), false, std::string());
437 if (!window)
438 return;
439 window->bindings_->AttachTo(agent_host);
441 window->ScheduleShow(DevToolsToggleAction::Show());
444 // static
445 void DevToolsWindow::ToggleDevToolsWindow(
446 Browser* browser,
447 const DevToolsToggleAction& action) {
448 if (action.type() == DevToolsToggleAction::kToggle &&
449 browser->is_devtools()) {
450 browser->tab_strip_model()->CloseAllTabs();
451 return;
454 ToggleDevToolsWindow(
455 browser->tab_strip_model()->GetActiveWebContents(),
456 action.type() == DevToolsToggleAction::kInspect,
457 action, "");
460 // static
461 void DevToolsWindow::OpenExternalFrontend(
462 Profile* profile,
463 const std::string& frontend_url,
464 const scoped_refptr<content::DevToolsAgentHost>& agent_host,
465 bool isWorker) {
466 DevToolsWindow* window = FindDevToolsWindow(agent_host.get());
467 if (!window) {
468 window = Create(profile, GURL(), nullptr, isWorker,
469 DevToolsUI::GetProxyURL(frontend_url).spec(), false, std::string());
470 if (!window)
471 return;
472 window->bindings_->AttachTo(agent_host);
475 window->ScheduleShow(DevToolsToggleAction::Show());
478 // static
479 void DevToolsWindow::ToggleDevToolsWindow(
480 content::WebContents* inspected_web_contents,
481 bool force_open,
482 const DevToolsToggleAction& action,
483 const std::string& settings) {
484 scoped_refptr<DevToolsAgentHost> agent(
485 DevToolsAgentHost::GetOrCreateFor(inspected_web_contents));
486 DevToolsWindow* window = FindDevToolsWindow(agent.get());
487 bool do_open = force_open;
488 if (!window) {
489 Profile* profile = Profile::FromBrowserContext(
490 inspected_web_contents->GetBrowserContext());
491 content::RecordAction(
492 base::UserMetricsAction("DevTools_InspectRenderer"));
493 window = Create(profile, GURL(), inspected_web_contents,
494 false, std::string(), true, settings);
495 if (!window)
496 return;
497 window->bindings_->AttachTo(agent.get());
498 do_open = true;
501 // Update toolbar to reflect DevTools changes.
502 window->UpdateBrowserToolbar();
504 // If window is docked and visible, we hide it on toggle. If window is
505 // undocked, we show (activate) it.
506 if (!window->is_docked_ || do_open)
507 window->ScheduleShow(action);
508 else
509 window->CloseWindow();
512 // static
513 void DevToolsWindow::InspectElement(
514 content::WebContents* inspected_web_contents,
515 int x,
516 int y) {
517 scoped_refptr<DevToolsAgentHost> agent(
518 DevToolsAgentHost::GetOrCreateFor(inspected_web_contents));
519 agent->InspectElement(x, y);
520 bool should_measure_time = FindDevToolsWindow(agent.get()) == NULL;
521 base::TimeTicks start_time = base::TimeTicks::Now();
522 // TODO(loislo): we should initiate DevTools window opening from within
523 // renderer. Otherwise, we still can hit a race condition here.
524 OpenDevToolsWindow(inspected_web_contents);
526 DevToolsWindow* window = FindDevToolsWindow(agent.get());
527 if (should_measure_time && window)
528 window->inspect_element_start_time_ = start_time;
531 void DevToolsWindow::ScheduleShow(const DevToolsToggleAction& action) {
532 if (life_stage_ == kLoadCompleted) {
533 Show(action);
534 return;
537 // Action will be done only after load completed.
538 action_on_load_ = action;
540 if (!can_dock_) {
541 // No harm to show always-undocked window right away.
542 is_docked_ = false;
543 Show(DevToolsToggleAction::Show());
547 void DevToolsWindow::Show(const DevToolsToggleAction& action) {
548 if (life_stage_ == kClosing)
549 return;
551 if (action.type() == DevToolsToggleAction::kNoOp)
552 return;
554 if (is_docked_) {
555 DCHECK(can_dock_);
556 Browser* inspected_browser = NULL;
557 int inspected_tab_index = -1;
558 FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
559 &inspected_browser,
560 &inspected_tab_index);
561 DCHECK(inspected_browser);
562 DCHECK(inspected_tab_index != -1);
564 // Tell inspected browser to update splitter and switch to inspected panel.
565 BrowserWindow* inspected_window = inspected_browser->window();
566 main_web_contents_->SetDelegate(this);
568 TabStripModel* tab_strip_model = inspected_browser->tab_strip_model();
569 tab_strip_model->ActivateTabAt(inspected_tab_index, true);
571 inspected_window->UpdateDevTools();
572 main_web_contents_->SetInitialFocus();
573 inspected_window->Show();
574 // On Aura, focusing once is not enough. Do it again.
575 // Note that focusing only here but not before isn't enough either. We just
576 // need to focus twice.
577 main_web_contents_->SetInitialFocus();
579 PrefsTabHelper::CreateForWebContents(main_web_contents_);
580 main_web_contents_->GetRenderViewHost()->SyncRendererPrefs();
582 DoAction(action);
583 return;
586 // Avoid consecutive window switching if the devtools window has been opened
587 // and the Inspect Element shortcut is pressed in the inspected tab.
588 bool should_show_window =
589 !browser_ || (action.type() != DevToolsToggleAction::kInspect);
591 if (!browser_)
592 CreateDevToolsBrowser();
594 if (should_show_window) {
595 browser_->window()->Show();
596 main_web_contents_->SetInitialFocus();
598 if (toolbox_web_contents_)
599 UpdateBrowserWindow();
601 DoAction(action);
604 // static
605 bool DevToolsWindow::HandleBeforeUnload(WebContents* frontend_contents,
606 bool proceed, bool* proceed_to_fire_unload) {
607 DevToolsWindow* window = AsDevToolsWindow(frontend_contents);
608 if (!window)
609 return false;
610 if (!window->intercepted_page_beforeunload_)
611 return false;
612 window->BeforeUnloadFired(frontend_contents, proceed,
613 proceed_to_fire_unload);
614 return true;
617 // static
618 bool DevToolsWindow::InterceptPageBeforeUnload(WebContents* contents) {
619 DevToolsWindow* window =
620 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
621 if (!window || window->intercepted_page_beforeunload_)
622 return false;
624 // Not yet loaded frontend will not handle beforeunload.
625 if (window->life_stage_ != kLoadCompleted)
626 return false;
628 window->intercepted_page_beforeunload_ = true;
629 // Handle case of devtools inspecting another devtools instance by passing
630 // the call up to the inspecting devtools instance.
631 if (!DevToolsWindow::InterceptPageBeforeUnload(window->main_web_contents_)) {
632 window->main_web_contents_->DispatchBeforeUnload(false);
634 return true;
637 // static
638 bool DevToolsWindow::NeedsToInterceptBeforeUnload(
639 WebContents* contents) {
640 DevToolsWindow* window =
641 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
642 return window && !window->intercepted_page_beforeunload_;
645 // static
646 bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(
647 Browser* browser) {
648 DCHECK(browser->is_devtools());
649 // When FastUnloadController is used, devtools frontend will be detached
650 // from the browser window at this point which means we've already fired
651 // beforeunload.
652 if (browser->tab_strip_model()->empty())
653 return true;
654 WebContents* contents =
655 browser->tab_strip_model()->GetWebContentsAt(0);
656 DevToolsWindow* window = AsDevToolsWindow(contents);
657 if (!window)
658 return false;
659 return window->intercepted_page_beforeunload_;
662 // static
663 void DevToolsWindow::OnPageCloseCanceled(WebContents* contents) {
664 DevToolsWindow* window =
665 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
666 if (!window)
667 return;
668 window->intercepted_page_beforeunload_ = false;
669 // Propagate to devtools opened on devtools if any.
670 DevToolsWindow::OnPageCloseCanceled(window->main_web_contents_);
673 DevToolsWindow::DevToolsWindow(Profile* profile,
674 WebContents* main_web_contents,
675 DevToolsUIBindings* bindings,
676 WebContents* inspected_web_contents,
677 bool can_dock)
678 : profile_(profile),
679 main_web_contents_(main_web_contents),
680 toolbox_web_contents_(nullptr),
681 bindings_(bindings),
682 browser_(nullptr),
683 is_docked_(true),
684 can_dock_(can_dock),
685 // This initialization allows external front-end to work without changes.
686 // We don't wait for docking call, but instead immediately show undocked.
687 // Passing "dockSide=undocked" parameter ensures proper UI.
688 life_stage_(can_dock ? kNotLoaded : kIsDockedSet),
689 action_on_load_(DevToolsToggleAction::NoOp()),
690 intercepted_page_beforeunload_(false) {
691 // Set up delegate, so we get fully-functional window immediately.
692 // It will not appear in UI though until |life_stage_ == kLoadCompleted|.
693 main_web_contents_->SetDelegate(this);
694 // Bindings take ownership over devtools as its delegate.
695 bindings_->SetDelegate(this);
696 // DevTools uses PageZoom::Zoom(), so main_web_contents_ requires a
697 // ZoomController.
698 ui_zoom::ZoomController::CreateForWebContents(main_web_contents_);
699 ui_zoom::ZoomController::FromWebContents(main_web_contents_)
700 ->SetShowsNotificationBubble(false);
702 g_instances.Get().push_back(this);
704 // There is no inspected_web_contents in case of various workers.
705 if (inspected_web_contents)
706 inspected_contents_observer_.reset(
707 new ObserverWithAccessor(inspected_web_contents));
709 // Initialize docked page to be of the right size.
710 if (can_dock_ && inspected_web_contents) {
711 content::RenderWidgetHostView* inspected_view =
712 inspected_web_contents->GetRenderWidgetHostView();
713 if (inspected_view && main_web_contents_->GetRenderWidgetHostView()) {
714 gfx::Size size = inspected_view->GetViewBounds().size();
715 main_web_contents_->GetRenderWidgetHostView()->SetSize(size);
719 event_forwarder_.reset(new DevToolsEventForwarder(this));
721 // Tag the DevTools main WebContents with its TaskManager specific UserData
722 // so that it shows up in the task manager.
723 task_management::WebContentsTags::CreateForDevToolsContents(
724 main_web_contents_);
727 // static
728 DevToolsWindow* DevToolsWindow::Create(
729 Profile* profile,
730 const GURL& frontend_url,
731 content::WebContents* inspected_web_contents,
732 bool shared_worker_frontend,
733 const std::string& remote_frontend,
734 bool can_dock,
735 const std::string& settings) {
736 if (profile->GetPrefs()->GetBoolean(prefs::kDevToolsDisabled) ||
737 base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode))
738 return nullptr;
740 if (inspected_web_contents) {
741 // Check for a place to dock.
742 Browser* browser = NULL;
743 int tab;
744 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents,
745 &browser, &tab) ||
746 browser->is_type_popup()) {
747 can_dock = false;
751 // Create WebContents with devtools.
752 GURL url(GetDevToolsURL(profile, frontend_url,
753 shared_worker_frontend,
754 remote_frontend,
755 can_dock, settings));
756 scoped_ptr<WebContents> main_web_contents(
757 WebContents::Create(WebContents::CreateParams(profile)));
758 main_web_contents->GetController().LoadURL(
759 DecorateFrontendURL(url), content::Referrer(),
760 ui::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string());
761 DevToolsUIBindings* bindings =
762 DevToolsUIBindings::ForWebContents(main_web_contents.get());
763 if (!bindings)
764 return nullptr;
766 return new DevToolsWindow(profile, main_web_contents.release(), bindings,
767 inspected_web_contents, can_dock);
770 // static
771 GURL DevToolsWindow::GetDevToolsURL(Profile* profile,
772 const GURL& base_url,
773 bool shared_worker_frontend,
774 const std::string& remote_frontend,
775 bool can_dock,
776 const std::string& settings) {
777 // Compatibility errors are encoded with data urls, pass them
778 // through with no decoration.
779 if (base_url.SchemeIs("data"))
780 return base_url;
782 std::string frontend_url(
783 !remote_frontend.empty() ?
784 remote_frontend :
785 base_url.is_empty() ? chrome::kChromeUIDevToolsURL : base_url.spec());
786 std::string url_string(
787 frontend_url +
788 ((frontend_url.find("?") == std::string::npos) ? "?" : "&"));
789 if (shared_worker_frontend)
790 url_string += "&isSharedWorker=true";
791 if (remote_frontend.size()) {
792 url_string += "&remoteFrontend=true";
793 } else {
794 url_string += "&remoteBase=" + DevToolsUI::GetRemoteBaseURL().spec();
796 if (can_dock)
797 url_string += "&can_dock=true";
798 if (settings.size())
799 url_string += "&settings=" + settings;
800 return GURL(url_string);
803 // static
804 DevToolsWindow* DevToolsWindow::FindDevToolsWindow(
805 DevToolsAgentHost* agent_host) {
806 if (!agent_host || g_instances == NULL)
807 return NULL;
808 DevToolsWindows* instances = g_instances.Pointer();
809 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
810 ++it) {
811 if ((*it)->bindings_->IsAttachedTo(agent_host))
812 return *it;
814 return NULL;
817 // static
818 DevToolsWindow* DevToolsWindow::AsDevToolsWindow(
819 content::WebContents* web_contents) {
820 if (!web_contents || g_instances == NULL)
821 return NULL;
822 DevToolsWindows* instances = g_instances.Pointer();
823 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
824 ++it) {
825 if ((*it)->main_web_contents_ == web_contents)
826 return *it;
828 return NULL;
831 WebContents* DevToolsWindow::OpenURLFromTab(
832 WebContents* source,
833 const content::OpenURLParams& params) {
834 DCHECK(source == main_web_contents_);
835 if (!params.url.SchemeIs(content::kChromeDevToolsScheme)) {
836 WebContents* inspected_web_contents = GetInspectedWebContents();
837 return inspected_web_contents ?
838 inspected_web_contents->OpenURL(params) : NULL;
841 bindings_->Reattach();
843 content::NavigationController::LoadURLParams load_url_params(params.url);
844 main_web_contents_->GetController().LoadURLWithParams(load_url_params);
845 return main_web_contents_;
848 void DevToolsWindow::ShowCertificateViewer(int certificate_id) {
849 ::ShowCertificateViewerByID(
850 is_docked_ ? GetInspectedWebContents() : main_web_contents_,
851 nullptr, certificate_id);
854 void DevToolsWindow::ActivateContents(WebContents* contents) {
855 if (is_docked_) {
856 WebContents* inspected_tab = GetInspectedWebContents();
857 if (inspected_tab)
858 inspected_tab->GetDelegate()->ActivateContents(inspected_tab);
859 } else if (browser_) {
860 browser_->window()->Activate();
864 void DevToolsWindow::AddNewContents(WebContents* source,
865 WebContents* new_contents,
866 WindowOpenDisposition disposition,
867 const gfx::Rect& initial_rect,
868 bool user_gesture,
869 bool* was_blocked) {
870 if (new_contents == toolbox_web_contents_) {
871 toolbox_web_contents_->SetDelegate(
872 new DevToolsToolboxDelegate(toolbox_web_contents_,
873 inspected_contents_observer_.get()));
874 if (main_web_contents_->GetRenderWidgetHostView() &&
875 toolbox_web_contents_->GetRenderWidgetHostView()) {
876 gfx::Size size =
877 main_web_contents_->GetRenderWidgetHostView()->GetViewBounds().size();
878 toolbox_web_contents_->GetRenderWidgetHostView()->SetSize(size);
880 UpdateBrowserWindow();
881 return;
884 WebContents* inspected_web_contents = GetInspectedWebContents();
885 if (inspected_web_contents) {
886 inspected_web_contents->GetDelegate()->AddNewContents(
887 source, new_contents, disposition, initial_rect, user_gesture,
888 was_blocked);
892 void DevToolsWindow::WebContentsCreated(WebContents* source_contents,
893 int opener_render_frame_id,
894 const std::string& frame_name,
895 const GURL& target_url,
896 WebContents* new_contents) {
897 if (target_url.SchemeIs(content::kChromeDevToolsScheme) &&
898 target_url.path().rfind("toolbox.html") != std::string::npos) {
899 CHECK(can_dock_);
900 if (toolbox_web_contents_)
901 delete toolbox_web_contents_;
902 toolbox_web_contents_ = new_contents;
904 // Tag the DevTools toolbox WebContents with its TaskManager specific
905 // UserData so that it shows up in the task manager.
906 task_management::WebContentsTags::CreateForDevToolsContents(
907 toolbox_web_contents_);
911 void DevToolsWindow::CloseContents(WebContents* source) {
912 CHECK(is_docked_);
913 life_stage_ = kClosing;
914 UpdateBrowserWindow();
915 // In case of docked main_web_contents_, we own it so delete here.
916 // Embedding DevTools window will be deleted as a result of
917 // DevToolsUIBindings destruction.
918 delete main_web_contents_;
921 void DevToolsWindow::ContentsZoomChange(bool zoom_in) {
922 DCHECK(is_docked_);
923 ui_zoom::PageZoom::Zoom(main_web_contents_, zoom_in ? content::PAGE_ZOOM_IN
924 : content::PAGE_ZOOM_OUT);
927 void DevToolsWindow::BeforeUnloadFired(WebContents* tab,
928 bool proceed,
929 bool* proceed_to_fire_unload) {
930 if (!intercepted_page_beforeunload_) {
931 // Docked devtools window closed directly.
932 if (proceed)
933 bindings_->Detach();
934 *proceed_to_fire_unload = proceed;
935 } else {
936 // Inspected page is attempting to close.
937 WebContents* inspected_web_contents = GetInspectedWebContents();
938 if (proceed) {
939 inspected_web_contents->DispatchBeforeUnload(false);
940 } else {
941 bool should_proceed;
942 inspected_web_contents->GetDelegate()->BeforeUnloadFired(
943 inspected_web_contents, false, &should_proceed);
944 DCHECK(!should_proceed);
946 *proceed_to_fire_unload = false;
950 bool DevToolsWindow::PreHandleKeyboardEvent(
951 WebContents* source,
952 const content::NativeWebKeyboardEvent& event,
953 bool* is_keyboard_shortcut) {
954 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
955 if (inspected_window) {
956 return inspected_window->PreHandleKeyboardEvent(event,
957 is_keyboard_shortcut);
959 return false;
962 void DevToolsWindow::HandleKeyboardEvent(
963 WebContents* source,
964 const content::NativeWebKeyboardEvent& event) {
965 if (event.windowsKeyCode == 0x08) {
966 // Do not navigate back in history on Windows (http://crbug.com/74156).
967 return;
969 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
970 if (inspected_window)
971 inspected_window->HandleKeyboardEvent(event);
974 content::JavaScriptDialogManager* DevToolsWindow::GetJavaScriptDialogManager(
975 WebContents* source) {
976 WebContents* inspected_web_contents = GetInspectedWebContents();
977 return (inspected_web_contents && inspected_web_contents->GetDelegate())
978 ? inspected_web_contents->GetDelegate()
979 ->GetJavaScriptDialogManager(inspected_web_contents)
980 : content::WebContentsDelegate::GetJavaScriptDialogManager(source);
983 content::ColorChooser* DevToolsWindow::OpenColorChooser(
984 WebContents* web_contents,
985 SkColor initial_color,
986 const std::vector<content::ColorSuggestion>& suggestions) {
987 return chrome::ShowColorChooser(web_contents, initial_color);
990 void DevToolsWindow::RunFileChooser(WebContents* web_contents,
991 const content::FileChooserParams& params) {
992 FileSelectHelper::RunFileChooser(web_contents, params);
995 bool DevToolsWindow::PreHandleGestureEvent(
996 WebContents* source,
997 const blink::WebGestureEvent& event) {
998 // Disable pinch zooming.
999 return event.type == blink::WebGestureEvent::GesturePinchBegin ||
1000 event.type == blink::WebGestureEvent::GesturePinchUpdate ||
1001 event.type == blink::WebGestureEvent::GesturePinchEnd;
1004 void DevToolsWindow::ActivateWindow() {
1005 if (life_stage_ != kLoadCompleted)
1006 return;
1007 if (is_docked_ && GetInspectedBrowserWindow())
1008 main_web_contents_->Focus();
1009 else if (!is_docked_ && !browser_->window()->IsActive())
1010 browser_->window()->Activate();
1013 void DevToolsWindow::CloseWindow() {
1014 DCHECK(is_docked_);
1015 life_stage_ = kClosing;
1016 main_web_contents_->DispatchBeforeUnload(false);
1019 void DevToolsWindow::SetInspectedPageBounds(const gfx::Rect& rect) {
1020 DevToolsContentsResizingStrategy strategy(rect);
1021 if (contents_resizing_strategy_.Equals(strategy))
1022 return;
1024 contents_resizing_strategy_.CopyFrom(strategy);
1025 UpdateBrowserWindow();
1028 void DevToolsWindow::InspectElementCompleted() {
1029 if (!inspect_element_start_time_.is_null()) {
1030 UMA_HISTOGRAM_TIMES("DevTools.InspectElement",
1031 base::TimeTicks::Now() - inspect_element_start_time_);
1032 inspect_element_start_time_ = base::TimeTicks();
1036 void DevToolsWindow::SetIsDocked(bool dock_requested) {
1037 if (life_stage_ == kClosing)
1038 return;
1040 DCHECK(can_dock_ || !dock_requested);
1041 if (!can_dock_)
1042 dock_requested = false;
1044 bool was_docked = is_docked_;
1045 is_docked_ = dock_requested;
1047 if (life_stage_ != kLoadCompleted) {
1048 // This is a first time call we waited for to initialize.
1049 life_stage_ = life_stage_ == kOnLoadFired ? kLoadCompleted : kIsDockedSet;
1050 if (life_stage_ == kLoadCompleted)
1051 LoadCompleted();
1052 return;
1055 if (dock_requested == was_docked)
1056 return;
1058 if (dock_requested && !was_docked) {
1059 // Detach window from the external devtools browser. It will lead to
1060 // the browser object's close and delete. Remove observer first.
1061 TabStripModel* tab_strip_model = browser_->tab_strip_model();
1062 tab_strip_model->DetachWebContentsAt(
1063 tab_strip_model->GetIndexOfWebContents(main_web_contents_));
1064 browser_ = NULL;
1065 } else if (!dock_requested && was_docked) {
1066 UpdateBrowserWindow();
1069 Show(DevToolsToggleAction::Show());
1072 void DevToolsWindow::OpenInNewTab(const std::string& url) {
1073 content::OpenURLParams params(
1074 GURL(url), content::Referrer(), NEW_FOREGROUND_TAB,
1075 ui::PAGE_TRANSITION_LINK, false);
1076 WebContents* inspected_web_contents = GetInspectedWebContents();
1077 if (!inspected_web_contents || !inspected_web_contents->OpenURL(params)) {
1078 chrome::HostDesktopType host_desktop_type =
1079 browser_ ? browser_->host_desktop_type() : chrome::GetActiveDesktop();
1081 chrome::ScopedTabbedBrowserDisplayer displayer(profile_, host_desktop_type);
1082 chrome::AddSelectedTabWithURL(displayer.browser(), GURL(url),
1083 ui::PAGE_TRANSITION_LINK);
1087 void DevToolsWindow::SetWhitelistedShortcuts(
1088 const std::string& message) {
1089 event_forwarder_->SetWhitelistedShortcuts(message);
1092 void DevToolsWindow::InspectedContentsClosing() {
1093 intercepted_page_beforeunload_ = false;
1094 life_stage_ = kClosing;
1095 main_web_contents_->ClosePage();
1098 InfoBarService* DevToolsWindow::GetInfoBarService() {
1099 return is_docked_ ?
1100 InfoBarService::FromWebContents(GetInspectedWebContents()) :
1101 InfoBarService::FromWebContents(main_web_contents_);
1104 void DevToolsWindow::RenderProcessGone(bool crashed) {
1105 // Docked DevToolsWindow owns its main_web_contents_ and must delete it.
1106 // Undocked main_web_contents_ are owned and handled by browser.
1107 // see crbug.com/369932
1108 if (is_docked_) {
1109 CloseContents(main_web_contents_);
1110 } else if (browser_ && crashed) {
1111 browser_->window()->Close();
1115 void DevToolsWindow::OnLoadCompleted() {
1116 // First seed inspected tab id for extension APIs.
1117 WebContents* inspected_web_contents = GetInspectedWebContents();
1118 if (inspected_web_contents) {
1119 SessionTabHelper* session_tab_helper =
1120 SessionTabHelper::FromWebContents(inspected_web_contents);
1121 if (session_tab_helper) {
1122 base::FundamentalValue tabId(session_tab_helper->session_id().id());
1123 bindings_->CallClientFunction("DevToolsAPI.setInspectedTabId",
1124 &tabId, NULL, NULL);
1128 if (life_stage_ == kClosing)
1129 return;
1131 // We could be in kLoadCompleted state already if frontend reloads itself.
1132 if (life_stage_ != kLoadCompleted) {
1133 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
1134 // Here we set kOnLoadFired.
1135 life_stage_ = life_stage_ == kIsDockedSet ? kLoadCompleted : kOnLoadFired;
1137 if (life_stage_ == kLoadCompleted)
1138 LoadCompleted();
1141 void DevToolsWindow::CreateDevToolsBrowser() {
1142 PrefService* prefs = profile_->GetPrefs();
1143 if (!prefs->GetDictionary(prefs::kAppWindowPlacement)->HasKey(kDevToolsApp)) {
1144 DictionaryPrefUpdate update(prefs, prefs::kAppWindowPlacement);
1145 base::DictionaryValue* wp_prefs = update.Get();
1146 base::DictionaryValue* dev_tools_defaults = new base::DictionaryValue;
1147 wp_prefs->Set(kDevToolsApp, dev_tools_defaults);
1148 dev_tools_defaults->SetInteger("left", 100);
1149 dev_tools_defaults->SetInteger("top", 100);
1150 dev_tools_defaults->SetInteger("right", 740);
1151 dev_tools_defaults->SetInteger("bottom", 740);
1152 dev_tools_defaults->SetBoolean("maximized", false);
1153 dev_tools_defaults->SetBoolean("always_on_top", false);
1156 browser_ = new Browser(Browser::CreateParams::CreateForDevTools(
1157 profile_,
1158 chrome::GetHostDesktopTypeForNativeView(
1159 main_web_contents_->GetNativeView())));
1160 browser_->tab_strip_model()->AddWebContents(
1161 main_web_contents_, -1, ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
1162 TabStripModel::ADD_ACTIVE);
1163 main_web_contents_->GetRenderViewHost()->SyncRendererPrefs();
1166 BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() {
1167 Browser* browser = NULL;
1168 int tab;
1169 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1170 &browser, &tab) ?
1171 browser->window() : NULL;
1174 void DevToolsWindow::DoAction(const DevToolsToggleAction& action) {
1175 switch (action.type()) {
1176 case DevToolsToggleAction::kShowConsole:
1177 bindings_->CallClientFunction(
1178 "DevToolsAPI.showConsole", NULL, NULL, NULL);
1179 break;
1181 case DevToolsToggleAction::kInspect:
1182 bindings_->CallClientFunction(
1183 "DevToolsAPI.enterInspectElementMode", NULL, NULL, NULL);
1184 break;
1186 case DevToolsToggleAction::kShow:
1187 case DevToolsToggleAction::kToggle:
1188 // Do nothing.
1189 break;
1191 case DevToolsToggleAction::kReveal: {
1192 const DevToolsToggleAction::RevealParams* params =
1193 action.params();
1194 CHECK(params);
1195 base::StringValue url_value(params->url);
1196 base::FundamentalValue line_value(static_cast<int>(params->line_number));
1197 base::FundamentalValue column_value(
1198 static_cast<int>(params->column_number));
1199 bindings_->CallClientFunction("DevToolsAPI.revealSourceLine",
1200 &url_value, &line_value, &column_value);
1201 break;
1203 default:
1204 NOTREACHED();
1205 break;
1209 void DevToolsWindow::UpdateBrowserToolbar() {
1210 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
1211 if (inspected_window)
1212 inspected_window->UpdateToolbar(NULL);
1215 void DevToolsWindow::UpdateBrowserWindow() {
1216 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
1217 if (inspected_window)
1218 inspected_window->UpdateDevTools();
1221 WebContents* DevToolsWindow::GetInspectedWebContents() {
1222 return inspected_contents_observer_
1223 ? inspected_contents_observer_->web_contents()
1224 : NULL;
1227 void DevToolsWindow::LoadCompleted() {
1228 Show(action_on_load_);
1229 action_on_load_ = DevToolsToggleAction::NoOp();
1230 if (!load_completed_callback_.is_null()) {
1231 load_completed_callback_.Run();
1232 load_completed_callback_ = base::Closure();
1236 void DevToolsWindow::SetLoadCompletedCallback(const base::Closure& closure) {
1237 if (life_stage_ == kLoadCompleted || life_stage_ == kClosing) {
1238 if (!closure.is_null())
1239 closure.Run();
1240 return;
1242 load_completed_callback_ = closure;
1245 bool DevToolsWindow::ForwardKeyboardEvent(
1246 const content::NativeWebKeyboardEvent& event) {
1247 return event_forwarder_->ForwardEvent(event);
1250 void DevToolsWindow::ReloadInspectedWebContents(bool ignore_cache) {
1251 base::FundamentalValue ignore_cache_value(ignore_cache);
1252 bindings_->CallClientFunction("DevToolsAPI.reloadInspectedPage",
1253 &ignore_cache_value, nullptr, nullptr);