blob: ce1824963a2f1bbb9cb0c69e1fbc85dc7229a0a1 [file] [log] [blame]
// Copyright 2019 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/media/android/cdm/per_device_provisioning_permission.h"
#include <utility>
#include "base/callback.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/no_destructor.h"
#include "base/time/time.h"
#include "chrome/browser/android/android_theme_resources.h"
#include "chrome/browser/permissions/permission_request.h"
#include "chrome/browser/permissions/permission_request_manager.h"
#include "chrome/grit/generated_resources.h"
#include "components/url_formatter/elide_url.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "media/base/android/media_drm_bridge.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/origin.h"
namespace {
// Only keep track of the last response for a short period of time.
constexpr base::TimeDelta kLastRequestDelta = base::TimeDelta::FromMinutes(15);
// Keep track of the last response. This is only kept in memory, so once Chrome
// quits it is forgotten.
class LastResponse {
public:
// If |origin| matches the previously saved |origin_| and this request is
// before |expiry_time|, return true indicating that the previous value should
// be used, and update |allowed| with the previous response. If the origin
// doesn't match or the previous response was too long ago, return false.
bool Matches(const url::Origin& origin, bool* allowed) {
if (!origin_.IsSameOriginWith(origin) || base::Time::Now() > expiry_time_)
return false;
*allowed = allowed_;
return true;
}
// Updates this object with the latest |origin| and |response|.
void Update(const url::Origin& origin, bool response) {
origin_ = origin;
expiry_time_ = base::Time::Now() + kLastRequestDelta;
allowed_ = response;
}
private:
url::Origin origin_;
base::Time expiry_time_;
bool allowed_ = false;
};
// Returns an object containing the last response. We only keep track of one
// response (the latest). This is done for simplicity, as it is unlikely that
// there will different origins getting to this path at the same time. Requests
// could be from different |render_frame_host| objects, but this matches what
// normal permission requests do when the decision is persisted in user's
// profile.
LastResponse& GetLastResponse() {
static base::NoDestructor<LastResponse> s_last_response;
return *s_last_response;
}
// A PermissionRequest to allow MediaDrmBridge to use per-device provisioning.
class PerDeviceProvisioningPermissionRequest : public PermissionRequest {
public:
PerDeviceProvisioningPermissionRequest(
const url::Origin& origin,
base::OnceCallback<void(bool)> callback)
: origin_(origin), callback_(std::move(callback)) {}
PermissionRequest::IconId GetIconId() const final {
return IDR_ANDROID_INFOBAR_PROTECTED_MEDIA_IDENTIFIER;
}
base::string16 GetMessageText() const final {
// Note that the string is specific to per-device provisioning.
return l10n_util::GetStringFUTF16(
IDS_PROTECTED_MEDIA_IDENTIFIER_PER_DEVICE_PROVISIONING_INFOBAR_TEXT,
url_formatter::FormatUrlForSecurityDisplay(
GetOrigin(), url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC));
}
base::string16 GetMessageTextFragment() const final {
return l10n_util::GetStringUTF16(
IDS_PROTECTED_MEDIA_IDENTIFIER_PERMISSION_FRAGMENT);
}
GURL GetOrigin() const final { return origin_.GetURL(); }
void PermissionGranted() final {
UpdateLastResponse(true);
std::move(callback_).Run(true);
}
void PermissionDenied() final {
UpdateLastResponse(false);
std::move(callback_).Run(false);
}
void Cancelled() final {
UpdateLastResponse(false);
std::move(callback_).Run(false);
}
void RequestFinished() final {
// The |callback_| may not have run if the prompt was ignored, e.g. the tab
// was closed while the prompt was displayed. Don't save this result as the
// last response since it wasn't really a user action.
if (callback_)
std::move(callback_).Run(false);
delete this;
}
PermissionRequestType GetPermissionRequestType() const final {
return PermissionRequestType::PERMISSION_PROTECTED_MEDIA_IDENTIFIER;
}
private:
// Can only be self-destructed. See RequestFinished().
~PerDeviceProvisioningPermissionRequest() final = default;
void UpdateLastResponse(bool allowed) {
GetLastResponse().Update(origin_, allowed);
}
const url::Origin origin_;
base::OnceCallback<void(bool)> callback_;
DISALLOW_COPY_AND_ASSIGN(PerDeviceProvisioningPermissionRequest);
};
} // namespace
void RequestPerDeviceProvisioningPermission(
content::RenderFrameHost* render_frame_host,
base::OnceCallback<void(bool)> callback) {
DVLOG(1) << __func__;
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(render_frame_host);
DCHECK(callback);
DCHECK(media::MediaDrmBridge::IsPerOriginProvisioningSupported())
<< "RequestPerDeviceProvisioningPermission() should only be called when "
"per-origin provisioning is supported.";
// Return the previous response if it was for the same origin.
bool last_response = false;
if (GetLastResponse().Matches(render_frame_host->GetLastCommittedOrigin(),
&last_response)) {
DVLOG(1) << "Using previous response: " << last_response;
std::move(callback).Run(last_response);
return;
}
auto* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host);
DCHECK(web_contents) << "WebContents not available.";
auto* permission_request_manager =
PermissionRequestManager::FromWebContents(web_contents);
if (!permission_request_manager) {
std::move(callback).Run(false);
return;
}
// The created PerDeviceProvisioningPermissionRequest deletes itself once
// complete. See PerDeviceProvisioningPermissionRequest::RequestFinished().
permission_request_manager->AddRequest(
new PerDeviceProvisioningPermissionRequest(
render_frame_host->GetLastCommittedOrigin(), std::move(callback)));
}