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
39using base::UserMetricsAction;
40using content::RenderViewHost;
41using content::WebContents;
42
43FullscreenController::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
60FullscreenController::~FullscreenController() {
61}
62
63bool FullscreenController::IsFullscreenForBrowser() const {
64  return window_->IsFullscreen() && !IsFullscreenCausedByTab();
65}
66
67void FullscreenController::ToggleBrowserFullscreenMode() {
68  extension_caused_fullscreen_ = GURL();
69  ToggleFullscreenModeInternal(BROWSER);
70}
71
72void 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
80bool FullscreenController::IsWindowFullscreenForTabOrPending() const {
81  return fullscreened_tab_ != NULL;
82}
83
84bool 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
94bool FullscreenController::IsFullscreenCausedByTab() const {
95  return state_prior_to_tab_fullscreen_ == STATE_NORMAL;
96}
97
98void 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
190bool 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)
199void 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)
213void FullscreenController::ToggleBrowserFullscreenWithChrome() {
214  ToggleFullscreenModeInternal(BROWSER_WITH_CHROME);
215}
216#endif
217
218bool FullscreenController::IsMouseLockRequested() const {
219  return mouse_lock_state_ == MOUSELOCK_REQUESTED;
220}
221
222bool FullscreenController::IsMouseLocked() const {
223  return mouse_lock_state_ == MOUSELOCK_ACCEPTED ||
224         mouse_lock_state_ == MOUSELOCK_ACCEPTED_SILENTLY;
225}
226
227void 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
280void FullscreenController::OnTabDeactivated(WebContents* web_contents) {
281  if (web_contents == fullscreened_tab_ || web_contents == mouse_lock_tab_)
282    ExitTabFullscreenOrMouseLockIfNecessary();
283}
284
285void 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
317void 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
334void 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
354bool 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
371void FullscreenController::ExitTabOrBrowserFullscreenToPreviousState() {
372  if (IsWindowFullscreenForTabOrPending())
373    ExitTabFullscreenOrMouseLockIfNecessary();
374  else if (IsFullscreenForBrowser())
375    ExitFullscreenModeInternal();
376}
377
378void 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
425void 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
447void FullscreenController::LostMouseLock() {
448  mouse_lock_state_ = MOUSELOCK_NOT_REQUESTED;
449  SetMouseLockTab(NULL);
450  NotifyMouseLockChange();
451  UpdateFullscreenExitBubbleContent();
452}
453
454void 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
463GURL 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
471FullscreenExitBubbleType 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
510void 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
524void 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
533void 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
540void 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
564void FullscreenController::NotifyMouseLockChange() {
565  content::NotificationService::current()->Notify(
566      chrome::NOTIFICATION_MOUSE_LOCK_CHANGED,
567      content::Source<FullscreenController>(this),
568      content::NotificationService::NoDetails());
569}
570
571void 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
612void 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
649void 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
663void FullscreenController::SetFullscreenedTab(WebContents* tab) {
664  fullscreened_tab_ = tab;
665  UpdateNotificationRegistrations();
666}
667
668void FullscreenController::SetMouseLockTab(WebContents* tab) {
669  mouse_lock_tab_ = tab;
670  UpdateNotificationRegistrations();
671}
672
673void FullscreenController::ExitTabFullscreenOrMouseLockIfNecessary() {
674  if (IsWindowFullscreenForTabOrPending())
675    ToggleFullscreenModeForTab(fullscreened_tab_, false);
676  else
677    NotifyTabOfExitIfNecessary();
678}
679
680void 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
691ContentSetting
692FullscreenController::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
700ContentSetting
701FullscreenController::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
710bool 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
717void FullscreenController::SetPrivilegedFullscreenForTesting(
718    bool is_privileged) {
719  is_privileged_fullscreen_for_testing_ = is_privileged;
720}
721
722bool 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
741bool 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
754void 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