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/fullscreen/fullscreen_controller.h"
8 #include "base/command_line.h"
9 #include "base/message_loop.h"
10 #include "chrome/browser/content_settings/host_content_settings_map.h"
11 #include "chrome/browser/download/download_shelf.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/browser_tabstrip.h"
15 #include "chrome/browser/ui/browser_window.h"
16 #include "chrome/browser/ui/tab_contents/tab_contents.h"
17 #include "chrome/common/chrome_notification_types.h"
18 #include "chrome/common/chrome_switches.h"
19 #include "chrome/common/extensions/extension.h"
20 #include "content/public/browser/navigation_details.h"
21 #include "content/public/browser/navigation_entry.h"
22 #include "content/public/browser/notification_service.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/render_widget_host_view.h"
25 #include "content/public/browser/user_metrics.h"
26 #include "content/public/browser/web_contents.h"
28 using content::RenderViewHost
;
29 using content::UserMetricsAction
;
30 using content::WebContents
;
32 FullscreenController::FullscreenController(Browser
* browser
)
33 : ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
35 window_(browser
->window()),
36 profile_(browser
->profile()),
37 fullscreened_tab_(NULL
),
38 tab_caused_fullscreen_(false),
39 tab_fullscreen_accepted_(false),
40 toggled_into_fullscreen_(false),
41 mouse_lock_tab_(NULL
),
42 mouse_lock_state_(MOUSELOCK_NOT_REQUESTED
),
43 reentrant_window_state_change_call_check_(false) {
48 FullscreenController::~FullscreenController() {
51 bool FullscreenController::IsFullscreenForBrowser() const {
52 return window_
->IsFullscreen() && !tab_caused_fullscreen_
;
55 void FullscreenController::ToggleFullscreenMode() {
56 extension_caused_fullscreen_
= GURL();
57 ToggleFullscreenModeInternal(false);
60 bool FullscreenController::IsFullscreenForTabOrPending() const {
61 return fullscreened_tab_
!= NULL
;
64 bool FullscreenController::IsFullscreenForTabOrPending(
65 const WebContents
* web_contents
) const {
66 const TabContents
* tab_contents
=
67 TabContents::FromWebContents(web_contents
);
68 if (!tab_contents
|| (tab_contents
!= fullscreened_tab_
))
70 DCHECK(web_contents
== chrome::GetActiveWebContents(browser_
));
74 void FullscreenController::ToggleFullscreenModeForTab(WebContents
* web_contents
,
75 bool enter_fullscreen
) {
76 if (web_contents
!= chrome::GetActiveWebContents(browser_
))
80 // For now, avoid breaking when initiating full screen tab mode while in
82 // TODO(robertshield): Find a way to reconcile tab-initiated fullscreen
83 // modes with metro snap.
84 if (IsInMetroSnapMode())
88 bool in_browser_or_tab_fullscreen_mode
;
89 #if defined(OS_MACOSX)
90 in_browser_or_tab_fullscreen_mode
= window_
->InPresentationMode();
92 in_browser_or_tab_fullscreen_mode
= window_
->IsFullscreen();
95 if (enter_fullscreen
) {
96 SetFullscreenedTab(TabContents::FromWebContents(web_contents
));
97 if (!in_browser_or_tab_fullscreen_mode
) {
98 tab_caused_fullscreen_
= true;
99 #if defined(OS_MACOSX)
100 TogglePresentationModeInternal(true);
102 ToggleFullscreenModeInternal(true);
105 // We need to update the fullscreen exit bubble, e.g., going from browser
106 // fullscreen to tab fullscreen will need to show different content.
107 const GURL
& url
= web_contents
->GetURL();
108 if (!tab_fullscreen_accepted_
) {
109 tab_fullscreen_accepted_
=
110 GetFullscreenSetting(url
) == CONTENT_SETTING_ALLOW
;
112 UpdateFullscreenExitBubbleContent();
114 // This is only a change between Browser and Tab fullscreen. We generate
115 // a fullscreen notification now because there is no window change.
116 PostFullscreenChangeNotification(true);
119 if (in_browser_or_tab_fullscreen_mode
) {
120 if (tab_caused_fullscreen_
) {
121 #if defined(OS_MACOSX)
122 TogglePresentationModeInternal(true);
124 ToggleFullscreenModeInternal(true);
127 // If currently there is a tab in "tab fullscreen" mode and fullscreen
128 // was not caused by it (i.e., previously it was in "browser fullscreen"
129 // mode), we need to switch back to "browser fullscreen" mode. In this
130 // case, all we have to do is notifying the tab that it has exited "tab
132 NotifyTabOfExitIfNecessary();
134 // This is only a change between Browser and Tab fullscreen. We generate
135 // a fullscreen notification now because there is no window change.
136 PostFullscreenChangeNotification(true);
142 void FullscreenController::ToggleFullscreenModeWithExtension(
143 const GURL
& extension_url
) {
144 // |extension_caused_fullscreen_| will be reset if this causes fullscreen to
146 extension_caused_fullscreen_
= extension_url
;
147 ToggleFullscreenModeInternal(false);
150 bool FullscreenController::IsInMetroSnapMode() {
152 return window_
->IsInMetroSnapMode();
159 void FullscreenController::SetMetroSnapMode(bool enable
) {
160 reentrant_window_state_change_call_check_
= false;
162 toggled_into_fullscreen_
= false;
163 window_
->SetMetroSnapMode(enable
);
165 // FullscreenController unit tests for metro snap assume that on Windows calls
166 // to WindowFullscreenStateChanged are reentrant. If that assumption is
167 // invalidated, the tests must be updated to maintain coverage.
168 CHECK(reentrant_window_state_change_call_check_
);
170 #endif // defined(OS_WIN)
172 #if defined(OS_MACOSX)
173 void FullscreenController::TogglePresentationMode() {
174 TogglePresentationModeInternal(false);
178 bool FullscreenController::IsMouseLockRequested() const {
179 return mouse_lock_state_
== MOUSELOCK_REQUESTED
;
182 bool FullscreenController::IsMouseLocked() const {
183 return mouse_lock_state_
== MOUSELOCK_ACCEPTED
||
184 mouse_lock_state_
== MOUSELOCK_ACCEPTED_SILENTLY
;
187 void FullscreenController::RequestToLockMouse(WebContents
* web_contents
,
189 bool last_unlocked_by_target
) {
190 DCHECK(!IsMouseLocked());
191 NotifyMouseLockChange();
193 // Must have a user gesture to prevent misbehaving sites from constantly
194 // re-locking the mouse. Exceptions are when the page has unlocked
195 // (i.e. not the user), or if we're in tab fullscreen (user gesture required
197 if (!last_unlocked_by_target
&& !user_gesture
&&
198 !IsFullscreenForTabOrPending(web_contents
)) {
199 web_contents
->GotResponseToLockMouseRequest(false);
202 SetMouseLockTab(TabContents::FromWebContents(web_contents
));
203 FullscreenExitBubbleType bubble_type
= GetFullscreenExitBubbleType();
205 switch (GetMouseLockSetting(web_contents
->GetURL())) {
206 case CONTENT_SETTING_ALLOW
:
207 // If bubble already displaying buttons we must not lock the mouse yet,
208 // or it would prevent pressing those buttons. Instead, merge the request.
209 if (fullscreen_bubble::ShowButtonsForType(bubble_type
)) {
210 mouse_lock_state_
= MOUSELOCK_REQUESTED
;
213 if (web_contents
->GotResponseToLockMouseRequest(true)) {
214 if (last_unlocked_by_target
) {
215 mouse_lock_state_
= MOUSELOCK_ACCEPTED_SILENTLY
;
217 mouse_lock_state_
= MOUSELOCK_ACCEPTED
;
220 SetMouseLockTab(NULL
);
221 mouse_lock_state_
= MOUSELOCK_NOT_REQUESTED
;
225 case CONTENT_SETTING_BLOCK
:
226 web_contents
->GotResponseToLockMouseRequest(false);
227 SetMouseLockTab(NULL
);
228 mouse_lock_state_
= MOUSELOCK_NOT_REQUESTED
;
230 case CONTENT_SETTING_ASK
:
231 mouse_lock_state_
= MOUSELOCK_REQUESTED
;
236 UpdateFullscreenExitBubbleContent();
239 void FullscreenController::OnTabDeactivated(WebContents
* web_contents
) {
240 const TabContents
* contents
= TabContents::FromWebContents(web_contents
);
242 (contents
== fullscreened_tab_
|| contents
== mouse_lock_tab_
)) {
243 ExitTabFullscreenOrMouseLockIfNecessary();
247 void FullscreenController::OnTabClosing(WebContents
* web_contents
) {
248 const TabContents
* contents
= TabContents::FromWebContents(web_contents
);
250 (contents
== fullscreened_tab_
|| contents
== mouse_lock_tab_
)) {
251 ExitTabFullscreenOrMouseLockIfNecessary();
252 // The call to exit fullscreen may result in asynchronous notification of
253 // fullscreen state change (e.g., on Linux). We don't want to rely on it
254 // to call NotifyTabOfExitIfNecessary(), because at that point
255 // |fullscreened_tab_| may not be valid. Instead, we call it here to clean
256 // up tab fullscreen related state.
257 NotifyTabOfExitIfNecessary();
261 void FullscreenController::WindowFullscreenStateChanged() {
262 reentrant_window_state_change_call_check_
= true;
264 bool exiting_fullscreen
;
265 #if defined(OS_MACOSX)
266 exiting_fullscreen
= !window_
->InPresentationMode();
268 exiting_fullscreen
= !window_
->IsFullscreen();
270 PostFullscreenChangeNotification(!exiting_fullscreen
);
271 if (exiting_fullscreen
)
272 NotifyTabOfExitIfNecessary();
273 if (exiting_fullscreen
)
274 window_
->GetDownloadShelf()->Unhide();
276 window_
->GetDownloadShelf()->Hide();
279 bool FullscreenController::HandleUserPressedEscape() {
280 if (IsFullscreenForTabOrPending() ||
281 IsMouseLocked() || IsMouseLockRequested()) {
282 ExitTabFullscreenOrMouseLockIfNecessary();
289 void FullscreenController::OnAcceptFullscreenPermission(
291 FullscreenExitBubbleType bubble_type
) {
292 bool mouse_lock
= false;
293 bool fullscreen
= false;
294 fullscreen_bubble::PermissionRequestedByType(bubble_type
, &fullscreen
,
296 DCHECK(!(fullscreen
&& tab_fullscreen_accepted_
));
297 DCHECK(!(mouse_lock
&& IsMouseLocked()));
299 HostContentSettingsMap
* settings_map
= profile_
->GetHostContentSettingsMap();
300 ContentSettingsPattern pattern
= ContentSettingsPattern::FromURL(url
);
302 if (mouse_lock
&& !IsMouseLocked()) {
303 DCHECK(IsMouseLockRequested());
304 // TODO(markusheintz): We should allow patterns for all possible URLs here.
305 if (pattern
.IsValid()) {
306 settings_map
->SetContentSetting(
307 pattern
, ContentSettingsPattern::Wildcard(),
308 CONTENT_SETTINGS_TYPE_MOUSELOCK
, std::string(),
309 CONTENT_SETTING_ALLOW
);
312 if (mouse_lock_tab_
&&
313 mouse_lock_tab_
->web_contents() &&
314 mouse_lock_tab_
->web_contents()->GotResponseToLockMouseRequest(true)) {
315 mouse_lock_state_
= MOUSELOCK_ACCEPTED
;
317 mouse_lock_state_
= MOUSELOCK_NOT_REQUESTED
;
318 SetMouseLockTab(NULL
);
320 NotifyMouseLockChange();
323 if (fullscreen
&& !tab_fullscreen_accepted_
) {
324 DCHECK(fullscreened_tab_
);
325 if (pattern
.IsValid()) {
326 settings_map
->SetContentSetting(
327 pattern
, ContentSettingsPattern::Wildcard(),
328 CONTENT_SETTINGS_TYPE_FULLSCREEN
, std::string(),
329 CONTENT_SETTING_ALLOW
);
331 tab_fullscreen_accepted_
= true;
333 UpdateFullscreenExitBubbleContent();
336 void FullscreenController::OnDenyFullscreenPermission(
337 FullscreenExitBubbleType bubble_type
) {
338 bool mouse_lock
= false;
339 bool fullscreen
= false;
340 fullscreen_bubble::PermissionRequestedByType(bubble_type
, &fullscreen
,
342 DCHECK(fullscreened_tab_
|| mouse_lock_tab_
);
343 DCHECK(!(fullscreen
&& tab_fullscreen_accepted_
));
344 DCHECK(!(mouse_lock
&& IsMouseLocked()));
347 DCHECK(IsMouseLockRequested());
348 mouse_lock_state_
= MOUSELOCK_NOT_REQUESTED
;
349 if (mouse_lock_tab_
&& mouse_lock_tab_
->web_contents())
350 mouse_lock_tab_
->web_contents()->GotResponseToLockMouseRequest(false);
351 SetMouseLockTab(NULL
);
352 NotifyMouseLockChange();
354 // UpdateFullscreenExitBubbleContent() must be called, but to avoid
355 // duplicate calls we do so only if not adjusting the fullscreen state
356 // below, which also calls UpdateFullscreenExitBubbleContent().
358 UpdateFullscreenExitBubbleContent();
362 ExitTabFullscreenOrMouseLockIfNecessary();
365 void FullscreenController::LostMouseLock() {
366 mouse_lock_state_
= MOUSELOCK_NOT_REQUESTED
;
367 SetMouseLockTab(NULL
);
368 NotifyMouseLockChange();
369 UpdateFullscreenExitBubbleContent();
372 void FullscreenController::Observe(int type
,
373 const content::NotificationSource
& source
,
374 const content::NotificationDetails
& details
) {
376 case content::NOTIFICATION_NAV_ENTRY_COMMITTED
:
377 if (content::Details
<content::LoadCommittedDetails
>(details
)->
378 is_navigation_to_different_page()) {
379 ExitTabFullscreenOrMouseLockIfNecessary();
384 NOTREACHED() << "Got a notification we didn't register for.";
388 FullscreenExitBubbleType
FullscreenController::GetFullscreenExitBubbleType()
390 // In kiosk mode we always want to be fullscreen and do not want to show
391 // exit instructions for browser mode fullscreen.
393 #if !defined(OS_MACOSX) // Kiosk mode not available on Mac.
394 kiosk
= CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode
);
397 if (mouse_lock_state_
== MOUSELOCK_ACCEPTED_SILENTLY
) {
398 return FEB_TYPE_NONE
;
401 if (fullscreened_tab_
) {
402 if (tab_fullscreen_accepted_
) {
403 if (IsMouseLocked()) {
404 return FEB_TYPE_FULLSCREEN_MOUSELOCK_EXIT_INSTRUCTION
;
405 } else if (IsMouseLockRequested()) {
406 return FEB_TYPE_MOUSELOCK_BUTTONS
;
408 return FEB_TYPE_FULLSCREEN_EXIT_INSTRUCTION
;
410 } else { // Full screen not yet accepted.
411 if (IsMouseLockRequested()) {
412 return FEB_TYPE_FULLSCREEN_MOUSELOCK_BUTTONS
;
414 return FEB_TYPE_FULLSCREEN_BUTTONS
;
417 } else { // Not tab full screen.
418 if (IsMouseLocked()) {
419 return FEB_TYPE_MOUSELOCK_EXIT_INSTRUCTION
;
420 } else if (IsMouseLockRequested()) {
421 return FEB_TYPE_MOUSELOCK_BUTTONS
;
423 if (!extension_caused_fullscreen_
.is_empty()) {
424 return FEB_TYPE_BROWSER_EXTENSION_FULLSCREEN_EXIT_INSTRUCTION
;
425 } else if (toggled_into_fullscreen_
&& !kiosk
) {
426 return FEB_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION
;
428 return FEB_TYPE_NONE
;
433 return FEB_TYPE_NONE
;
436 void FullscreenController::UpdateNotificationRegistrations() {
437 if (fullscreened_tab_
&& mouse_lock_tab_
)
438 DCHECK(fullscreened_tab_
== mouse_lock_tab_
);
440 TabContents
* tab
= fullscreened_tab_
? fullscreened_tab_
: mouse_lock_tab_
;
442 if (tab
&& registrar_
.IsEmpty()) {
443 registrar_
.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED
,
444 content::Source
<content::NavigationController
>(
445 &tab
->web_contents()->GetController()));
446 } else if (!tab
&& !registrar_
.IsEmpty()) {
447 registrar_
.RemoveAll();
451 void FullscreenController::PostFullscreenChangeNotification(
452 bool is_fullscreen
) {
453 MessageLoop::current()->PostTask(FROM_HERE
,
454 base::Bind(&FullscreenController::NotifyFullscreenChange
,
455 ptr_factory_
.GetWeakPtr(), is_fullscreen
));
458 void FullscreenController::NotifyFullscreenChange(bool is_fullscreen
) {
459 content::NotificationService::current()->Notify(
460 chrome::NOTIFICATION_FULLSCREEN_CHANGED
,
461 content::Source
<FullscreenController
>(this),
462 content::Details
<bool>(&is_fullscreen
));
465 void FullscreenController::NotifyTabOfExitIfNecessary() {
466 if (fullscreened_tab_
) {
467 RenderViewHost
* rvh
=
468 fullscreened_tab_
->web_contents()->GetRenderViewHost();
469 SetFullscreenedTab(NULL
);
470 tab_caused_fullscreen_
= false;
471 tab_fullscreen_accepted_
= false;
473 rvh
->ExitFullscreen();
476 if (mouse_lock_tab_
) {
477 WebContents
* web_contents
= mouse_lock_tab_
->web_contents();
478 if (IsMouseLockRequested()) {
479 web_contents
->GotResponseToLockMouseRequest(false);
480 NotifyMouseLockChange();
481 } else if (web_contents
->GetRenderViewHost() &&
482 web_contents
->GetRenderViewHost()->GetView()) {
483 web_contents
->GetRenderViewHost()->GetView()->UnlockMouse();
485 SetMouseLockTab(NULL
);
486 mouse_lock_state_
= MOUSELOCK_NOT_REQUESTED
;
489 UpdateFullscreenExitBubbleContent();
492 void FullscreenController::NotifyMouseLockChange() {
493 content::NotificationService::current()->Notify(
494 chrome::NOTIFICATION_MOUSE_LOCK_CHANGED
,
495 content::Source
<FullscreenController
>(this),
496 content::NotificationService::NoDetails());
499 // TODO(koz): Change |for_tab| to an enum.
500 void FullscreenController::ToggleFullscreenModeInternal(bool for_tab
) {
502 // When in Metro snap mode, toggling in and out of fullscreen is prevented.
503 if (IsInMetroSnapMode())
507 toggled_into_fullscreen_
= !window_
->IsFullscreen();
509 // In kiosk mode, we always want to be fullscreen. When the browser first
510 // starts we're not yet fullscreen, so let the initial toggle go through.
511 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode
) &&
512 !toggled_into_fullscreen_
)
517 url
= chrome::GetActiveWebContents(browser_
)->GetURL();
518 tab_fullscreen_accepted_
= toggled_into_fullscreen_
&&
519 GetFullscreenSetting(url
) == CONTENT_SETTING_ALLOW
;
521 if (!extension_caused_fullscreen_
.is_empty())
522 url
= extension_caused_fullscreen_
;
523 content::RecordAction(UserMetricsAction("ToggleFullscreen"));
525 if (toggled_into_fullscreen_
) {
526 window_
->EnterFullscreen(url
, GetFullscreenExitBubbleType());
528 #if defined(OS_MACOSX)
529 if (window_
->InPresentationMode())
530 window_
->ExitPresentationMode();
533 window_
->ExitFullscreen();
534 extension_caused_fullscreen_
= GURL();
536 UpdateFullscreenExitBubbleContent();
538 // Once the window has become fullscreen it'll call back to
539 // WindowFullscreenStateChanged(). We don't do this immediately as
540 // BrowserWindow::EnterFullscreen() asks for bookmark_bar_state_, so we let
541 // the BrowserWindow invoke WindowFullscreenStateChanged when appropriate.
544 #if defined(OS_MACOSX)
545 void FullscreenController::TogglePresentationModeInternal(bool for_tab
) {
546 toggled_into_fullscreen_
= !window_
->InPresentationMode();
549 url
= chrome::GetActiveWebContents(browser_
)->GetURL();
550 tab_fullscreen_accepted_
= toggled_into_fullscreen_
&&
551 GetFullscreenSetting(url
) == CONTENT_SETTING_ALLOW
;
553 if (!window_
->InPresentationMode())
554 window_
->EnterPresentationMode(url
, GetFullscreenExitBubbleType());
556 window_
->ExitFullscreen();
557 UpdateFullscreenExitBubbleContent();
559 // WindowFullscreenStateChanged will be called by BrowserWindowController
560 // when the transition completes.
564 void FullscreenController::SetFullscreenedTab(TabContents
* tab
) {
565 fullscreened_tab_
= tab
;
566 UpdateNotificationRegistrations();
569 void FullscreenController::SetMouseLockTab(TabContents
* tab
) {
570 mouse_lock_tab_
= tab
;
571 UpdateNotificationRegistrations();
574 void FullscreenController::ExitTabFullscreenOrMouseLockIfNecessary() {
575 if (tab_caused_fullscreen_
)
576 ToggleFullscreenMode();
578 NotifyTabOfExitIfNecessary();
581 void FullscreenController::UpdateFullscreenExitBubbleContent() {
583 if (fullscreened_tab_
)
584 url
= fullscreened_tab_
->web_contents()->GetURL();
585 else if (mouse_lock_tab_
)
586 url
= mouse_lock_tab_
->web_contents()->GetURL();
587 else if (!extension_caused_fullscreen_
.is_empty())
588 url
= extension_caused_fullscreen_
;
590 FullscreenExitBubbleType bubble_type
= GetFullscreenExitBubbleType();
592 // If bubble displays buttons, unlock mouse to allow pressing them.
593 if (fullscreen_bubble::ShowButtonsForType(bubble_type
) &&
595 mouse_lock_tab_
->web_contents()) {
596 WebContents
* web_contents
= mouse_lock_tab_
->web_contents();
597 if (web_contents
&& web_contents
->GetRenderViewHost() &&
598 web_contents
->GetRenderViewHost()->GetView())
599 web_contents
->GetRenderViewHost()->GetView()->UnlockMouse();
602 window_
->UpdateFullscreenExitBubbleContent(url
, bubble_type
);
606 FullscreenController::GetFullscreenSetting(const GURL
& url
) const {
607 if (url
.SchemeIsFile())
608 return CONTENT_SETTING_ALLOW
;
610 return profile_
->GetHostContentSettingsMap()->GetContentSetting(url
, url
,
611 CONTENT_SETTINGS_TYPE_FULLSCREEN
, std::string());
615 FullscreenController::GetMouseLockSetting(const GURL
& url
) const {
616 if (url
.SchemeIsFile())
617 return CONTENT_SETTING_ALLOW
;
619 HostContentSettingsMap
* settings_map
= profile_
->GetHostContentSettingsMap();
620 return settings_map
->GetContentSetting(url
, url
,
621 CONTENT_SETTINGS_TYPE_MOUSELOCK
, std::string());