blob: 64009d6ce6ca3c6512a48519b042db269eaf08ed [file] [log] [blame]
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// 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/mouse_lock_controller.h"
#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.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_controller.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
using content::RenderViewHost;
using content::WebContents;
namespace {
const char kMouseLockBubbleReshowsHistogramName[] =
"ExclusiveAccess.BubbleReshowsPerSession.MouseLock";
// The amount of time to disallow repeated pointer lock calls after the user
// successfully escapes from one lock request.
constexpr base::TimeDelta kEffectiveUserEscapeDuration =
base::Milliseconds(1250);
} // namespace
MouseLockController::MouseLockController(ExclusiveAccessManager* manager)
: ExclusiveAccessControllerBase(manager),
mouse_lock_state_(MOUSELOCK_UNLOCKED),
fake_mouse_lock_for_test_(false),
bubble_hide_callback_for_test_() {}
MouseLockController::~MouseLockController() {
}
bool MouseLockController::IsMouseLocked() const {
return mouse_lock_state_ == MOUSELOCK_LOCKED ||
mouse_lock_state_ == MOUSELOCK_LOCKED_SILENTLY;
}
bool MouseLockController::IsMouseLockedSilently() const {
return mouse_lock_state_ == MOUSELOCK_LOCKED_SILENTLY;
}
void MouseLockController::RequestToLockMouse(WebContents* web_contents,
bool user_gesture,
bool last_unlocked_by_target) {
DCHECK(!IsMouseLocked());
if (lock_state_callback_for_test_)
std::move(lock_state_callback_for_test_).Run();
// To prevent misbehaving sites from constantly re-locking the mouse, the
// lock-requesting page must have transient user activation and it must not
// request for a lock within |kEffectiveUserEscapeDuration| time since the
// user successfully escaped from a previous lock. Exceptions are when the
// page has unlocked (i.e. not the user), or if we're in tab fullscreen (which
// requires its own transient user activation).
if (!last_unlocked_by_target &&
!exclusive_access_manager()
->fullscreen_controller()
->IsFullscreenForTabOrPending(web_contents)) {
if (!user_gesture) {
web_contents->GotResponseToLockMouseRequest(
blink::mojom::PointerLockResult::kRequiresUserGesture);
return;
}
if (base::TimeTicks::Now() <
last_user_escape_time_ + kEffectiveUserEscapeDuration) {
web_contents->GotResponseToLockMouseRequest(
blink::mojom::PointerLockResult::kUserRejected);
return;
}
}
SetTabWithExclusiveAccess(web_contents);
// Lock mouse.
if (fake_mouse_lock_for_test_ ||
web_contents->GotResponseToLockMouseRequest(
blink::mojom::PointerLockResult::kSuccess)) {
if (last_unlocked_by_target &&
web_contents_granted_silent_mouse_lock_permission_ == web_contents) {
mouse_lock_state_ = MOUSELOCK_LOCKED_SILENTLY;
} else {
mouse_lock_state_ = MOUSELOCK_LOCKED;
}
} else {
SetTabWithExclusiveAccess(nullptr);
mouse_lock_state_ = MOUSELOCK_UNLOCKED;
}
if (!ShouldSuppressBubbleReshowForStateChange()) {
exclusive_access_manager()->UpdateExclusiveAccessExitBubbleContent(
base::BindOnce(&MouseLockController::OnBubbleHidden,
weak_ptr_factory_.GetWeakPtr(), web_contents));
}
}
void MouseLockController::ExitExclusiveAccessIfNecessary() {
NotifyTabExclusiveAccessLost();
}
void MouseLockController::NotifyTabExclusiveAccessLost() {
WebContents* tab = exclusive_access_tab();
if (tab) {
UnlockMouse();
SetTabWithExclusiveAccess(nullptr);
mouse_lock_state_ = MOUSELOCK_UNLOCKED;
exclusive_access_manager()->UpdateExclusiveAccessExitBubbleContent(
ExclusiveAccessBubbleHideCallback());
}
}
void MouseLockController::RecordBubbleReshowsHistogram(
int bubble_reshow_count) {
UMA_HISTOGRAM_COUNTS_100(kMouseLockBubbleReshowsHistogramName,
bubble_reshow_count);
}
bool MouseLockController::HandleUserPressedEscape() {
if (IsMouseLocked()) {
ExitExclusiveAccessIfNecessary();
last_user_escape_time_ = base::TimeTicks::Now();
return true;
}
return false;
}
void MouseLockController::ExitExclusiveAccessToPreviousState() {
// Nothing to do for mouse lock.
}
void MouseLockController::LostMouseLock() {
if (lock_state_callback_for_test_)
std::move(lock_state_callback_for_test_).Run();
RecordExitingUMA();
mouse_lock_state_ = MOUSELOCK_UNLOCKED;
SetTabWithExclusiveAccess(nullptr);
if (!ShouldSuppressBubbleReshowForStateChange()) {
exclusive_access_manager()->UpdateExclusiveAccessExitBubbleContent(
ExclusiveAccessBubbleHideCallback());
}
}
void MouseLockController::UnlockMouse() {
WebContents* tab = exclusive_access_tab();
if (!tab)
return;
content::RenderWidgetHostView* mouse_lock_view = nullptr;
RenderViewHost* const rvh =
exclusive_access_tab()->GetMainFrame()->GetRenderViewHost();
if (rvh)
mouse_lock_view = rvh->GetWidget()->GetView();
if (mouse_lock_view)
mouse_lock_view->UnlockMouse();
}
void MouseLockController::OnBubbleHidden(
WebContents* web_contents,
ExclusiveAccessBubbleHideReason reason) {
if (bubble_hide_callback_for_test_)
bubble_hide_callback_for_test_.Run(reason);
// Allow silent mouse lock if the bubble has been display for a period of
// time and dismissed due to timeout.
if (reason == ExclusiveAccessBubbleHideReason::kTimeout)
web_contents_granted_silent_mouse_lock_permission_ = web_contents;
}
bool MouseLockController::ShouldSuppressBubbleReshowForStateChange() {
ExclusiveAccessBubbleType bubble_type =
exclusive_access_manager()->GetExclusiveAccessExitBubbleType();
return (mouse_lock_state_ == MOUSELOCK_LOCKED &&
bubble_type ==
EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_MOUSELOCK_EXIT_INSTRUCTION) ||
(mouse_lock_state_ == MOUSELOCK_UNLOCKED &&
bubble_type ==
EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION);
}