910e62b5创建于 1月15日历史提交
// Copyright 2015 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/pointer_lock_controller.h"

#include "base/functional/bind.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/exclusive_access_permission_manager.h"
#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/permissions/features.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 {

// 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

PointerLockController::PointerLockController(ExclusiveAccessManager* manager)
    : ExclusiveAccessControllerBase(manager),
      pointer_lock_state_(POINTERLOCK_UNLOCKED),
      fake_pointer_lock_for_test_(false) {}

PointerLockController::~PointerLockController() = default;

bool PointerLockController::IsPointerLocked() const {
  return pointer_lock_state_ == POINTERLOCK_LOCKED ||
         pointer_lock_state_ == POINTERLOCK_LOCKED_SILENTLY;
}

bool PointerLockController::IsPointerLockedSilently() const {
  return pointer_lock_state_ == POINTERLOCK_LOCKED_SILENTLY;
}

void PointerLockController::RequestToLockPointer(WebContents* web_contents,
                                                 bool user_gesture,
                                                 bool last_unlocked_by_target) {
  DCHECK(!IsPointerLocked());

  // To prevent misbehaving sites from constantly re-locking the pointer, 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 && !web_contents->IsFullscreen()) {
    if (!user_gesture) {
      web_contents->GotResponseToPointerLockRequest(
          blink::mojom::PointerLockResult::kRequiresUserGesture);
      if (lock_state_callback_for_test_) {
        std::move(lock_state_callback_for_test_).Run();
      }
      return;
    }
    if (base::TimeTicks::Now() <
        last_user_escape_time_ + kEffectiveUserEscapeDuration) {
      web_contents->GotResponseToPointerLockRequest(
          blink::mojom::PointerLockResult::kUserRejected);
      if (lock_state_callback_for_test_) {
        std::move(lock_state_callback_for_test_).Run();
      }
      return;
    }
  }

  content::GlobalRenderFrameHostId rfh_id =
      web_contents->GetPrimaryMainFrame()->GetGlobalId();
    LockPointer(web_contents->GetWeakPtr(), rfh_id, last_unlocked_by_target);
}

bool PointerLockController::IsWaitingForPointerLockPrompt(
    WebContents* web_contents) {
  return IsWaitingForPointerLockPromptHelper(
      web_contents->GetPrimaryMainFrame()->GetGlobalId());
}

void PointerLockController::ExitExclusiveAccessIfNecessary() {
  NotifyTabExclusiveAccessLost();
}

void PointerLockController::NotifyTabExclusiveAccessLost() {
  WebContents* tab = exclusive_access_tab();
  if (tab) {
    UnlockPointer();
    SetTabWithExclusiveAccess(nullptr);
    pointer_lock_state_ = POINTERLOCK_UNLOCKED;
    exclusive_access_manager()->UpdateBubble(base::NullCallback());
  }
}

bool PointerLockController::HandleUserPressedEscape() {
  if (IsPointerLocked()) {
    ExitExclusiveAccessIfNecessary();
    last_user_escape_time_ = base::TimeTicks::Now();
    return true;
  }

  return false;
}

void PointerLockController::HandleUserHeldEscape() {
  HandleUserPressedEscape();
}

void PointerLockController::HandleUserReleasedEscapeEarly() {}

bool PointerLockController::RequiresPressAndHoldEscToExit() const {
  return false;
}

void PointerLockController::ExitExclusiveAccessToPreviousState() {
  if (lock_state_callback_for_test_) {
    std::move(lock_state_callback_for_test_).Run();
  }

  pointer_lock_state_ = POINTERLOCK_UNLOCKED;
  SetTabWithExclusiveAccess(nullptr);

  if (!ShouldSuppressBubbleReshowForStateChange()) {
    exclusive_access_manager()->UpdateBubble(base::NullCallback());
  }
}

