cef/libcef/browser/permission_prompt.cc

322 lines
12 KiB
C++

// Copyright 2022 The Chromium Embedded Framework Authors. Portions copyright
// 2016 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 "cef/libcef/browser/permission_prompt.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "cef/libcef/browser/browser_host_base.h"
#include "chrome/browser/ui/permission_bubble/permission_prompt.h"
namespace permission_prompt {
namespace {
uint64_t g_next_prompt_id = 0;
using DelegateCallback =
base::OnceCallback<void(cef_permission_request_result_t)>;
class CefPermissionPromptCallbackImpl : public CefPermissionPromptCallback {
public:
using CallbackType = base::OnceCallback<void(cef_permission_request_result_t,
bool /*notify_delegate*/)>;
explicit CefPermissionPromptCallbackImpl(CallbackType&& callback)
: callback_(std::move(callback)) {}
CefPermissionPromptCallbackImpl(const CefPermissionPromptCallbackImpl&) =
delete;
CefPermissionPromptCallbackImpl& operator=(
const CefPermissionPromptCallbackImpl&) = delete;
// Don't need to execute the callback in this destructor because this object
// will always be kept alive until after the CefPermissionPrompt is destroyed,
// and that object will disconnect/execute the callback in its destructor.
~CefPermissionPromptCallbackImpl() override = default;
void Continue(cef_permission_request_result_t result) override {
if (CEF_CURRENTLY_ON_UIT()) {
if (!callback_.is_null()) {
auto callback = base::BindOnce(std::move(callback_), result,
/*notify_delegate=*/true);
if (safe_to_run_sync_) {
std::move(callback).Run();
} else {
CEF_POST_TASK(CEF_UIT, std::move(callback));
}
}
} else {
CEF_POST_TASK(CEF_UIT,
base::BindOnce(&CefPermissionPromptCallbackImpl::Continue,
this, result));
}
}
[[nodiscard]] CallbackType Disconnect() { return std::move(callback_); }
bool IsDisconnected() const { return callback_.is_null(); }
void MarkSafeToRunSync() { safe_to_run_sync_ = true; }
private:
// Callback execution from inside CreatePermissionPromptImpl must be async,
// otherwise PermissionRequestManager state will be incorrect.
bool safe_to_run_sync_ = false;
CallbackType callback_;
IMPLEMENT_REFCOUNTING(CefPermissionPromptCallbackImpl);
};
// Implementation based on PermissionPromptAndroid.
class CefPermissionPrompt : public permissions::PermissionPrompt {
public:
explicit CefPermissionPrompt(Delegate* delegate) : delegate_(delegate) {
DCHECK(delegate_);
}
CefPermissionPrompt(const CefPermissionPrompt&) = delete;
CefPermissionPrompt& operator=(const CefPermissionPrompt&) = delete;
// Expect to be destroyed (and the UI needs to go) when:
// 1. A navigation happens, tab/webcontents is being closed; with the current
// GetTabSwitchingBehavior() implementation, this instance survives the tab
// being backgrounded.
// 2. The permission request is resolved (accept, deny, dismiss).
// 3. A higher priority request comes in.
~CefPermissionPrompt() override {
CEF_REQUIRE_UIT();
if (callback_) {
// If the callback is non-null at this point then we still need to execute
// it in order to notify the client.
auto callback = callback_->Disconnect();
if (!callback.is_null()) {
std::move(callback).Run(CEF_PERMISSION_RESULT_IGNORE,
/*notify_delegate=*/false);
}
}
}
// Used to associate the client callback when OnShowPermissionPrompt is
// handled.
void AttachClientCallback(
CefRefPtr<CefPermissionPromptCallbackImpl> callback) {
DCHECK(callback);
callback_ = callback;
callback_->MarkSafeToRunSync();
}
// Used to tie Delegate access to this object's lifespan.
DelegateCallback MakeDelegateCallback() {
return base::BindOnce(&CefPermissionPrompt::NotifyDelegate,
weak_ptr_factory_.GetWeakPtr());
}
// PermissionPrompt methods:
bool UpdateAnchor() override { return true; }
TabSwitchingBehavior GetTabSwitchingBehavior() override {
return TabSwitchingBehavior::kKeepPromptAlive;
}
permissions::PermissionPromptDisposition GetPromptDisposition()
const override {
return permissions::PermissionPromptDisposition::CUSTOM_MODAL_DIALOG;
}
std::optional<gfx::Rect> GetViewBoundsInScreen() const override {
return std::nullopt;
}
bool ShouldFinalizeRequestAfterDecided() const override { return true; }
std::vector<permissions::ElementAnchoredBubbleVariant> GetPromptVariants()
const override {
return {};
}
std::optional<permissions::feature_params::PermissionElementPromptPosition>
GetPromptPosition() const override {
return std::nullopt;
}
bool IsAskPrompt() const override { return true; }
private:
// We don't expose AcceptThisTime() because it's a special case for
// Geolocation (see DCHECK in PrefProvider::SetWebsiteSetting).
void NotifyDelegate(cef_permission_request_result_t result) {
switch (result) {
case CEF_PERMISSION_RESULT_ACCEPT:
delegate_->Accept();
break;
case CEF_PERMISSION_RESULT_DENY:
delegate_->Deny();
break;
case CEF_PERMISSION_RESULT_DISMISS:
delegate_->Dismiss();
break;
case CEF_PERMISSION_RESULT_IGNORE:
delegate_->Ignore();
break;
}
}
// |delegate_| is the PermissionRequestManager, which owns this object.
const raw_ptr<Delegate> delegate_;
CefRefPtr<CefPermissionPromptCallbackImpl> callback_;
base::WeakPtrFactory<CefPermissionPrompt> weak_ptr_factory_{this};
};
// |notify_delegate| will be false if called from the CefPermissionPrompt
// destructor.
void ExecuteResult(CefRefPtr<CefBrowserHostBase> browser,
uint64_t prompt_id,
DelegateCallback delegate_callback,
cef_permission_request_result_t result,
bool notify_delegate) {
CEF_REQUIRE_UIT();
if (auto client = browser->GetClient()) {
if (auto handler = client->GetPermissionHandler()) {
handler->OnDismissPermissionPrompt(browser, prompt_id, result);
}
}
if (notify_delegate) {
// Will be a no-op if this executes after the CefPermissionPrompt was
// destroyed.
std::move(delegate_callback).Run(result);
}
}
cef_permission_request_types_t GetCefRequestType(
permissions::RequestType type) {
switch (type) {
case permissions::RequestType::kArSession:
return CEF_PERMISSION_TYPE_AR_SESSION;
case permissions::RequestType::kCameraPanTiltZoom:
return CEF_PERMISSION_TYPE_CAMERA_PAN_TILT_ZOOM;
case permissions::RequestType::kCameraStream:
return CEF_PERMISSION_TYPE_CAMERA_STREAM;
case permissions::RequestType::kCapturedSurfaceControl:
return CEF_PERMISSION_TYPE_CAPTURED_SURFACE_CONTROL;
case permissions::RequestType::kClipboard:
return CEF_PERMISSION_TYPE_CLIPBOARD;
case permissions::RequestType::kDiskQuota:
return CEF_PERMISSION_TYPE_DISK_QUOTA;
case permissions::RequestType::kLocalFonts:
return CEF_PERMISSION_TYPE_LOCAL_FONTS;
case permissions::RequestType::kGeolocation:
return CEF_PERMISSION_TYPE_GEOLOCATION;
case permissions::RequestType::kHandTracking:
return CEF_PERMISSION_TYPE_HAND_TRACKING;
case permissions::RequestType::kIdentityProvider:
return CEF_PERMISSION_TYPE_IDENTITY_PROVIDER;
case permissions::RequestType::kIdleDetection:
return CEF_PERMISSION_TYPE_IDLE_DETECTION;
case permissions::RequestType::kMicStream:
return CEF_PERMISSION_TYPE_MIC_STREAM;
case permissions::RequestType::kMidiSysex:
return CEF_PERMISSION_TYPE_MIDI_SYSEX;
case permissions::RequestType::kMultipleDownloads:
return CEF_PERMISSION_TYPE_MULTIPLE_DOWNLOADS;
case permissions::RequestType::kNotifications:
return CEF_PERMISSION_TYPE_NOTIFICATIONS;
case permissions::RequestType::kKeyboardLock:
return CEF_PERMISSION_TYPE_KEYBOARD_LOCK;
case permissions::RequestType::kPointerLock:
return CEF_PERMISSION_TYPE_POINTER_LOCK;
#if BUILDFLAG(IS_WIN)
case permissions::RequestType::kProtectedMediaIdentifier:
return CEF_PERMISSION_TYPE_PROTECTED_MEDIA_IDENTIFIER;
#endif
case permissions::RequestType::kRegisterProtocolHandler:
return CEF_PERMISSION_TYPE_REGISTER_PROTOCOL_HANDLER;
case permissions::RequestType::kStorageAccess:
return CEF_PERMISSION_TYPE_STORAGE_ACCESS;
case permissions::RequestType::kTopLevelStorageAccess:
return CEF_PERMISSION_TYPE_TOP_LEVEL_STORAGE_ACCESS;
case permissions::RequestType::kVrSession:
return CEF_PERMISSION_TYPE_VR_SESSION;
case permissions::RequestType::kWebAppInstallation:
return CEF_PERMISSION_TYPE_WEB_APP_INSTALLATION;
case permissions::RequestType::kWindowManagement:
return CEF_PERMISSION_TYPE_WINDOW_MANAGEMENT;
case permissions::RequestType::kFileSystemAccess:
return CEF_PERMISSION_TYPE_FILE_SYSTEM_ACCESS;
}
DCHECK(false);
return CEF_PERMISSION_TYPE_NONE;
}
uint32_t GetRequestedPermissions(
permissions::PermissionPrompt::Delegate* delegate) {
uint32_t permissions = CEF_PERMISSION_TYPE_NONE;
for (const auto& request : delegate->Requests()) {
permissions |= GetCefRequestType(request->request_type());
}
return permissions;
}
std::unique_ptr<permissions::PermissionPrompt> CreatePermissionPromptImpl(
content::WebContents* web_contents,
permissions::PermissionPrompt::Delegate* delegate,
bool* default_handling) {
CEF_REQUIRE_UIT();
bool is_alloy_style = false;
if (auto browser = CefBrowserHostBase::GetBrowserForContents(web_contents)) {
is_alloy_style = browser->IsAlloyStyle();
if (auto client = browser->GetClient()) {
if (auto handler = client->GetPermissionHandler()) {
auto permission_prompt =
std::make_unique<CefPermissionPrompt>(delegate);
const auto prompt_id = ++g_next_prompt_id;
auto callback =
base::BindOnce(&ExecuteResult, browser, prompt_id,
permission_prompt->MakeDelegateCallback());
CefRefPtr<CefPermissionPromptCallbackImpl> callbackImpl(
new CefPermissionPromptCallbackImpl(std::move(callback)));
bool handled = handler->OnShowPermissionPrompt(
browser, prompt_id, delegate->GetRequestingOrigin().spec(),
GetRequestedPermissions(delegate), callbackImpl.get());
if (callbackImpl->IsDisconnected() || handled) {
// Callback execution will be async.
LOG_IF(ERROR, !handled)
<< "Should return true from OnShowPermissionPrompt when "
"executing the callback";
*default_handling = false;
permission_prompt->AttachClientCallback(callbackImpl);
return permission_prompt;
} else {
// Proceed with default handling. |callback| is discarded without
// execution.
callback = callbackImpl->Disconnect();
}
}
}
}
if (is_alloy_style) {
LOG(INFO) << "Implement OnShowPermissionPrompt to override default IGNORE "
"handling of permission prompts.";
}
// Proceed with default handling. This will be IGNORE with Alloy style and
// default UI prompt with Chrome style.
*default_handling = true;
return nullptr;
}
} // namespace
void RegisterCreateCallback() {
SetCreatePermissionPromptFunction(&CreatePermissionPromptImpl);
}
} // namespace permission_prompt