blob: c38ce5558472c785837f1d4d2542f2edf2ffab58 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
#include "base/auto_reset.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/notimplemented.h"
#include "base/observer_list.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "chrome/browser/app_mode/app_mode_utils.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
#include "chrome/browser/ui/exclusive_access/fullscreen_within_tab_helper.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/history_types.h"
#include "content/public/browser/fullscreen_types.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_descriptor_util.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/display/types/display_constants.h"
#if !BUILDFLAG(IS_MAC)
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#endif
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/ui/blocked_content/popunder_preventer.h"
#endif // !BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
#include "chrome/browser/safe_browsing/safe_browsing_service.h" // nogncheck
#include "components/safe_browsing/content/browser/safe_browsing_service_interface.h" // nogncheck
#endif
using content::WebContents;
namespace {
constexpr char kHistogramFullscreenWebsiteStateAtApiRequest[] =
"WebCore.Fullscreen.WebsiteStateAtApiRequest";
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class WebsiteStateAtFullscreenRequest {
kNotAllowlistedNotVisited = 0,
kNotAllowlistedVisited = 1,
kNotAllowlistedVisitStateUnknown = 2,
kAllowlistedNotVisited = 3,
kAllowlistedVisited = 4,
kAllowlistedVisitStateUnknown = 5,
kAllowlistStateUnknownNotVisited = 6,
kAllowlistStateUnknownVisited = 7,
kAllowlistStateUnknownVisitStateUnknown = 8,
kMaxValue = kAllowlistStateUnknownVisitStateUnknown,
};
bool IsAnotherScreen(const WebContents& web_contents,
const int64_t display_id) {
if (display_id == display::kInvalidDisplayId) {
return false;
}
return display_id != FullscreenController::GetDisplayId(web_contents);
}
void RecordWebsiteStateAtApiRequest(history::HistoryLastVisitResult result,
std::optional<bool> on_allowlist) {
auto state = WebsiteStateAtFullscreenRequest::kNotAllowlistedNotVisited;
if (!result.success) {
if (!on_allowlist.has_value()) {
state = WebsiteStateAtFullscreenRequest::
kAllowlistStateUnknownVisitStateUnknown;
} else if (*on_allowlist) {
state = WebsiteStateAtFullscreenRequest::kAllowlistedVisitStateUnknown;
} else {
state = WebsiteStateAtFullscreenRequest::kNotAllowlistedVisitStateUnknown;
}
} else if (!result.last_visit.is_null()) {
if (!on_allowlist.has_value()) {
state = WebsiteStateAtFullscreenRequest::kAllowlistStateUnknownVisited;
} else if (*on_allowlist) {
state = WebsiteStateAtFullscreenRequest::kAllowlistedVisited;
} else {
state = WebsiteStateAtFullscreenRequest::kNotAllowlistedVisited;
}
} else if (!on_allowlist.has_value()) {
state = WebsiteStateAtFullscreenRequest::kAllowlistStateUnknownNotVisited;
} else if (*on_allowlist) {
state = WebsiteStateAtFullscreenRequest::kAllowlistedNotVisited;
}
base::UmaHistogramEnumeration(kHistogramFullscreenWebsiteStateAtApiRequest,
state);
}
void CheckUrlForAllowlistAndRecordMetric(
const GURL& url,
history::HistoryLastVisitResult result) {
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
auto* safe_browsing_service_internal =
reinterpret_cast<safe_browsing::SafeBrowsingServiceInterface*>(
g_browser_process->safe_browsing_service());
if (!safe_browsing_service_internal ||
!safe_browsing_service_internal->database_manager()) {
RecordWebsiteStateAtApiRequest(result, std::nullopt);
return;
}
safe_browsing_service_internal->database_manager()
->CheckUrlForHighConfidenceAllowlist(
url,
base::BindOnce(
[](history::HistoryLastVisitResult result, bool on_allowlist,
std::optional<safe_browsing::SafeBrowsingDatabaseManager::
HighConfidenceAllowlistCheckLoggingDetails>
logging_details) {
RecordWebsiteStateAtApiRequest(result, on_allowlist);
},
result));
#else
RecordWebsiteStateAtApiRequest(result, std::nullopt);
#endif
}
} // namespace
FullscreenController::FullscreenController(ExclusiveAccessManager* manager)
: ExclusiveAccessControllerBase(manager) {}
FullscreenController::~FullscreenController() = default;
void FullscreenController::AddObserver(FullscreenObserver* observer) {
observer_list_.AddObserver(observer);
}
void FullscreenController::RemoveObserver(FullscreenObserver* observer) {
observer_list_.RemoveObserver(observer);
}
int64_t FullscreenController::GetDisplayId(const WebContents& web_contents) {
if (auto* screen = display::Screen::GetScreen()) {
// crbug.com/1347558 WebContents::GetNativeView is const-incorrect.
// const_cast is used to access GetNativeView(). Also GetDisplayNearestView
// should accept const gfx::NativeView, but there is other const
// incorrectness down the call chain in some implementations.
auto display = screen->GetDisplayNearestView(
const_cast<WebContents&>(web_contents).GetNativeView());
return display.id();
}
return display::kInvalidDisplayId;
}
bool FullscreenController::IsFullscreenForBrowser() const {
return exclusive_access_manager()->context()->IsFullscreen() &&
!IsFullscreenCausedByTab();
}
void FullscreenController::ToggleBrowserFullscreenMode(bool user_initiated) {
extension_url_.reset();
ToggleFullscreenModeInternal(BROWSER, nullptr, display::kInvalidDisplayId,
user_initiated);
}
void FullscreenController::ToggleBrowserFullscreenModeWithExtension(
const GURL& extension_url) {
// |extension_url_| will be reset if this causes fullscreen to
// exit.
extension_url_ = extension_url;
ToggleFullscreenModeInternal(BROWSER, nullptr, display::kInvalidDisplayId,
/*user_initiated=*/false);
}
bool FullscreenController::IsWindowFullscreenForTabOrPending() const {
return exclusive_access_tab() || is_tab_fullscreen_for_testing_;
}
bool FullscreenController::IsExtensionFullscreenOrPending() const {
return extension_url_.has_value();
}
bool FullscreenController::IsControllerInitiatedFullscreen() const {
return toggled_into_fullscreen_;
}
bool FullscreenController::IsTabFullscreen() const {
return tab_fullscreen_ || is_tab_fullscreen_for_testing_;
}
content::FullscreenState FullscreenController::GetFullscreenState(
const content::WebContents* web_contents) const {
content::FullscreenState state;
CHECK(web_contents) << "Null web_contents passed to GetFullscreenState";
// Handle screen-captured tab fullscreen and `is_tab_fullscreen_for_testing_`.
if (IsFullscreenWithinTab(web_contents)) {
state.target_mode = content::FullscreenMode::kPseudoContent;
return state;
}
// Handle not fullscreen, browser fullscreen, and exiting tab fullscreen.
if (!tab_fullscreen_ || web_contents != exclusive_access_tab()) {
state.target_mode = content::FullscreenMode::kWindowed;
return state;
}
// Handle tab fullscreen and entering tab fullscreen.
state.target_mode = content::FullscreenMode::kContent;
state.target_display_id =
(tab_fullscreen_target_display_id_ != display::kInvalidDisplayId)
? tab_fullscreen_target_display_id_
: GetDisplayId(*web_contents);
return state;
}
bool FullscreenController::IsFullscreenCausedByTab() const {
return state_prior_to_tab_fullscreen_ == STATE_NORMAL;
}
bool FullscreenController::CanEnterFullscreenModeForTab(
content::RenderFrameHost* requesting_frame) {
DCHECK(requesting_frame);
auto* web_contents = WebContents::FromRenderFrameHost(requesting_frame);
DCHECK(web_contents);
if (web_contents != exclusive_access_manager()
->context()
->GetWebContentsForExclusiveAccess()) {
return false;
}
return true;
}
void FullscreenController::EnterFullscreenModeForTab(
content::RenderFrameHost* requesting_frame,
const int64_t display_id) {
RecordMetricsOnFullscreenApiRequested(requesting_frame);
DCHECK(requesting_frame);
// This function should never fail. Any possible failures must be checked in
// |CanEnterFullscreenModeForTab()| instead. Silently dropping the request
// could cause requestFullscreen promises to hang. If we are in this function,
// the renderer expects a visual property update to call
// |blink::FullscreenController::DidEnterFullscreen| to resolve promises.
DCHECK(CanEnterFullscreenModeForTab(requesting_frame));
auto* web_contents = WebContents::FromRenderFrameHost(requesting_frame);
DCHECK(web_contents);
if (MaybeToggleFullscreenWithinTab(web_contents, true)) {
// During tab capture of fullscreen-within-tab views, the browser window
// fullscreen state is unchanged, so return now.
return;
}
#if !BUILDFLAG(IS_ANDROID)
if (!popunder_preventer_) {
popunder_preventer_ = std::make_unique<PopunderPreventer>(web_contents);
} else {
popunder_preventer_->WillActivateWebContents(web_contents);
}
#endif // !BUILDFLAG(IS_ANDROID)
// Keep the current state. |SetTabWithExclusiveAccess| may change the return
// value of |IsWindowFullscreenForTabOrPending|.
const bool requesting_another_screen =
IsAnotherScreen(*web_contents, display_id);
const bool was_window_fullscreen_for_tab_or_pending =
!requesting_another_screen && IsWindowFullscreenForTabOrPending();
if (exclusive_access_tab() && exclusive_access_tab() != web_contents) {
// This unexpected condition may be hit in practice; see crbug.com/1456875.
// In known circumstances it is safe to just clear the exclusive_access_tab,
// but behavior and assumptions should be rectified; see crbug.com/1244121.
NOTIMPLEMENTED() << "Conflicting exclusive access tab assignment detected";
SetTabWithExclusiveAccess(nullptr);
}
SetTabWithExclusiveAccess(web_contents);
requesting_origin_ = requesting_frame->GetLastCommittedOrigin();
if (was_window_fullscreen_for_tab_or_pending) {
// While an element is in fullscreen, requesting fullscreen for a different
// element in the tab is handled in the renderer process if both elements
// are in the same process. But the request will come to the browser when
// the element is in a different process, such as OOPIF, because the
// renderer doesn't know if an element in other renderer process is in
// fullscreen.
DCHECK(tab_fullscreen_);
} else {
ExclusiveAccessContext* exclusive_access_context =
exclusive_access_manager()->context();
// This is needed on Mac as entering into Tab Fullscreen might change the
// top UI style.
exclusive_access_context->UpdateUIForTabFullscreen();
state_prior_to_tab_fullscreen_ =
IsFullscreenForBrowser() ? STATE_BROWSER_FULLSCREEN : STATE_NORMAL;
if (!exclusive_access_context->IsFullscreen() ||
requesting_another_screen) {
EnterFullscreenModeInternal(TAB, requesting_frame, display_id);
return;
}
// We need to update the fullscreen exit bubble, e.g., going from browser
// fullscreen to tab fullscreen will need to show different content.
tab_fullscreen_ = true;
exclusive_access_manager()->UpdateBubble(base::NullCallback());
}
// This is only a change between Browser and Tab fullscreen. We generate
// a fullscreen notification now because there is no window change.
PostFullscreenChangeNotification();
}
void FullscreenController::ExitFullscreenModeForTab(WebContents* web_contents) {
if (MaybeToggleFullscreenWithinTab(web_contents, false)) {
// During tab capture of fullscreen-within-tab views, the browser window
// fullscreen state is unchanged, so return now.
return;
}
if (!IsWindowFullscreenForTabOrPending() ||
web_contents != exclusive_access_tab()) {
return;
}
ExclusiveAccessContext* exclusive_access_context =
exclusive_access_manager()->context();
if (!exclusive_access_context->IsFullscreen()) {
return;
}
if (IsFullscreenCausedByTab()) {
// Tab Fullscreen -> Normal.
ExitFullscreenModeInternal();
return;
}
// Tab Fullscreen -> Browser Fullscreen.
// Exiting tab fullscreen mode may require updating top UI.
// All exiting tab fullscreen to non-fullscreen mode cases are handled in
// BrowserNonClientFrameView::OnFullscreenStateChanged(); but exiting tab
// fullscreen to browser fullscreen should be handled here.
const bool was_browser_fullscreen =
state_prior_to_tab_fullscreen_ == STATE_BROWSER_FULLSCREEN;
NotifyTabExclusiveAccessLost();
if (was_browser_fullscreen) {
exclusive_access_context->UpdateUIForTabFullscreen();
}
// For Tab Fullscreen -> Browser Fullscreen, enter browser fullscreen on the
// display that originated the browser fullscreen prior to the tab fullscreen.
// crbug.com/1313606.
if (was_browser_fullscreen && web_contents &&
display_id_prior_to_tab_fullscreen_ != display::kInvalidDisplayId &&
display_id_prior_to_tab_fullscreen_ != GetDisplayId(*web_contents)) {
EnterFullscreenModeInternal(BROWSER, nullptr,
display_id_prior_to_tab_fullscreen_);
return;
}
// Notify observers now, when reverting from Tab fullscreen to Browser
// fullscreen on the same display. Exiting fullscreen, or reverting to Browser
// fullscreen on another display, triggers additional controller logic above,
// which will notify observers when appropriate.
PostFullscreenChangeNotification();
}
#if !BUILDFLAG(IS_ANDROID)
void FullscreenController::FullscreenTabOpeningPopup(
content::WebContents* opener,
content::WebContents* popup) {
if (!popunder_preventer_) {
return;
}
DCHECK_EQ(exclusive_access_tab(), opener);
popunder_preventer_->AddPotentialPopunder(popup);
}
#endif // !BUILDFLAG(IS_ANDROID)
void FullscreenController::OnTabDeactivated(
content::WebContents* web_contents) {
base::AutoReset<raw_ptr<content::WebContents>> auto_resetter(
&deactivated_contents_, web_contents);
ExclusiveAccessControllerBase::OnTabDeactivated(web_contents);
}
void FullscreenController::OnTabDetachedFromView(WebContents* old_contents) {
if (!IsFullscreenWithinTab(old_contents)) {
return;
}
// A fullscreen-within-tab view undergoing screen capture has been detached
// and is no longer visible to the user. Set it to exactly the WebContents'
// preferred size. See 'FullscreenWithinTab Note'.
//
// When the user later selects the tab to show |old_contents| again, UI code
// elsewhere (e.g., views::WebView) will resize the view to fit within the
// browser window once again.
// If the view has been detached from the browser window (e.g., to drag a tab
// off into a new browser window), return immediately to avoid an unnecessary
// resize.
if (!old_contents->GetDelegate()) {
return;
}
// Do nothing if tab capture ended after toggling fullscreen, or a preferred
// size was never specified by the capturer.
if (!old_contents->IsBeingCaptured() ||
old_contents->GetPreferredSize().IsEmpty()) {
return;
}
old_contents->Resize(gfx::Rect(old_contents->GetPreferredSize()));
}
void FullscreenController::OnTabClosing(WebContents* web_contents) {
if (IsFullscreenWithinTab(web_contents)) {
web_contents->ExitFullscreen(
/* will_cause_resize */ IsFullscreenCausedByTab());
} else {
ExclusiveAccessControllerBase::OnTabClosing(web_contents);
}
}
void FullscreenController::WindowFullscreenStateChanged() {
ExclusiveAccessContext* const exclusive_access_context =
exclusive_access_manager()->context();
bool exiting_fullscreen = !exclusive_access_context->IsFullscreen();
PostFullscreenChangeNotification();
if (exiting_fullscreen) {
toggled_into_fullscreen_ = false;
extension_url_.reset();
NotifyTabExclusiveAccessLost();
} else {
toggled_into_fullscreen_ = true;
if (!IsRunningInAppMode()) {
exclusive_access_manager()->UpdateBubble(base::NullCallback(),
/*force_update=*/true);
}
if (!fullscreen_start_time_) {
fullscreen_start_time_ = base::TimeTicks::Now();
}
// This must be posted because keyboard lock engages right after entering
// fullscreen, and we want to record the keyboard/pointer lock state after
// that.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&FullscreenController::RecordMetricsOnEnteringFullscreen,
ptr_factory_.GetWeakPtr()));
}
}
void FullscreenController::FullscreenTransitionCompleted() {
if (fullscreen_transition_complete_callback_) {
std::move(fullscreen_transition_complete_callback_).Run();
}
#if DCHECK_IS_ON()
if (started_fullscreen_transition_ && IsTabFullscreen()) {
DCHECK(exclusive_access_tab());
DCHECK_EQ(tab_fullscreen_target_display_id_,
GetDisplayId(*exclusive_access_tab()));
}
#endif // DCHECK_IS_ON()
tab_fullscreen_target_display_id_ = display::kInvalidDisplayId;
started_fullscreen_transition_ = false;
#if !BUILDFLAG(IS_ANDROID)
if (!IsTabFullscreen()) {
// Activate any popup windows created while content fullscreen, after exit.
popunder_preventer_.reset();
}
#endif // !BUILDFLAG(IS_ANDROID)
}
void FullscreenController::RunOrDeferUntilTransitionIsComplete(
base::OnceClosure callback) {
if (started_fullscreen_transition_) {
fullscreen_transition_complete_callback_ = std::move(callback);
} else {
std::move(callback).Run();
}
}
bool FullscreenController::HandleUserPressedEscape() {
WebContents* const active_web_contents =
exclusive_access_manager()->context()->GetWebContentsForExclusiveAccess();
if (IsFullscreenWithinTab(active_web_contents)) {
active_web_contents->ExitFullscreen(
/* will_cause_resize */ IsFullscreenCausedByTab());
return true;
}
if (!IsWindowFullscreenForTabOrPending()) {
return false;
}
ExitExclusiveAccessIfNecessary();
base::RecordAction(base::UserMetricsAction("ExitFullscreen_Esc"));
return true;
}
void FullscreenController::HandleUserHeldEscape() {
CHECK(exclusive_access_manager());
CHECK(exclusive_access_manager()->context());
ExclusiveAccessContext* const exclusive_access_context =
exclusive_access_manager()->context();
if (RequiresPressAndHoldEscToExit() &&
exclusive_access_context->CanUserExitFullscreen()) {
ExitFullscreenModeInternal();
base::RecordAction(
base::UserMetricsAction("ExitFullscreen_PressAndHoldEsc"));
}
}
void FullscreenController::HandleUserReleasedEscapeEarly() {}
bool FullscreenController::RequiresPressAndHoldEscToExit() const {
return IsFullscreenForBrowser();
}
void FullscreenController::ExitExclusiveAccessToPreviousState() {
if (IsWindowFullscreenForTabOrPending()) {
ExitFullscreenModeForTab(exclusive_access_tab());
} else if (IsFullscreenForBrowser()) {
ExitFullscreenModeInternal();
}
}
url::Origin FullscreenController::GetOriginForExclusiveAccessBubble() const {
if (exclusive_access_tab()) {
return GetRequestingOrigin();
}
if (extension_url_.has_value()) {
return url::Origin::Create(extension_url_.value());
}
return url::Origin();
}
void FullscreenController::ExitExclusiveAccessIfNecessary() {
if (IsWindowFullscreenForTabOrPending()) {
ExitFullscreenModeForTab(exclusive_access_tab());
} else {
NotifyTabExclusiveAccessLost();
}
}
void FullscreenController::PostFullscreenChangeNotification() {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&FullscreenController::NotifyFullscreenChange,
ptr_factory_.GetWeakPtr()));
}
void FullscreenController::NotifyFullscreenChange() {
for (auto& observer : observer_list_) {
observer.OnFullscreenStateChanged();
}
}
void FullscreenController::NotifyTabExclusiveAccessLost() {
if (exclusive_access_tab()) {
WebContents* web_contents = exclusive_access_tab();
SetTabWithExclusiveAccess(nullptr);
requesting_origin_ = url::Origin();
bool will_cause_resize = IsFullscreenCausedByTab();
state_prior_to_tab_fullscreen_ = STATE_INVALID;
tab_fullscreen_ = false;
web_contents->ExitFullscreen(will_cause_resize);
exclusive_access_manager()->UpdateBubble(base::NullCallback());
}
}
void FullscreenController::ToggleFullscreenModeInternal(
FullscreenInternalOption option,
content::RenderFrameHost* requesting_frame,
const int64_t display_id,
bool user_initiated) {
ExclusiveAccessContext* const exclusive_access_context =
exclusive_access_manager()->context();
bool enter_fullscreen = !exclusive_access_context->IsFullscreen();
if (enter_fullscreen &&
(exclusive_access_context->CanUserEnterFullscreen() || !user_initiated)) {
EnterFullscreenModeInternal(option, requesting_frame, display_id);
}
if (!enter_fullscreen &&
(exclusive_access_context->CanUserExitFullscreen() || !user_initiated)) {
ExitFullscreenModeInternal();
}
}
void FullscreenController::EnterFullscreenModeInternal(
FullscreenInternalOption option,
content::RenderFrameHost* requesting_frame,
int64_t display_id) {
#if !BUILDFLAG(IS_MAC)
// Do not enter fullscreen mode if disallowed by pref. This prevents the user
// from manually entering fullscreen mode and also disables kiosk mode on
// desktop platforms.
if (!exclusive_access_manager()
->context()
->GetProfile()
->GetPrefs()
->GetBoolean(prefs::kFullscreenAllowed)) {
return;
}
#endif
started_fullscreen_transition_ = true;
toggled_into_fullscreen_ = true;
bool entering_tab_fullscreen = option == TAB && !tab_fullscreen_;
url::Origin origin;
if (option == TAB) {
origin = GetRequestingOrigin();
tab_fullscreen_ = true;
WebContents* web_contents =
WebContents::FromRenderFrameHost(requesting_frame);
// Do not enter tab fullscreen if there is no web contents for the
// requesting frame (This normally shouldn't happen).
DCHECK(web_contents);
if (!web_contents) {
return;
}
int64_t current_display = GetDisplayId(*web_contents);
if (display_id != display::kInvalidDisplayId) {
// Check, but do not prompt, for permission to request a specific screen.
// Sites generally need permission to get `display_id` in the first place.
if (!requesting_frame ||
requesting_frame->GetBrowserContext()
->GetPermissionController()
->GetPermissionStatusForCurrentDocument(
content::PermissionDescriptorUtil::
CreatePermissionDescriptorForPermissionType(
blink::PermissionType::WINDOW_MANAGEMENT),
requesting_frame) !=
blink::mojom::PermissionStatus::GRANTED) {
display_id = display::kInvalidDisplayId;
} else if (entering_tab_fullscreen) {
display_id_prior_to_tab_fullscreen_ = current_display;
}
}
tab_fullscreen_target_display_id_ =
display_id == display::kInvalidDisplayId ? current_display : display_id;
} else {
if (extension_url_) {
origin = url::Origin::Create(extension_url_.value());
}
}
fullscreen_start_time_ = base::TimeTicks::Now();
if (option == BROWSER) {
base::RecordAction(base::UserMetricsAction("ToggleFullscreen"));
}
// TODO(scheib): Record metrics for WITH_TOOLBAR, without counting transitions
// from tab fullscreen out to browser with toolbar.
exclusive_access_manager()->context()->EnterFullscreen(
origin, exclusive_access_manager()->GetExclusiveAccessExitBubbleType(),
display_id);
// WindowFullscreenStateChanged() is called once the window is fullscreen.
}
void FullscreenController::ExitFullscreenModeInternal() {
// In kiosk mode, we always want to be fullscreen.
if (IsRunningInAppMode()) {
return;
}
// `fullscreen_start_time_` is null if a fullscreen tab moves to a new window.
if (fullscreen_start_time_ && exclusive_access_tab()) {
ukm::SourceId source_id =
exclusive_access_tab()->GetPrimaryMainFrame()->GetPageUkmSourceId();
ukm::builders::Fullscreen_Exit(source_id)
.SetSessionDuration(ukm::GetSemanticBucketMinForDurationTiming(
(base::TimeTicks::Now() - fullscreen_start_time_.value())
.InMilliseconds()))
.Record(ukm::UkmRecorder::Get());
fullscreen_start_time_.reset();
}
toggled_into_fullscreen_ = false;
started_fullscreen_transition_ = true;
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID)
// Mac windows report a state change instantly, and so we must also clear
// state_prior_to_tab_fullscreen_ to match them else other logic using
// state_prior_to_tab_fullscreen_ will be incorrect.
// On Android the state of fullscreen is keep in the Java Fullscreen
// Controller. The change is instant so we notify about access lost to
// keep the state coherent.
NotifyTabExclusiveAccessLost();
#endif
exclusive_access_manager()->context()->ExitFullscreen();
extension_url_.reset();
exclusive_access_manager()->UpdateBubble(base::NullCallback());
}
bool FullscreenController::MaybeToggleFullscreenWithinTab(
WebContents* web_contents,
bool enter_fullscreen) {
if (enter_fullscreen) {
if (web_contents->IsBeingVisiblyCaptured()) {
FullscreenWithinTabHelper::CreateForWebContents(web_contents);
FullscreenWithinTabHelper::FromWebContents(web_contents)
->SetIsFullscreenWithinTab(true);
return true;
}
} else {
if (IsFullscreenWithinTab(web_contents)) {
FullscreenWithinTabHelper::RemoveForWebContents(web_contents);
return true;
}
}
return false;
}
bool FullscreenController::IsFullscreenWithinTab(
const WebContents* web_contents) const {
if (is_tab_fullscreen_for_testing_) {
return true;
}
// Note: On Mac, some of the OnTabXXX() methods get called with a nullptr
// value
// for web_contents. Check for that here.
const FullscreenWithinTabHelper* const helper =
web_contents ? FullscreenWithinTabHelper::FromWebContents(web_contents)
: nullptr;
if (helper && helper->is_fullscreen_within_tab()) {
DCHECK_NE(exclusive_access_tab(), web_contents);
return true;
}
return false;
}
url::Origin FullscreenController::GetRequestingOrigin() const {
DCHECK(exclusive_access_tab());
if (!requesting_origin_.opaque()) {
return requesting_origin_;
}
return url::Origin::Create(exclusive_access_tab()->GetLastCommittedURL());
}
url::Origin FullscreenController::GetEmbeddingOrigin() const {
DCHECK(exclusive_access_tab());
return url::Origin::Create(exclusive_access_tab()->GetLastCommittedURL());
}
void FullscreenController::RecordMetricsOnFullscreenApiRequested(
content::RenderFrameHost* requesting_frame) {
history::HistoryService* service =
HistoryServiceFactory::GetForProfileWithoutCreating(
exclusive_access_manager()->context()->GetProfile());
if (service) {
// Check if the origin has been visited more than a day ago and whether it's
// on an allowlist, then record those bits of information in a metric.
service->GetLastVisitToOrigin(
requesting_frame->GetLastCommittedOrigin(), base::Time(),
base::Time::Now() - base::Days(1),
base::BindOnce(&CheckUrlForAllowlistAndRecordMetric,
GURL(requesting_frame->GetLastCommittedURL())),
&task_tracker_);
} else {
// The history is unknown, so just check if the URL is on the allowlist and
// record that.
CheckUrlForAllowlistAndRecordMetric(requesting_frame->GetLastCommittedURL(),
history::HistoryLastVisitResult());
}
}
void FullscreenController::RecordMetricsOnEnteringFullscreen() {
if (IsFullscreenCausedByTab()) {
exclusive_access_manager()->RecordLockStateOnEnteringApiFullscreen();
} else {
exclusive_access_manager()->RecordLockStateOnEnteringBrowserFullscreen();
}
}