void PointerLockController::UnlockPointer() {
  WebContents* tab = exclusive_access_tab();

  if (!tab) {
    return;
  }

  hosts_waiting_for_pointer_lock_permission_prompt_.erase(
      tab->GetPrimaryMainFrame()->GetGlobalId());

  content::RenderWidgetHostView* pointer_lock_view = nullptr;
  RenderViewHost* const rvh =
      exclusive_access_tab()->GetPrimaryMainFrame()->GetRenderViewHost();
  if (rvh) {
    pointer_lock_view = rvh->GetWidget()->GetView();
  }

  if (pointer_lock_view) {
    pointer_lock_view->UnlockPointer();
  }
}

void PointerLockController::LockPointer(
    base::WeakPtr<content::WebContents> web_contents,
    content::GlobalRenderFrameHostId rfh_id,
    bool last_unlocked_by_target) {
  hosts_waiting_for_pointer_lock_permission_prompt_.erase(rfh_id);

  if (!web_contents) {
    if (lock_state_callback_for_test_) {
      std::move(lock_state_callback_for_test_).Run();
    }
    return;
  }
  SetTabWithExclusiveAccess(web_contents.get());
  // Focus may have moved to the modal, so move it back to the WebContents.
  web_contents->Focus();

  // Lock the mouse pointer.
  if (fake_pointer_lock_for_test_ ||
      web_contents->GotResponseToPointerLockRequest(
          blink::mojom::PointerLockResult::kSuccess)) {
    if (last_unlocked_by_target &&
        web_contents_granted_silent_pointer_lock_permission_ ==
            web_contents.get()) {
      pointer_lock_state_ = POINTERLOCK_LOCKED_SILENTLY;
    } else {
      pointer_lock_state_ = POINTERLOCK_LOCKED;
    }
  } else {
    SetTabWithExclusiveAccess(nullptr);
    pointer_lock_state_ = POINTERLOCK_UNLOCKED;
  }

  if (!ShouldSuppressBubbleReshowForStateChange()) {
    exclusive_access_manager()->UpdateBubble(
        base::BindOnce(&PointerLockController::OnBubbleHidden,
                       weak_ptr_factory_.GetWeakPtr(), web_contents));
  }
  if (lock_state_callback_for_test_) {
    std::move(lock_state_callback_for_test_).Run();
  }
}

void PointerLockController::RejectRequestToLockPointer(
    base::WeakPtr<content::WebContents> web_contents,
    content::GlobalRenderFrameHostId rfh_id) {
  DCHECK(IsWaitingForPointerLockPromptHelper(rfh_id));
  hosts_waiting_for_pointer_lock_permission_prompt_.erase(rfh_id);

  if (!web_contents) {
    if (lock_state_callback_for_test_) {
      std::move(lock_state_callback_for_test_).Run();
    }
    return;
  }

  // Focus has moved to the modal, so move it back to the WebContents.
  web_contents->Focus();
  web_contents->GotResponseToPointerLockRequest(
      blink::mojom::PointerLockResult::kUserRejected);
  if (lock_state_callback_for_test_) {
    std::move(lock_state_callback_for_test_).Run();
  }
}

void PointerLockController::OnBubbleHidden(
    base::WeakPtr<content::WebContents> web_contents,
    ExclusiveAccessBubbleHideReason reason) {
  if (bubble_hide_callback_for_test_) {
    bubble_hide_callback_for_test_.Run(reason);
  }

  // Allow silent pointer 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_pointer_lock_permission_ = web_contents.get();
  } else {
    web_contents_granted_silent_pointer_lock_permission_ = nullptr;
  }
}

bool PointerLockController::ShouldSuppressBubbleReshowForStateChange() {
  ExclusiveAccessBubbleType bubble_type =
      exclusive_access_manager()->GetExclusiveAccessExitBubbleType();
  return (pointer_lock_state_ == POINTERLOCK_LOCKED &&
          bubble_type ==
              EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_POINTERLOCK_EXIT_INSTRUCTION) ||
         (pointer_lock_state_ == POINTERLOCK_UNLOCKED &&
          bubble_type ==
              EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION);
}

bool PointerLockController::IsWaitingForPointerLockPromptHelper(
    content::GlobalRenderFrameHostId rfh_id) {
  return hosts_waiting_for_pointer_lock_permission_prompt_.contains(rfh_id);
}