blob: 1576d76aa9fd83e64b920d211a839c9309e61cd4 [file] [log] [blame]
// Copyright 2024 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/exclusive_access_permission_manager.h"
#include "base/time/time.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
namespace {
// We will gather requests for this amount of time, then send them all at once.
constexpr int kRequestDelayMs = 100;
} // namespace
ExclusiveAccessPermissionManager::ExclusiveAccessPermissionManager(
ExclusiveAccessContext* exclusive_access_context)
: exclusive_access_context_(exclusive_access_context) {}
ExclusiveAccessPermissionManager::~ExclusiveAccessPermissionManager() = default;
void ExclusiveAccessPermissionManager::QueuePermissionRequest(
blink::PermissionType permission_type,
base::OnceClosure granted_callback,
base::OnceClosure denied_callback,
content::WebContents* web_contents) {
if (!web_contents) {
std::move(denied_callback).Run();
return;
}
content::GlobalRenderFrameHostId rfh_id =
web_contents->GetPrimaryMainFrame()->GetGlobalId();
if (!pending_requests_.contains(rfh_id)) {
pending_requests_.emplace(std::piecewise_construct,
std::forward_as_tuple(rfh_id),
std::forward_as_tuple());
}
PendingRequests& requests = pending_requests_.find(rfh_id)->second;
requests.pending.emplace_back(permission_type, std::move(granted_callback),
std::move(denied_callback));
if (requests.waiting_responses > 0) {
// We've already started making requests, so send the new request
// immediately.
RequestPermissions(rfh_id, web_contents->GetWeakPtr());
} else if (requests.pending.size() == 1) {
// This is the first (recent) request for `web_contents`, so wait to see if
// more requests come in, and then make the request.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ExclusiveAccessPermissionManager::RequestPermissions,
weak_factory_.GetWeakPtr(), rfh_id,
web_contents->GetWeakPtr()),
base::Milliseconds(kRequestDelayMs));
} else {
// No-op, because the request will be handled by RequestPermissions() that
// was already posted by an earlier request.
}
}
ExclusiveAccessPermissionManager::PermissionRequest::PermissionRequest(
blink::PermissionType type,
base::OnceClosure granted_callback,
base::OnceClosure denied_callback)
: type(type),
granted_callback(std::move(granted_callback)),
denied_callback(std::move(denied_callback)) {}
ExclusiveAccessPermissionManager::PermissionRequest::PermissionRequest(
PermissionRequest&& other)
: PermissionRequest(other.type,
std::move(other.granted_callback),
std::move(other.denied_callback)) {}
ExclusiveAccessPermissionManager::PermissionRequest::~PermissionRequest() =
default;
ExclusiveAccessPermissionManager::PendingRequests::PendingRequests() = default;
ExclusiveAccessPermissionManager::PendingRequests::~PendingRequests() = default;
void ExclusiveAccessPermissionManager::RequestPermissions(
content::GlobalRenderFrameHostId rfh_id,
base::WeakPtr<content::WebContents> web_contents) {
if (!web_contents) {
pending_requests_.erase(rfh_id);
return;
}
content::RenderFrameHost* rfh = web_contents->GetPrimaryMainFrame();
auto requests_it = pending_requests_.find(rfh_id);
if (requests_it == pending_requests_.end()) {
return;
}
PendingRequests& requests = requests_it->second;
requests.waiting_responses += requests.pending.size();
for (PermissionRequest& request : requests.pending) {
// TODO: crbug.com/332984845 - Set the actual user gesture state.
content::PermissionRequestDescription description(request.type,
/*user_gesture=*/false);
GetPermissionController(web_contents.get())
->RequestPermissionsFromCurrentDocument(
rfh, std::move(description),
base::BindOnce(
&ExclusiveAccessPermissionManager::HandleRequestResult,
weak_factory_.GetWeakPtr(), rfh_id,
std::move(request.granted_callback),
std::move(request.denied_callback)));
}
// `requests_it` may have been invalidated by HandleRequestResult(), so
// we must re-fetch it.
requests_it = pending_requests_.find(rfh_id);
if (requests_it != pending_requests_.end()) {
requests_it->second.pending.clear();
}
}
void ExclusiveAccessPermissionManager::HandleRequestResult(
content::GlobalRenderFrameHostId rfh_id,
base::OnceClosure granted_callback,
base::OnceClosure denied_callback,
const std::vector<blink::mojom::PermissionStatus>& status) {
auto requests_it = pending_requests_.find(rfh_id);
if (requests_it == pending_requests_.end()) {
NOTREACHED();
return;
}
PendingRequests& requests = requests_it->second;
CHECK_EQ(status.size(), 1u);
std::vector<base::OnceClosure> result_callbacks;
switch (status[0]) {
case blink::mojom::PermissionStatus::GRANTED:
requests.result_callbacks.push_back(std::move(granted_callback));
break;
case blink::mojom::PermissionStatus::DENIED:
case blink::mojom::PermissionStatus::ASK:
requests.result_callbacks.push_back(std::move(denied_callback));
break;
}
if (requests.result_callbacks.size() == requests.waiting_responses) {
// We queue the granted/denied callbacks until all the requests have been
// responded to, then call them all at once. If we don't do this, in the
// case where both pointer lock and keyboard lock are requested but the
// pointer lock permission has already been granted, pointer can be locked
// first and then immediately unlocked by the permission prompt for keyboard
// lock.
for (base::OnceClosure& result_callback : requests.result_callbacks) {
std::move(result_callback).Run();
}
pending_requests_.erase(requests_it);
}
}
content::PermissionController*
ExclusiveAccessPermissionManager::GetPermissionController(
content::WebContents* web_contents) {
if (permission_controller_for_test_) {
return permission_controller_for_test_;
}
return web_contents->GetBrowserContext()->GetPermissionController();
}