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"
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/profiles/profile.h"
15 #include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
16 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
17 #include "chrome/browser/ui/exclusive_access/fullscreen_within_tab_helper.h"
18 #include "chrome/browser/ui/status_bubble.h"
19 #include "chrome/browser/ui/tabs/tab_strip_model.h"
20 #include "chrome/browser/ui/web_contents_sizer.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "components/content_settings/core/browser/host_content_settings_map.h"
23 #include "content/public/browser/navigation_details.h"
24 #include "content/public/browser/navigation_entry.h"
25 #include "content/public/browser/notification_service.h"
26 #include "content/public/browser/render_view_host.h"
27 #include "content/public/browser/render_widget_host_view.h"
28 #include "content/public/browser/user_metrics.h"
29 #include "content/public/browser/web_contents.h"
30 #include "extensions/common/extension.h"
32 #if !defined(OS_MACOSX)
33 #include "base/prefs/pref_service.h"
34 #include "chrome/common/pref_names.h"
37 using base::UserMetricsAction
;
38 using content::RenderViewHost
;
39 using content::WebContents
;
41 FullscreenController::FullscreenController(ExclusiveAccessManager
* manager
)
42 : ExclusiveAccessControllerBase(manager
),
43 state_prior_to_tab_fullscreen_(STATE_INVALID
),
44 tab_fullscreen_accepted_(false),
45 toggled_into_fullscreen_(false),
46 reentrant_window_state_change_call_check_(false),
47 is_privileged_fullscreen_for_testing_(false),
51 FullscreenController::~FullscreenController() {
54 bool FullscreenController::IsFullscreenForBrowser() const {
55 return exclusive_access_manager()->context()->IsFullscreen() &&
56 !IsFullscreenCausedByTab();
59 void FullscreenController::ToggleBrowserFullscreenMode() {
60 extension_caused_fullscreen_
= GURL();
61 ToggleFullscreenModeInternal(BROWSER
);
64 void FullscreenController::ToggleBrowserFullscreenWithToolbar() {
65 ToggleFullscreenModeInternal(BROWSER_WITH_TOOLBAR
);
68 void FullscreenController::ToggleBrowserFullscreenModeWithExtension(
69 const GURL
& extension_url
) {
70 // |extension_caused_fullscreen_| will be reset if this causes fullscreen to
72 extension_caused_fullscreen_
= extension_url
;
73 ToggleFullscreenModeInternal(BROWSER
);
76 bool FullscreenController::IsWindowFullscreenForTabOrPending() const {
77 return exclusive_access_tab() != nullptr;
80 bool FullscreenController::IsExtensionFullscreenOrPending() const {
81 return !extension_caused_fullscreen_
.is_empty();
84 bool FullscreenController::IsControllerInitiatedFullscreen() const {
85 return toggled_into_fullscreen_
;
88 bool FullscreenController::IsUserAcceptedFullscreen() const {
89 return tab_fullscreen_accepted_
;
92 bool FullscreenController::IsFullscreenForTabOrPending(
93 const WebContents
* web_contents
) const {
94 if (web_contents
== exclusive_access_tab()) {
95 DCHECK(web_contents
==
96 exclusive_access_manager()->context()->GetActiveWebContents());
97 DCHECK(web_contents
->GetCapturerCount() == 0);
100 return IsFullscreenForCapturedTab(web_contents
);
103 bool FullscreenController::IsFullscreenCausedByTab() const {
104 return state_prior_to_tab_fullscreen_
== STATE_NORMAL
;
107 void FullscreenController::EnterFullscreenModeForTab(WebContents
* web_contents
,
108 const GURL
& origin
) {
109 DCHECK(web_contents
);
111 if (MaybeToggleFullscreenForCapturedTab(web_contents
, true)) {
112 // During tab capture of fullscreen-within-tab views, the browser window
113 // fullscreen state is unchanged, so return now.
118 exclusive_access_manager()->context()->GetActiveWebContents() ||
119 IsWindowFullscreenForTabOrPending()) {
124 // For now, avoid breaking when initiating full screen tab mode while in
126 // TODO(robertshield): Find a way to reconcile tab-initiated fullscreen
127 // modes with metro snap.
128 if (IsInMetroSnapMode())
132 SetTabWithExclusiveAccess(web_contents
);
133 fullscreened_origin_
= origin
;
135 ExclusiveAccessContext
* exclusive_access_context
=
136 exclusive_access_manager()->context();
138 if (!exclusive_access_context
->IsFullscreen()) {
139 // Normal -> Tab Fullscreen.
140 state_prior_to_tab_fullscreen_
= STATE_NORMAL
;
141 ToggleFullscreenModeInternal(TAB
);
145 if (exclusive_access_context
->IsFullscreenWithToolbar()) {
146 // Browser Fullscreen with Toolbar -> Tab Fullscreen (no toolbar).
147 exclusive_access_context
->UpdateFullscreenWithToolbar(false);
148 state_prior_to_tab_fullscreen_
= STATE_BROWSER_FULLSCREEN_WITH_TOOLBAR
;
150 // Browser Fullscreen without Toolbar -> Tab Fullscreen.
151 state_prior_to_tab_fullscreen_
= STATE_BROWSER_FULLSCREEN_NO_TOOLBAR
;
154 // We need to update the fullscreen exit bubble, e.g., going from browser
155 // fullscreen to tab fullscreen will need to show different content.
156 if (!tab_fullscreen_accepted_
) {
157 tab_fullscreen_accepted_
= GetFullscreenSetting() == CONTENT_SETTING_ALLOW
;
159 exclusive_access_manager()->UpdateExclusiveAccessExitBubbleContent();
161 // This is only a change between Browser and Tab fullscreen. We generate
162 // a fullscreen notification now because there is no window change.
163 PostFullscreenChangeNotification(true);
166 void FullscreenController::ExitFullscreenModeForTab(WebContents
* web_contents
) {
167 if (MaybeToggleFullscreenForCapturedTab(web_contents
, false)) {
168 // During tab capture of fullscreen-within-tab views, the browser window
169 // fullscreen state is unchanged, so return now.
173 if (!IsWindowFullscreenForTabOrPending() ||
174 web_contents
!= exclusive_access_tab()) {
179 // For now, avoid breaking when initiating full screen tab mode while in
181 // TODO(robertshield): Find a way to reconcile tab-initiated fullscreen
182 // modes with metro snap.
183 if (IsInMetroSnapMode())
187 ExclusiveAccessContext
* exclusive_access_context
=
188 exclusive_access_manager()->context();
190 if (!exclusive_access_context
->IsFullscreen())
193 if (IsFullscreenCausedByTab()) {
194 // Tab Fullscreen -> Normal.
195 ToggleFullscreenModeInternal(TAB
);
199 // Tab Fullscreen -> Browser Fullscreen (with or without toolbar).
200 if (state_prior_to_tab_fullscreen_
== STATE_BROWSER_FULLSCREEN_WITH_TOOLBAR
) {
201 // Tab Fullscreen (no toolbar) -> Browser Fullscreen with Toolbar.
202 exclusive_access_context
->UpdateFullscreenWithToolbar(true);
205 #if defined(OS_MACOSX)
206 // Clear the bubble URL, which forces the Mac UI to redraw.
207 exclusive_access_manager()->UpdateExclusiveAccessExitBubbleContent();
208 #endif // defined(OS_MACOSX)
210 // If currently there is a tab in "tab fullscreen" mode and fullscreen
211 // was not caused by it (i.e., previously it was in "browser fullscreen"
212 // mode), we need to switch back to "browser fullscreen" mode. In this
213 // case, all we have to do is notifying the tab that it has exited "tab
215 NotifyTabExclusiveAccessLost();
217 // This is only a change between Browser and Tab fullscreen. We generate
218 // a fullscreen notification now because there is no window change.
219 PostFullscreenChangeNotification(true);
223 bool FullscreenController::IsInMetroSnapMode() {
224 return exclusive_access_manager()->context()->IsInMetroSnapMode();
227 void FullscreenController::SetMetroSnapMode(bool enable
) {
228 reentrant_window_state_change_call_check_
= false;
230 toggled_into_fullscreen_
= false;
231 exclusive_access_manager()->context()->SetMetroSnapMode(enable
);
233 // FullscreenController unit tests for metro snap assume that on Windows calls
234 // to WindowFullscreenStateChanged are reentrant. If that assumption is
235 // invalidated, the tests must be updated to maintain coverage.
236 CHECK(reentrant_window_state_change_call_check_
);
238 #endif // defined(OS_WIN)
240 void FullscreenController::OnTabDetachedFromView(WebContents
* old_contents
) {
241 if (!IsFullscreenForCapturedTab(old_contents
))
244 // A fullscreen-within-tab view undergoing screen capture has been detached
245 // and is no longer visible to the user. Set it to exactly the WebContents'
246 // preferred size. See 'FullscreenWithinTab Note'.
248 // When the user later selects the tab to show |old_contents| again, UI code
249 // elsewhere (e.g., views::WebView) will resize the view to fit within the
250 // browser window once again.
252 // If the view has been detached from the browser window (e.g., to drag a tab
253 // off into a new browser window), return immediately to avoid an unnecessary
255 if (!old_contents
->GetDelegate())
258 // Do nothing if tab capture ended after toggling fullscreen, or a preferred
259 // size was never specified by the capturer.
260 if (old_contents
->GetCapturerCount() == 0 ||
261 old_contents
->GetPreferredSize().IsEmpty()) {
265 content::RenderWidgetHostView
* const current_fs_view
=
266 old_contents
->GetFullscreenRenderWidgetHostView();
268 current_fs_view
->SetSize(old_contents
->GetPreferredSize());
269 ResizeWebContents(old_contents
, old_contents
->GetPreferredSize());
272 void FullscreenController::OnTabClosing(WebContents
* web_contents
) {
273 if (IsFullscreenForCapturedTab(web_contents
)) {
274 web_contents
->ExitFullscreen();
276 ExclusiveAccessControllerBase::OnTabClosing(web_contents
);
280 void FullscreenController::WindowFullscreenStateChanged() {
281 reentrant_window_state_change_call_check_
= true;
282 ExclusiveAccessContext
* const exclusive_access_context
=
283 exclusive_access_manager()->context();
284 bool exiting_fullscreen
= !exclusive_access_context
->IsFullscreen();
286 PostFullscreenChangeNotification(!exiting_fullscreen
);
287 if (exiting_fullscreen
) {
288 toggled_into_fullscreen_
= false;
289 extension_caused_fullscreen_
= GURL();
290 NotifyTabExclusiveAccessLost();
291 exclusive_access_context
->UnhideDownloadShelf();
293 exclusive_access_context
->HideDownloadShelf();
297 bool FullscreenController::HandleUserPressedEscape() {
298 WebContents
* const active_web_contents
=
299 exclusive_access_manager()->context()->GetActiveWebContents();
300 if (IsFullscreenForCapturedTab(active_web_contents
)) {
301 active_web_contents
->ExitFullscreen();
303 } else if (IsWindowFullscreenForTabOrPending()) {
304 ExitExclusiveAccessIfNecessary();
311 void FullscreenController::ExitExclusiveAccessToPreviousState() {
312 if (IsWindowFullscreenForTabOrPending())
313 ExitFullscreenModeForTab(exclusive_access_tab());
314 else if (IsFullscreenForBrowser())
315 ExitFullscreenModeInternal();
318 bool FullscreenController::OnAcceptExclusiveAccessPermission() {
319 ExclusiveAccessBubbleType bubble_type
=
320 exclusive_access_manager()->GetExclusiveAccessExitBubbleType();
321 bool fullscreen
= false;
322 exclusive_access_bubble::PermissionRequestedByType(bubble_type
, &fullscreen
,
324 DCHECK(!(fullscreen
&& tab_fullscreen_accepted_
));
326 if (fullscreen
&& !tab_fullscreen_accepted_
) {
327 DCHECK(exclusive_access_tab());
328 // Origins can enter fullscreen even when embedded in other origins.
329 // Permission is tracked based on the combinations of requester and
330 // embedder. Thus, even if a requesting origin has been previously approved
331 // for embedder A, it will not be approved when embedded in a different
334 // However, an exception is made when a requester and an embedder are the
335 // same origin. In other words, if the requester is the top-level frame. If
336 // that combination is ALLOWED, then future requests from that origin will
337 // succeed no matter what the embedder is. For example, if youtube.com
338 // is visited and user selects ALLOW. Later user visits example.com which
339 // embeds youtube.com in an iframe, which is then ALLOWED to go fullscreen.
340 GURL requester
= GetRequestingOrigin();
341 GURL embedder
= GetEmbeddingOrigin();
342 ContentSettingsPattern primary_pattern
=
343 ContentSettingsPattern::FromURLNoWildcard(requester
);
344 ContentSettingsPattern secondary_pattern
=
345 ContentSettingsPattern::FromURLNoWildcard(embedder
);
347 // ContentSettings requires valid patterns and the patterns might be invalid
348 // in some edge cases like if the current frame is about:blank.
350 // Do not store preference on file:// URLs, they don't have a clean
352 // TODO(estark): Revisit this when crbug.com/455882 is fixed.
353 if (!requester
.SchemeIsFile() && !embedder
.SchemeIsFile() &&
354 primary_pattern
.IsValid() && secondary_pattern
.IsValid()) {
355 HostContentSettingsMap
* settings_map
= exclusive_access_manager()
358 ->GetHostContentSettingsMap();
359 settings_map
->SetContentSetting(
360 primary_pattern
, secondary_pattern
, CONTENT_SETTINGS_TYPE_FULLSCREEN
,
361 std::string(), CONTENT_SETTING_ALLOW
);
363 tab_fullscreen_accepted_
= true;
370 bool FullscreenController::OnDenyExclusiveAccessPermission() {
371 if (IsWindowFullscreenForTabOrPending()) {
372 ExitExclusiveAccessIfNecessary();
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());
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
) {
421 // When in Metro snap mode, toggling in and out of fullscreen is prevented.
422 if (IsInMetroSnapMode())
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();
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())
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
)) {
460 if (enter_fullscreen
)
461 EnterFullscreenModeInternal(option
);
463 ExitFullscreenModeInternal();
466 void FullscreenController::EnterFullscreenModeInternal(
467 FullscreenInternalOption option
) {
468 toggled_into_fullscreen_
= true;
471 url
= GetRequestingOrigin();
472 tab_fullscreen_accepted_
= GetFullscreenSetting() == CONTENT_SETTING_ALLOW
;
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();
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 (exclusive_access_manager()
534 ->GetHostContentSettingsMap()
535 ->GetContentSetting(url
, url
, CONTENT_SETTINGS_TYPE_FULLSCREEN
,
536 std::string()) == CONTENT_SETTING_ALLOW
) {
537 return CONTENT_SETTING_ALLOW
;
540 // See the comment above the call to |SetContentSetting()| for how the
541 // requesting and embedding origins interact with each other wrt permissions.
542 return exclusive_access_manager()
545 ->GetHostContentSettingsMap()
546 ->GetContentSetting(url
, GetEmbeddingOrigin(),
547 CONTENT_SETTINGS_TYPE_FULLSCREEN
, std::string());
550 bool FullscreenController::IsPrivilegedFullscreenForTab() const {
551 const bool embedded_widget_present
=
552 exclusive_access_tab() &&
553 exclusive_access_tab()->GetFullscreenRenderWidgetHostView();
554 return embedded_widget_present
|| is_privileged_fullscreen_for_testing_
;
557 void FullscreenController::SetPrivilegedFullscreenForTesting(
558 bool is_privileged
) {
559 is_privileged_fullscreen_for_testing_
= is_privileged
;
562 bool FullscreenController::MaybeToggleFullscreenForCapturedTab(
563 WebContents
* web_contents
, bool enter_fullscreen
) {
564 if (enter_fullscreen
) {
565 if (web_contents
->GetCapturerCount() > 0) {
566 FullscreenWithinTabHelper::CreateForWebContents(web_contents
);
567 FullscreenWithinTabHelper::FromWebContents(web_contents
)->
568 SetIsFullscreenForCapturedTab(true);
572 if (IsFullscreenForCapturedTab(web_contents
)) {
573 FullscreenWithinTabHelper::RemoveForWebContents(web_contents
);
581 bool FullscreenController::IsFullscreenForCapturedTab(
582 const WebContents
* web_contents
) const {
583 // Note: On Mac, some of the OnTabXXX() methods get called with a nullptr
585 // for web_contents. Check for that here.
586 const FullscreenWithinTabHelper
* const helper
=
587 web_contents
? FullscreenWithinTabHelper::FromWebContents(web_contents
)
589 if (helper
&& helper
->is_fullscreen_for_captured_tab()) {
590 DCHECK_NE(exclusive_access_tab(), web_contents
);
596 GURL
FullscreenController::GetRequestingOrigin() const {
597 DCHECK(exclusive_access_tab());
599 if (!fullscreened_origin_
.is_empty())
600 return fullscreened_origin_
;
602 return exclusive_access_tab()->GetLastCommittedURL();
605 GURL
FullscreenController::GetEmbeddingOrigin() const {
606 DCHECK(exclusive_access_tab());
608 return exclusive_access_tab()->GetLastCommittedURL();