Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / exclusive_access / fullscreen_controller.cc
blob3c7018ccf49f8d54c38ee112054071ec6037fc46
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/ui/exclusive_access/fullscreen_controller.h"
7 #include "base/bind.h"
8 #include "base/command_line.h"
9 #include "base/location.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "chrome/browser/app_mode/app_mode_utils.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
17 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
18 #include "chrome/browser/ui/exclusive_access/fullscreen_within_tab_helper.h"
19 #include "chrome/browser/ui/status_bubble.h"
20 #include "chrome/browser/ui/tabs/tab_strip_model.h"
21 #include "chrome/browser/ui/web_contents_sizer.h"
22 #include "chrome/common/chrome_switches.h"
23 #include "components/content_settings/core/browser/host_content_settings_map.h"
24 #include "content/public/browser/navigation_details.h"
25 #include "content/public/browser/navigation_entry.h"
26 #include "content/public/browser/notification_service.h"
27 #include "content/public/browser/render_view_host.h"
28 #include "content/public/browser/render_widget_host_view.h"
29 #include "content/public/browser/user_metrics.h"
30 #include "content/public/browser/web_contents.h"
31 #include "extensions/common/extension.h"
33 #if !defined(OS_MACOSX)
34 #include "base/prefs/pref_service.h"
35 #include "chrome/common/pref_names.h"
36 #endif
38 using base::UserMetricsAction;
39 using content::RenderViewHost;
40 using content::WebContents;
42 FullscreenController::FullscreenController(ExclusiveAccessManager* manager)
43 : ExclusiveAccessControllerBase(manager),
44 state_prior_to_tab_fullscreen_(STATE_INVALID),
45 tab_fullscreen_accepted_(false),
46 toggled_into_fullscreen_(false),
47 reentrant_window_state_change_call_check_(false),
48 is_privileged_fullscreen_for_testing_(false),
49 ptr_factory_(this) {
52 FullscreenController::~FullscreenController() {
55 bool FullscreenController::IsFullscreenForBrowser() const {
56 return exclusive_access_manager()->context()->IsFullscreen() &&
57 !IsFullscreenCausedByTab();
60 void FullscreenController::ToggleBrowserFullscreenMode() {
61 extension_caused_fullscreen_ = GURL();
62 ToggleFullscreenModeInternal(BROWSER);
65 void FullscreenController::ToggleBrowserFullscreenWithToolbar() {
66 ToggleFullscreenModeInternal(BROWSER_WITH_TOOLBAR);
69 void FullscreenController::ToggleBrowserFullscreenModeWithExtension(
70 const GURL& extension_url) {
71 // |extension_caused_fullscreen_| will be reset if this causes fullscreen to
72 // exit.
73 extension_caused_fullscreen_ = extension_url;
74 ToggleFullscreenModeInternal(BROWSER);
77 bool FullscreenController::IsWindowFullscreenForTabOrPending() const {
78 return exclusive_access_tab() != nullptr;
81 bool FullscreenController::IsExtensionFullscreenOrPending() const {
82 return !extension_caused_fullscreen_.is_empty();
85 bool FullscreenController::IsControllerInitiatedFullscreen() const {
86 return toggled_into_fullscreen_;
89 bool FullscreenController::IsUserAcceptedFullscreen() const {
90 return tab_fullscreen_accepted_;
93 bool FullscreenController::IsFullscreenForTabOrPending(
94 const WebContents* web_contents) const {
95 if (web_contents == exclusive_access_tab()) {
96 DCHECK(web_contents ==
97 exclusive_access_manager()->context()->GetActiveWebContents());
98 DCHECK(web_contents->GetCapturerCount() == 0);
99 return true;
101 return IsFullscreenForCapturedTab(web_contents);
104 bool FullscreenController::IsFullscreenCausedByTab() const {
105 return state_prior_to_tab_fullscreen_ == STATE_NORMAL;
108 void FullscreenController::EnterFullscreenModeForTab(WebContents* web_contents,
109 const GURL& origin) {
110 DCHECK(web_contents);
112 if (MaybeToggleFullscreenForCapturedTab(web_contents, true)) {
113 // During tab capture of fullscreen-within-tab views, the browser window
114 // fullscreen state is unchanged, so return now.
115 return;
118 if (web_contents !=
119 exclusive_access_manager()->context()->GetActiveWebContents() ||
120 IsWindowFullscreenForTabOrPending()) {
121 return;
124 #if defined(OS_WIN)
125 // For now, avoid breaking when initiating full screen tab mode while in
126 // a metro snap.
127 // TODO(robertshield): Find a way to reconcile tab-initiated fullscreen
128 // modes with metro snap.
129 if (IsInMetroSnapMode())
130 return;
131 #endif
133 SetTabWithExclusiveAccess(web_contents);
134 fullscreened_origin_ = origin;
136 ExclusiveAccessContext* exclusive_access_context =
137 exclusive_access_manager()->context();
139 if (!exclusive_access_context->IsFullscreen()) {
140 // Normal -> Tab Fullscreen.
141 state_prior_to_tab_fullscreen_ = STATE_NORMAL;
142 ToggleFullscreenModeInternal(TAB);
143 return;
146 if (exclusive_access_context->IsFullscreenWithToolbar()) {
147 // Browser Fullscreen with Toolbar -> Tab Fullscreen (no toolbar).
148 exclusive_access_context->UpdateFullscreenWithToolbar(false);
149 state_prior_to_tab_fullscreen_ = STATE_BROWSER_FULLSCREEN_WITH_TOOLBAR;
150 } else {
151 // Browser Fullscreen without Toolbar -> Tab Fullscreen.
152 state_prior_to_tab_fullscreen_ = STATE_BROWSER_FULLSCREEN_NO_TOOLBAR;
155 // We need to update the fullscreen exit bubble, e.g., going from browser
156 // fullscreen to tab fullscreen will need to show different content.
157 if (!tab_fullscreen_accepted_) {
158 tab_fullscreen_accepted_ = GetFullscreenSetting() == CONTENT_SETTING_ALLOW;
160 exclusive_access_manager()->UpdateExclusiveAccessExitBubbleContent();
162 // This is only a change between Browser and Tab fullscreen. We generate
163 // a fullscreen notification now because there is no window change.
164 PostFullscreenChangeNotification(true);
167 void FullscreenController::ExitFullscreenModeForTab(WebContents* web_contents) {
168 if (MaybeToggleFullscreenForCapturedTab(web_contents, false)) {
169 // During tab capture of fullscreen-within-tab views, the browser window
170 // fullscreen state is unchanged, so return now.
171 return;
174 if (!IsWindowFullscreenForTabOrPending() ||
175 web_contents != exclusive_access_tab()) {
176 return;
179 #if defined(OS_WIN)
180 // For now, avoid breaking when initiating full screen tab mode while in
181 // a metro snap.
182 // TODO(robertshield): Find a way to reconcile tab-initiated fullscreen
183 // modes with metro snap.
184 if (IsInMetroSnapMode())
185 return;
186 #endif
188 ExclusiveAccessContext* exclusive_access_context =
189 exclusive_access_manager()->context();
191 if (!exclusive_access_context->IsFullscreen())
192 return;
194 if (IsFullscreenCausedByTab()) {
195 // Tab Fullscreen -> Normal.
196 ToggleFullscreenModeInternal(TAB);
197 return;
200 // Tab Fullscreen -> Browser Fullscreen (with or without toolbar).
201 if (state_prior_to_tab_fullscreen_ == STATE_BROWSER_FULLSCREEN_WITH_TOOLBAR) {
202 // Tab Fullscreen (no toolbar) -> Browser Fullscreen with Toolbar.
203 exclusive_access_context->UpdateFullscreenWithToolbar(true);
206 #if defined(OS_MACOSX)
207 // Clear the bubble URL, which forces the Mac UI to redraw.
208 exclusive_access_manager()->UpdateExclusiveAccessExitBubbleContent();
209 #endif // defined(OS_MACOSX)
211 // If currently there is a tab in "tab fullscreen" mode and fullscreen
212 // was not caused by it (i.e., previously it was in "browser fullscreen"
213 // mode), we need to switch back to "browser fullscreen" mode. In this
214 // case, all we have to do is notifying the tab that it has exited "tab
215 // fullscreen" mode.
216 NotifyTabExclusiveAccessLost();
218 // This is only a change between Browser and Tab fullscreen. We generate
219 // a fullscreen notification now because there is no window change.
220 PostFullscreenChangeNotification(true);
223 #if defined(OS_WIN)
224 bool FullscreenController::IsInMetroSnapMode() {
225 return exclusive_access_manager()->context()->IsInMetroSnapMode();
228 void FullscreenController::SetMetroSnapMode(bool enable) {
229 reentrant_window_state_change_call_check_ = false;
231 toggled_into_fullscreen_ = false;
232 exclusive_access_manager()->context()->SetMetroSnapMode(enable);
234 // FullscreenController unit tests for metro snap assume that on Windows calls
235 // to WindowFullscreenStateChanged are reentrant. If that assumption is
236 // invalidated, the tests must be updated to maintain coverage.
237 CHECK(reentrant_window_state_change_call_check_);
239 #endif // defined(OS_WIN)
241 void FullscreenController::OnTabDetachedFromView(WebContents* old_contents) {
242 if (!IsFullscreenForCapturedTab(old_contents))
243 return;
245 // A fullscreen-within-tab view undergoing screen capture has been detached
246 // and is no longer visible to the user. Set it to exactly the WebContents'
247 // preferred size. See 'FullscreenWithinTab Note'.
249 // When the user later selects the tab to show |old_contents| again, UI code
250 // elsewhere (e.g., views::WebView) will resize the view to fit within the
251 // browser window once again.
253 // If the view has been detached from the browser window (e.g., to drag a tab
254 // off into a new browser window), return immediately to avoid an unnecessary
255 // resize.
256 if (!old_contents->GetDelegate())
257 return;
259 // Do nothing if tab capture ended after toggling fullscreen, or a preferred
260 // size was never specified by the capturer.
261 if (old_contents->GetCapturerCount() == 0 ||
262 old_contents->GetPreferredSize().IsEmpty()) {
263 return;
266 content::RenderWidgetHostView* const current_fs_view =
267 old_contents->GetFullscreenRenderWidgetHostView();
268 if (current_fs_view)
269 current_fs_view->SetSize(old_contents->GetPreferredSize());
270 ResizeWebContents(old_contents, old_contents->GetPreferredSize());
273 void FullscreenController::OnTabClosing(WebContents* web_contents) {
274 if (IsFullscreenForCapturedTab(web_contents)) {
275 web_contents->ExitFullscreen();
276 } else {
277 ExclusiveAccessControllerBase::OnTabClosing(web_contents);
281 void FullscreenController::WindowFullscreenStateChanged() {
282 reentrant_window_state_change_call_check_ = true;
283 ExclusiveAccessContext* const exclusive_access_context =
284 exclusive_access_manager()->context();
285 bool exiting_fullscreen = !exclusive_access_context->IsFullscreen();
287 PostFullscreenChangeNotification(!exiting_fullscreen);
288 if (exiting_fullscreen) {
289 toggled_into_fullscreen_ = false;
290 extension_caused_fullscreen_ = GURL();
291 NotifyTabExclusiveAccessLost();
292 exclusive_access_context->UnhideDownloadShelf();
293 } else {
294 exclusive_access_context->HideDownloadShelf();
298 bool FullscreenController::HandleUserPressedEscape() {
299 WebContents* const active_web_contents =
300 exclusive_access_manager()->context()->GetActiveWebContents();
301 if (IsFullscreenForCapturedTab(active_web_contents)) {
302 active_web_contents->ExitFullscreen();
303 return true;
304 } else if (IsWindowFullscreenForTabOrPending()) {
305 ExitExclusiveAccessIfNecessary();
306 return true;
309 return false;
312 void FullscreenController::ExitExclusiveAccessToPreviousState() {
313 if (IsWindowFullscreenForTabOrPending())
314 ExitFullscreenModeForTab(exclusive_access_tab());
315 else if (IsFullscreenForBrowser())
316 ExitFullscreenModeInternal();
319 bool FullscreenController::OnAcceptExclusiveAccessPermission() {
320 ExclusiveAccessBubbleType bubble_type =
321 exclusive_access_manager()->GetExclusiveAccessExitBubbleType();
322 bool fullscreen = false;
323 exclusive_access_bubble::PermissionRequestedByType(bubble_type, &fullscreen,
324 nullptr);
325 DCHECK(!(fullscreen && tab_fullscreen_accepted_));
327 if (fullscreen && !tab_fullscreen_accepted_) {
328 DCHECK(exclusive_access_tab());
329 // Origins can enter fullscreen even when embedded in other origins.
330 // Permission is tracked based on the combinations of requester and
331 // embedder. Thus, even if a requesting origin has been previously approved
332 // for embedder A, it will not be approved when embedded in a different
333 // origin B.
335 // However, an exception is made when a requester and an embedder are the
336 // same origin. In other words, if the requester is the top-level frame. If
337 // that combination is ALLOWED, then future requests from that origin will
338 // succeed no matter what the embedder is. For example, if youtube.com
339 // is visited and user selects ALLOW. Later user visits example.com which
340 // embeds youtube.com in an iframe, which is then ALLOWED to go fullscreen.
341 GURL requester = GetRequestingOrigin();
342 GURL embedder = GetEmbeddingOrigin();
343 ContentSettingsPattern primary_pattern =
344 ContentSettingsPattern::FromURLNoWildcard(requester);
345 ContentSettingsPattern secondary_pattern =
346 ContentSettingsPattern::FromURLNoWildcard(embedder);
348 // ContentSettings requires valid patterns and the patterns might be invalid
349 // in some edge cases like if the current frame is about:blank.
351 // Do not store preference on file:// URLs, they don't have a clean
352 // origin policy.
353 // TODO(estark): Revisit this when crbug.com/455882 is fixed.
354 if (!requester.SchemeIsFile() && !embedder.SchemeIsFile() &&
355 primary_pattern.IsValid() && secondary_pattern.IsValid()) {
356 HostContentSettingsMap* settings_map =
357 HostContentSettingsMapFactory::GetForProfile(
358 exclusive_access_manager()->context()->GetProfile());
359 settings_map->SetContentSetting(
360 primary_pattern, secondary_pattern, CONTENT_SETTINGS_TYPE_FULLSCREEN,
361 std::string(), CONTENT_SETTING_ALLOW);
363 tab_fullscreen_accepted_ = true;
364 return true;
367 return false;
370 bool FullscreenController::OnDenyExclusiveAccessPermission() {
371 if (IsWindowFullscreenForTabOrPending()) {
372 ExitExclusiveAccessIfNecessary();
373 return true;
376 return false;
379 GURL FullscreenController::GetURLForExclusiveAccessBubble() const {
380 if (exclusive_access_tab())
381 return GetRequestingOrigin();
382 return extension_caused_fullscreen_;
385 void FullscreenController::ExitExclusiveAccessIfNecessary() {
386 if (IsWindowFullscreenForTabOrPending())
387 ExitFullscreenModeForTab(exclusive_access_tab());
388 else
389 NotifyTabExclusiveAccessLost();
392 void FullscreenController::PostFullscreenChangeNotification(
393 bool is_fullscreen) {
394 base::ThreadTaskRunnerHandle::Get()->PostTask(
395 FROM_HERE, base::Bind(&FullscreenController::NotifyFullscreenChange,
396 ptr_factory_.GetWeakPtr(), is_fullscreen));
399 void FullscreenController::NotifyFullscreenChange(bool is_fullscreen) {
400 content::NotificationService::current()->Notify(
401 chrome::NOTIFICATION_FULLSCREEN_CHANGED,
402 content::Source<FullscreenController>(this),
403 content::Details<bool>(&is_fullscreen));
406 void FullscreenController::NotifyTabExclusiveAccessLost() {
407 if (exclusive_access_tab()) {
408 WebContents* web_contents = exclusive_access_tab();
409 SetTabWithExclusiveAccess(nullptr);
410 fullscreened_origin_ = GURL();
411 state_prior_to_tab_fullscreen_ = STATE_INVALID;
412 tab_fullscreen_accepted_ = false;
413 web_contents->ExitFullscreen();
414 exclusive_access_manager()->UpdateExclusiveAccessExitBubbleContent();
418 void FullscreenController::ToggleFullscreenModeInternal(
419 FullscreenInternalOption option) {
420 #if defined(OS_WIN)
421 // When in Metro snap mode, toggling in and out of fullscreen is prevented.
422 if (IsInMetroSnapMode())
423 return;
424 #endif
426 ExclusiveAccessContext* const exclusive_access_context =
427 exclusive_access_manager()->context();
428 bool enter_fullscreen = !exclusive_access_context->IsFullscreen();
430 // When a Mac user requests a toggle they may be toggling between
431 // FullscreenWithoutChrome and FullscreenWithToolbar.
432 if (exclusive_access_context->IsFullscreen() &&
433 !IsWindowFullscreenForTabOrPending() &&
434 exclusive_access_context->SupportsFullscreenWithToolbar()) {
435 if (option == BROWSER_WITH_TOOLBAR) {
436 enter_fullscreen = enter_fullscreen ||
437 !exclusive_access_context->IsFullscreenWithToolbar();
438 } else {
439 enter_fullscreen = enter_fullscreen ||
440 exclusive_access_context->IsFullscreenWithToolbar();
444 // In kiosk mode, we always want to be fullscreen. When the browser first
445 // starts we're not yet fullscreen, so let the initial toggle go through.
446 if (chrome::IsRunningInAppMode() && exclusive_access_context->IsFullscreen())
447 return;
449 #if !defined(OS_MACOSX)
450 // Do not enter fullscreen mode if disallowed by pref. This prevents the user
451 // from manually entering fullscreen mode and also disables kiosk mode on
452 // desktop platforms.
453 if (enter_fullscreen &&
454 !exclusive_access_context->GetProfile()->GetPrefs()->GetBoolean(
455 prefs::kFullscreenAllowed)) {
456 return;
458 #endif
460 if (enter_fullscreen)
461 EnterFullscreenModeInternal(option);
462 else
463 ExitFullscreenModeInternal();
466 void FullscreenController::EnterFullscreenModeInternal(
467 FullscreenInternalOption option) {
468 toggled_into_fullscreen_ = true;
469 GURL url;
470 if (option == TAB) {
471 url = GetRequestingOrigin();
472 tab_fullscreen_accepted_ = GetFullscreenSetting() == CONTENT_SETTING_ALLOW;
473 } else {
474 if (!extension_caused_fullscreen_.is_empty())
475 url = extension_caused_fullscreen_;
478 if (option == BROWSER)
479 content::RecordAction(UserMetricsAction("ToggleFullscreen"));
480 // TODO(scheib): Record metrics for WITH_TOOLBAR, without counting transitions
481 // from tab fullscreen out to browser with toolbar.
483 exclusive_access_manager()->context()->EnterFullscreen(
484 url, exclusive_access_manager()->GetExclusiveAccessExitBubbleType(),
485 option == BROWSER_WITH_TOOLBAR);
487 exclusive_access_manager()->UpdateExclusiveAccessExitBubbleContent();
489 // Once the window has become fullscreen it'll call back to
490 // WindowFullscreenStateChanged(). We don't do this immediately as
491 // BrowserWindow::EnterFullscreen() asks for bookmark_bar_state_, so we let
492 // the BrowserWindow invoke WindowFullscreenStateChanged when appropriate.
495 void FullscreenController::ExitFullscreenModeInternal() {
496 toggled_into_fullscreen_ = false;
497 #if defined(OS_MACOSX)
498 // Mac windows report a state change instantly, and so we must also clear
499 // state_prior_to_tab_fullscreen_ to match them else other logic using
500 // state_prior_to_tab_fullscreen_ will be incorrect.
501 NotifyTabExclusiveAccessLost();
502 #endif
503 exclusive_access_manager()->context()->ExitFullscreen();
504 extension_caused_fullscreen_ = GURL();
506 exclusive_access_manager()->UpdateExclusiveAccessExitBubbleContent();
509 ContentSetting FullscreenController::GetFullscreenSetting() const {
510 DCHECK(exclusive_access_tab());
512 // If simplified UI is enabled, never ask the user, just auto-allow.
513 // TODO(mgiuca): Should we allow the user to block use of fullscreen?
514 // http://crbug.com/515747.
515 if (ExclusiveAccessManager::IsSimplifiedFullscreenUIEnabled())
516 return CONTENT_SETTING_ALLOW;
518 GURL url = GetRequestingOrigin();
520 // Always ask on file:// URLs, since we can't meaningfully make the
521 // decision stick for a particular origin.
522 // TODO(estark): Revisit this when crbug.com/455882 is fixed.
523 if (url.SchemeIsFile())
524 return CONTENT_SETTING_ASK;
526 if (IsPrivilegedFullscreenForTab())
527 return CONTENT_SETTING_ALLOW;
529 // If the permission was granted to the website with no embedder, it should
530 // always be allowed, even if embedded.
531 if (HostContentSettingsMapFactory::GetForProfile(
532 exclusive_access_manager()->context()->GetProfile())
533 ->GetContentSetting(url, url, CONTENT_SETTINGS_TYPE_FULLSCREEN,
534 std::string()) == CONTENT_SETTING_ALLOW) {
535 return CONTENT_SETTING_ALLOW;
538 // See the comment above the call to |SetContentSetting()| for how the
539 // requesting and embedding origins interact with each other wrt permissions.
540 return HostContentSettingsMapFactory::GetForProfile(
541 exclusive_access_manager()->context()->GetProfile())
542 ->GetContentSetting(url, GetEmbeddingOrigin(),
543 CONTENT_SETTINGS_TYPE_FULLSCREEN, std::string());
546 bool FullscreenController::IsPrivilegedFullscreenForTab() const {
547 const bool embedded_widget_present =
548 exclusive_access_tab() &&
549 exclusive_access_tab()->GetFullscreenRenderWidgetHostView();
550 return embedded_widget_present || is_privileged_fullscreen_for_testing_;
553 void FullscreenController::SetPrivilegedFullscreenForTesting(
554 bool is_privileged) {
555 is_privileged_fullscreen_for_testing_ = is_privileged;
558 bool FullscreenController::MaybeToggleFullscreenForCapturedTab(
559 WebContents* web_contents, bool enter_fullscreen) {
560 if (enter_fullscreen) {
561 if (web_contents->GetCapturerCount() > 0) {
562 FullscreenWithinTabHelper::CreateForWebContents(web_contents);
563 FullscreenWithinTabHelper::FromWebContents(web_contents)->
564 SetIsFullscreenForCapturedTab(true);
565 return true;
567 } else {
568 if (IsFullscreenForCapturedTab(web_contents)) {
569 FullscreenWithinTabHelper::RemoveForWebContents(web_contents);
570 return true;
574 return false;
577 bool FullscreenController::IsFullscreenForCapturedTab(
578 const WebContents* web_contents) const {
579 // Note: On Mac, some of the OnTabXXX() methods get called with a nullptr
580 // value
581 // for web_contents. Check for that here.
582 const FullscreenWithinTabHelper* const helper =
583 web_contents ? FullscreenWithinTabHelper::FromWebContents(web_contents)
584 : nullptr;
585 if (helper && helper->is_fullscreen_for_captured_tab()) {
586 DCHECK_NE(exclusive_access_tab(), web_contents);
587 return true;
589 return false;
592 GURL FullscreenController::GetRequestingOrigin() const {
593 DCHECK(exclusive_access_tab());
595 if (!fullscreened_origin_.is_empty())
596 return fullscreened_origin_;
598 return exclusive_access_tab()->GetLastCommittedURL();
601 GURL FullscreenController::GetEmbeddingOrigin() const {
602 DCHECK(exclusive_access_tab());
604 return exclusive_access_tab()->GetLastCommittedURL();