357 lines
12 KiB
C++
357 lines
12 KiB
C++
// Copyright 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 "libcef/browser/permissions/permission_manager.h"
|
|
|
|
#include "libcef/browser/browser_context.h"
|
|
#include "libcef/browser/permissions/permission_util.h"
|
|
|
|
#include "base/callback.h"
|
|
#include "components/content_settings/core/browser/host_content_settings_map.h"
|
|
#include "content/public/browser/permission_type.h"
|
|
#include "content/public/browser/render_frame_host.h"
|
|
#include "content/public/browser/render_process_host.h"
|
|
#include "content/public/browser/web_contents.h"
|
|
|
|
using blink::mojom::PermissionStatus;
|
|
using content::PermissionType;
|
|
|
|
namespace {
|
|
|
|
// Helper method to convert ContentSetting to PermissionStatus.
|
|
PermissionStatus ContentSettingToPermissionStatus(ContentSetting setting) {
|
|
switch (setting) {
|
|
case CONTENT_SETTING_ALLOW:
|
|
case CONTENT_SETTING_SESSION_ONLY:
|
|
return PermissionStatus::GRANTED;
|
|
case CONTENT_SETTING_BLOCK:
|
|
return PermissionStatus::DENIED;
|
|
case CONTENT_SETTING_ASK:
|
|
return PermissionStatus::ASK;
|
|
case CONTENT_SETTING_DETECT_IMPORTANT_CONTENT:
|
|
case CONTENT_SETTING_DEFAULT:
|
|
case CONTENT_SETTING_NUM_SETTINGS:
|
|
break;
|
|
}
|
|
|
|
NOTREACHED();
|
|
return PermissionStatus::DENIED;
|
|
}
|
|
|
|
// Helper method to convert PermissionStatus to ContentSetting.
|
|
ContentSetting PermissionStatusToContentSetting(PermissionStatus status) {
|
|
switch (status) {
|
|
case PermissionStatus::GRANTED:
|
|
return CONTENT_SETTING_ALLOW;
|
|
case PermissionStatus::DENIED:
|
|
return CONTENT_SETTING_BLOCK;
|
|
case PermissionStatus::ASK:
|
|
return CONTENT_SETTING_ASK;
|
|
}
|
|
|
|
NOTREACHED();
|
|
return CONTENT_SETTING_BLOCK;
|
|
}
|
|
|
|
// Wrap a callback taking a PermissionStatus to pass it as a callback taking a
|
|
// ContentSetting.
|
|
void ContentSettingToPermissionStatusCallbackWrapper(
|
|
const base::Callback<void(PermissionStatus)>& callback,
|
|
ContentSetting setting) {
|
|
callback.Run(ContentSettingToPermissionStatus(setting));
|
|
}
|
|
|
|
// Returns whether the permission has a constant PermissionStatus value (i.e.
|
|
// always approved or always denied).
|
|
bool IsConstantPermission(PermissionType type) {
|
|
switch (type) {
|
|
case PermissionType::MIDI:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void PermissionRequestResponseCallbackWrapper(
|
|
const base::Callback<void(PermissionStatus)>& callback,
|
|
const std::vector<PermissionStatus>& vector) {
|
|
DCHECK_EQ(vector.size(), 1ul);
|
|
callback.Run(vector[0]);
|
|
}
|
|
|
|
// Function used for handling permission types which do not change their
|
|
// value i.e. they are always approved or always denied etc.
|
|
// CONTENT_SETTING_DEFAULT is returned if the permission needs further handling.
|
|
// This function should only be called when IsConstantPermission has returned
|
|
// true for the PermissionType.
|
|
ContentSetting GetContentSettingForConstantPermission(PermissionType type) {
|
|
DCHECK(IsConstantPermission(type));
|
|
switch (type) {
|
|
case PermissionType::MIDI:
|
|
return CONTENT_SETTING_ALLOW;
|
|
default:
|
|
return CONTENT_SETTING_DEFAULT;
|
|
}
|
|
}
|
|
|
|
PermissionStatus GetPermissionStatusForConstantPermission(PermissionType type) {
|
|
return ContentSettingToPermissionStatus(
|
|
GetContentSettingForConstantPermission(type));
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
class CefPermissionManager::PendingRequest {
|
|
public:
|
|
PendingRequest(
|
|
content::RenderFrameHost* render_frame_host,
|
|
const std::vector<PermissionType> permissions,
|
|
const base::Callback<void(const std::vector<PermissionStatus>&)>&
|
|
callback)
|
|
: render_process_id_(render_frame_host->GetProcess()->GetID()),
|
|
render_frame_id_(render_frame_host->GetRoutingID()),
|
|
callback_(callback),
|
|
permissions_(permissions),
|
|
results_(permissions.size(), PermissionStatus::DENIED),
|
|
remaining_results_(permissions.size()) {}
|
|
|
|
void SetPermissionStatus(int permission_id, PermissionStatus status) {
|
|
DCHECK(!IsComplete());
|
|
|
|
results_[permission_id] = status;
|
|
--remaining_results_;
|
|
}
|
|
|
|
bool IsComplete() const { return remaining_results_ == 0; }
|
|
|
|
int render_process_id() const { return render_process_id_; }
|
|
int render_frame_id() const { return render_frame_id_; }
|
|
|
|
const base::Callback<void(const std::vector<PermissionStatus>&)> callback()
|
|
const {
|
|
return callback_;
|
|
}
|
|
|
|
std::vector<PermissionType> permissions() const { return permissions_; }
|
|
|
|
std::vector<PermissionStatus> results() const { return results_; }
|
|
|
|
private:
|
|
int render_process_id_;
|
|
int render_frame_id_;
|
|
const base::Callback<void(const std::vector<PermissionStatus>&)> callback_;
|
|
std::vector<PermissionType> permissions_;
|
|
std::vector<PermissionStatus> results_;
|
|
size_t remaining_results_;
|
|
};
|
|
|
|
struct CefPermissionManager::Subscription {
|
|
PermissionType permission;
|
|
GURL requesting_origin;
|
|
GURL embedding_origin;
|
|
base::Callback<void(PermissionStatus)> callback;
|
|
ContentSetting current_value;
|
|
};
|
|
|
|
CefPermissionManager::CefPermissionManager(CefBrowserContext* profile)
|
|
: profile_(profile), context_(profile), weak_ptr_factory_(this) {}
|
|
|
|
CefPermissionManager::~CefPermissionManager() {
|
|
if (!subscriptions_.IsEmpty())
|
|
profile_->GetHostContentSettingsMap()->RemoveObserver(this);
|
|
}
|
|
|
|
int CefPermissionManager::RequestPermission(
|
|
PermissionType permission,
|
|
content::RenderFrameHost* render_frame_host,
|
|
const GURL& requesting_origin,
|
|
bool user_gesture,
|
|
const base::Callback<void(PermissionStatus)>& callback) {
|
|
return RequestPermissions(
|
|
std::vector<PermissionType>(1, permission), render_frame_host,
|
|
requesting_origin, user_gesture,
|
|
base::Bind(&PermissionRequestResponseCallbackWrapper, callback));
|
|
}
|
|
|
|
int CefPermissionManager::RequestPermissions(
|
|
const std::vector<PermissionType>& permissions,
|
|
content::RenderFrameHost* render_frame_host,
|
|
const GURL& requesting_origin,
|
|
bool user_gesture,
|
|
const base::Callback<void(const std::vector<PermissionStatus>&)>&
|
|
callback) {
|
|
if (permissions.empty()) {
|
|
callback.Run(std::vector<PermissionStatus>());
|
|
return kNoPendingOperation;
|
|
}
|
|
|
|
content::WebContents* web_contents =
|
|
content::WebContents::FromRenderFrameHost(render_frame_host);
|
|
GURL embedding_origin = web_contents->GetLastCommittedURL().GetOrigin();
|
|
|
|
std::unique_ptr<PendingRequest> pending_request =
|
|
base::MakeUnique<PendingRequest>(render_frame_host, permissions,
|
|
callback);
|
|
int request_id = pending_requests_.Add(std::move(pending_request));
|
|
|
|
const PermissionRequestID request(render_frame_host, request_id);
|
|
|
|
for (size_t i = 0; i < permissions.size(); ++i) {
|
|
const PermissionType permission = permissions[i];
|
|
|
|
if (IsConstantPermission(permission) ||
|
|
!context_.SupportsPermission(permission)) {
|
|
OnPermissionsRequestResponseStatus(
|
|
request_id, i,
|
|
GetPermissionStatus(permission, requesting_origin, embedding_origin));
|
|
continue;
|
|
}
|
|
|
|
context_.RequestPermission(
|
|
permission, web_contents, request, requesting_origin,
|
|
base::Bind(
|
|
&ContentSettingToPermissionStatusCallbackWrapper,
|
|
base::Bind(
|
|
&CefPermissionManager::OnPermissionsRequestResponseStatus,
|
|
weak_ptr_factory_.GetWeakPtr(), request_id, i)));
|
|
}
|
|
|
|
// The request might have been resolved already.
|
|
if (!pending_requests_.Lookup(request_id))
|
|
return kNoPendingOperation;
|
|
|
|
return request_id;
|
|
}
|
|
|
|
void CefPermissionManager::OnPermissionsRequestResponseStatus(
|
|
int request_id,
|
|
int permission_id,
|
|
PermissionStatus status) {
|
|
PendingRequest* pending_request = pending_requests_.Lookup(request_id);
|
|
pending_request->SetPermissionStatus(permission_id, status);
|
|
|
|
if (!pending_request->IsComplete())
|
|
return;
|
|
|
|
pending_request->callback().Run(pending_request->results());
|
|
pending_requests_.Remove(request_id);
|
|
}
|
|
|
|
void CefPermissionManager::CancelPermissionRequest(int request_id) {
|
|
PendingRequest* pending_request = pending_requests_.Lookup(request_id);
|
|
if (!pending_request)
|
|
return;
|
|
|
|
content::RenderFrameHost* render_frame_host =
|
|
content::RenderFrameHost::FromID(pending_request->render_process_id(),
|
|
pending_request->render_frame_id());
|
|
DCHECK(render_frame_host);
|
|
content::WebContents* web_contents =
|
|
content::WebContents::FromRenderFrameHost(render_frame_host);
|
|
DCHECK(web_contents);
|
|
|
|
const PermissionRequestID request(pending_request->render_process_id(),
|
|
pending_request->render_frame_id(),
|
|
request_id);
|
|
for (PermissionType permission : pending_request->permissions()) {
|
|
if (!context_.SupportsPermission(permission))
|
|
continue;
|
|
context_.CancelPermissionRequest(permission, web_contents, request);
|
|
}
|
|
pending_requests_.Remove(request_id);
|
|
}
|
|
|
|
void CefPermissionManager::ResetPermission(PermissionType permission,
|
|
const GURL& requesting_origin,
|
|
const GURL& embedding_origin) {
|
|
if (!context_.SupportsPermission(permission))
|
|
return;
|
|
context_.ResetPermission(permission, requesting_origin, embedding_origin);
|
|
}
|
|
|
|
PermissionStatus CefPermissionManager::GetPermissionStatus(
|
|
PermissionType permission,
|
|
const GURL& requesting_origin,
|
|
const GURL& embedding_origin) {
|
|
if (IsConstantPermission(permission))
|
|
return GetPermissionStatusForConstantPermission(permission);
|
|
|
|
if (!context_.SupportsPermission(permission))
|
|
return PermissionStatus::DENIED;
|
|
|
|
return ContentSettingToPermissionStatus(context_.GetPermissionStatus(
|
|
permission, requesting_origin, embedding_origin));
|
|
}
|
|
|
|
int CefPermissionManager::SubscribePermissionStatusChange(
|
|
PermissionType permission,
|
|
const GURL& requesting_origin,
|
|
const GURL& embedding_origin,
|
|
const base::Callback<void(PermissionStatus)>& callback) {
|
|
if (subscriptions_.IsEmpty())
|
|
profile_->GetHostContentSettingsMap()->AddObserver(this);
|
|
|
|
std::unique_ptr<Subscription> subscription = base::MakeUnique<Subscription>();
|
|
subscription->permission = permission;
|
|
subscription->requesting_origin = requesting_origin;
|
|
subscription->embedding_origin = embedding_origin;
|
|
subscription->callback = callback;
|
|
|
|
subscription->current_value = PermissionStatusToContentSetting(
|
|
GetPermissionStatus(permission, subscription->requesting_origin,
|
|
subscription->embedding_origin));
|
|
|
|
return subscriptions_.Add(std::move(subscription));
|
|
}
|
|
|
|
void CefPermissionManager::UnsubscribePermissionStatusChange(
|
|
int subscription_id) {
|
|
// Whether |subscription_id| is known will be checked by the Remove() call.
|
|
subscriptions_.Remove(subscription_id);
|
|
|
|
if (subscriptions_.IsEmpty())
|
|
profile_->GetHostContentSettingsMap()->RemoveObserver(this);
|
|
}
|
|
|
|
void CefPermissionManager::OnContentSettingChanged(
|
|
const ContentSettingsPattern& primary_pattern,
|
|
const ContentSettingsPattern& secondary_pattern,
|
|
ContentSettingsType content_type,
|
|
std::string resource_identifier) {
|
|
std::list<base::Closure> callbacks;
|
|
|
|
for (SubscriptionsMap::iterator iter(&subscriptions_); !iter.IsAtEnd();
|
|
iter.Advance()) {
|
|
Subscription* subscription = iter.GetCurrentValue();
|
|
if (permission_util::PermissionTypeToContentSetting(
|
|
subscription->permission) != content_type) {
|
|
continue;
|
|
}
|
|
|
|
if (primary_pattern.IsValid() &&
|
|
!primary_pattern.Matches(subscription->requesting_origin))
|
|
continue;
|
|
if (secondary_pattern.IsValid() &&
|
|
!secondary_pattern.Matches(subscription->embedding_origin))
|
|
continue;
|
|
|
|
ContentSetting new_value =
|
|
PermissionStatusToContentSetting(GetPermissionStatus(
|
|
subscription->permission, subscription->requesting_origin,
|
|
subscription->embedding_origin));
|
|
if (subscription->current_value == new_value)
|
|
continue;
|
|
|
|
subscription->current_value = new_value;
|
|
|
|
// Add the callback to |callbacks| which will be run after the loop to
|
|
// prevent re-entrance issues.
|
|
callbacks.push_back(base::Bind(
|
|
subscription->callback, ContentSettingToPermissionStatus(new_value)));
|
|
}
|
|
|
|
for (const auto& callback : callbacks)
|
|
callback.Run();
|
|
}
|