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.
4
5 #include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
6
7 #include "base/bind.h"
8 #include "base/command_line.h"
9 #include "base/message_loop/message_loop.h"
10 #include "chrome/browser/app_mode/app_mode_utils.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/content_settings/host_content_settings_map.h"
13 #include "chrome/browser/download/download_shelf.h"
14 #include "chrome/browser/fullscreen.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/ui/browser.h"
17 #include "chrome/browser/ui/browser_window.h"
18 #include "chrome/browser/ui/fullscreen/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 "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"
31
32 #if defined(OS_MACOSX)
33 #include "base/mac/mac_util.h"
34 #else
35 #include "base/prefs/pref_service.h"
36 #include "chrome/common/pref_names.h"
37 #endif
38
39 using base::UserMetricsAction;
40 using content::RenderViewHost;
41 using content::WebContents;
42
FullscreenController(Browser * browser)43 FullscreenController::FullscreenController(Browser* browser)
44 : browser_(browser),
45 window_(browser->window()),
46 profile_(browser->profile()),
47 fullscreened_tab_(NULL),
48 state_prior_to_tab_fullscreen_(STATE_INVALID),
49 tab_fullscreen_accepted_(false),
50 toggled_into_fullscreen_(false),
51 mouse_lock_tab_(NULL),
52 mouse_lock_state_(MOUSELOCK_NOT_REQUESTED),
53 reentrant_window_state_change_call_check_(false),
54 is_privileged_fullscreen_for_testing_(false),
55 ptr_factory_(this) {
56 DCHECK(window_);
57 DCHECK(profile_);
58 }
59
~FullscreenController()60 FullscreenController::~FullscreenController() {
61 }
62
IsFullscreenForBrowser() const63 bool FullscreenController::IsFullscreenForBrowser() const {
64 return window_->IsFullscreen() && !IsFullscreenCausedByTab();
65 }
66
ToggleBrowserFullscreenMode()67 void FullscreenController::ToggleBrowserFullscreenMode() {
68 extension_caused_fullscreen_ = GURL();
69 ToggleFullscreenModeInternal(BROWSER);
70 }
71
ToggleBrowserFullscreenModeWithExtension(const GURL & extension_url)72 void FullscreenController::ToggleBrowserFullscreenModeWithExtension(
73 const GURL& extension_url) {
74 // |extension_caused_fullscreen_| will be reset if this causes fullscreen to
75 // exit.
76 extension_caused_fullscreen_ = extension_url;
77 ToggleFullscreenModeInternal(BROWSER);
78 }
79
IsWindowFullscreenForTabOrPending() const80 bool FullscreenController::IsWindowFullscreenForTabOrPending() const {
81 return fullscreened_tab_ != NULL;
82 }
83
IsFullscreenForTabOrPending(const WebContents * web_contents) const84 bool FullscreenController::IsFullscreenForTabOrPending(
85 const WebContents* web_contents) const {
86 if (web_contents == fullscreened_tab_) {
87 DCHECK(web_contents == browser_->tab_strip_model()->GetActiveWebContents());
88 DCHECK(web_contents->GetCapturerCount() == 0);
89 return true;
90 }
91 return IsFullscreenForCapturedTab(web_contents);
92 }
93
IsFullscreenCausedByTab() const94 bool FullscreenController::IsFullscreenCausedByTab() const {
95 return state_prior_to_tab_fullscreen_ == STATE_NORMAL;
96 }
97
ToggleFullscreenModeForTab(WebContents * web_contents,bool enter_fullscreen)98 void FullscreenController::ToggleFullscreenModeForTab(WebContents* web_contents,
99 bool enter_fullscreen) {
100 if (MaybeToggleFullscreenForCapturedTab(web_contents, enter_fullscreen)) {
101 // During tab capture of fullscreen-within-tab views, the browser window
102 // fullscreen state is unchanged, so return now.
103 return;
104 }
105 if (fullscreened_tab_) {
106 if (web_contents != fullscreened_tab_)
107 return;
108 } else if (
109 web_contents != browser_->tab_strip_model()->GetActiveWebContents()) {
110 return;
111 }
112 if (IsWindowFullscreenForTabOrPending() == enter_fullscreen)
113 return;
114
115 #if defined(OS_WIN)
116 // For now, avoid breaking when initiating full screen tab mode while in
117 // a metro snap.
118 // TODO(robertshield): Find a way to reconcile tab-initiated fullscreen
119 // modes with metro snap.
120 if (IsInMetroSnapMode())
121 return;
122 #endif
123
124 bool in_browser_or_tab_fullscreen_mode = window_->IsFullscreen();
125
126 if (enter_fullscreen) {
127 SetFullscreenedTab(web_contents);
128 if (!in_browser_or_tab_fullscreen_mode) {
129 state_prior_to_tab_fullscreen_ = STATE_NORMAL;
130 ToggleFullscreenModeInternal(TAB);
131 } else {
132 #if defined(OS_MACOSX)
133 state_prior_to_tab_fullscreen_ =
134 window_->IsFullscreenWithChrome()
135 ? STATE_BROWSER_FULLSCREEN_WITH_CHROME
136 : STATE_BROWSER_FULLSCREEN_NO_CHROME;
137
138 // The browser is in AppKit fullscreen. Remove the chrome, if it's
139 // present.
140 window_->EnterFullscreenWithoutChrome();
141 #else
142 state_prior_to_tab_fullscreen_ = STATE_BROWSER_FULLSCREEN_NO_CHROME;
143 #endif // defined(OS_MACOSX)
144
145 // We need to update the fullscreen exit bubble, e.g., going from browser
146 // fullscreen to tab fullscreen will need to show different content.
147 const GURL& url = web_contents->GetURL();
148 if (!tab_fullscreen_accepted_) {
149 tab_fullscreen_accepted_ =
150 GetFullscreenSetting(url) == CONTENT_SETTING_ALLOW;
151 }
152 UpdateFullscreenExitBubbleContent();
153
154 // This is only a change between Browser and Tab fullscreen. We generate
155 // a fullscreen notification now because there is no window change.
156 PostFullscreenChangeNotification(true);
157 }
158 } else {
159 if (in_browser_or_tab_fullscreen_mode) {
160 if (IsFullscreenCausedByTab()) {
161 ToggleFullscreenModeInternal(TAB);
162 } else {
163 #if defined(OS_MACOSX)
164 if (state_prior_to_tab_fullscreen_ ==
165 STATE_BROWSER_FULLSCREEN_WITH_CHROME) {
166 // The browser is still in AppKit Fullscreen. This just adds back the
167 // chrome.
168 window_->EnterFullscreenWithChrome();
169 }
170
171 // Clear the bubble URL, which forces the Mac UI to redraw.
172 UpdateFullscreenExitBubbleContent();
173 #endif // defined(OS_MACOSX)
174
175 // If currently there is a tab in "tab fullscreen" mode and fullscreen
176 // was not caused by it (i.e., previously it was in "browser fullscreen"
177 // mode), we need to switch back to "browser fullscreen" mode. In this
178 // case, all we have to do is notifying the tab that it has exited "tab
179 // fullscreen" mode.
180 NotifyTabOfExitIfNecessary();
181
182 // This is only a change between Browser and Tab fullscreen. We generate
183 // a fullscreen notification now because there is no window change.
184 PostFullscreenChangeNotification(true);
185 }
186 }
187 }
188 }
189
IsInMetroSnapMode()190 bool FullscreenController::IsInMetroSnapMode() {
191 #if defined(OS_WIN)
192 return window_->IsInMetroSnapMode();
193 #else
194 return false;
195 #endif
196 }
197
198 #if defined(OS_WIN)
SetMetroSnapMode(bool enable)199 void FullscreenController::SetMetroSnapMode(bool enable) {
200 reentrant_window_state_change_call_check_ = false;
201
202 toggled_into_fullscreen_ = false;
203 window_->SetMetroSnapMode(enable);
204
205 // FullscreenController unit tests for metro snap assume that on Windows calls
206 // to WindowFullscreenStateChanged are reentrant. If that assumption is
207 // invalidated, the tests must be updated to maintain coverage.
208 CHECK(reentrant_window_state_change_call_check_);
209 }
210 #endif // defined(OS_WIN)
211
212 #if defined(OS_MACOSX)
ToggleBrowserFullscreenWithChrome()213 void FullscreenController::ToggleBrowserFullscreenWithChrome() {
214 ToggleFullscreenModeInternal(BROWSER_WITH_CHROME);
215 }
216 #endif
217
IsMouseLockRequested() const218 bool FullscreenController::IsMouseLockRequested() const {
219 return mouse_lock_state_ == MOUSELOCK_REQUESTED;
220 }
221
IsMouseLocked() const222 bool FullscreenController::IsMouseLocked() const {
223 return mouse_lock_state_ == MOUSELOCK_ACCEPTED ||
224 mouse_lock_state_ == MOUSELOCK_ACCEPTED_SILENTLY;
225 }
226
RequestToLockMouse(WebContents * web_contents,bool user_gesture,bool last_unlocked_by_target)227 void FullscreenController::RequestToLockMouse(WebContents* web_contents,
228 bool user_gesture,
229 bool last_unlocked_by_target) {
230 DCHECK(!IsMouseLocked());
231 NotifyMouseLockChange();
232
233 // Must have a user gesture to prevent misbehaving sites from constantly
234 // re-locking the mouse. Exceptions are when the page has unlocked
235 // (i.e. not the user), or if we're in tab fullscreen (user gesture required
236 // for that)
237 if (!last_unlocked_by_target && !user_gesture &&
238 !IsFullscreenForTabOrPending(web_contents)) {
239 web_contents->GotResponseToLockMouseRequest(false);
240 return;
241 }
242 SetMouseLockTab(web_contents);
243 FullscreenExitBubbleType bubble_type = GetFullscreenExitBubbleType();
244
245 switch (GetMouseLockSetting(web_contents->GetURL())) {
246 case CONTENT_SETTING_ALLOW:
247 // If bubble already displaying buttons we must not lock the mouse yet,
248 // or it would prevent pressing those buttons. Instead, merge the request.
249 if (!IsPrivilegedFullscreenForTab() &&
250 fullscreen_bubble::ShowButtonsForType(bubble_type)) {
251 mouse_lock_state_ = MOUSELOCK_REQUESTED;
252 } else {
253 // Lock mouse.
254 if (web_contents->GotResponseToLockMouseRequest(true)) {
255 if (last_unlocked_by_target) {
256 mouse_lock_state_ = MOUSELOCK_ACCEPTED_SILENTLY;
257 } else {
258 mouse_lock_state_ = MOUSELOCK_ACCEPTED;
259 }
260 } else {
261 SetMouseLockTab(NULL);
262 mouse_lock_state_ = MOUSELOCK_NOT_REQUESTED;
263 }
264 }
265 break;
266 case CONTENT_SETTING_BLOCK:
267 web_contents->GotResponseToLockMouseRequest(false);
268 SetMouseLockTab(NULL);
269 mouse_lock_state_ = MOUSELOCK_NOT_REQUESTED;
270 break;
271 case CONTENT_SETTING_ASK:
272 mouse_lock_state_ = MOUSELOCK_REQUESTED;
273 break;
274 default:
275 NOTREACHED();
276 }
277 UpdateFullscreenExitBubbleContent();
278 }
279
OnTabDeactivated(WebContents * web_contents)280 void FullscreenController::OnTabDeactivated(WebContents* web_contents) {
281 if (web_contents == fullscreened_tab_ || web_contents == mouse_lock_tab_)
282 ExitTabFullscreenOrMouseLockIfNecessary();
283 }
284
OnTabDetachedFromView(WebContents * old_contents)285 void FullscreenController::OnTabDetachedFromView(WebContents* old_contents) {
286 if (!IsFullscreenForCapturedTab(old_contents))
287 return;
288
289 // A fullscreen-within-tab view undergoing screen capture has been detached
290 // and is no longer visible to the user. Set it to exactly the WebContents'
291 // preferred size. See 'FullscreenWithinTab Note'.
292 //
293 // When the user later selects the tab to show |old_contents| again, UI code
294 // elsewhere (e.g., views::WebView) will resize the view to fit within the
295 // browser window once again.
296
297 // If the view has been detached from the browser window (e.g., to drag a tab
298 // off into a new browser window), return immediately to avoid an unnecessary
299 // resize.
300 if (!old_contents->GetDelegate())
301 return;
302
303 // Do nothing if tab capture ended after toggling fullscreen, or a preferred
304 // size was never specified by the capturer.
305 if (old_contents->GetCapturerCount() == 0 ||
306 old_contents->GetPreferredSize().IsEmpty()) {
307 return;
308 }
309
310 content::RenderWidgetHostView* const current_fs_view =
311 old_contents->GetFullscreenRenderWidgetHostView();
312 if (current_fs_view)
313 current_fs_view->SetSize(old_contents->GetPreferredSize());
314 ResizeWebContents(old_contents, old_contents->GetPreferredSize());
315 }
316
OnTabClosing(WebContents * web_contents)317 void FullscreenController::OnTabClosing(WebContents* web_contents) {
318 if (IsFullscreenForCapturedTab(web_contents)) {
319 RenderViewHost* const rvh = web_contents->GetRenderViewHost();
320 if (rvh)
321 rvh->ExitFullscreen();
322 } else if (web_contents == fullscreened_tab_ ||
323 web_contents == mouse_lock_tab_) {
324 ExitTabFullscreenOrMouseLockIfNecessary();
325 // The call to exit fullscreen may result in asynchronous notification of
326 // fullscreen state change (e.g., on Linux). We don't want to rely on it
327 // to call NotifyTabOfExitIfNecessary(), because at that point
328 // |fullscreened_tab_| may not be valid. Instead, we call it here to clean
329 // up tab fullscreen related state.
330 NotifyTabOfExitIfNecessary();
331 }
332 }
333
WindowFullscreenStateChanged()334 void FullscreenController::WindowFullscreenStateChanged() {
335 reentrant_window_state_change_call_check_ = true;
336
337 bool exiting_fullscreen = !window_->IsFullscreen();
338
339 PostFullscreenChangeNotification(!exiting_fullscreen);
340 if (exiting_fullscreen) {
341 toggled_into_fullscreen_ = false;
342 extension_caused_fullscreen_ = GURL();
343 NotifyTabOfExitIfNecessary();
344 }
345 if (exiting_fullscreen) {
346 window_->GetDownloadShelf()->Unhide();
347 } else {
348 window_->GetDownloadShelf()->Hide();
349 if (window_->GetStatusBubble())
350 window_->GetStatusBubble()->Hide();
351 }
352 }
353
HandleUserPressedEscape()354 bool FullscreenController::HandleUserPressedEscape() {
355 WebContents* const active_web_contents =
356 browser_->tab_strip_model()->GetActiveWebContents();
357 if (IsFullscreenForCapturedTab(active_web_contents)) {
358 RenderViewHost* const rvh = active_web_contents->GetRenderViewHost();
359 if (rvh)
360 rvh->ExitFullscreen();
361 return true;
362 } else if (IsWindowFullscreenForTabOrPending() ||
363 IsMouseLocked() || IsMouseLockRequested()) {
364 ExitTabFullscreenOrMouseLockIfNecessary();
365 return true;
366 }
367
368 return false;
369 }
370
ExitTabOrBrowserFullscreenToPreviousState()371 void FullscreenController::ExitTabOrBrowserFullscreenToPreviousState() {
372 if (IsWindowFullscreenForTabOrPending())
373 ExitTabFullscreenOrMouseLockIfNecessary();
374 else if (IsFullscreenForBrowser())
375 ExitFullscreenModeInternal();
376 }
377
OnAcceptFullscreenPermission()378 void FullscreenController::OnAcceptFullscreenPermission() {
379 FullscreenExitBubbleType bubble_type = GetFullscreenExitBubbleType();
380 bool mouse_lock = false;
381 bool fullscreen = false;
382 fullscreen_bubble::PermissionRequestedByType(bubble_type, &fullscreen,
383 &mouse_lock);
384 DCHECK(!(fullscreen && tab_fullscreen_accepted_));
385 DCHECK(!(mouse_lock && IsMouseLocked()));
386
387 HostContentSettingsMap* settings_map = profile_->GetHostContentSettingsMap();
388
389 GURL url = GetFullscreenExitBubbleURL();
390 ContentSettingsPattern pattern = ContentSettingsPattern::FromURL(url);
391
392 if (mouse_lock && !IsMouseLocked()) {
393 DCHECK(IsMouseLockRequested());
394 // TODO(markusheintz): We should allow patterns for all possible URLs here.
395 if (pattern.IsValid()) {
396 settings_map->SetContentSetting(
397 pattern, ContentSettingsPattern::Wildcard(),
398 CONTENT_SETTINGS_TYPE_MOUSELOCK, std::string(),
399 CONTENT_SETTING_ALLOW);
400 }
401
402 if (mouse_lock_tab_ &&
403 mouse_lock_tab_->GotResponseToLockMouseRequest(true)) {
404 mouse_lock_state_ = MOUSELOCK_ACCEPTED;
405 } else {
406 mouse_lock_state_ = MOUSELOCK_NOT_REQUESTED;
407 SetMouseLockTab(NULL);
408 }
409 NotifyMouseLockChange();
410 }
411
412 if (fullscreen && !tab_fullscreen_accepted_) {
413 DCHECK(fullscreened_tab_);
414 if (pattern.IsValid()) {
415 settings_map->SetContentSetting(
416 pattern, ContentSettingsPattern::Wildcard(),
417 CONTENT_SETTINGS_TYPE_FULLSCREEN, std::string(),
418 CONTENT_SETTING_ALLOW);
419 }
420 tab_fullscreen_accepted_ = true;
421 }
422 UpdateFullscreenExitBubbleContent();
423 }
424
OnDenyFullscreenPermission()425 void FullscreenController::OnDenyFullscreenPermission() {
426 if (!fullscreened_tab_ && !mouse_lock_tab_)
427 return;
428
429 if (IsMouseLockRequested()) {
430 mouse_lock_state_ = MOUSELOCK_NOT_REQUESTED;
431 if (mouse_lock_tab_)
432 mouse_lock_tab_->GotResponseToLockMouseRequest(false);
433 SetMouseLockTab(NULL);
434 NotifyMouseLockChange();
435
436 // UpdateFullscreenExitBubbleContent() must be called, but to avoid
437 // duplicate calls we do so only if not adjusting the fullscreen state
438 // below, which also calls UpdateFullscreenExitBubbleContent().
439 if (!IsWindowFullscreenForTabOrPending())
440 UpdateFullscreenExitBubbleContent();
441 }
442
443 if (IsWindowFullscreenForTabOrPending())
444 ExitTabFullscreenOrMouseLockIfNecessary();
445 }
446
LostMouseLock()447 void FullscreenController::LostMouseLock() {
448 mouse_lock_state_ = MOUSELOCK_NOT_REQUESTED;
449 SetMouseLockTab(NULL);
450 NotifyMouseLockChange();
451 UpdateFullscreenExitBubbleContent();
452 }
453
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)454 void FullscreenController::Observe(int type,
455 const content::NotificationSource& source,
456 const content::NotificationDetails& details) {
457 DCHECK_EQ(content::NOTIFICATION_NAV_ENTRY_COMMITTED, type);
458 if (content::Details<content::LoadCommittedDetails>(details)->
459 is_navigation_to_different_page())
460 ExitTabFullscreenOrMouseLockIfNecessary();
461 }
462
GetFullscreenExitBubbleURL() const463 GURL FullscreenController::GetFullscreenExitBubbleURL() const {
464 if (fullscreened_tab_)
465 return fullscreened_tab_->GetURL();
466 if (mouse_lock_tab_)
467 return mouse_lock_tab_->GetURL();
468 return extension_caused_fullscreen_;
469 }
470
GetFullscreenExitBubbleType() const471 FullscreenExitBubbleType FullscreenController::GetFullscreenExitBubbleType()
472 const {
473 // In kiosk and exclusive app mode we always want to be fullscreen and do not
474 // want to show exit instructions for browser mode fullscreen.
475 bool app_mode = false;
476 #if !defined(OS_MACOSX) // App mode (kiosk) is not available on Mac yet.
477 app_mode = chrome::IsRunningInAppMode();
478 #endif
479
480 if (mouse_lock_state_ == MOUSELOCK_ACCEPTED_SILENTLY)
481 return FEB_TYPE_NONE;
482
483 if (!fullscreened_tab_) {
484 if (IsMouseLocked())
485 return FEB_TYPE_MOUSELOCK_EXIT_INSTRUCTION;
486 if (IsMouseLockRequested())
487 return FEB_TYPE_MOUSELOCK_BUTTONS;
488 if (!extension_caused_fullscreen_.is_empty())
489 return FEB_TYPE_BROWSER_EXTENSION_FULLSCREEN_EXIT_INSTRUCTION;
490 if (toggled_into_fullscreen_ && !app_mode)
491 return FEB_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION;
492 return FEB_TYPE_NONE;
493 }
494
495 if (tab_fullscreen_accepted_) {
496 if (IsPrivilegedFullscreenForTab())
497 return FEB_TYPE_NONE;
498 if (IsMouseLocked())
499 return FEB_TYPE_FULLSCREEN_MOUSELOCK_EXIT_INSTRUCTION;
500 if (IsMouseLockRequested())
501 return FEB_TYPE_MOUSELOCK_BUTTONS;
502 return FEB_TYPE_FULLSCREEN_EXIT_INSTRUCTION;
503 }
504
505 if (IsMouseLockRequested())
506 return FEB_TYPE_FULLSCREEN_MOUSELOCK_BUTTONS;
507 return FEB_TYPE_FULLSCREEN_BUTTONS;
508 }
509
UpdateNotificationRegistrations()510 void FullscreenController::UpdateNotificationRegistrations() {
511 if (fullscreened_tab_ && mouse_lock_tab_)
512 DCHECK(fullscreened_tab_ == mouse_lock_tab_);
513
514 WebContents* tab = fullscreened_tab_ ? fullscreened_tab_ : mouse_lock_tab_;
515
516 if (tab && registrar_.IsEmpty()) {
517 registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
518 content::Source<content::NavigationController>(&tab->GetController()));
519 } else if (!tab && !registrar_.IsEmpty()) {
520 registrar_.RemoveAll();
521 }
522 }
523
PostFullscreenChangeNotification(bool is_fullscreen)524 void FullscreenController::PostFullscreenChangeNotification(
525 bool is_fullscreen) {
526 base::MessageLoop::current()->PostTask(
527 FROM_HERE,
528 base::Bind(&FullscreenController::NotifyFullscreenChange,
529 ptr_factory_.GetWeakPtr(),
530 is_fullscreen));
531 }
532
NotifyFullscreenChange(bool is_fullscreen)533 void FullscreenController::NotifyFullscreenChange(bool is_fullscreen) {
534 content::NotificationService::current()->Notify(
535 chrome::NOTIFICATION_FULLSCREEN_CHANGED,
536 content::Source<FullscreenController>(this),
537 content::Details<bool>(&is_fullscreen));
538 }
539
NotifyTabOfExitIfNecessary()540 void FullscreenController::NotifyTabOfExitIfNecessary() {
541 if (fullscreened_tab_) {
542 RenderViewHost* rvh = fullscreened_tab_->GetRenderViewHost();
543 SetFullscreenedTab(NULL);
544 state_prior_to_tab_fullscreen_ = STATE_INVALID;
545 tab_fullscreen_accepted_ = false;
546 if (rvh)
547 rvh->ExitFullscreen();
548 }
549
550 if (mouse_lock_tab_) {
551 if (IsMouseLockRequested()) {
552 mouse_lock_tab_->GotResponseToLockMouseRequest(false);
553 NotifyMouseLockChange();
554 } else {
555 UnlockMouse();
556 }
557 SetMouseLockTab(NULL);
558 mouse_lock_state_ = MOUSELOCK_NOT_REQUESTED;
559 }
560
561 UpdateFullscreenExitBubbleContent();
562 }
563
NotifyMouseLockChange()564 void FullscreenController::NotifyMouseLockChange() {
565 content::NotificationService::current()->Notify(
566 chrome::NOTIFICATION_MOUSE_LOCK_CHANGED,
567 content::Source<FullscreenController>(this),
568 content::NotificationService::NoDetails());
569 }
570
ToggleFullscreenModeInternal(FullscreenInternalOption option)571 void FullscreenController::ToggleFullscreenModeInternal(
572 FullscreenInternalOption option) {
573 #if defined(OS_WIN)
574 // When in Metro snap mode, toggling in and out of fullscreen is prevented.
575 if (IsInMetroSnapMode())
576 return;
577 #endif
578
579 bool enter_fullscreen = !window_->IsFullscreen();
580 #if defined(OS_MACOSX)
581 // When a Mac user requests a toggle they may be toggling between
582 // FullscreenWithoutChrome and FullscreenWithChrome.
583 if (!IsWindowFullscreenForTabOrPending()) {
584 if (option == BROWSER_WITH_CHROME)
585 enter_fullscreen |= window_->IsFullscreenWithoutChrome();
586 else
587 enter_fullscreen |= window_->IsFullscreenWithChrome();
588 }
589 #endif
590
591 // In kiosk mode, we always want to be fullscreen. When the browser first
592 // starts we're not yet fullscreen, so let the initial toggle go through.
593 if (chrome::IsRunningInAppMode() && window_->IsFullscreen())
594 return;
595
596 #if !defined(OS_MACOSX)
597 // Do not enter fullscreen mode if disallowed by pref. This prevents the user
598 // from manually entering fullscreen mode and also disables kiosk mode on
599 // desktop platforms.
600 if (enter_fullscreen &&
601 !profile_->GetPrefs()->GetBoolean(prefs::kFullscreenAllowed)) {
602 return;
603 }
604 #endif
605
606 if (enter_fullscreen)
607 EnterFullscreenModeInternal(option);
608 else
609 ExitFullscreenModeInternal();
610 }
611
EnterFullscreenModeInternal(FullscreenInternalOption option)612 void FullscreenController::EnterFullscreenModeInternal(
613 FullscreenInternalOption option) {
614 toggled_into_fullscreen_ = true;
615 GURL url;
616 if (option == TAB) {
617 url = browser_->tab_strip_model()->GetActiveWebContents()->GetURL();
618 tab_fullscreen_accepted_ =
619 GetFullscreenSetting(url) == CONTENT_SETTING_ALLOW;
620 } else {
621 if (!extension_caused_fullscreen_.is_empty())
622 url = extension_caused_fullscreen_;
623 }
624
625 if (option == BROWSER)
626 content::RecordAction(UserMetricsAction("ToggleFullscreen"));
627 // TODO(scheib): Record metrics for WITH_CHROME, without counting transitions
628 // from tab fullscreen out to browser with chrome.
629
630 #if defined(OS_MACOSX)
631 if (option == BROWSER_WITH_CHROME) {
632 CHECK(chrome::mac::SupportsSystemFullscreen());
633 window_->EnterFullscreenWithChrome();
634 } else {
635 #else
636 {
637 #endif
638 window_->EnterFullscreen(url, GetFullscreenExitBubbleType());
639 }
640
641 UpdateFullscreenExitBubbleContent();
642
643 // Once the window has become fullscreen it'll call back to
644 // WindowFullscreenStateChanged(). We don't do this immediately as
645 // BrowserWindow::EnterFullscreen() asks for bookmark_bar_state_, so we let
646 // the BrowserWindow invoke WindowFullscreenStateChanged when appropriate.
647 }
648
649 void FullscreenController::ExitFullscreenModeInternal() {
650 toggled_into_fullscreen_ = false;
651 #if defined(OS_MACOSX)
652 // Mac windows report a state change instantly, and so we must also clear
653 // state_prior_to_tab_fullscreen_ to match them else other logic using
654 // state_prior_to_tab_fullscreen_ will be incorrect.
655 NotifyTabOfExitIfNecessary();
656 #endif
657 window_->ExitFullscreen();
658 extension_caused_fullscreen_ = GURL();
659
660 UpdateFullscreenExitBubbleContent();
661 }
662
663 void FullscreenController::SetFullscreenedTab(WebContents* tab) {
664 fullscreened_tab_ = tab;
665 UpdateNotificationRegistrations();
666 }
667
668 void FullscreenController::SetMouseLockTab(WebContents* tab) {
669 mouse_lock_tab_ = tab;
670 UpdateNotificationRegistrations();
671 }
672
673 void FullscreenController::ExitTabFullscreenOrMouseLockIfNecessary() {
674 if (IsWindowFullscreenForTabOrPending())
675 ToggleFullscreenModeForTab(fullscreened_tab_, false);
676 else
677 NotifyTabOfExitIfNecessary();
678 }
679
680 void FullscreenController::UpdateFullscreenExitBubbleContent() {
681 GURL url = GetFullscreenExitBubbleURL();
682 FullscreenExitBubbleType bubble_type = GetFullscreenExitBubbleType();
683
684 // If bubble displays buttons, unlock mouse to allow pressing them.
685 if (fullscreen_bubble::ShowButtonsForType(bubble_type) && IsMouseLocked())
686 UnlockMouse();
687
688 window_->UpdateFullscreenExitBubbleContent(url, bubble_type);
689 }
690
691 ContentSetting
692 FullscreenController::GetFullscreenSetting(const GURL& url) const {
693 if (IsPrivilegedFullscreenForTab() || url.SchemeIsFile())
694 return CONTENT_SETTING_ALLOW;
695
696 return profile_->GetHostContentSettingsMap()->GetContentSetting(url, url,
697 CONTENT_SETTINGS_TYPE_FULLSCREEN, std::string());
698 }
699
700 ContentSetting
701 FullscreenController::GetMouseLockSetting(const GURL& url) const {
702 if (IsPrivilegedFullscreenForTab() || url.SchemeIsFile())
703 return CONTENT_SETTING_ALLOW;
704
705 HostContentSettingsMap* settings_map = profile_->GetHostContentSettingsMap();
706 return settings_map->GetContentSetting(url, url,
707 CONTENT_SETTINGS_TYPE_MOUSELOCK, std::string());
708 }
709
710 bool FullscreenController::IsPrivilegedFullscreenForTab() const {
711 const bool embedded_widget_present =
712 fullscreened_tab_ &&
713 fullscreened_tab_->GetFullscreenRenderWidgetHostView();
714 return embedded_widget_present || is_privileged_fullscreen_for_testing_;
715 }
716
717 void FullscreenController::SetPrivilegedFullscreenForTesting(
718 bool is_privileged) {
719 is_privileged_fullscreen_for_testing_ = is_privileged;
720 }
721
722 bool FullscreenController::MaybeToggleFullscreenForCapturedTab(
723 WebContents* web_contents, bool enter_fullscreen) {
724 if (enter_fullscreen) {
725 if (web_contents->GetCapturerCount() > 0) {
726 FullscreenWithinTabHelper::CreateForWebContents(web_contents);
727 FullscreenWithinTabHelper::FromWebContents(web_contents)->
728 SetIsFullscreenForCapturedTab(true);
729 return true;
730 }
731 } else {
732 if (IsFullscreenForCapturedTab(web_contents)) {
733 FullscreenWithinTabHelper::RemoveForWebContents(web_contents);
734 return true;
735 }
736 }
737
738 return false;
739 }
740
741 bool FullscreenController::IsFullscreenForCapturedTab(
742 const WebContents* web_contents) const {
743 // Note: On Mac, some of the OnTabXXX() methods get called with a NULL value
744 // for web_contents. Check for that here.
745 const FullscreenWithinTabHelper* const helper = web_contents ?
746 FullscreenWithinTabHelper::FromWebContents(web_contents) : NULL;
747 if (helper && helper->is_fullscreen_for_captured_tab()) {
748 DCHECK_NE(fullscreened_tab_, web_contents);
749 return true;
750 }
751 return false;
752 }
753
754 void FullscreenController::UnlockMouse() {
755 if (!mouse_lock_tab_)
756 return;
757 content::RenderWidgetHostView* mouse_lock_view =
758 (fullscreened_tab_ == mouse_lock_tab_ && IsPrivilegedFullscreenForTab()) ?
759 mouse_lock_tab_->GetFullscreenRenderWidgetHostView() : NULL;
760 if (!mouse_lock_view) {
761 RenderViewHost* const rvh = mouse_lock_tab_->GetRenderViewHost();
762 if (rvh)
763 mouse_lock_view = rvh->GetView();
764 }
765 if (mouse_lock_view)
766 mouse_lock_view->UnlockMouse();
767 }
768