Refactor WebsiteSettings to operate on a SecurityInfo
[chromium-blink-merge.git] / chrome / browser / devtools / devtools_window.cc
blob8e617d73ab16f0b06e40a51819cb47fb568fe1f2
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/profiles/profile.h"
19 #include "chrome/browser/sessions/session_tab_helper.h"
20 #include "chrome/browser/task_management/web_contents_tags.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/browser_dialogs.h"
23 #include "chrome/browser/ui/browser_iterator.h"
24 #include "chrome/browser/ui/browser_list.h"
25 #include "chrome/browser/ui/browser_tabstrip.h"
26 #include "chrome/browser/ui/browser_window.h"
27 #include "chrome/browser/ui/host_desktop.h"
28 #include "chrome/browser/ui/prefs/prefs_tab_helper.h"
29 #include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
30 #include "chrome/browser/ui/tabs/tab_strip_model.h"
31 #include "chrome/browser/ui/webui/devtools_ui.h"
32 #include "chrome/common/chrome_switches.h"
33 #include "chrome/common/pref_names.h"
34 #include "chrome/common/url_constants.h"
35 #include "components/pref_registry/pref_registry_syncable.h"
36 #include "components/syncable_prefs/pref_service_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 // static
532 content::DevToolsExternalAgentProxyDelegate*
533 DevToolsWindow::CreateWebSocketAPIChannel(const std::string& path) {
534 if (path.find("/devtools/frontend_api") != 0)
535 return nullptr;
536 DevToolsWindows* instances = g_instances.Pointer();
537 if (g_instances == nullptr)
538 return nullptr;
539 for (DevToolsWindow* window : *instances) {
540 auto result = window->bindings_->CreateWebSocketAPIChannel();
541 if (result)
542 return result;
544 return nullptr;
547 void DevToolsWindow::ScheduleShow(const DevToolsToggleAction& action) {
548 if (life_stage_ == kLoadCompleted) {
549 Show(action);
550 return;
553 // Action will be done only after load completed.
554 action_on_load_ = action;
556 if (!can_dock_) {
557 // No harm to show always-undocked window right away.
558 is_docked_ = false;
559 Show(DevToolsToggleAction::Show());
563 void DevToolsWindow::Show(const DevToolsToggleAction& action) {
564 if (life_stage_ == kClosing)
565 return;
567 if (action.type() == DevToolsToggleAction::kNoOp)
568 return;
570 if (is_docked_) {
571 DCHECK(can_dock_);
572 Browser* inspected_browser = NULL;
573 int inspected_tab_index = -1;
574 FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
575 &inspected_browser,
576 &inspected_tab_index);
577 DCHECK(inspected_browser);
578 DCHECK(inspected_tab_index != -1);
580 // Tell inspected browser to update splitter and switch to inspected panel.
581 BrowserWindow* inspected_window = inspected_browser->window();
582 main_web_contents_->SetDelegate(this);
584 TabStripModel* tab_strip_model = inspected_browser->tab_strip_model();
585 tab_strip_model->ActivateTabAt(inspected_tab_index, true);
587 inspected_window->UpdateDevTools();
588 main_web_contents_->SetInitialFocus();
589 inspected_window->Show();
590 // On Aura, focusing once is not enough. Do it again.
591 // Note that focusing only here but not before isn't enough either. We just
592 // need to focus twice.
593 main_web_contents_->SetInitialFocus();
595 PrefsTabHelper::CreateForWebContents(main_web_contents_);
596 main_web_contents_->GetRenderViewHost()->SyncRendererPrefs();
598 DoAction(action);
599 return;
602 // Avoid consecutive window switching if the devtools window has been opened
603 // and the Inspect Element shortcut is pressed in the inspected tab.
604 bool should_show_window =
605 !browser_ || (action.type() != DevToolsToggleAction::kInspect);
607 if (!browser_)
608 CreateDevToolsBrowser();
610 if (should_show_window) {
611 browser_->window()->Show();
612 main_web_contents_->SetInitialFocus();
614 if (toolbox_web_contents_)
615 UpdateBrowserWindow();
617 DoAction(action);
620 // static
621 bool DevToolsWindow::HandleBeforeUnload(WebContents* frontend_contents,
622 bool proceed, bool* proceed_to_fire_unload) {
623 DevToolsWindow* window = AsDevToolsWindow(frontend_contents);
624 if (!window)
625 return false;
626 if (!window->intercepted_page_beforeunload_)
627 return false;
628 window->BeforeUnloadFired(frontend_contents, proceed,
629 proceed_to_fire_unload);
630 return true;
633 // static
634 bool DevToolsWindow::InterceptPageBeforeUnload(WebContents* contents) {
635 DevToolsWindow* window =
636 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
637 if (!window || window->intercepted_page_beforeunload_)
638 return false;
640 // Not yet loaded frontend will not handle beforeunload.
641 if (window->life_stage_ != kLoadCompleted)
642 return false;
644 window->intercepted_page_beforeunload_ = true;
645 // Handle case of devtools inspecting another devtools instance by passing
646 // the call up to the inspecting devtools instance.
647 if (!DevToolsWindow::InterceptPageBeforeUnload(window->main_web_contents_)) {
648 window->main_web_contents_->DispatchBeforeUnload(false);
650 return true;
653 // static
654 bool DevToolsWindow::NeedsToInterceptBeforeUnload(
655 WebContents* contents) {
656 DevToolsWindow* window =
657 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
658 return window && !window->intercepted_page_beforeunload_;
661 // static
662 bool DevToolsWindow::HasFiredBeforeUnloadEventForDevToolsBrowser(
663 Browser* browser) {
664 DCHECK(browser->is_devtools());
665 // When FastUnloadController is used, devtools frontend will be detached
666 // from the browser window at this point which means we've already fired
667 // beforeunload.
668 if (browser->tab_strip_model()->empty())
669 return true;
670 WebContents* contents =
671 browser->tab_strip_model()->GetWebContentsAt(0);
672 DevToolsWindow* window = AsDevToolsWindow(contents);
673 if (!window)
674 return false;
675 return window->intercepted_page_beforeunload_;
678 // static
679 void DevToolsWindow::OnPageCloseCanceled(WebContents* contents) {
680 DevToolsWindow* window =
681 DevToolsWindow::GetInstanceForInspectedWebContents(contents);
682 if (!window)
683 return;
684 window->intercepted_page_beforeunload_ = false;
685 // Propagate to devtools opened on devtools if any.
686 DevToolsWindow::OnPageCloseCanceled(window->main_web_contents_);
689 DevToolsWindow::DevToolsWindow(Profile* profile,
690 WebContents* main_web_contents,
691 DevToolsUIBindings* bindings,
692 WebContents* inspected_web_contents,
693 bool can_dock)
694 : profile_(profile),
695 main_web_contents_(main_web_contents),
696 toolbox_web_contents_(nullptr),
697 bindings_(bindings),
698 browser_(nullptr),
699 is_docked_(true),
700 can_dock_(can_dock),
701 // This initialization allows external front-end to work without changes.
702 // We don't wait for docking call, but instead immediately show undocked.
703 // Passing "dockSide=undocked" parameter ensures proper UI.
704 life_stage_(can_dock ? kNotLoaded : kIsDockedSet),
705 action_on_load_(DevToolsToggleAction::NoOp()),
706 intercepted_page_beforeunload_(false) {
707 // Set up delegate, so we get fully-functional window immediately.
708 // It will not appear in UI though until |life_stage_ == kLoadCompleted|.
709 main_web_contents_->SetDelegate(this);
710 // Bindings take ownership over devtools as its delegate.
711 bindings_->SetDelegate(this);
712 // DevTools uses PageZoom::Zoom(), so main_web_contents_ requires a
713 // ZoomController.
714 ui_zoom::ZoomController::CreateForWebContents(main_web_contents_);
715 ui_zoom::ZoomController::FromWebContents(main_web_contents_)
716 ->SetShowsNotificationBubble(false);
718 g_instances.Get().push_back(this);
720 // There is no inspected_web_contents in case of various workers.
721 if (inspected_web_contents)
722 inspected_contents_observer_.reset(
723 new ObserverWithAccessor(inspected_web_contents));
725 // Initialize docked page to be of the right size.
726 if (can_dock_ && inspected_web_contents) {
727 content::RenderWidgetHostView* inspected_view =
728 inspected_web_contents->GetRenderWidgetHostView();
729 if (inspected_view && main_web_contents_->GetRenderWidgetHostView()) {
730 gfx::Size size = inspected_view->GetViewBounds().size();
731 main_web_contents_->GetRenderWidgetHostView()->SetSize(size);
735 event_forwarder_.reset(new DevToolsEventForwarder(this));
737 // Tag the DevTools main WebContents with its TaskManager specific UserData
738 // so that it shows up in the task manager.
739 task_management::WebContentsTags::CreateForDevToolsContents(
740 main_web_contents_);
743 // static
744 DevToolsWindow* DevToolsWindow::Create(
745 Profile* profile,
746 const GURL& frontend_url,
747 content::WebContents* inspected_web_contents,
748 bool shared_worker_frontend,
749 const std::string& remote_frontend,
750 bool can_dock,
751 const std::string& settings) {
752 if (profile->GetPrefs()->GetBoolean(prefs::kDevToolsDisabled) ||
753 base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode))
754 return nullptr;
756 if (inspected_web_contents) {
757 // Check for a place to dock.
758 Browser* browser = NULL;
759 int tab;
760 if (!FindInspectedBrowserAndTabIndex(inspected_web_contents,
761 &browser, &tab) ||
762 browser->is_type_popup()) {
763 can_dock = false;
767 // Create WebContents with devtools.
768 GURL url(GetDevToolsURL(profile, frontend_url,
769 shared_worker_frontend,
770 remote_frontend,
771 can_dock, settings));
772 scoped_ptr<WebContents> main_web_contents(
773 WebContents::Create(WebContents::CreateParams(profile)));
774 main_web_contents->GetController().LoadURL(
775 DecorateFrontendURL(url), content::Referrer(),
776 ui::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string());
777 DevToolsUIBindings* bindings =
778 DevToolsUIBindings::ForWebContents(main_web_contents.get());
779 if (!bindings)
780 return nullptr;
782 return new DevToolsWindow(profile, main_web_contents.release(), bindings,
783 inspected_web_contents, can_dock);
786 // static
787 GURL DevToolsWindow::GetDevToolsURL(Profile* profile,
788 const GURL& base_url,
789 bool shared_worker_frontend,
790 const std::string& remote_frontend,
791 bool can_dock,
792 const std::string& settings) {
793 // Compatibility errors are encoded with data urls, pass them
794 // through with no decoration.
795 if (base_url.SchemeIs("data"))
796 return base_url;
798 std::string frontend_url(
799 !remote_frontend.empty() ?
800 remote_frontend :
801 base_url.is_empty() ? chrome::kChromeUIDevToolsURL : base_url.spec());
802 std::string url_string(
803 frontend_url +
804 ((frontend_url.find("?") == std::string::npos) ? "?" : "&"));
805 if (shared_worker_frontend)
806 url_string += "&isSharedWorker=true";
807 if (remote_frontend.size()) {
808 url_string += "&remoteFrontend=true";
809 } else {
810 url_string += "&remoteBase=" + DevToolsUI::GetRemoteBaseURL().spec();
812 if (can_dock)
813 url_string += "&can_dock=true";
814 if (settings.size())
815 url_string += "&settings=" + settings;
816 return GURL(url_string);
819 // static
820 DevToolsWindow* DevToolsWindow::FindDevToolsWindow(
821 DevToolsAgentHost* agent_host) {
822 if (!agent_host || g_instances == NULL)
823 return NULL;
824 DevToolsWindows* instances = g_instances.Pointer();
825 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
826 ++it) {
827 if ((*it)->bindings_->IsAttachedTo(agent_host))
828 return *it;
830 return NULL;
833 // static
834 DevToolsWindow* DevToolsWindow::AsDevToolsWindow(
835 content::WebContents* web_contents) {
836 if (!web_contents || g_instances == NULL)
837 return NULL;
838 DevToolsWindows* instances = g_instances.Pointer();
839 for (DevToolsWindows::iterator it(instances->begin()); it != instances->end();
840 ++it) {
841 if ((*it)->main_web_contents_ == web_contents)
842 return *it;
844 return NULL;
847 WebContents* DevToolsWindow::OpenURLFromTab(
848 WebContents* source,
849 const content::OpenURLParams& params) {
850 DCHECK(source == main_web_contents_);
851 if (!params.url.SchemeIs(content::kChromeDevToolsScheme)) {
852 WebContents* inspected_web_contents = GetInspectedWebContents();
853 return inspected_web_contents ?
854 inspected_web_contents->OpenURL(params) : NULL;
857 bindings_->Reattach();
859 content::NavigationController::LoadURLParams load_url_params(params.url);
860 main_web_contents_->GetController().LoadURLWithParams(load_url_params);
861 return main_web_contents_;
864 void DevToolsWindow::ShowCertificateViewer(int certificate_id) {
865 ::ShowCertificateViewerByID(
866 is_docked_ ? GetInspectedWebContents() : main_web_contents_,
867 nullptr, certificate_id);
870 void DevToolsWindow::ActivateContents(WebContents* contents) {
871 if (is_docked_) {
872 WebContents* inspected_tab = GetInspectedWebContents();
873 if (inspected_tab)
874 inspected_tab->GetDelegate()->ActivateContents(inspected_tab);
875 } else if (browser_) {
876 browser_->window()->Activate();
880 void DevToolsWindow::AddNewContents(WebContents* source,
881 WebContents* new_contents,
882 WindowOpenDisposition disposition,
883 const gfx::Rect& initial_rect,
884 bool user_gesture,
885 bool* was_blocked) {
886 if (new_contents == toolbox_web_contents_) {
887 toolbox_web_contents_->SetDelegate(
888 new DevToolsToolboxDelegate(toolbox_web_contents_,
889 inspected_contents_observer_.get()));
890 if (main_web_contents_->GetRenderWidgetHostView() &&
891 toolbox_web_contents_->GetRenderWidgetHostView()) {
892 gfx::Size size =
893 main_web_contents_->GetRenderWidgetHostView()->GetViewBounds().size();
894 toolbox_web_contents_->GetRenderWidgetHostView()->SetSize(size);
896 UpdateBrowserWindow();
897 return;
900 WebContents* inspected_web_contents = GetInspectedWebContents();
901 if (inspected_web_contents) {
902 inspected_web_contents->GetDelegate()->AddNewContents(
903 source, new_contents, disposition, initial_rect, user_gesture,
904 was_blocked);
908 void DevToolsWindow::WebContentsCreated(WebContents* source_contents,
909 int opener_render_frame_id,
910 const std::string& frame_name,
911 const GURL& target_url,
912 WebContents* new_contents) {
913 if (target_url.SchemeIs(content::kChromeDevToolsScheme) &&
914 target_url.path().rfind("toolbox.html") != std::string::npos) {
915 CHECK(can_dock_);
916 if (toolbox_web_contents_)
917 delete toolbox_web_contents_;
918 toolbox_web_contents_ = new_contents;
920 // Tag the DevTools toolbox WebContents with its TaskManager specific
921 // UserData so that it shows up in the task manager.
922 task_management::WebContentsTags::CreateForDevToolsContents(
923 toolbox_web_contents_);
927 void DevToolsWindow::CloseContents(WebContents* source) {
928 CHECK(is_docked_);
929 life_stage_ = kClosing;
930 UpdateBrowserWindow();
931 // In case of docked main_web_contents_, we own it so delete here.
932 // Embedding DevTools window will be deleted as a result of
933 // DevToolsUIBindings destruction.
934 delete main_web_contents_;
937 void DevToolsWindow::ContentsZoomChange(bool zoom_in) {
938 DCHECK(is_docked_);
939 ui_zoom::PageZoom::Zoom(main_web_contents_, zoom_in ? content::PAGE_ZOOM_IN
940 : content::PAGE_ZOOM_OUT);
943 void DevToolsWindow::BeforeUnloadFired(WebContents* tab,
944 bool proceed,
945 bool* proceed_to_fire_unload) {
946 if (!intercepted_page_beforeunload_) {
947 // Docked devtools window closed directly.
948 if (proceed)
949 bindings_->Detach();
950 *proceed_to_fire_unload = proceed;
951 } else {
952 // Inspected page is attempting to close.
953 WebContents* inspected_web_contents = GetInspectedWebContents();
954 if (proceed) {
955 inspected_web_contents->DispatchBeforeUnload(false);
956 } else {
957 bool should_proceed;
958 inspected_web_contents->GetDelegate()->BeforeUnloadFired(
959 inspected_web_contents, false, &should_proceed);
960 DCHECK(!should_proceed);
962 *proceed_to_fire_unload = false;
966 bool DevToolsWindow::PreHandleKeyboardEvent(
967 WebContents* source,
968 const content::NativeWebKeyboardEvent& event,
969 bool* is_keyboard_shortcut) {
970 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
971 if (inspected_window) {
972 return inspected_window->PreHandleKeyboardEvent(event,
973 is_keyboard_shortcut);
975 return false;
978 void DevToolsWindow::HandleKeyboardEvent(
979 WebContents* source,
980 const content::NativeWebKeyboardEvent& event) {
981 if (event.windowsKeyCode == 0x08) {
982 // Do not navigate back in history on Windows (http://crbug.com/74156).
983 return;
985 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
986 if (inspected_window)
987 inspected_window->HandleKeyboardEvent(event);
990 content::JavaScriptDialogManager* DevToolsWindow::GetJavaScriptDialogManager(
991 WebContents* source) {
992 WebContents* inspected_web_contents = GetInspectedWebContents();
993 return (inspected_web_contents && inspected_web_contents->GetDelegate())
994 ? inspected_web_contents->GetDelegate()
995 ->GetJavaScriptDialogManager(inspected_web_contents)
996 : content::WebContentsDelegate::GetJavaScriptDialogManager(source);
999 content::ColorChooser* DevToolsWindow::OpenColorChooser(
1000 WebContents* web_contents,
1001 SkColor initial_color,
1002 const std::vector<content::ColorSuggestion>& suggestions) {
1003 return chrome::ShowColorChooser(web_contents, initial_color);
1006 void DevToolsWindow::RunFileChooser(WebContents* web_contents,
1007 const content::FileChooserParams& params) {
1008 FileSelectHelper::RunFileChooser(web_contents, params);
1011 bool DevToolsWindow::PreHandleGestureEvent(
1012 WebContents* source,
1013 const blink::WebGestureEvent& event) {
1014 // Disable pinch zooming.
1015 return event.type == blink::WebGestureEvent::GesturePinchBegin ||
1016 event.type == blink::WebGestureEvent::GesturePinchUpdate ||
1017 event.type == blink::WebGestureEvent::GesturePinchEnd;
1020 void DevToolsWindow::ActivateWindow() {
1021 if (life_stage_ != kLoadCompleted)
1022 return;
1023 if (is_docked_ && GetInspectedBrowserWindow())
1024 main_web_contents_->Focus();
1025 else if (!is_docked_ && !browser_->window()->IsActive())
1026 browser_->window()->Activate();
1029 void DevToolsWindow::CloseWindow() {
1030 DCHECK(is_docked_);
1031 life_stage_ = kClosing;
1032 main_web_contents_->DispatchBeforeUnload(false);
1035 void DevToolsWindow::SetInspectedPageBounds(const gfx::Rect& rect) {
1036 DevToolsContentsResizingStrategy strategy(rect);
1037 if (contents_resizing_strategy_.Equals(strategy))
1038 return;
1040 contents_resizing_strategy_.CopyFrom(strategy);
1041 UpdateBrowserWindow();
1044 void DevToolsWindow::InspectElementCompleted() {
1045 if (!inspect_element_start_time_.is_null()) {
1046 UMA_HISTOGRAM_TIMES("DevTools.InspectElement",
1047 base::TimeTicks::Now() - inspect_element_start_time_);
1048 inspect_element_start_time_ = base::TimeTicks();
1052 void DevToolsWindow::SetIsDocked(bool dock_requested) {
1053 if (life_stage_ == kClosing)
1054 return;
1056 DCHECK(can_dock_ || !dock_requested);
1057 if (!can_dock_)
1058 dock_requested = false;
1060 bool was_docked = is_docked_;
1061 is_docked_ = dock_requested;
1063 if (life_stage_ != kLoadCompleted) {
1064 // This is a first time call we waited for to initialize.
1065 life_stage_ = life_stage_ == kOnLoadFired ? kLoadCompleted : kIsDockedSet;
1066 if (life_stage_ == kLoadCompleted)
1067 LoadCompleted();
1068 return;
1071 if (dock_requested == was_docked)
1072 return;
1074 if (dock_requested && !was_docked) {
1075 // Detach window from the external devtools browser. It will lead to
1076 // the browser object's close and delete. Remove observer first.
1077 TabStripModel* tab_strip_model = browser_->tab_strip_model();
1078 tab_strip_model->DetachWebContentsAt(
1079 tab_strip_model->GetIndexOfWebContents(main_web_contents_));
1080 browser_ = NULL;
1081 } else if (!dock_requested && was_docked) {
1082 UpdateBrowserWindow();
1085 Show(DevToolsToggleAction::Show());
1088 void DevToolsWindow::OpenInNewTab(const std::string& url) {
1089 content::OpenURLParams params(
1090 GURL(url), content::Referrer(), NEW_FOREGROUND_TAB,
1091 ui::PAGE_TRANSITION_LINK, false);
1092 WebContents* inspected_web_contents = GetInspectedWebContents();
1093 if (!inspected_web_contents || !inspected_web_contents->OpenURL(params)) {
1094 chrome::HostDesktopType host_desktop_type =
1095 browser_ ? browser_->host_desktop_type() : chrome::GetActiveDesktop();
1097 chrome::ScopedTabbedBrowserDisplayer displayer(profile_, host_desktop_type);
1098 chrome::AddSelectedTabWithURL(displayer.browser(), GURL(url),
1099 ui::PAGE_TRANSITION_LINK);
1103 void DevToolsWindow::SetWhitelistedShortcuts(
1104 const std::string& message) {
1105 event_forwarder_->SetWhitelistedShortcuts(message);
1108 void DevToolsWindow::InspectedContentsClosing() {
1109 intercepted_page_beforeunload_ = false;
1110 life_stage_ = kClosing;
1111 main_web_contents_->ClosePage();
1114 InfoBarService* DevToolsWindow::GetInfoBarService() {
1115 return is_docked_ ?
1116 InfoBarService::FromWebContents(GetInspectedWebContents()) :
1117 InfoBarService::FromWebContents(main_web_contents_);
1120 void DevToolsWindow::RenderProcessGone(bool crashed) {
1121 // Docked DevToolsWindow owns its main_web_contents_ and must delete it.
1122 // Undocked main_web_contents_ are owned and handled by browser.
1123 // see crbug.com/369932
1124 if (is_docked_) {
1125 CloseContents(main_web_contents_);
1126 } else if (browser_ && crashed) {
1127 browser_->window()->Close();
1131 void DevToolsWindow::OnLoadCompleted() {
1132 // First seed inspected tab id for extension APIs.
1133 WebContents* inspected_web_contents = GetInspectedWebContents();
1134 if (inspected_web_contents) {
1135 SessionTabHelper* session_tab_helper =
1136 SessionTabHelper::FromWebContents(inspected_web_contents);
1137 if (session_tab_helper) {
1138 base::FundamentalValue tabId(session_tab_helper->session_id().id());
1139 bindings_->CallClientFunction("DevToolsAPI.setInspectedTabId",
1140 &tabId, NULL, NULL);
1144 if (life_stage_ == kClosing)
1145 return;
1147 // We could be in kLoadCompleted state already if frontend reloads itself.
1148 if (life_stage_ != kLoadCompleted) {
1149 // Load is completed when both kIsDockedSet and kOnLoadFired happened.
1150 // Here we set kOnLoadFired.
1151 life_stage_ = life_stage_ == kIsDockedSet ? kLoadCompleted : kOnLoadFired;
1153 if (life_stage_ == kLoadCompleted)
1154 LoadCompleted();
1157 void DevToolsWindow::CreateDevToolsBrowser() {
1158 PrefService* prefs = profile_->GetPrefs();
1159 if (!prefs->GetDictionary(prefs::kAppWindowPlacement)->HasKey(kDevToolsApp)) {
1160 DictionaryPrefUpdate update(prefs, prefs::kAppWindowPlacement);
1161 base::DictionaryValue* wp_prefs = update.Get();
1162 base::DictionaryValue* dev_tools_defaults = new base::DictionaryValue;
1163 wp_prefs->Set(kDevToolsApp, dev_tools_defaults);
1164 dev_tools_defaults->SetInteger("left", 100);
1165 dev_tools_defaults->SetInteger("top", 100);
1166 dev_tools_defaults->SetInteger("right", 740);
1167 dev_tools_defaults->SetInteger("bottom", 740);
1168 dev_tools_defaults->SetBoolean("maximized", false);
1169 dev_tools_defaults->SetBoolean("always_on_top", false);
1172 browser_ = new Browser(Browser::CreateParams::CreateForDevTools(
1173 profile_,
1174 chrome::GetHostDesktopTypeForNativeView(
1175 main_web_contents_->GetNativeView())));
1176 browser_->tab_strip_model()->AddWebContents(
1177 main_web_contents_, -1, ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
1178 TabStripModel::ADD_ACTIVE);
1179 main_web_contents_->GetRenderViewHost()->SyncRendererPrefs();
1182 BrowserWindow* DevToolsWindow::GetInspectedBrowserWindow() {
1183 Browser* browser = NULL;
1184 int tab;
1185 return FindInspectedBrowserAndTabIndex(GetInspectedWebContents(),
1186 &browser, &tab) ?
1187 browser->window() : NULL;
1190 void DevToolsWindow::DoAction(const DevToolsToggleAction& action) {
1191 switch (action.type()) {
1192 case DevToolsToggleAction::kShowConsole:
1193 bindings_->CallClientFunction(
1194 "DevToolsAPI.showConsole", NULL, NULL, NULL);
1195 break;
1197 case DevToolsToggleAction::kInspect:
1198 bindings_->CallClientFunction(
1199 "DevToolsAPI.enterInspectElementMode", NULL, NULL, NULL);
1200 break;
1202 case DevToolsToggleAction::kShow:
1203 case DevToolsToggleAction::kToggle:
1204 // Do nothing.
1205 break;
1207 case DevToolsToggleAction::kReveal: {
1208 const DevToolsToggleAction::RevealParams* params =
1209 action.params();
1210 CHECK(params);
1211 base::StringValue url_value(params->url);
1212 base::FundamentalValue line_value(static_cast<int>(params->line_number));
1213 base::FundamentalValue column_value(
1214 static_cast<int>(params->column_number));
1215 bindings_->CallClientFunction("DevToolsAPI.revealSourceLine",
1216 &url_value, &line_value, &column_value);
1217 break;
1219 default:
1220 NOTREACHED();
1221 break;
1225 void DevToolsWindow::UpdateBrowserToolbar() {
1226 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
1227 if (inspected_window)
1228 inspected_window->UpdateToolbar(NULL);
1231 void DevToolsWindow::UpdateBrowserWindow() {
1232 BrowserWindow* inspected_window = GetInspectedBrowserWindow();
1233 if (inspected_window)
1234 inspected_window->UpdateDevTools();
1237 WebContents* DevToolsWindow::GetInspectedWebContents() {
1238 return inspected_contents_observer_
1239 ? inspected_contents_observer_->web_contents()
1240 : NULL;
1243 void DevToolsWindow::LoadCompleted() {
1244 Show(action_on_load_);
1245 action_on_load_ = DevToolsToggleAction::NoOp();
1246 if (!load_completed_callback_.is_null()) {
1247 load_completed_callback_.Run();
1248 load_completed_callback_ = base::Closure();
1252 void DevToolsWindow::SetLoadCompletedCallback(const base::Closure& closure) {
1253 if (life_stage_ == kLoadCompleted || life_stage_ == kClosing) {
1254 if (!closure.is_null())
1255 closure.Run();
1256 return;
1258 load_completed_callback_ = closure;
1261 bool DevToolsWindow::ForwardKeyboardEvent(
1262 const content::NativeWebKeyboardEvent& event) {
1263 return event_forwarder_->ForwardEvent(event);
1266 void DevToolsWindow::ReloadInspectedWebContents(bool ignore_cache) {
1267 base::FundamentalValue ignore_cache_value(ignore_cache);
1268 bindings_->CallClientFunction("DevToolsAPI.reloadInspectedPage",
1269 &ignore_cache_value, nullptr, nullptr);