Revert "media: Remove MediaLicense* code and usage"
This reverts commit e307cce88c
.
Reason for revert: will come up with a strategy and discussion for
handling privacy concerns regarding being able to delete MediaLicense
data.
Original change's description:
> media: Remove MediaLicense* code and usage
>
> This CL deletes a lot of the MediaLicense* code and usages of it from
> the codebase.
>
> OBSOLETE_HISTOGRAMS=No longer using the MediaLicense* code.
>
> Bug: 40272342
> Change-Id: I46a2d8bf53ca684467255b6e21e3cd7e1441c4c4
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5904345
> Commit-Queue: Vikram Pasupathy <vpasupathy@chromium.org>
> Reviewed-by: Evan Liu <evliu@google.com>
> Reviewed-by: Evan Stade <estade@chromium.org>
> Reviewed-by: John Rummell <jrummell@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1372190}
Bug: 40272342
Change-Id: I1cbb4669d932ec153b1aae59a6b5164a6afde2ea
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5955369
Reviewed-by: Evan Stade <estade@chromium.org>
Reviewed-by: Evan Liu <evliu@google.com>
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Commit-Queue: Vikram Pasupathy <vpasupathy@chromium.org>
Reviewed-by: John Rummell <jrummell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1372853}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
13d7cb32ad
commit
bda8b474d3
components/services/storage/public/cpp
content
browser
BUILD.gn
media
cdm_file_impl.cccdm_file_impl.hmedia_interface_proxy.ccmedia_license_database.ccmedia_license_database.hmedia_license_manager.ccmedia_license_manager.hmedia_license_manager_unittest.ccmedia_license_quota_client.ccmedia_license_quota_client.hmedia_license_storage_host.ccmedia_license_storage_host.hmedia_license_storage_host_unittest.cc
storage_partition_impl.ccstorage_partition_impl.htest
storage/browser/quota
tools/metrics/histograms/metadata/media
@ -26,8 +26,6 @@ const base::FilePath::CharType kServiceWorkerDirectory[] =
|
||||
|
||||
// The path where media license data is persisted on disk, relative to the path
|
||||
// for the respective storage bucket.
|
||||
// TODO(crbug.com/40272342): Remove this and the file name below after the
|
||||
// deletion logic for the actual MediaLicense.db lands and completes.
|
||||
const base::FilePath::CharType kMediaLicenseDirectory[] =
|
||||
FILE_PATH_LITERAL("Media Licenses");
|
||||
|
||||
|
@ -3029,6 +3029,14 @@ source_set("browser") {
|
||||
"media/cdm_storage_database.h",
|
||||
"media/cdm_storage_manager.cc",
|
||||
"media/cdm_storage_manager.h",
|
||||
"media/media_license_database.cc",
|
||||
"media/media_license_database.h",
|
||||
"media/media_license_manager.cc",
|
||||
"media/media_license_manager.h",
|
||||
"media/media_license_quota_client.cc",
|
||||
"media/media_license_quota_client.h",
|
||||
"media/media_license_storage_host.cc",
|
||||
"media/media_license_storage_host.h",
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "content/browser/media/cdm_storage_manager.h"
|
||||
#include "content/browser/media/media_license_storage_host.h"
|
||||
#include "media/cdm/cdm_helpers.h"
|
||||
#include "media/cdm/cdm_type.h"
|
||||
#include "media/mojo/mojom/cdm_storage.mojom.h"
|
||||
@ -58,6 +59,21 @@ bool CdmFileImpl::IsValidName(const std::string& name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
CdmFileImpl::CdmFileImpl(
|
||||
MediaLicenseStorageHost* host,
|
||||
const media::CdmType& cdm_type,
|
||||
const std::string& file_name,
|
||||
mojo::PendingAssociatedReceiver<media::mojom::CdmFile> pending_receiver)
|
||||
: file_name_(file_name), cdm_type_(cdm_type), host_(host) {
|
||||
DVLOG(3) << __func__ << " " << file_name_;
|
||||
DCHECK(IsValidName(file_name_));
|
||||
DCHECK(host_);
|
||||
|
||||
receiver_.Bind(std::move(pending_receiver));
|
||||
receiver_.set_disconnect_handler(base::BindOnce(
|
||||
&CdmFileImpl::OnReceiverDisconnect, weak_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
CdmFileImpl::CdmFileImpl(
|
||||
CdmStorageManager* manager,
|
||||
const blink::StorageKey& storage_key,
|
||||
@ -91,7 +107,7 @@ CdmFileImpl::~CdmFileImpl() {
|
||||
void CdmFileImpl::Read(ReadCallback callback) {
|
||||
DVLOG(3) << __func__ << " file: " << file_name_;
|
||||
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||||
DCHECK(cdm_storage_manager_);
|
||||
DCHECK(host_ || cdm_storage_manager_);
|
||||
|
||||
// Only 1 Read() or Write() is allowed at any time.
|
||||
if (read_callback_ || write_callback_) {
|
||||
@ -103,9 +119,15 @@ void CdmFileImpl::Read(ReadCallback callback) {
|
||||
read_callback_ = std::move(callback);
|
||||
start_time_ = base::TimeTicks::Now();
|
||||
|
||||
cdm_storage_manager_->ReadFile(
|
||||
storage_key_, cdm_type_, file_name_,
|
||||
base::BindOnce(&CdmFileImpl::DidRead, weak_factory_.GetWeakPtr()));
|
||||
if (host_) {
|
||||
host_->ReadFile(
|
||||
cdm_type_, file_name_,
|
||||
base::BindOnce(&CdmFileImpl::DidRead, weak_factory_.GetWeakPtr()));
|
||||
} else {
|
||||
cdm_storage_manager_->ReadFile(
|
||||
storage_key_, cdm_type_, file_name_,
|
||||
base::BindOnce(&CdmFileImpl::DidRead, weak_factory_.GetWeakPtr()));
|
||||
}
|
||||
}
|
||||
|
||||
void CdmFileImpl::DidRead(std::optional<std::vector<uint8_t>> data) {
|
||||
@ -113,7 +135,7 @@ void CdmFileImpl::DidRead(std::optional<std::vector<uint8_t>> data) {
|
||||
<< ", success: " << (data.has_value() ? "yes" : "no");
|
||||
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||||
DCHECK(read_callback_);
|
||||
DCHECK(cdm_storage_manager_);
|
||||
DCHECK(host_ || cdm_storage_manager_);
|
||||
|
||||
bool success = data.has_value();
|
||||
ReportFileOperationUMA(success, kReadFile);
|
||||
@ -131,7 +153,7 @@ void CdmFileImpl::Write(const std::vector<uint8_t>& data,
|
||||
WriteCallback callback) {
|
||||
DVLOG(3) << __func__ << " file: " << file_name_ << ", size: " << data.size();
|
||||
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||||
DCHECK(cdm_storage_manager_);
|
||||
DCHECK(host_ || cdm_storage_manager_);
|
||||
|
||||
// Only 1 Read() or Write() is allowed at any time.
|
||||
if (read_callback_ || write_callback_) {
|
||||
@ -159,22 +181,29 @@ void CdmFileImpl::Write(const std::vector<uint8_t>& data,
|
||||
return;
|
||||
}
|
||||
|
||||
cdm_storage_manager_->WriteFile(
|
||||
storage_key_, cdm_type_, file_name_, data,
|
||||
base::BindOnce(&CdmFileImpl::DidWrite, weak_factory_.GetWeakPtr()));
|
||||
if (host_) {
|
||||
host_->WriteFile(
|
||||
cdm_type_, file_name_, data,
|
||||
base::BindOnce(&CdmFileImpl::DidWrite, weak_factory_.GetWeakPtr()));
|
||||
} else {
|
||||
cdm_storage_manager_->WriteFile(
|
||||
storage_key_, cdm_type_, file_name_, data,
|
||||
base::BindOnce(&CdmFileImpl::DidWrite, weak_factory_.GetWeakPtr()));
|
||||
}
|
||||
}
|
||||
|
||||
void CdmFileImpl::ReportFileOperationUMA(bool success,
|
||||
const std::string& operation) {
|
||||
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||||
DCHECK(cdm_storage_manager_);
|
||||
DCHECK(host_ || cdm_storage_manager_);
|
||||
|
||||
// Strings for UMA names.
|
||||
static const char kUmaPrefix[] = "Media.EME.CdmFileIO";
|
||||
static const char kTimeTo[] = "TimeTo";
|
||||
|
||||
const std::string mode_suffix =
|
||||
cdm_storage_manager_->in_memory() ? "Incognito" : "Normal";
|
||||
const bool in_memory =
|
||||
(host_) ? host_->in_memory() : cdm_storage_manager_->in_memory();
|
||||
const std::string mode_suffix = in_memory ? "Incognito" : "Normal";
|
||||
|
||||
// Records the result to the base histogram as well as splitting it out by
|
||||
// incognito or normal mode.
|
||||
@ -199,7 +228,7 @@ void CdmFileImpl::DidWrite(bool success) {
|
||||
DVLOG(3) << __func__ << " file: " << file_name_;
|
||||
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||||
DCHECK(write_callback_);
|
||||
DCHECK(cdm_storage_manager_);
|
||||
DCHECK(host_ || cdm_storage_manager_);
|
||||
|
||||
ReportFileOperationUMA(success, kWriteFile);
|
||||
|
||||
@ -216,20 +245,27 @@ void CdmFileImpl::DeleteFile() {
|
||||
DVLOG(3) << __func__;
|
||||
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||||
DCHECK(write_callback_);
|
||||
DCHECK(cdm_storage_manager_);
|
||||
DCHECK(host_ || cdm_storage_manager_);
|
||||
|
||||
DVLOG(3) << "Deleting " << file_name_;
|
||||
|
||||
cdm_storage_manager_->DeleteFile(
|
||||
storage_key_, cdm_type_, file_name_,
|
||||
base::BindOnce(&CdmFileImpl::DidDeleteFile, weak_factory_.GetWeakPtr()));
|
||||
if (host_) {
|
||||
host_->DeleteFile(cdm_type_, file_name_,
|
||||
base::BindOnce(&CdmFileImpl::DidDeleteFile,
|
||||
weak_factory_.GetWeakPtr()));
|
||||
} else {
|
||||
cdm_storage_manager_->DeleteFile(
|
||||
storage_key_, cdm_type_, file_name_,
|
||||
base::BindOnce(&CdmFileImpl::DidDeleteFile,
|
||||
weak_factory_.GetWeakPtr()));
|
||||
}
|
||||
}
|
||||
|
||||
void CdmFileImpl::DidDeleteFile(bool success) {
|
||||
DVLOG(3) << __func__ << " file: " << file_name_;
|
||||
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||||
DCHECK(write_callback_);
|
||||
DCHECK(cdm_storage_manager_);
|
||||
DCHECK(host_ || cdm_storage_manager_);
|
||||
|
||||
ReportFileOperationUMA(success, kDeleteFile);
|
||||
|
||||
@ -244,11 +280,16 @@ void CdmFileImpl::DidDeleteFile(bool success) {
|
||||
|
||||
void CdmFileImpl::OnReceiverDisconnect() {
|
||||
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
|
||||
DCHECK(cdm_storage_manager_);
|
||||
DCHECK(host_ || cdm_storage_manager_);
|
||||
|
||||
// May delete `this`.
|
||||
cdm_storage_manager_->OnFileReceiverDisconnect(
|
||||
file_name_, cdm_type_, storage_key_, base::PassKey<CdmFileImpl>());
|
||||
if (host_) {
|
||||
host_->OnFileReceiverDisconnect(file_name_, cdm_type_,
|
||||
base::PassKey<CdmFileImpl>());
|
||||
} else {
|
||||
cdm_storage_manager_->OnFileReceiverDisconnect(
|
||||
file_name_, cdm_type_, storage_key_, base::PassKey<CdmFileImpl>());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace content
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "third_party/blink/public/common/storage_key/storage_key.h"
|
||||
|
||||
namespace content {
|
||||
class MediaLicenseStorageHost;
|
||||
class CdmStorageManager;
|
||||
|
||||
// This class implements the media::mojom::CdmFile interface.
|
||||
@ -31,8 +32,14 @@ class CdmFileImpl final : public media::mojom::CdmFile {
|
||||
|
||||
// This "file" is actually just an entry in a custom backend for CDM, uniquely
|
||||
// identified by a storage key, CDM type, and file name. File operations are
|
||||
// routed through `manager` which is owned by the storage partition.
|
||||
// This constructor is used by the CdmStorageManager.
|
||||
// routed through `host` which is owned by the storage partition.
|
||||
CdmFileImpl(
|
||||
MediaLicenseStorageHost* host,
|
||||
const media::CdmType& cdm_type,
|
||||
const std::string& file_name,
|
||||
mojo::PendingAssociatedReceiver<media::mojom::CdmFile> pending_receiver);
|
||||
|
||||
// As Above. This constructor is used by CdmStorageManager.
|
||||
CdmFileImpl(
|
||||
CdmStorageManager* manager,
|
||||
const blink::StorageKey& storage_key,
|
||||
@ -62,7 +69,8 @@ class CdmFileImpl final : public media::mojom::CdmFile {
|
||||
|
||||
void OnReceiverDisconnect();
|
||||
|
||||
// This receiver is associated with the CdmStorageManager which creates it.
|
||||
// This receiver is associated with the MediaLicenseStorageHost which creates
|
||||
// it.
|
||||
mojo::AssociatedReceiver<media::mojom::CdmFile> receiver_{this};
|
||||
|
||||
const std::string file_name_;
|
||||
@ -78,6 +86,10 @@ class CdmFileImpl final : public media::mojom::CdmFile {
|
||||
base::TimeTicks start_time_;
|
||||
|
||||
// Backing store which CDM file operations are routed through.
|
||||
// Owned by MediaLicenseManager.
|
||||
const raw_ptr<MediaLicenseStorageHost> host_ = nullptr;
|
||||
|
||||
// New backing store which CDM file operations are routed through.
|
||||
// CdmStorageManager owns the lifetime of this object and will outlive it.
|
||||
const raw_ptr<CdmStorageManager> cdm_storage_manager_ = nullptr;
|
||||
|
||||
|
@ -51,6 +51,7 @@
|
||||
#include "base/feature_list.h"
|
||||
#include "base/metrics/histogram_macros.h"
|
||||
#include "content/browser/media/cdm_storage_manager.h"
|
||||
#include "content/browser/media/media_license_manager.h"
|
||||
#include "media/base/key_system_names.h"
|
||||
#include "media/mojo/mojom/cdm_service.mojom.h"
|
||||
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
|
||||
|
297
content/browser/media/media_license_database.cc
Normal file
297
content/browser/media/media_license_database.cc
Normal file
@ -0,0 +1,297 @@
|
||||
// Copyright 2022 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "content/browser/media/media_license_database.h"
|
||||
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
#include "sql/database.h"
|
||||
#include "sql/meta_table.h"
|
||||
#include "sql/sqlite_result_code_values.h"
|
||||
#include "sql/statement.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
using MediaLicenseStorageHostOpenError =
|
||||
MediaLicenseStorageHost::MediaLicenseStorageHostOpenError;
|
||||
|
||||
namespace {
|
||||
|
||||
static const int kVersionNumber = 1;
|
||||
|
||||
const char kUmaPrefix[] = "Media.EME.MediaLicenseDatabaseSQLiteError";
|
||||
const char kUmaPrefixWithPeriod[] =
|
||||
"Media.EME.MediaLicenseDatabaseSQLiteError.";
|
||||
|
||||
} // namespace
|
||||
|
||||
MediaLicenseDatabase::MediaLicenseDatabase(const base::FilePath& path)
|
||||
: path_(path),
|
||||
// Use a smaller cache, since access will be fairly infrequent and random.
|
||||
// Given the expected record sizes (~100s of bytes) and key sizes (<100
|
||||
// bytes) and that we'll typically only be pulling one file at a time
|
||||
// (playback), specify a large page size to allow inner nodes can pack
|
||||
// many keys, to keep the index B-tree flat.
|
||||
db_(sql::DatabaseOptions{.page_size = 32768, .cache_size = 8}) {}
|
||||
|
||||
MediaLicenseDatabase::~MediaLicenseDatabase() = default;
|
||||
|
||||
MediaLicenseStorageHostOpenError MediaLicenseDatabase::OpenFile(
|
||||
const media::CdmType& cdm_type,
|
||||
const std::string& file_name) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
// The media license code doesn't distinguish between an empty file and a
|
||||
// file which does not exist, so don't bother inserting an empty row into
|
||||
// the database.
|
||||
return OpenDatabase();
|
||||
}
|
||||
|
||||
std::optional<std::vector<uint8_t>> MediaLicenseDatabase::ReadFile(
|
||||
const media::CdmType& cdm_type,
|
||||
const std::string& file_name) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (OpenDatabase() != MediaLicenseStorageHostOpenError::kOk) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static constexpr char kSelectSql[] =
|
||||
"SELECT data FROM licenses WHERE cdm_type=? AND file_name=?";
|
||||
DCHECK(db_.IsSQLValid(kSelectSql));
|
||||
|
||||
last_operation_ = "ReadFile";
|
||||
|
||||
sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSelectSql));
|
||||
statement.BindString(0, cdm_type.ToString());
|
||||
statement.BindString(1, file_name);
|
||||
|
||||
if (!statement.Step()) {
|
||||
// Failing here is expected if the "file" has not yet been written to and
|
||||
// the row does not yet exist. The media license code doesn't distinguish
|
||||
// between an empty file and a file which does not exist, so just return
|
||||
// an empty file without erroring.
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
if (!statement.ColumnBlobAsVector(0, &data)) {
|
||||
DVLOG(1) << "Error reading media license data.";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
last_operation_.reset();
|
||||
return data;
|
||||
}
|
||||
|
||||
bool MediaLicenseDatabase::WriteFile(const media::CdmType& cdm_type,
|
||||
const std::string& file_name,
|
||||
const std::vector<uint8_t>& data) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (OpenDatabase() != MediaLicenseStorageHostOpenError::kOk) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr char kInsertSql[] =
|
||||
// clang-format off
|
||||
"INSERT OR REPLACE INTO licenses(cdm_type,file_name,data) "
|
||||
"VALUES(?,?,?)";
|
||||
// clang-format on
|
||||
DCHECK(db_.IsSQLValid(kInsertSql));
|
||||
|
||||
last_operation_ = "WriteFile";
|
||||
last_write_file_size_ = data.size();
|
||||
|
||||
sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kInsertSql));
|
||||
statement.BindString(0, cdm_type.ToString());
|
||||
statement.BindString(1, file_name);
|
||||
statement.BindBlob(2, data);
|
||||
bool success = statement.Run();
|
||||
|
||||
if (!success)
|
||||
DVLOG(1) << "Error writing media license data.";
|
||||
|
||||
last_operation_.reset();
|
||||
return success;
|
||||
}
|
||||
|
||||
bool MediaLicenseDatabase::DeleteFile(const media::CdmType& cdm_type,
|
||||
const std::string& file_name) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (OpenDatabase() != MediaLicenseStorageHostOpenError::kOk) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr char kDeleteSql[] =
|
||||
"DELETE FROM licenses WHERE cdm_type=? AND file_name=?";
|
||||
DCHECK(db_.IsSQLValid(kDeleteSql));
|
||||
|
||||
last_operation_ = "DeleteFile";
|
||||
|
||||
sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kDeleteSql));
|
||||
statement.BindString(0, cdm_type.ToString());
|
||||
statement.BindString(1, file_name);
|
||||
bool success = statement.Run();
|
||||
|
||||
if (!success)
|
||||
DVLOG(1) << "Error writing media license data.";
|
||||
|
||||
last_operation_.reset();
|
||||
return success;
|
||||
}
|
||||
|
||||
bool MediaLicenseDatabase::ClearDatabase() {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
db_.Close();
|
||||
|
||||
if (path_.empty()) {
|
||||
// Memory associated with an in-memory database will be released when the
|
||||
// database is closed above.
|
||||
return true;
|
||||
}
|
||||
|
||||
return sql::Database::Delete(path_);
|
||||
}
|
||||
|
||||
uint64_t MediaLicenseDatabase::GetDatabaseSize() {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
static constexpr char kPageCountSql[] = "PRAGMA page_count";
|
||||
DCHECK(db_.IsSQLValid(kPageCountSql));
|
||||
|
||||
last_operation_ = "QueryPageCount";
|
||||
|
||||
sql::Statement statement_count(
|
||||
db_.GetCachedStatement(SQL_FROM_HERE, kPageCountSql));
|
||||
statement_count.Step();
|
||||
|
||||
uint64_t page_count = statement_count.ColumnInt(0);
|
||||
|
||||
static constexpr char kPageSizeSql[] = "PRAGMA page_size";
|
||||
DCHECK(db_.IsSQLValid(kPageSizeSql));
|
||||
|
||||
last_operation_ = "QueryPageSize";
|
||||
|
||||
sql::Statement statement_size(
|
||||
db_.GetCachedStatement(SQL_FROM_HERE, kPageSizeSql));
|
||||
statement_size.Step();
|
||||
|
||||
uint64_t page_size = statement_size.ColumnInt(0);
|
||||
|
||||
last_operation_.reset();
|
||||
|
||||
return page_count * page_size;
|
||||
}
|
||||
|
||||
// Opens and sets up a database if one is not already set up.
|
||||
MediaLicenseStorageHostOpenError MediaLicenseDatabase::OpenDatabase(
|
||||
bool is_retry) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (db_.is_open())
|
||||
return MediaLicenseStorageHostOpenError::kOk;
|
||||
|
||||
bool success = false;
|
||||
|
||||
// If this is not the first call to `OpenDatabase()` because we are re-trying
|
||||
// initialization, then the error callback will have previously been set.
|
||||
db_.reset_error_callback();
|
||||
|
||||
// base::Unretained is safe becase |db_| is owned by |this|
|
||||
db_.set_error_callback(base::BindRepeating(
|
||||
&MediaLicenseDatabase::OnDatabaseError, base::Unretained(this)));
|
||||
|
||||
if (path_.empty()) {
|
||||
success = db_.OpenInMemory();
|
||||
} else {
|
||||
// Ensure `path`'s parent directory exists.
|
||||
auto error = base::File::Error::FILE_OK;
|
||||
if (!base::CreateDirectoryAndGetError(path_.DirName(), &error)) {
|
||||
DVLOG(1) << "Failed to open CDM database: "
|
||||
<< base::File::ErrorToString(error);
|
||||
base::UmaHistogramExactLinear(
|
||||
"Media.EME.MediaLicenseDatabaseCreateDirectoryError", -error,
|
||||
-base::File::FILE_ERROR_MAX);
|
||||
return MediaLicenseStorageHostOpenError::kBucketNotFound;
|
||||
}
|
||||
DCHECK_EQ(error, base::File::Error::FILE_OK);
|
||||
|
||||
success = db_.Open(path_);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
DVLOG(1) << "Failed to open CDM database: " << db_.GetErrorMessage();
|
||||
return MediaLicenseStorageHostOpenError::kDatabaseOpenError;
|
||||
}
|
||||
|
||||
sql::MetaTable meta_table;
|
||||
if (!meta_table.Init(&db_, kVersionNumber, kVersionNumber)) {
|
||||
DVLOG(1) << "Could not initialize Media License database metadata table.";
|
||||
// Wipe the database and start over. If we've already wiped the database and
|
||||
// are still failing, just return false.
|
||||
db_.Raze();
|
||||
return is_retry ? MediaLicenseStorageHostOpenError::kDatabaseRazeError
|
||||
: OpenDatabase(/*is_retry=*/true);
|
||||
}
|
||||
|
||||
if (meta_table.GetCompatibleVersionNumber() > kVersionNumber) {
|
||||
// This should only happen if the user downgrades the Chrome channel (for
|
||||
// example, from Beta to Stable). If that results in an incompatible schema,
|
||||
// we need to wipe the database and start over.
|
||||
DVLOG(1) << "Media License database is too new, kVersionNumber"
|
||||
<< kVersionNumber << ", GetCompatibleVersionNumber="
|
||||
<< meta_table.GetCompatibleVersionNumber();
|
||||
db_.Raze();
|
||||
return is_retry ? MediaLicenseStorageHostOpenError::kDatabaseRazeError
|
||||
: OpenDatabase(/*is_retry=*/true);
|
||||
}
|
||||
|
||||
// Set up the table.
|
||||
static constexpr char kCreateTableSql[] =
|
||||
// clang-format off
|
||||
"CREATE TABLE IF NOT EXISTS licenses("
|
||||
"cdm_type TEXT NOT NULL,"
|
||||
"file_name TEXT NOT NULL,"
|
||||
"data BLOB NOT NULL,"
|
||||
"PRIMARY KEY(cdm_type,file_name))";
|
||||
// clang-format on
|
||||
DCHECK(db_.IsSQLValid(kCreateTableSql));
|
||||
|
||||
if (!db_.Execute(kCreateTableSql)) {
|
||||
DVLOG(1) << "Failed to execute " << kCreateTableSql;
|
||||
return MediaLicenseStorageHostOpenError::kSQLExecutionError;
|
||||
}
|
||||
|
||||
return MediaLicenseStorageHostOpenError::kOk;
|
||||
}
|
||||
|
||||
void MediaLicenseDatabase::OnDatabaseError(int error, sql::Statement* stmt) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
sql::UmaHistogramSqliteResult(kUmaPrefix, error);
|
||||
|
||||
if (last_operation_) {
|
||||
sql::UmaHistogramSqliteResult(kUmaPrefixWithPeriod + *last_operation_,
|
||||
error);
|
||||
|
||||
// Log the size of the data in bytes if the error was a full disk error to
|
||||
// track size of data being rejected by the MediaLicenseDatabase.
|
||||
if (last_operation_ == "WriteFile" &&
|
||||
sql::ToSqliteResultCode(error) == sql::SqliteResultCode::kFullDisk &&
|
||||
last_write_file_size_) {
|
||||
base::UmaHistogramCustomCounts(
|
||||
"Media.EME.MediaLicenseDatabase.WriteFile.FullDiskDataSizeBytes",
|
||||
last_write_file_size_.value(), /*min=*/1,
|
||||
/*exclusive_max=*/60 * 1024, /*buckets=*/100);
|
||||
}
|
||||
|
||||
last_operation_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace content
|
63
content/browser/media/media_license_database.h
Normal file
63
content/browser/media/media_license_database.h
Normal file
@ -0,0 +1,63 @@
|
||||
// Copyright 2022 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_BROWSER_MEDIA_MEDIA_LICENSE_DATABASE_H_
|
||||
#define CONTENT_BROWSER_MEDIA_MEDIA_LICENSE_DATABASE_H_
|
||||
|
||||
#include "base/sequence_checker.h"
|
||||
#include "content/browser/media/media_license_storage_host.h"
|
||||
#include "media/cdm/cdm_type.h"
|
||||
#include "sql/database.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
// Helper class which encapsulates all database logic for storing media license
|
||||
// data.
|
||||
//
|
||||
// This class must be constructed and used on a sequence which allows blocking.
|
||||
class MediaLicenseDatabase {
|
||||
public:
|
||||
// The database will be in-memory if `path` is empty.
|
||||
explicit MediaLicenseDatabase(const base::FilePath& path);
|
||||
~MediaLicenseDatabase();
|
||||
|
||||
MediaLicenseStorageHost::MediaLicenseStorageHostOpenError OpenFile(
|
||||
const media::CdmType& cdm_type,
|
||||
const std::string& file_name);
|
||||
std::optional<std::vector<uint8_t>> ReadFile(const media::CdmType& cdm_type,
|
||||
const std::string& file_name);
|
||||
bool WriteFile(const media::CdmType& cdm_type,
|
||||
const std::string& file_name,
|
||||
const std::vector<uint8_t>& data);
|
||||
bool DeleteFile(const media::CdmType& cdm_type, const std::string& file_name);
|
||||
|
||||
bool ClearDatabase();
|
||||
|
||||
uint64_t GetDatabaseSize();
|
||||
|
||||
private:
|
||||
// Opens and sets up a database if one is not already set up.
|
||||
MediaLicenseStorageHost::MediaLicenseStorageHostOpenError OpenDatabase(
|
||||
bool is_retry = false);
|
||||
|
||||
void OnDatabaseError(int error, sql::Statement* stmt);
|
||||
|
||||
SEQUENCE_CHECKER(sequence_checker_);
|
||||
|
||||
// Empty if the database is in-memory.
|
||||
const base::FilePath path_;
|
||||
|
||||
// A descriptor of the last SQL statement that was executed, used for metrics.
|
||||
std::optional<std::string> last_operation_;
|
||||
|
||||
// Integer of last file size that the CDM sent to be written, used for
|
||||
// metrics.
|
||||
std::optional<int> last_write_file_size_;
|
||||
|
||||
sql::Database db_ GUARDED_BY_CONTEXT(sequence_checker_);
|
||||
};
|
||||
|
||||
} // namespace content
|
||||
|
||||
#endif // CONTENT_BROWSER_MEDIA_MEDIA_LICENSE_DATABASE_H_
|
231
content/browser/media/media_license_manager.cc
Normal file
231
content/browser/media/media_license_manager.cc
Normal file
@ -0,0 +1,231 @@
|
||||
// Copyright 2022 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "content/browser/media/media_license_manager.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/containers/flat_map.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/functional/callback_forward.h"
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
#include "base/not_fatal_until.h"
|
||||
#include "base/sequence_checker.h"
|
||||
#include "base/task/sequenced_task_runner.h"
|
||||
#include "base/task/task_traits.h"
|
||||
#include "base/task/thread_pool.h"
|
||||
#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
|
||||
#include "components/services/storage/public/cpp/buckets/constants.h"
|
||||
#include "components/services/storage/public/cpp/constants.h"
|
||||
#include "content/browser/media/media_license_database.h"
|
||||
#include "content/browser/media/media_license_storage_host.h"
|
||||
#include "media/cdm/cdm_type.h"
|
||||
#include "sql/database.h"
|
||||
#include "sql/sqlite_result_code.h"
|
||||
#include "third_party/blink/public/common/storage_key/storage_key.h"
|
||||
#include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h"
|
||||
#include "url/origin.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
using MediaLicenseStorageHostOpenError =
|
||||
MediaLicenseStorageHost::MediaLicenseStorageHostOpenError;
|
||||
|
||||
namespace {
|
||||
|
||||
// Creates a task runner suitable for running SQLite database operations.
|
||||
scoped_refptr<base::SequencedTaskRunner> CreateDatabaseTaskRunner() {
|
||||
// We use a SequencedTaskRunner so that there is a global ordering to a
|
||||
// storage key's directory operations.
|
||||
return base::ThreadPool::CreateSequencedTaskRunner({
|
||||
// Needed for file I/O.
|
||||
base::MayBlock(),
|
||||
|
||||
// Reasonable compromise, given that a few database operations are
|
||||
// blocking, while most operations are not. We should be able to do better
|
||||
// when we get scheduling APIs on the Web Platform.
|
||||
base::TaskPriority::USER_VISIBLE,
|
||||
|
||||
// Needed to allow for clearing site data on shutdown.
|
||||
base::TaskShutdownBehavior::BLOCK_SHUTDOWN,
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MediaLicenseManager::MediaLicenseManager(
|
||||
bool in_memory,
|
||||
scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
|
||||
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy)
|
||||
: db_runner_(CreateDatabaseTaskRunner()),
|
||||
in_memory_(in_memory),
|
||||
special_storage_policy_(std::move(special_storage_policy)),
|
||||
quota_manager_proxy_(std::move(quota_manager_proxy)),
|
||||
// Using a raw pointer is safe since `quota_client_` is owned by
|
||||
// this instance.
|
||||
quota_client_(this),
|
||||
quota_client_receiver_("a_client_) {
|
||||
if (quota_manager_proxy_) {
|
||||
// Quota client assumes all backends have registered.
|
||||
quota_manager_proxy_->RegisterClient(
|
||||
quota_client_receiver_.BindNewPipeAndPassRemote(),
|
||||
storage::QuotaClientType::kMediaLicense,
|
||||
{blink::mojom::StorageType::kTemporary});
|
||||
}
|
||||
}
|
||||
|
||||
MediaLicenseManager::~MediaLicenseManager() = default;
|
||||
|
||||
void MediaLicenseManager::OpenCdmStorage(
|
||||
const CdmStorageBindingContext& binding_context,
|
||||
mojo::PendingReceiver<media::mojom::CdmStorage> receiver) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
const auto& storage_key = binding_context.storage_key;
|
||||
auto it_hosts = hosts_.find(storage_key);
|
||||
if (it_hosts != hosts_.end()) {
|
||||
// A storage host for this storage key already exists.
|
||||
it_hosts->second->BindReceiver(binding_context, std::move(receiver));
|
||||
return;
|
||||
}
|
||||
|
||||
auto& receiver_list = pending_receivers_[storage_key];
|
||||
receiver_list.emplace_back(binding_context, std::move(receiver));
|
||||
if (receiver_list.size() > 1) {
|
||||
// If a pending receiver for this storage key already existed, there is
|
||||
// an in-flight `UpdateOrCreateBucket()` call for this storage key.
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the default bucket for `storage_key`.
|
||||
quota_manager_proxy()->UpdateOrCreateBucket(
|
||||
storage::BucketInitParams::ForDefaultBucket(storage_key),
|
||||
base::SequencedTaskRunner::GetCurrentDefault(),
|
||||
base::BindOnce(&MediaLicenseManager::DidGetBucket,
|
||||
weak_factory_.GetWeakPtr(), storage_key));
|
||||
}
|
||||
|
||||
void MediaLicenseManager::DidGetBucket(
|
||||
const blink::StorageKey& storage_key,
|
||||
storage::QuotaErrorOr<storage::BucketInfo> result) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
auto it = pending_receivers_.find(storage_key);
|
||||
CHECK(it != pending_receivers_.end(), base::NotFatalUntil::M130);
|
||||
|
||||
auto receivers_list = std::move(it->second);
|
||||
pending_receivers_.erase(it);
|
||||
DCHECK_GT(receivers_list.size(), 0u);
|
||||
|
||||
storage::BucketLocator bucket_locator;
|
||||
if (result.has_value()) {
|
||||
bucket_locator = result->ToBucketLocator();
|
||||
} else {
|
||||
// Use the null locator, but update the `storage_key` field so
|
||||
// `storage_host` can be identified when it is to be removed from `hosts_`.
|
||||
// We could consider falling back to using an in-memory database in this
|
||||
// case, but failing here seems easier to reason about from a website
|
||||
// author's point of view.
|
||||
sql::UmaHistogramSqliteResult(
|
||||
"Media.EME.MediaLicenseDatabaseOpenSQLiteError",
|
||||
result.error().sqlite_error);
|
||||
base::UmaHistogramEnumeration(
|
||||
"Media.EME.MediaLicenseDatabaseOpenQuotaError",
|
||||
result.error().quota_error);
|
||||
MediaLicenseStorageHost::ReportDatabaseOpenError(
|
||||
MediaLicenseStorageHostOpenError::kBucketLocatorError, in_memory());
|
||||
DCHECK(bucket_locator.id.is_null());
|
||||
bucket_locator.storage_key = storage_key;
|
||||
}
|
||||
|
||||
// All receivers associated with `storage_key` will be bound to the same host.
|
||||
auto storage_host =
|
||||
std::make_unique<MediaLicenseStorageHost>(this, bucket_locator);
|
||||
|
||||
for (auto& context_and_receiver : receivers_list) {
|
||||
storage_host->BindReceiver(context_and_receiver.first,
|
||||
std::move(context_and_receiver.second));
|
||||
}
|
||||
|
||||
hosts_.emplace(storage_key, std::move(storage_host));
|
||||
}
|
||||
|
||||
void MediaLicenseManager::DeleteBucketData(
|
||||
const storage::BucketLocator& bucket,
|
||||
storage::mojom::QuotaClient::DeleteBucketDataCallback callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
auto it_hosts = hosts_.find(bucket.storage_key);
|
||||
if (it_hosts != hosts_.end()) {
|
||||
// Let the host gracefully handle data deletion.
|
||||
it_hosts->second->DeleteBucketData(
|
||||
base::BindOnce(&MediaLicenseManager::DidDeleteBucketData,
|
||||
weak_factory_.GetWeakPtr(), std::move(callback)));
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have an in-memory profile, any data for the storage key would have
|
||||
// lived in the associated MediaLicenseStorageHost.
|
||||
if (in_memory()) {
|
||||
std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise delete database file.
|
||||
auto path = GetDatabasePath(bucket);
|
||||
db_runner()->PostTaskAndReplyWithResult(
|
||||
FROM_HERE, base::BindOnce(&sql::Database::Delete, path),
|
||||
base::BindOnce(&MediaLicenseManager::DidDeleteBucketData,
|
||||
weak_factory_.GetWeakPtr(), std::move(callback)));
|
||||
}
|
||||
|
||||
void MediaLicenseManager::DidDeleteBucketData(
|
||||
storage::mojom::QuotaClient::DeleteBucketDataCallback callback,
|
||||
bool success) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
std::move(callback).Run(success ? blink::mojom::QuotaStatusCode::kOk
|
||||
: blink::mojom::QuotaStatusCode::kUnknown);
|
||||
}
|
||||
|
||||
base::FilePath MediaLicenseManager::GetDatabasePath(
|
||||
const storage::BucketLocator& bucket_locator) {
|
||||
if (in_memory())
|
||||
return base::FilePath();
|
||||
|
||||
auto media_license_dir = quota_manager_proxy()->GetClientBucketPath(
|
||||
bucket_locator, storage::QuotaClientType::kMediaLicense);
|
||||
return media_license_dir.Append(storage::kMediaLicenseDatabaseFileName);
|
||||
}
|
||||
|
||||
void MediaLicenseManager::OnHostReceiverDisconnect(
|
||||
MediaLicenseStorageHost* host,
|
||||
base::PassKey<MediaLicenseStorageHost> pass_key) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
DCHECK(host);
|
||||
|
||||
if (in_memory()) {
|
||||
// Don't delete `host` for an in-memory profile, since the data is not safe
|
||||
// to delete yet. For example, a site may be re-visited within the same
|
||||
// incognito session. `host` will be destroyed when `this` is destroyed.
|
||||
return;
|
||||
}
|
||||
|
||||
DCHECK_GT(hosts_.count(host->storage_key()), 0ul);
|
||||
DCHECK_EQ(hosts_[host->storage_key()].get(), host);
|
||||
|
||||
if (!host->has_empty_receiver_set())
|
||||
return;
|
||||
|
||||
size_t count_removed = hosts_.erase(host->storage_key());
|
||||
DCHECK_EQ(count_removed, 1u);
|
||||
}
|
||||
|
||||
} // namespace content
|
137
content/browser/media/media_license_manager.h
Normal file
137
content/browser/media/media_license_manager.h
Normal file
@ -0,0 +1,137 @@
|
||||
// Copyright 2022 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_BROWSER_MEDIA_MEDIA_LICENSE_MANAGER_H_
|
||||
#define CONTENT_BROWSER_MEDIA_MEDIA_LICENSE_MANAGER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "base/containers/flat_map.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/functional/callback_forward.h"
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "base/task/sequenced_task_runner.h"
|
||||
#include "base/types/pass_key.h"
|
||||
#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
|
||||
#include "content/browser/media/cdm_storage_common.h"
|
||||
#include "content/browser/media/cdm_storage_manager.h"
|
||||
#include "content/browser/media/media_license_quota_client.h"
|
||||
#include "content/common/content_export.h"
|
||||
#include "media/cdm/cdm_type.h"
|
||||
#include "media/mojo/mojom/cdm_storage.mojom.h"
|
||||
#include "storage/browser/quota/quota_manager_proxy.h"
|
||||
#include "third_party/blink/public/common/storage_key/storage_key.h"
|
||||
|
||||
namespace content {
|
||||
class MediaLicenseStorageHost;
|
||||
|
||||
// Each StoragePartition owns exactly one instance of this class. This class
|
||||
// creates and destroys MediaLicenseStorageHost instances to meet the
|
||||
// demands for CDM from different storage keys.
|
||||
//
|
||||
// This class is not thread-safe, and all access to an instance must happen on
|
||||
// the same sequence.
|
||||
class CONTENT_EXPORT MediaLicenseManager {
|
||||
public:
|
||||
MediaLicenseManager(
|
||||
bool in_memory,
|
||||
scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy,
|
||||
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy);
|
||||
MediaLicenseManager(const MediaLicenseManager&) = delete;
|
||||
MediaLicenseManager& operator=(const MediaLicenseManager&) = delete;
|
||||
~MediaLicenseManager();
|
||||
|
||||
void OpenCdmStorage(const CdmStorageBindingContext& binding_context,
|
||||
mojo::PendingReceiver<media::mojom::CdmStorage> receiver);
|
||||
|
||||
// Called by the MediaLicenseQuotaClient.
|
||||
void DeleteBucketData(
|
||||
const storage::BucketLocator& bucket,
|
||||
storage::mojom::QuotaClient::DeleteBucketDataCallback callback);
|
||||
|
||||
// Returns an empty path if the database is in-memory.
|
||||
base::FilePath GetDatabasePath(const storage::BucketLocator& bucket_locator);
|
||||
|
||||
// Called when a receiver is disconnected from a MediaLicenseStorageHost.
|
||||
//
|
||||
// `host` must be owned by this manager. `host` may be deleted.
|
||||
void OnHostReceiverDisconnect(
|
||||
MediaLicenseStorageHost* host,
|
||||
base::PassKey<MediaLicenseStorageHost> pass_key);
|
||||
|
||||
const scoped_refptr<storage::QuotaManagerProxy>& quota_manager_proxy() const {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
return quota_manager_proxy_;
|
||||
}
|
||||
|
||||
const scoped_refptr<base::SequencedTaskRunner>& db_runner() const {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
return db_runner_;
|
||||
}
|
||||
|
||||
bool in_memory() const {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
return in_memory_;
|
||||
}
|
||||
|
||||
void set_cdm_storage_manager(CdmStorageManager* cdm_storage_manager) {
|
||||
cdm_storage_manager_ = cdm_storage_manager;
|
||||
}
|
||||
|
||||
CdmStorageManager* cdm_storage_manager() { return cdm_storage_manager_; }
|
||||
|
||||
private:
|
||||
void DidGetBucket(const blink::StorageKey& storage_key,
|
||||
storage::QuotaErrorOr<storage::BucketInfo> result);
|
||||
|
||||
void DidDeleteBucketData(
|
||||
storage::mojom::QuotaClient::DeleteBucketDataCallback callback,
|
||||
bool success);
|
||||
|
||||
SEQUENCE_CHECKER(sequence_checker_);
|
||||
|
||||
// Task runner which all database operations are routed through.
|
||||
const scoped_refptr<base::SequencedTaskRunner> db_runner_;
|
||||
|
||||
const bool in_memory_;
|
||||
|
||||
// Tracks special rights for apps and extensions, may be null.
|
||||
const scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy_;
|
||||
|
||||
const scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_;
|
||||
|
||||
base::flat_map<blink::StorageKey, std::unique_ptr<MediaLicenseStorageHost>>
|
||||
hosts_ GUARDED_BY_CONTEXT(sequence_checker_);
|
||||
|
||||
// Maps storage keys to a list of receivers which are awaiting bucket
|
||||
// information from the quota system before they can be bound.
|
||||
base::flat_map<
|
||||
blink::StorageKey,
|
||||
std::vector<std::pair<CdmStorageBindingContext,
|
||||
mojo::PendingReceiver<media::mojom::CdmStorage>>>>
|
||||
pending_receivers_;
|
||||
|
||||
MediaLicenseQuotaClient quota_client_ GUARDED_BY_CONTEXT(sequence_checker_);
|
||||
|
||||
// Once the QuotaClient receiver is destroyed, the underlying mojo connection
|
||||
// is closed. Callbacks associated with mojo calls received over this
|
||||
// connection may only be dropped after the connection is closed. For this
|
||||
// reason, it's preferable to have the receiver be destroyed as early as
|
||||
// possible during the MediaLicenseManager destruction process.
|
||||
mojo::Receiver<storage::mojom::QuotaClient> quota_client_receiver_
|
||||
GUARDED_BY_CONTEXT(sequence_checker_);
|
||||
|
||||
// Owned by 'StoragePartitionImpl' so we don't have to handle deletion.
|
||||
// This pointer is safe because `StoragePartitionImpl` declares the
|
||||
// `cdm_storage_manager` before the `media_license_manager`, and members are
|
||||
// destructed in reverse order of declaration.
|
||||
raw_ptr<CdmStorageManager> cdm_storage_manager_ = nullptr;
|
||||
|
||||
base::WeakPtrFactory<MediaLicenseManager> weak_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace content
|
||||
|
||||
#endif // CONTENT_BROWSER_MEDIA_MEDIA_LICENSE_MANAGER_H_
|
421
content/browser/media/media_license_manager_unittest.cc
Normal file
421
content/browser/media/media_license_manager_unittest.cc
Normal file
@ -0,0 +1,421 @@
|
||||
// Copyright 2022 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifdef UNSAFE_BUFFERS_BUILD
|
||||
// TODO(crbug.com/342213636): Remove this and spanify to fix the errors.
|
||||
#pragma allow_unsafe_buffers
|
||||
#endif
|
||||
|
||||
#include "content/browser/media/media_license_manager.h"
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/file_enumerator.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/files/scoped_temp_dir.h"
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "base/task/single_thread_task_runner.h"
|
||||
#include "base/test/gmock_expected_support.h"
|
||||
#include "base/test/task_environment.h"
|
||||
#include "base/test/test_future.h"
|
||||
#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
|
||||
#include "components/services/storage/public/cpp/constants.h"
|
||||
#include "content/browser/media/cdm_storage_common.h"
|
||||
#include "content/browser/media/media_license_quota_client.h"
|
||||
#include "content/public/browser/storage_partition.h"
|
||||
#include "media/cdm/cdm_type.h"
|
||||
#include "media/mojo/mojom/cdm_storage.mojom-forward.h"
|
||||
#include "mojo/public/cpp/bindings/associated_remote.h"
|
||||
#include "storage/browser/file_system/file_system_context.h"
|
||||
#include "storage/browser/test/mock_quota_manager.h"
|
||||
#include "storage/browser/test/mock_quota_manager_proxy.h"
|
||||
#include "storage/browser/test/test_file_system_context.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "third_party/blink/public/common/storage_key/storage_key.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
namespace {
|
||||
|
||||
const media::CdmType kCdmType{1234, 5678};
|
||||
|
||||
const char kExampleOrigin[] = "https://example.com";
|
||||
|
||||
} // namespace
|
||||
|
||||
class MediaLicenseManagerTest : public testing::Test {
|
||||
public:
|
||||
MediaLicenseManagerTest() : in_memory_(false) {}
|
||||
explicit MediaLicenseManagerTest(bool in_memory) : in_memory_(in_memory) {}
|
||||
|
||||
void SetUp() override {
|
||||
ASSERT_TRUE(profile_path_.CreateUniqueTempDir());
|
||||
quota_manager_ = base::MakeRefCounted<storage::MockQuotaManager>(
|
||||
in_memory_, in_memory_ ? base::FilePath() : profile_path_.GetPath(),
|
||||
base::SingleThreadTaskRunner::GetCurrentDefault().get(),
|
||||
/*special storage policy=*/nullptr);
|
||||
quota_manager_proxy_ = base::MakeRefCounted<storage::MockQuotaManagerProxy>(
|
||||
static_cast<storage::MockQuotaManager*>(quota_manager_.get()),
|
||||
base::SingleThreadTaskRunner::GetCurrentDefault());
|
||||
manager_ = std::make_unique<MediaLicenseManager>(
|
||||
in_memory_,
|
||||
/*special storage policy=*/nullptr, quota_manager_proxy_);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
// Let the client go away before dropping a ref of the quota manager proxy.
|
||||
quota_manager_ = nullptr;
|
||||
quota_manager_proxy_ = nullptr;
|
||||
|
||||
task_environment_.RunUntilIdle();
|
||||
EXPECT_TRUE(profile_path_.Delete());
|
||||
}
|
||||
|
||||
// Hard-coded to the default bucket, since this API should never be used in
|
||||
// non-default buckets anyways.
|
||||
storage::QuotaErrorOr<storage::BucketLocator> GetOrCreateBucket(
|
||||
const blink::StorageKey& storage_key) {
|
||||
base::test::TestFuture<storage::QuotaErrorOr<storage::BucketInfo>> future;
|
||||
quota_manager_->UpdateOrCreateBucket(
|
||||
storage::BucketInitParams::ForDefaultBucket(storage_key),
|
||||
future.GetCallback());
|
||||
return future.Take().transform(&storage::BucketInfo::ToBucketLocator);
|
||||
}
|
||||
|
||||
mojo::AssociatedRemote<media::mojom::CdmFile> OpenCdmFile(
|
||||
const mojo::Remote<media::mojom::CdmStorage>& storage,
|
||||
const std::string& file_name) {
|
||||
mojo::AssociatedRemote<media::mojom::CdmFile> cdm_file;
|
||||
|
||||
base::test::TestFuture<media::mojom::CdmStorage::Status,
|
||||
mojo::PendingAssociatedRemote<media::mojom::CdmFile>>
|
||||
open_future;
|
||||
storage->Open(file_name, open_future.GetCallback());
|
||||
|
||||
auto result = open_future.Take();
|
||||
EXPECT_EQ(std::get<0>(result), media::mojom::CdmStorage::Status::kSuccess);
|
||||
cdm_file.Bind(std::move(std::get<1>(result)));
|
||||
return cdm_file;
|
||||
}
|
||||
|
||||
void Write(const mojo::AssociatedRemote<media::mojom::CdmFile>& cdm_file,
|
||||
const std::string& data) {
|
||||
base::test::TestFuture<media::mojom::CdmFile::Status> write_future;
|
||||
cdm_file->Write(
|
||||
std::vector<uint8_t>(data.data(), data.data() + data.size()),
|
||||
write_future.GetCallback());
|
||||
EXPECT_EQ(write_future.Get(), media::mojom::CdmFile::Status::kSuccess);
|
||||
}
|
||||
|
||||
// Reads the previously opened `cdm_file` and check that its contents match
|
||||
// `expected_data`.
|
||||
void ExpectFileContents(
|
||||
const mojo::AssociatedRemote<media::mojom::CdmFile>& cdm_file,
|
||||
std::string_view expected_data) {
|
||||
base::test::TestFuture<media::mojom::CdmFile::Status, std::vector<uint8_t>>
|
||||
future;
|
||||
cdm_file->Read(future.GetCallback<media::mojom::CdmFile::Status,
|
||||
const std::vector<uint8_t>&>());
|
||||
|
||||
media::mojom::CdmFile::Status status = future.Get<0>();
|
||||
auto data = future.Get<1>();
|
||||
EXPECT_EQ(status, media::mojom::CdmFile::Status::kSuccess);
|
||||
EXPECT_EQ(std::string_view(reinterpret_cast<const char*>(data.data()),
|
||||
data.size()),
|
||||
expected_data);
|
||||
}
|
||||
|
||||
base::FilePath FindMediaLicenseDatabase() {
|
||||
base::FileEnumerator file_enumerator(profile_path_.GetPath(),
|
||||
/*recursive=*/true,
|
||||
base::FileEnumerator::FILES);
|
||||
|
||||
base::FilePath file;
|
||||
while (!(file = file_enumerator.Next()).empty()) {
|
||||
if (file.BaseName().value() == storage::kMediaLicenseDatabaseFileName) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
return base::FilePath();
|
||||
}
|
||||
|
||||
protected:
|
||||
const bool in_memory_;
|
||||
|
||||
scoped_refptr<storage::MockQuotaManager> quota_manager_;
|
||||
scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_;
|
||||
|
||||
// This must be above MediaLicenseManager, to ensure that no file is accessed
|
||||
// when the temporary directory is deleted.
|
||||
base::ScopedTempDir profile_path_;
|
||||
base::test::TaskEnvironment task_environment_;
|
||||
|
||||
std::unique_ptr<MediaLicenseManager> manager_;
|
||||
};
|
||||
|
||||
TEST_F(MediaLicenseManagerTest, DeleteBucketData) {
|
||||
const std::string kTestData("Test Data");
|
||||
mojo::Remote<media::mojom::CdmStorage> remote;
|
||||
blink::StorageKey storage_key =
|
||||
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
|
||||
ASSERT_OK_AND_ASSIGN(storage::BucketLocator bucket,
|
||||
GetOrCreateBucket(storage_key));
|
||||
CdmStorageBindingContext binding_context(storage_key, kCdmType);
|
||||
|
||||
// Open CDM storage for a storage key.
|
||||
manager_->OpenCdmStorage(binding_context,
|
||||
remote.BindNewPipeAndPassReceiver());
|
||||
auto cdm_file = OpenCdmFile(remote, "test_file");
|
||||
|
||||
// Write some data.
|
||||
Write(cdm_file, kTestData);
|
||||
|
||||
auto database_file = FindMediaLicenseDatabase();
|
||||
|
||||
EXPECT_FALSE(database_file.empty());
|
||||
|
||||
// Delete data for this storage key.
|
||||
base::test::TestFuture<blink::mojom::QuotaStatusCode> delete_future;
|
||||
manager_->DeleteBucketData(bucket, delete_future.GetCallback());
|
||||
EXPECT_EQ(delete_future.Get(), blink::mojom::QuotaStatusCode::kOk);
|
||||
|
||||
// Confirm that the database was deleted, but the Media License directory was
|
||||
// not.
|
||||
EXPECT_FALSE(base::PathExists(database_file));
|
||||
EXPECT_TRUE(base::DirectoryExists(database_file.DirName()));
|
||||
|
||||
// Confirm that the file is now empty.
|
||||
ExpectFileContents(cdm_file, "");
|
||||
}
|
||||
|
||||
TEST_F(MediaLicenseManagerTest, DeleteBucketDataClosedStorage) {
|
||||
const std::string kTestData("Test Data");
|
||||
mojo::Remote<media::mojom::CdmStorage> remote;
|
||||
blink::StorageKey storage_key =
|
||||
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
|
||||
ASSERT_OK_AND_ASSIGN(storage::BucketLocator bucket,
|
||||
GetOrCreateBucket(storage_key));
|
||||
CdmStorageBindingContext binding_context(storage_key, kCdmType);
|
||||
|
||||
// Open CDM storage for a storage key.
|
||||
manager_->OpenCdmStorage(binding_context,
|
||||
remote.BindNewPipeAndPassReceiver());
|
||||
auto cdm_file = OpenCdmFile(remote, "test_file");
|
||||
|
||||
Write(cdm_file, kTestData);
|
||||
|
||||
auto database_file = FindMediaLicenseDatabase();
|
||||
EXPECT_FALSE(database_file.empty());
|
||||
|
||||
// We should still be able to wipe data to a closed storage.
|
||||
cdm_file.reset();
|
||||
remote.reset();
|
||||
|
||||
EXPECT_TRUE(base::PathExists(database_file));
|
||||
|
||||
// Delete data for this storage key.
|
||||
base::test::TestFuture<blink::mojom::QuotaStatusCode> delete_future;
|
||||
manager_->DeleteBucketData(bucket, delete_future.GetCallback());
|
||||
EXPECT_EQ(delete_future.Get(), blink::mojom::QuotaStatusCode::kOk);
|
||||
|
||||
// Confirm that the database was deleted, but the Media License
|
||||
// directory was not.
|
||||
EXPECT_FALSE(base::PathExists(database_file));
|
||||
EXPECT_TRUE(base::DirectoryExists(database_file.DirName()));
|
||||
}
|
||||
|
||||
TEST_F(MediaLicenseManagerTest, DeleteBucketDataOpenConnection) {
|
||||
const std::string kTestData("Test Data");
|
||||
mojo::Remote<media::mojom::CdmStorage> remote;
|
||||
blink::StorageKey storage_key =
|
||||
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
|
||||
ASSERT_OK_AND_ASSIGN(storage::BucketLocator bucket,
|
||||
GetOrCreateBucket(storage_key));
|
||||
CdmStorageBindingContext binding_context(storage_key, kCdmType);
|
||||
|
||||
// Open CDM storage for a storage key.
|
||||
manager_->OpenCdmStorage(binding_context,
|
||||
remote.BindNewPipeAndPassReceiver());
|
||||
auto cdm_file = OpenCdmFile(remote, "test_file");
|
||||
|
||||
Write(cdm_file, kTestData);
|
||||
|
||||
auto database_file = FindMediaLicenseDatabase();
|
||||
EXPECT_FALSE(database_file.empty());
|
||||
|
||||
// Delete data for this storage key.
|
||||
base::test::TestFuture<blink::mojom::QuotaStatusCode> delete_future;
|
||||
manager_->DeleteBucketData(bucket, delete_future.GetCallback());
|
||||
EXPECT_EQ(delete_future.Get(), blink::mojom::QuotaStatusCode::kOk);
|
||||
|
||||
// Confirm that the database was deleted, but the Media License directory was
|
||||
// not.
|
||||
EXPECT_FALSE(base::PathExists(database_file));
|
||||
EXPECT_TRUE(base::DirectoryExists(database_file.DirName()));
|
||||
|
||||
// Confirm that the file is now empty.
|
||||
ExpectFileContents(cdm_file, "");
|
||||
|
||||
// Write some more data. This should succeed.
|
||||
Write(cdm_file, kTestData);
|
||||
|
||||
EXPECT_TRUE(base::PathExists(database_file));
|
||||
EXPECT_TRUE(base::PathExists(database_file.DirName()));
|
||||
}
|
||||
|
||||
TEST_F(MediaLicenseManagerTest, BucketCreationFailed) {
|
||||
const std::string kTestData("Test Data");
|
||||
mojo::Remote<media::mojom::CdmStorage> remote;
|
||||
blink::StorageKey storage_key =
|
||||
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
|
||||
ASSERT_OK_AND_ASSIGN(storage::BucketLocator bucket,
|
||||
GetOrCreateBucket(storage_key));
|
||||
CdmStorageBindingContext binding_context(storage_key, kCdmType);
|
||||
|
||||
// Disable the quota database, causing GetOrCreateBucket() to fail.
|
||||
quota_manager_->SetDisableDatabase(/*disable=*/true);
|
||||
|
||||
// Open CDM storage for a storage key.
|
||||
manager_->OpenCdmStorage(binding_context,
|
||||
remote.BindNewPipeAndPassReceiver());
|
||||
// Opening a CDM file should fail.
|
||||
base::test::TestFuture<media::mojom::CdmStorage::Status,
|
||||
mojo::PendingAssociatedRemote<media::mojom::CdmFile>>
|
||||
open_future;
|
||||
remote->Open("test_file", open_future.GetCallback());
|
||||
|
||||
auto result = open_future.Take();
|
||||
EXPECT_EQ(std::get<0>(result), media::mojom::CdmStorage::Status::kFailure);
|
||||
EXPECT_FALSE(std::get<1>(result).is_valid());
|
||||
}
|
||||
|
||||
class MediaLicenseManagerIncognitoTest : public MediaLicenseManagerTest {
|
||||
public:
|
||||
MediaLicenseManagerIncognitoTest()
|
||||
: MediaLicenseManagerTest(/*in_memory=*/true) {}
|
||||
};
|
||||
|
||||
TEST_F(MediaLicenseManagerIncognitoTest, DeleteBucketData) {
|
||||
const std::string kTestData("Test Data");
|
||||
mojo::Remote<media::mojom::CdmStorage> remote;
|
||||
blink::StorageKey storage_key =
|
||||
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
|
||||
ASSERT_OK_AND_ASSIGN(storage::BucketLocator bucket,
|
||||
GetOrCreateBucket(storage_key));
|
||||
CdmStorageBindingContext binding_context(storage_key, kCdmType);
|
||||
|
||||
// Open CDM storage for a storage key.
|
||||
manager_->OpenCdmStorage(binding_context,
|
||||
remote.BindNewPipeAndPassReceiver());
|
||||
auto cdm_file = OpenCdmFile(remote, "test_file");
|
||||
|
||||
// Write some data.
|
||||
Write(cdm_file, kTestData);
|
||||
|
||||
// We should be able to read the written file.
|
||||
ExpectFileContents(cdm_file, kTestData);
|
||||
|
||||
// Delete data for this storage key.
|
||||
base::test::TestFuture<blink::mojom::QuotaStatusCode> delete_future;
|
||||
manager_->DeleteBucketData(bucket, delete_future.GetCallback());
|
||||
EXPECT_EQ(delete_future.Get(), blink::mojom::QuotaStatusCode::kOk);
|
||||
|
||||
// Confirm that the file is now empty.
|
||||
ExpectFileContents(cdm_file, "");
|
||||
}
|
||||
|
||||
TEST_F(MediaLicenseManagerIncognitoTest, DeleteBucketDataClosedStorage) {
|
||||
const std::string kTestData("Test Data");
|
||||
mojo::Remote<media::mojom::CdmStorage> remote;
|
||||
blink::StorageKey storage_key =
|
||||
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
|
||||
ASSERT_OK_AND_ASSIGN(storage::BucketLocator bucket,
|
||||
GetOrCreateBucket(storage_key));
|
||||
CdmStorageBindingContext binding_context(storage_key, kCdmType);
|
||||
|
||||
// Open CDM storage for a storage key.
|
||||
manager_->OpenCdmStorage(binding_context,
|
||||
remote.BindNewPipeAndPassReceiver());
|
||||
auto cdm_file = OpenCdmFile(remote, "test_file");
|
||||
|
||||
Write(cdm_file, kTestData);
|
||||
|
||||
// There should be no db_file since its in memory.
|
||||
EXPECT_TRUE(FindMediaLicenseDatabase().empty());
|
||||
|
||||
// We should still be able to wipe data to a closed storage.
|
||||
cdm_file.reset();
|
||||
remote.reset();
|
||||
|
||||
// Delete data for this storage key.
|
||||
base::test::TestFuture<blink::mojom::QuotaStatusCode> delete_future;
|
||||
manager_->DeleteBucketData(bucket, delete_future.GetCallback());
|
||||
EXPECT_EQ(delete_future.Get(), blink::mojom::QuotaStatusCode::kOk);
|
||||
}
|
||||
|
||||
TEST_F(MediaLicenseManagerIncognitoTest, DeleteBucketDataOpenConnection) {
|
||||
const std::string kTestData("Test Data");
|
||||
mojo::Remote<media::mojom::CdmStorage> remote;
|
||||
blink::StorageKey storage_key =
|
||||
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
|
||||
ASSERT_OK_AND_ASSIGN(storage::BucketLocator bucket,
|
||||
GetOrCreateBucket(storage_key));
|
||||
CdmStorageBindingContext binding_context(storage_key, kCdmType);
|
||||
|
||||
// Open CDM storage for a storage key.
|
||||
manager_->OpenCdmStorage(binding_context,
|
||||
remote.BindNewPipeAndPassReceiver());
|
||||
auto cdm_file = OpenCdmFile(remote, "test_file");
|
||||
|
||||
Write(cdm_file, kTestData);
|
||||
|
||||
ExpectFileContents(cdm_file, kTestData);
|
||||
|
||||
// There should be no db file since its in memory.
|
||||
EXPECT_TRUE(FindMediaLicenseDatabase().empty());
|
||||
|
||||
// Delete data for this storage key.
|
||||
base::test::TestFuture<blink::mojom::QuotaStatusCode> delete_future;
|
||||
manager_->DeleteBucketData(bucket, delete_future.GetCallback());
|
||||
EXPECT_EQ(delete_future.Get(), blink::mojom::QuotaStatusCode::kOk);
|
||||
|
||||
// Confirm that the file is now empty.
|
||||
ExpectFileContents(cdm_file, "");
|
||||
|
||||
// Write some more data. This should succeed.
|
||||
Write(cdm_file, kTestData);
|
||||
|
||||
// Check that no file was created.
|
||||
EXPECT_TRUE(FindMediaLicenseDatabase().empty());
|
||||
}
|
||||
|
||||
TEST_F(MediaLicenseManagerIncognitoTest, BucketCreationFailed) {
|
||||
const std::string kTestData("Test Data");
|
||||
mojo::Remote<media::mojom::CdmStorage> remote;
|
||||
blink::StorageKey storage_key =
|
||||
blink::StorageKey::CreateFromStringForTesting(kExampleOrigin);
|
||||
ASSERT_OK_AND_ASSIGN(storage::BucketLocator bucket,
|
||||
GetOrCreateBucket(storage_key));
|
||||
CdmStorageBindingContext binding_context(storage_key, kCdmType);
|
||||
|
||||
// Disable the quota database, causing GetOrCreateBucket() to fail.
|
||||
quota_manager_->SetDisableDatabase(/*disable=*/true);
|
||||
|
||||
// Open CDM storage for a storage key.
|
||||
manager_->OpenCdmStorage(binding_context,
|
||||
remote.BindNewPipeAndPassReceiver());
|
||||
// Opening a CDM file should fail.
|
||||
base::test::TestFuture<media::mojom::CdmStorage::Status,
|
||||
mojo::PendingAssociatedRemote<media::mojom::CdmFile>>
|
||||
open_future;
|
||||
remote->Open("test_file", open_future.GetCallback());
|
||||
|
||||
auto result = open_future.Take();
|
||||
EXPECT_EQ(std::get<0>(result), media::mojom::CdmStorage::Status::kFailure);
|
||||
EXPECT_FALSE(std::get<1>(result).is_valid());
|
||||
}
|
||||
|
||||
} // namespace content
|
58
content/browser/media/media_license_quota_client.cc
Normal file
58
content/browser/media/media_license_quota_client.cc
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright 2022 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "content/browser/media/media_license_quota_client.h"
|
||||
|
||||
#include "base/sequence_checker.h"
|
||||
#include "content/browser/media/media_license_manager.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
MediaLicenseQuotaClient::MediaLicenseQuotaClient(MediaLicenseManager* manager)
|
||||
: manager_(manager) {}
|
||||
|
||||
MediaLicenseQuotaClient::~MediaLicenseQuotaClient() {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
}
|
||||
|
||||
void MediaLicenseQuotaClient::GetBucketUsage(
|
||||
const storage::BucketLocator& bucket,
|
||||
GetBucketUsageCallback callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
// Media license data does not count against quota.
|
||||
// TODO(crbug.com/40218094): Consider counting this data against quota.
|
||||
std::move(callback).Run(0);
|
||||
}
|
||||
|
||||
void MediaLicenseQuotaClient::GetStorageKeysForType(
|
||||
blink::mojom::StorageType type,
|
||||
GetStorageKeysForTypeCallback callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
DCHECK_EQ(type, blink::mojom::StorageType::kTemporary);
|
||||
|
||||
// This method is only used to bootstrap existing Storage API data into the
|
||||
// QuotaDatabase. Since this is a new backend, there is no existing data to
|
||||
// bootstrap.
|
||||
std::move(callback).Run({});
|
||||
}
|
||||
|
||||
void MediaLicenseQuotaClient::DeleteBucketData(
|
||||
const storage::BucketLocator& bucket,
|
||||
DeleteBucketDataCallback callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
manager_->DeleteBucketData(bucket, std::move(callback));
|
||||
}
|
||||
|
||||
void MediaLicenseQuotaClient::PerformStorageCleanup(
|
||||
blink::mojom::StorageType type,
|
||||
PerformStorageCleanupCallback callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
// Nothing to do here.
|
||||
std::move(callback).Run();
|
||||
}
|
||||
|
||||
} // namespace content
|
42
content/browser/media/media_license_quota_client.h
Normal file
42
content/browser/media/media_license_quota_client.h
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2022 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_BROWSER_MEDIA_MEDIA_LICENSE_QUOTA_CLIENT_H_
|
||||
#define CONTENT_BROWSER_MEDIA_MEDIA_LICENSE_QUOTA_CLIENT_H_
|
||||
|
||||
#include "components/services/storage/public/mojom/quota_client.mojom.h"
|
||||
#include "content/common/content_export.h"
|
||||
|
||||
namespace content {
|
||||
class MediaLicenseManager;
|
||||
|
||||
// Integrates media licenses with the quota system.
|
||||
//
|
||||
// Each MediaLicenseManager owns exactly one MediaLicenseQuotaClient.
|
||||
class CONTENT_EXPORT MediaLicenseQuotaClient
|
||||
: public storage::mojom::QuotaClient {
|
||||
public:
|
||||
explicit MediaLicenseQuotaClient(MediaLicenseManager* manager);
|
||||
~MediaLicenseQuotaClient() override;
|
||||
|
||||
// storage::mojom::QuotaClient implementation:
|
||||
void GetBucketUsage(const storage::BucketLocator& bucket,
|
||||
GetBucketUsageCallback callback) override;
|
||||
void GetStorageKeysForType(blink::mojom::StorageType type,
|
||||
GetStorageKeysForTypeCallback callback) override;
|
||||
void DeleteBucketData(const storage::BucketLocator& bucket,
|
||||
DeleteBucketDataCallback callback) override;
|
||||
void PerformStorageCleanup(blink::mojom::StorageType type,
|
||||
PerformStorageCleanupCallback callback) override;
|
||||
|
||||
private:
|
||||
SEQUENCE_CHECKER(sequence_checker_);
|
||||
|
||||
const raw_ptr<MediaLicenseManager> manager_
|
||||
GUARDED_BY_CONTEXT(sequence_checker_);
|
||||
};
|
||||
|
||||
} // namespace content
|
||||
|
||||
#endif // CONTENT_BROWSER_MEDIA_MEDIA_LICENSE_QUOTA_CLIENT_H_
|
312
content/browser/media/media_license_storage_host.cc
Normal file
312
content/browser/media/media_license_storage_host.cc
Normal file
@ -0,0 +1,312 @@
|
||||
// Copyright 2022 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "content/browser/media/media_license_storage_host.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/containers/contains.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
#include "base/notreached.h"
|
||||
#include "base/sequence_checker.h"
|
||||
#include "base/task/sequenced_task_runner.h"
|
||||
#include "base/types/pass_key.h"
|
||||
#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
|
||||
#include "content/browser/media/cdm_file_impl.h"
|
||||
#include "content/browser/media/media_license_database.h"
|
||||
#include "content/browser/media/media_license_manager.h"
|
||||
#include "media/cdm/cdm_type.h"
|
||||
#include "media/mojo/mojom/cdm_storage.mojom.h"
|
||||
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
|
||||
#include "storage/browser/file_system/file_system_context.h"
|
||||
#include "third_party/blink/public/common/storage_key/storage_key.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
namespace {
|
||||
constexpr uint64_t kBytesPerKB = 1024;
|
||||
constexpr int kMinDatabaseSizeKB = 0;
|
||||
// Used for histogram reporting, the max size of the database we expect in KB.
|
||||
constexpr uint64_t kMaxDatabaseSizeKB = 512000 * 10;
|
||||
constexpr int kSizeKBBuckets = 1000;
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
void MediaLicenseStorageHost::ReportDatabaseOpenError(
|
||||
MediaLicenseStorageHostOpenError error,
|
||||
bool is_incognito) {
|
||||
DCHECK_NE(error, MediaLicenseStorageHostOpenError::kOk);
|
||||
const std::string kDatabaseOpenErrorUmaName =
|
||||
"Media.EME.MediaLicenseStorageHostOpenError";
|
||||
base::UmaHistogramEnumeration(kDatabaseOpenErrorUmaName, error);
|
||||
|
||||
if (is_incognito) {
|
||||
base::UmaHistogramEnumeration(kDatabaseOpenErrorUmaName + ".Incognito",
|
||||
error);
|
||||
} else {
|
||||
base::UmaHistogramEnumeration(kDatabaseOpenErrorUmaName + ".NotIncognito",
|
||||
error);
|
||||
}
|
||||
}
|
||||
|
||||
MediaLicenseStorageHost::MediaLicenseStorageHost(
|
||||
MediaLicenseManager* manager,
|
||||
const storage::BucketLocator& bucket_locator)
|
||||
: manager_(manager),
|
||||
bucket_locator_(bucket_locator),
|
||||
db_(manager_->db_runner(), manager_->GetDatabasePath(bucket_locator_)) {
|
||||
DCHECK(manager_);
|
||||
|
||||
// base::Unretained is safe here because this MediaLicenseStorageHost owns
|
||||
// `receivers_`. So, the unretained MediaLicenseStorageHost is guaranteed to
|
||||
// outlive `receivers_` and the closure that it uses.
|
||||
receivers_.set_disconnect_handler(base::BindRepeating(
|
||||
&MediaLicenseStorageHost::OnReceiverDisconnect, base::Unretained(this)));
|
||||
}
|
||||
|
||||
MediaLicenseStorageHost::~MediaLicenseStorageHost() {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
}
|
||||
|
||||
void MediaLicenseStorageHost::Open(const std::string& file_name,
|
||||
OpenCallback callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (bucket_locator_.id.is_null()) {
|
||||
DVLOG(1) << "Could not retrieve valid bucket.";
|
||||
ReportDatabaseOpenError(MediaLicenseStorageHostOpenError::kInvalidBucket,
|
||||
in_memory());
|
||||
std::move(callback).Run(Status::kFailure, mojo::NullAssociatedRemote());
|
||||
return;
|
||||
}
|
||||
|
||||
if (file_name.empty()) {
|
||||
DVLOG(1) << "No file specified.";
|
||||
ReportDatabaseOpenError(MediaLicenseStorageHostOpenError::kNoFileSpecified,
|
||||
in_memory());
|
||||
std::move(callback).Run(Status::kFailure, mojo::NullAssociatedRemote());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CdmFileImpl::IsValidName(file_name)) {
|
||||
ReportDatabaseOpenError(MediaLicenseStorageHostOpenError::kInvalidFileName,
|
||||
in_memory());
|
||||
std::move(callback).Run(Status::kFailure, mojo::NullAssociatedRemote());
|
||||
return;
|
||||
}
|
||||
|
||||
const CdmStorageBindingContext& binding_context =
|
||||
receivers_.current_context();
|
||||
db_.AsyncCall(&MediaLicenseDatabase::OpenFile)
|
||||
.WithArgs(binding_context.cdm_type, file_name)
|
||||
.Then(base::BindOnce(&MediaLicenseStorageHost::DidOpenFile,
|
||||
weak_factory_.GetWeakPtr(), file_name,
|
||||
binding_context, std::move(callback)));
|
||||
}
|
||||
|
||||
void MediaLicenseStorageHost::BindReceiver(
|
||||
const CdmStorageBindingContext& binding_context,
|
||||
mojo::PendingReceiver<media::mojom::CdmStorage> receiver) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
DCHECK_EQ(binding_context.storage_key, bucket_locator_.storage_key);
|
||||
|
||||
receivers_.Add(this, std::move(receiver), binding_context);
|
||||
}
|
||||
|
||||
void MediaLicenseStorageHost::DidOpenFile(
|
||||
const std::string& file_name,
|
||||
CdmStorageBindingContext binding_context,
|
||||
OpenCallback callback,
|
||||
MediaLicenseStorageHostOpenError error) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (error != MediaLicenseStorageHostOpenError::kOk) {
|
||||
ReportDatabaseOpenError(error, in_memory());
|
||||
std::move(callback).Run(Status::kFailure, mojo::NullAssociatedRemote());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check whether this CDM file is in-use.
|
||||
CdmFileId id(file_name, binding_context.cdm_type);
|
||||
if (base::Contains(cdm_files_, id)) {
|
||||
std::move(callback).Run(Status::kInUse, mojo::NullAssociatedRemote());
|
||||
return;
|
||||
}
|
||||
|
||||
// File was opened successfully, so create the binding and return success.
|
||||
mojo::PendingAssociatedRemote<media::mojom::CdmFile> cdm_file;
|
||||
|
||||
// `this` is safe here since `cdm_file_impl` is owned by this instance.
|
||||
cdm_files_.emplace(id, std::make_unique<CdmFileImpl>(
|
||||
this, binding_context.cdm_type, file_name,
|
||||
cdm_file.InitWithNewEndpointAndPassReceiver()));
|
||||
|
||||
// We don't actually touch the database here, but notify the quota system
|
||||
// anyways since conceptually we're creating an empty file.
|
||||
manager_->quota_manager_proxy()->NotifyBucketModified(
|
||||
storage::QuotaClientType::kMediaLicense, bucket_locator_, /*delta=*/0,
|
||||
/*modification_time=*/base::Time::Now(),
|
||||
base::SequencedTaskRunner::GetCurrentDefault(),
|
||||
base::BindOnce(std::move(callback), Status::kSuccess,
|
||||
std::move(cdm_file)));
|
||||
}
|
||||
|
||||
void MediaLicenseStorageHost::ReadFile(const media::CdmType& cdm_type,
|
||||
const std::string& file_name,
|
||||
ReadFileCallback callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
manager_->quota_manager_proxy()->NotifyBucketAccessed(
|
||||
bucket_locator_,
|
||||
/*access_time=*/base::Time::Now());
|
||||
|
||||
// This is to read from the cdm_storage database when the migration is active
|
||||
// and we've already migrated the data from the cdm_storage database to the
|
||||
// media_license database.
|
||||
if (manager_->cdm_storage_manager() &&
|
||||
base::Contains(files_migrated_,
|
||||
CdmFileIdTwo{file_name, cdm_type, storage_key()})) {
|
||||
manager_->cdm_storage_manager()->ReadFile(storage_key(), cdm_type,
|
||||
file_name, std::move(callback));
|
||||
return;
|
||||
}
|
||||
|
||||
db_.AsyncCall(&MediaLicenseDatabase::ReadFile)
|
||||
.WithArgs(cdm_type, file_name)
|
||||
.Then(base::BindOnce(&MediaLicenseStorageHost::DidReadFile,
|
||||
weak_factory_.GetWeakPtr(), cdm_type, file_name,
|
||||
std::move(callback)));
|
||||
}
|
||||
|
||||
void MediaLicenseStorageHost::DidReadFile(
|
||||
const media::CdmType& cdm_type,
|
||||
const std::string& file_name,
|
||||
ReadFileCallback callback,
|
||||
std::optional<std::vector<uint8_t>> data) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
// The code only reaches this callback during the migration when this is our
|
||||
// first time reading this specific storage_key, cdm_type, and file name. If
|
||||
// the data has value, then we write it to the cdm_storage database and from
|
||||
// then on, read from the cdm_storage database through the media_license code
|
||||
// when the migration is active.
|
||||
if (data.has_value() && manager_->cdm_storage_manager()) {
|
||||
manager_->cdm_storage_manager()->WriteFile(
|
||||
storage_key(), cdm_type, file_name, data.value(), base::DoNothing());
|
||||
files_migrated_.emplace_back(file_name, cdm_type, storage_key());
|
||||
}
|
||||
|
||||
if (!database_size_reported_) {
|
||||
db_.AsyncCall(&MediaLicenseDatabase::GetDatabaseSize)
|
||||
.Then(base::BindOnce(&MediaLicenseStorageHost::DidGetDatabaseSize,
|
||||
weak_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
std::move(callback).Run(data);
|
||||
}
|
||||
|
||||
void MediaLicenseStorageHost::DidGetDatabaseSize(const uint64_t size) {
|
||||
// One time report DatabaseSize.
|
||||
|
||||
base::UmaHistogramCustomCounts(
|
||||
"Media.EME.MediaLicenseStorageHost.CurrentDatabaseUsageKB",
|
||||
size / kBytesPerKB, kMinDatabaseSizeKB, kMaxDatabaseSizeKB,
|
||||
kSizeKBBuckets);
|
||||
|
||||
database_size_reported_ = true;
|
||||
}
|
||||
|
||||
void MediaLicenseStorageHost::WriteFile(const media::CdmType& cdm_type,
|
||||
const std::string& file_name,
|
||||
const std::vector<uint8_t>& data,
|
||||
WriteFileCallback callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (manager_->cdm_storage_manager()) {
|
||||
// We don't populate the callback because we want the
|
||||
// `MediaLicenseStorageHost` to still maintain control. We just call in to
|
||||
// the `CdmStorageManager` object to be able to update the
|
||||
// `CdmStorageDatabase` to keep it in line with `MediaLicenseDatabase`.
|
||||
manager_->cdm_storage_manager()->WriteFile(
|
||||
storage_key(), cdm_type, file_name, data, base::DoNothing());
|
||||
}
|
||||
|
||||
db_.AsyncCall(&MediaLicenseDatabase::WriteFile)
|
||||
.WithArgs(cdm_type, file_name, data)
|
||||
.Then(base::BindOnce(&MediaLicenseStorageHost::DidWriteFile,
|
||||
weak_factory_.GetWeakPtr(), std::move(callback)));
|
||||
}
|
||||
|
||||
void MediaLicenseStorageHost::DidWriteFile(WriteFileCallback callback,
|
||||
bool success) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (!success) {
|
||||
manager_->quota_manager_proxy()->OnClientWriteFailed(storage_key());
|
||||
std::move(callback).Run(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass `delta`=0 since media license data does not count against quota.
|
||||
// TODO(crbug.com/40218094): Consider counting this data against quota.
|
||||
manager_->quota_manager_proxy()->NotifyBucketModified(
|
||||
storage::QuotaClientType::kMediaLicense, bucket_locator_, /*delta=*/0,
|
||||
/*modification_time=*/base::Time::Now(),
|
||||
base::SequencedTaskRunner::GetCurrentDefault(),
|
||||
base::BindOnce(std::move(callback), success));
|
||||
}
|
||||
|
||||
void MediaLicenseStorageHost::DeleteFile(const media::CdmType& cdm_type,
|
||||
const std::string& file_name,
|
||||
DeleteFileCallback callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (manager_->cdm_storage_manager()) {
|
||||
// We don't populate the callback because we want the
|
||||
// `MediaLicenseStorageHost` to still maintain control. We just call in to
|
||||
// the `CdmStorageManager` object to be able to update the
|
||||
// `CdmStorageDatabase` to keep it in line with `MediaLicenseDatabase`.
|
||||
// TODO(crbug.com/40272342): Create UMA to track failures from the
|
||||
// MediaLicense* path, as we choose to fail silently to not affect the
|
||||
// current code-path's behavior and affect CDM operations.
|
||||
manager_->cdm_storage_manager()->DeleteFile(storage_key(), cdm_type,
|
||||
file_name, base::DoNothing());
|
||||
}
|
||||
|
||||
db_.AsyncCall(&MediaLicenseDatabase::DeleteFile)
|
||||
.WithArgs(cdm_type, file_name)
|
||||
.Then(base::BindOnce(&MediaLicenseStorageHost::DidWriteFile,
|
||||
weak_factory_.GetWeakPtr(), std::move(callback)));
|
||||
}
|
||||
|
||||
void MediaLicenseStorageHost::DeleteBucketData(
|
||||
base::OnceCallback<void(bool)> callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
db_.AsyncCall(&MediaLicenseDatabase::ClearDatabase).Then(std::move(callback));
|
||||
}
|
||||
|
||||
void MediaLicenseStorageHost::OnReceiverDisconnect() {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
// May delete `this`.
|
||||
manager_->OnHostReceiverDisconnect(this,
|
||||
base::PassKey<MediaLicenseStorageHost>());
|
||||
}
|
||||
|
||||
void MediaLicenseStorageHost::OnFileReceiverDisconnect(
|
||||
const std::string& name,
|
||||
const media::CdmType& cdm_type,
|
||||
base::PassKey<CdmFileImpl> /*pass_key*/) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
auto count = cdm_files_.erase(CdmFileId(name, cdm_type));
|
||||
DCHECK_GT(count, 0u);
|
||||
}
|
||||
|
||||
} // namespace content
|
165
content/browser/media/media_license_storage_host.h
Normal file
165
content/browser/media/media_license_storage_host.h
Normal file
@ -0,0 +1,165 @@
|
||||
// Copyright 2022 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CONTENT_BROWSER_MEDIA_MEDIA_LICENSE_STORAGE_HOST_H_
|
||||
#define CONTENT_BROWSER_MEDIA_MEDIA_LICENSE_STORAGE_HOST_H_
|
||||
|
||||
#include "base/containers/unique_ptr_adapters.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/functional/callback_forward.h"
|
||||
#include "base/thread_annotations.h"
|
||||
#include "base/threading/sequence_bound.h"
|
||||
#include "base/types/pass_key.h"
|
||||
#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
|
||||
#include "components/services/storage/public/mojom/quota_client.mojom.h"
|
||||
#include "content/browser/media/cdm_storage_common.h"
|
||||
#include "content/browser/media/media_license_manager.h"
|
||||
#include "content/common/content_export.h"
|
||||
#include "media/mojo/mojom/cdm_storage.mojom.h"
|
||||
#include "mojo/public/cpp/bindings/receiver_set.h"
|
||||
#include "mojo/public/cpp/bindings/unique_associated_receiver_set.h"
|
||||
#include "third_party/blink/public/common/storage_key/storage_key.h"
|
||||
|
||||
namespace content {
|
||||
class CdmFileImpl;
|
||||
class MediaLicenseDatabase;
|
||||
|
||||
// Per-storage-key backend for media license (CDM) files. MediaLicenseManager
|
||||
// owns an instance of this class for each storage key that is actively using
|
||||
// CDM files. Each instance owns all CdmStorage receivers for the corresponding
|
||||
// storage key.
|
||||
class CONTENT_EXPORT MediaLicenseStorageHost : public media::mojom::CdmStorage {
|
||||
public:
|
||||
using ReadFileCallback =
|
||||
base::OnceCallback<void(std::optional<std::vector<uint8_t>>)>;
|
||||
using WriteFileCallback = base::OnceCallback<void(bool)>;
|
||||
using DeleteFileCallback = base::OnceCallback<void(bool)>;
|
||||
|
||||
// These values are persisted to logs. Entries should not be renumbered and
|
||||
// numeric values should never be reused.
|
||||
enum class MediaLicenseStorageHostOpenError {
|
||||
kOk = -1,
|
||||
kInvalidBucket = 0, // The database's path could not be determined
|
||||
// because the default storage bucket for the
|
||||
// StorageKey could not be retrieved.
|
||||
kNoFileSpecified = 1, // No file was specified.
|
||||
kInvalidFileName = 2, // File name specified was invalid.
|
||||
kDatabaseOpenError = 3, // Error occurred at the Database level.
|
||||
kBucketNotFound = 4, // If the default Storage Bucket for the StorageKey
|
||||
// is not found.
|
||||
kDatabaseRazeError = 5, // The database was in an invalid state and failed
|
||||
// to be razed.
|
||||
kSQLExecutionError = 6, // Error executing the SQL statement.
|
||||
kBucketLocatorError = 7, // Error with the bucket locator. This error was
|
||||
// introduced after the previous errors so that
|
||||
// we can drill down deeper on the source of the
|
||||
// errors.
|
||||
kMaxValue = kBucketLocatorError
|
||||
};
|
||||
|
||||
static void ReportDatabaseOpenError(MediaLicenseStorageHostOpenError error,
|
||||
bool in_memory);
|
||||
|
||||
MediaLicenseStorageHost(MediaLicenseManager* manager,
|
||||
const storage::BucketLocator& bucket_locator);
|
||||
~MediaLicenseStorageHost() override;
|
||||
|
||||
// media::mojom::CdmStorage implementation.
|
||||
void Open(const std::string& file_name, OpenCallback callback) final;
|
||||
|
||||
void BindReceiver(const CdmStorageBindingContext& binding_context,
|
||||
mojo::PendingReceiver<media::mojom::CdmStorage> receiver);
|
||||
|
||||
// CDM file operations.
|
||||
void ReadFile(const media::CdmType& cdm_type,
|
||||
const std::string& file_name,
|
||||
ReadFileCallback callback);
|
||||
void WriteFile(const media::CdmType& cdm_type,
|
||||
const std::string& file_name,
|
||||
const std::vector<uint8_t>& data,
|
||||
WriteFileCallback callback);
|
||||
void DeleteFile(const media::CdmType& cdm_type,
|
||||
const std::string& file_name,
|
||||
DeleteFileCallback callback);
|
||||
|
||||
void DeleteBucketData(base::OnceCallback<void(bool)> callback);
|
||||
|
||||
void OnFileReceiverDisconnect(const std::string& name,
|
||||
const media::CdmType& cdm_type,
|
||||
base::PassKey<CdmFileImpl> pass_key);
|
||||
|
||||
// True if there are no receivers connected to this host.
|
||||
//
|
||||
// The MediaLicenseManagerImpl that owns this host is expected to destroy the
|
||||
// host when it isn't serving any receivers.
|
||||
bool has_empty_receiver_set() const {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
return receivers_.empty();
|
||||
}
|
||||
|
||||
const blink::StorageKey& storage_key() { return bucket_locator_.storage_key; }
|
||||
|
||||
bool in_memory() const {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
return manager_->in_memory();
|
||||
}
|
||||
|
||||
private:
|
||||
void OnReceiverDisconnect();
|
||||
|
||||
void DidOpenFile(const std::string& file_name,
|
||||
CdmStorageBindingContext binding_context,
|
||||
OpenCallback callback,
|
||||
MediaLicenseStorageHostOpenError error);
|
||||
void DidGetDatabaseSize(const uint64_t size);
|
||||
void DidReadFile(const media::CdmType& cdm_type,
|
||||
const std::string& file_name,
|
||||
ReadFileCallback callback,
|
||||
std::optional<std::vector<uint8_t>> data);
|
||||
void DidWriteFile(WriteFileCallback callback, bool success);
|
||||
|
||||
SEQUENCE_CHECKER(sequence_checker_);
|
||||
|
||||
// Track MediaLicenseDatabaseSize
|
||||
bool database_size_reported_ = false;
|
||||
|
||||
// MediaLicenseManager instance which owns this object.
|
||||
const raw_ptr<MediaLicenseManager> manager_
|
||||
GUARDED_BY_CONTEXT(sequence_checker_);
|
||||
|
||||
// Media licenses are only supported from the default bucket.
|
||||
// `bucket_locator_` corresponds to the default bucket for the StorageKey this
|
||||
// host represents.
|
||||
const storage::BucketLocator bucket_locator_;
|
||||
|
||||
// This keeps track of the 'CdmFileIdTwo' values that have been migrated to
|
||||
// the CdmStorageDatabase after the first read, so that when the Cdm goes to
|
||||
// read during the migration, the second time and onwards, we read from the
|
||||
// CdmStorageDatabase instead of the MediaLicenseDatabase. Note that this is
|
||||
// not a permanent storage, so it has to be repopulated when user restarts
|
||||
// Chrome.
|
||||
std::vector<CdmFileIdTwo> files_migrated_
|
||||
GUARDED_BY_CONTEXT(sequence_checker_);
|
||||
|
||||
// All file operations are run through this member.
|
||||
base::SequenceBound<MediaLicenseDatabase> db_
|
||||
GUARDED_BY_CONTEXT(sequence_checker_);
|
||||
|
||||
// All receivers for frames and workers whose storage key is `storage_key()`.
|
||||
mojo::ReceiverSet<media::mojom::CdmStorage, CdmStorageBindingContext>
|
||||
receivers_ GUARDED_BY_CONTEXT(sequence_checker_);
|
||||
|
||||
// Keep track of all media::mojom::CdmFile receivers, as each CdmFileImpl
|
||||
// object keeps a reference to |this|. If |this| goes away unexpectedly,
|
||||
// all remaining CdmFile receivers will be closed.
|
||||
std::map<CdmFileId, std::unique_ptr<CdmFileImpl>> cdm_files_
|
||||
GUARDED_BY_CONTEXT(sequence_checker_);
|
||||
|
||||
base::WeakPtrFactory<MediaLicenseStorageHost> weak_factory_
|
||||
GUARDED_BY_CONTEXT(sequence_checker_){this};
|
||||
};
|
||||
|
||||
} // namespace content
|
||||
|
||||
#endif // CONTENT_BROWSER_MEDIA_MEDIA_LICENSE_STORAGE_HOST_H_
|
359
content/browser/media/media_license_storage_host_unittest.cc
Normal file
359
content/browser/media/media_license_storage_host_unittest.cc
Normal file
@ -0,0 +1,359 @@
|
||||
// Copyright 2017 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/file.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/functional/callback.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/test/bind.h"
|
||||
#include "base/test/scoped_feature_list.h"
|
||||
#include "base/test/test_future.h"
|
||||
#include "content/browser/media/cdm_storage_common.h"
|
||||
#include "content/browser/media/media_license_manager.h"
|
||||
#include "content/browser/storage_partition_impl.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/storage_partition.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "content/public/common/content_features.h"
|
||||
#include "content/public/test/navigation_simulator.h"
|
||||
#include "content/public/test/test_renderer_host.h"
|
||||
#include "media/cdm/cdm_type.h"
|
||||
#include "media/mojo/mojom/cdm_storage.mojom.h"
|
||||
#include "mojo/public/cpp/bindings/associated_remote.h"
|
||||
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
|
||||
#include "mojo/public/cpp/bindings/remote.h"
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "third_party/blink/public/common/storage_key/storage_key.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
using media::mojom::CdmFile;
|
||||
using media::mojom::CdmStorage;
|
||||
|
||||
namespace content {
|
||||
|
||||
namespace {
|
||||
|
||||
const media::CdmType kTestCdmType{1234, 5678};
|
||||
|
||||
const char kTestOrigin[] = "http://www.test.com";
|
||||
|
||||
// Helper functions to manipulate RenderFrameHosts.
|
||||
|
||||
void SimulateNavigation(raw_ptr<RenderFrameHost, DanglingUntriaged>* rfh,
|
||||
const GURL& url) {
|
||||
auto navigation_simulator =
|
||||
NavigationSimulator::CreateRendererInitiated(url, *rfh);
|
||||
navigation_simulator->Commit();
|
||||
*rfh = navigation_simulator->GetFinalRenderFrameHost();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class CdmStorageTest : public RenderViewHostTestHarness {
|
||||
public:
|
||||
CdmStorageTest()
|
||||
: RenderViewHostTestHarness(
|
||||
content::BrowserTaskEnvironment::REAL_IO_THREAD) {
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetUp() final {
|
||||
RenderViewHostTestHarness::SetUp();
|
||||
rfh_ = web_contents()->GetPrimaryMainFrame();
|
||||
RenderFrameHostTester::For(rfh_)->InitializeRenderFrameIfNeeded();
|
||||
SimulateNavigation(&rfh_, GURL(kTestOrigin));
|
||||
|
||||
cdm_storage_manager()->OpenCdmStorage(
|
||||
CdmStorageBindingContext(
|
||||
blink::StorageKey::CreateFromStringForTesting(kTestOrigin),
|
||||
kTestCdmType),
|
||||
cdm_storage_.BindNewPipeAndPassReceiver());
|
||||
}
|
||||
|
||||
// Open the file |name|. Returns true if the file returned is valid, false
|
||||
// otherwise. On success |cdm_file| is bound to the CdmFileImpl object.
|
||||
bool Open(const std::string& name,
|
||||
mojo::AssociatedRemote<CdmFile>& cdm_file) {
|
||||
DVLOG(3) << __func__;
|
||||
|
||||
base::test::TestFuture<CdmStorage::Status,
|
||||
mojo::PendingAssociatedRemote<CdmFile>>
|
||||
future;
|
||||
cdm_storage_->Open(name, future.GetCallback());
|
||||
|
||||
CdmStorage::Status status = future.Get<0>();
|
||||
mojo::PendingAssociatedRemote<CdmFile> actual_file =
|
||||
std::move(std::get<1>(future.Take()));
|
||||
if (!actual_file) {
|
||||
DCHECK_NE(status, CdmStorage::Status::kSuccess);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open() returns a mojo::PendingAssociatedRemote<CdmFile>, so bind it to
|
||||
// the mojo::AssociatedRemote<CdmFileAssociated> provided.
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file_remote;
|
||||
cdm_file_remote.Bind(std::move(actual_file));
|
||||
cdm_file = std::move(cdm_file_remote);
|
||||
|
||||
return status == CdmStorage::Status::kSuccess;
|
||||
}
|
||||
|
||||
// Reads the contents of the previously opened |cdm_file|. If successful,
|
||||
// true is returned and |data| is updated with the contents of the file.
|
||||
// If unable to read the file, false is returned.
|
||||
bool Read(CdmFile* cdm_file, std::vector<uint8_t>& data) {
|
||||
DVLOG(3) << __func__;
|
||||
|
||||
base::test::TestFuture<CdmFile::Status, std::vector<uint8_t>> future;
|
||||
cdm_file->Read(
|
||||
future.GetCallback<CdmFile::Status, const std::vector<uint8_t>&>());
|
||||
|
||||
CdmFile::Status status = future.Get<0>();
|
||||
data = future.Get<1>();
|
||||
return status == CdmFile::Status::kSuccess;
|
||||
}
|
||||
|
||||
// Writes |data| to the previously opened |cdm_file|, replacing the contents
|
||||
// of the file. Returns true if successful, false otherwise.
|
||||
bool Write(CdmFile* cdm_file, const std::vector<uint8_t>& data) {
|
||||
DVLOG(3) << __func__;
|
||||
|
||||
base::test::TestFuture<CdmFile::Status> future;
|
||||
cdm_file->Write(data, future.GetCallback());
|
||||
|
||||
CdmFile::Status status = future.Get();
|
||||
return status == CdmFile::Status::kSuccess;
|
||||
}
|
||||
|
||||
MediaLicenseManager* media_license_manager() const {
|
||||
auto* media_license_manager =
|
||||
static_cast<StoragePartitionImpl*>(rfh_->GetStoragePartition())
|
||||
->GetMediaLicenseManager();
|
||||
DCHECK(media_license_manager);
|
||||
return media_license_manager;
|
||||
}
|
||||
|
||||
CdmStorageManager* cdm_storage_manager() const {
|
||||
auto* cdm_storage_manager = static_cast<CdmStorageManager*>(
|
||||
static_cast<StoragePartitionImpl*>(rfh_->GetStoragePartition())
|
||||
->GetCdmStorageDataModel());
|
||||
DCHECK(cdm_storage_manager);
|
||||
return cdm_storage_manager;
|
||||
}
|
||||
|
||||
raw_ptr<RenderFrameHost, DanglingUntriaged> rfh_ = nullptr;
|
||||
mojo::Remote<CdmStorage> cdm_storage_;
|
||||
base::test::ScopedFeatureList scoped_feature_list_;
|
||||
};
|
||||
|
||||
TEST_F(CdmStorageTest, InvalidFileName) {
|
||||
// Anything other than ASCII letter, digits, and -._ will fail. Add a
|
||||
// Unicode character to the name.
|
||||
const char kFileName[] = "openfile\u1234";
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file;
|
||||
EXPECT_FALSE(Open(kFileName, cdm_file));
|
||||
ASSERT_FALSE(cdm_file.is_bound());
|
||||
}
|
||||
|
||||
TEST_F(CdmStorageTest, InvalidFileNameEmpty) {
|
||||
const char kFileName[] = "";
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file;
|
||||
EXPECT_FALSE(Open(kFileName, cdm_file));
|
||||
ASSERT_FALSE(cdm_file.is_bound());
|
||||
}
|
||||
|
||||
TEST_F(CdmStorageTest, InvalidFileNameStartWithUnderscore) {
|
||||
const char kFileName[] = "_invalid";
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file;
|
||||
EXPECT_FALSE(Open(kFileName, cdm_file));
|
||||
ASSERT_FALSE(cdm_file.is_bound());
|
||||
}
|
||||
|
||||
TEST_F(CdmStorageTest, InvalidFileNameTooLong) {
|
||||
// Limit is 256 characters, so try a file name with 257.
|
||||
const std::string kFileName(257, 'a');
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file;
|
||||
EXPECT_FALSE(Open(kFileName, cdm_file));
|
||||
ASSERT_FALSE(cdm_file.is_bound());
|
||||
}
|
||||
|
||||
TEST_F(CdmStorageTest, OpenFile) {
|
||||
const char kFileName[] = "test_file_name";
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file;
|
||||
EXPECT_TRUE(Open(kFileName, cdm_file));
|
||||
ASSERT_TRUE(cdm_file.is_bound());
|
||||
}
|
||||
|
||||
TEST_F(CdmStorageTest, OpenFileLocked) {
|
||||
const char kFileName[] = "test_file_name";
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file1;
|
||||
EXPECT_TRUE(Open(kFileName, cdm_file1));
|
||||
ASSERT_TRUE(cdm_file1.is_bound());
|
||||
|
||||
// Second attempt on the same file should fail as the file is locked.
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file2;
|
||||
EXPECT_FALSE(Open(kFileName, cdm_file2));
|
||||
ASSERT_FALSE(cdm_file2.is_bound());
|
||||
|
||||
// Now close the first file and try again. It should be free now.
|
||||
cdm_file1.reset();
|
||||
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file3;
|
||||
EXPECT_TRUE(Open(kFileName, cdm_file3));
|
||||
ASSERT_TRUE(cdm_file3.is_bound());
|
||||
}
|
||||
|
||||
TEST_F(CdmStorageTest, MultipleFiles) {
|
||||
const char kFileName1[] = "file1";
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file1;
|
||||
EXPECT_TRUE(Open(kFileName1, cdm_file1));
|
||||
ASSERT_TRUE(cdm_file1.is_bound());
|
||||
|
||||
const char kFileName2[] = "file2";
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file2;
|
||||
EXPECT_TRUE(Open(kFileName2, cdm_file2));
|
||||
ASSERT_TRUE(cdm_file2.is_bound());
|
||||
|
||||
const char kFileName3[] = "file3";
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file3;
|
||||
EXPECT_TRUE(Open(kFileName3, cdm_file3));
|
||||
ASSERT_TRUE(cdm_file3.is_bound());
|
||||
}
|
||||
|
||||
TEST_F(CdmStorageTest, WriteThenReadFile) {
|
||||
const char kFileName[] = "test_file_name";
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file;
|
||||
EXPECT_TRUE(Open(kFileName, cdm_file));
|
||||
ASSERT_TRUE(cdm_file.is_bound());
|
||||
|
||||
// Write several bytes and read them back.
|
||||
std::vector<uint8_t> kTestData = {'r', 'a', 'n', 'd', 'o', 'm'};
|
||||
EXPECT_TRUE(Write(cdm_file.get(), kTestData));
|
||||
|
||||
std::vector<uint8_t> data_read;
|
||||
EXPECT_TRUE(Read(cdm_file.get(), data_read));
|
||||
EXPECT_EQ(kTestData, data_read);
|
||||
}
|
||||
|
||||
TEST_F(CdmStorageTest, ReadThenWriteEmptyFile) {
|
||||
const char kFileName[] = "empty_file_name";
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file;
|
||||
EXPECT_TRUE(Open(kFileName, cdm_file));
|
||||
ASSERT_TRUE(cdm_file.is_bound());
|
||||
|
||||
// New file should be empty.
|
||||
std::vector<uint8_t> data_read;
|
||||
EXPECT_TRUE(Read(cdm_file.get(), data_read));
|
||||
EXPECT_EQ(0u, data_read.size());
|
||||
|
||||
// Write nothing.
|
||||
EXPECT_TRUE(Write(cdm_file.get(), std::vector<uint8_t>()));
|
||||
|
||||
// Should still be empty.
|
||||
EXPECT_TRUE(Read(cdm_file.get(), data_read));
|
||||
EXPECT_EQ(0u, data_read.size());
|
||||
}
|
||||
|
||||
TEST_F(CdmStorageTest, ParallelRead) {
|
||||
const char kFileName[] = "duplicate_read_file_name";
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file;
|
||||
EXPECT_TRUE(Open(kFileName, cdm_file));
|
||||
ASSERT_TRUE(cdm_file.is_bound());
|
||||
|
||||
// Attempts to reads the contents of the previously opened |cdm_file| twice.
|
||||
// We don't really care about the data, just that the first read succeeds.
|
||||
base::test::TestFuture<CdmFile::Status, std::vector<uint8_t>> future1;
|
||||
base::test::TestFuture<CdmFile::Status, std::vector<uint8_t>> future2;
|
||||
|
||||
cdm_file->Read(
|
||||
future1.GetCallback<CdmFile::Status, const std::vector<uint8_t>&>());
|
||||
cdm_file->Read(
|
||||
future2.GetCallback<CdmFile::Status, const std::vector<uint8_t>&>());
|
||||
|
||||
EXPECT_TRUE(future1.Wait());
|
||||
EXPECT_TRUE(future2.Wait());
|
||||
|
||||
CdmFile::Status status1 = future1.Get<0>();
|
||||
CdmFile::Status status2 = future2.Get<0>();
|
||||
|
||||
// The first call should succeed. The second call might fail, if its blocked
|
||||
// by our locking system, or might succeed, if the reads are processed faster
|
||||
// than expected.
|
||||
EXPECT_TRUE(status1 == CdmFile::Status::kSuccess &&
|
||||
(status2 == CdmFile::Status::kFailure ||
|
||||
status2 == CdmFile::Status::kSuccess))
|
||||
<< "status 1: " << status1 << ", status2: " << status2;
|
||||
}
|
||||
|
||||
TEST_F(CdmStorageTest, ParallelWrite) {
|
||||
const char kFileName[] = "duplicate_write_file_name";
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file;
|
||||
EXPECT_TRUE(Open(kFileName, cdm_file));
|
||||
ASSERT_TRUE(cdm_file.is_bound());
|
||||
|
||||
// Attempts to write the contents of the previously opened |cdm_file| twice.
|
||||
// We don't really care about the data, just that the first write succeeds.
|
||||
base::test::TestFuture<CdmFile::Status> future1;
|
||||
base::test::TestFuture<CdmFile::Status> future2;
|
||||
|
||||
cdm_file->Write({1, 2, 3}, future1.GetCallback());
|
||||
cdm_file->Write({4, 5, 6}, future2.GetCallback());
|
||||
|
||||
EXPECT_TRUE(future1.Wait());
|
||||
EXPECT_TRUE(future2.Wait());
|
||||
|
||||
CdmFile::Status status1 = future1.Get();
|
||||
CdmFile::Status status2 = future2.Get();
|
||||
|
||||
// The first call should succeed. The second call might fail, if its blocked
|
||||
// by our locking system, or might succeed, if the writes are processed
|
||||
// faster than expected.
|
||||
EXPECT_TRUE(status1 == CdmFile::Status::kSuccess &&
|
||||
(status2 == CdmFile::Status::kSuccess ||
|
||||
status2 == CdmFile::Status::kFailure))
|
||||
<< "status 1: " << status1 << ", status2: " << status2;
|
||||
}
|
||||
|
||||
TEST_F(CdmStorageTest, VerifyMigrationWorks) {
|
||||
const char kFileName[] = "test_file_name";
|
||||
mojo::AssociatedRemote<CdmFile> cdm_file;
|
||||
EXPECT_TRUE(Open(kFileName, cdm_file));
|
||||
ASSERT_TRUE(cdm_file.is_bound());
|
||||
|
||||
// Write several bytes and read them back.
|
||||
std::vector<uint8_t> kTestData = {'r', 'a', 'n', 'd', 'o', 'm'};
|
||||
EXPECT_TRUE(Write(cdm_file.get(), kTestData));
|
||||
|
||||
std::vector<uint8_t> data_read;
|
||||
EXPECT_TRUE(Read(cdm_file.get(), data_read));
|
||||
EXPECT_EQ(data_read, kTestData);
|
||||
|
||||
base::test::TestFuture<std::optional<std::vector<uint8_t>>> read_future;
|
||||
cdm_storage_manager()->ReadFile(
|
||||
blink::StorageKey::CreateFromStringForTesting(kTestOrigin),
|
||||
kTestCdmType, kFileName, read_future.GetCallback());
|
||||
EXPECT_EQ(read_future.Get(), kTestData);
|
||||
|
||||
// Write nothing.
|
||||
EXPECT_TRUE(Write(cdm_file.get(), std::vector<uint8_t>()));
|
||||
|
||||
// Should still be empty.
|
||||
EXPECT_TRUE(Read(cdm_file.get(), data_read));
|
||||
EXPECT_THAT(data_read, testing::IsEmpty());
|
||||
|
||||
base::test::TestFuture<std::optional<std::vector<uint8_t>>>
|
||||
read_empty_file_future;
|
||||
cdm_storage_manager()->ReadFile(
|
||||
blink::StorageKey::CreateFromStringForTesting(kTestOrigin),
|
||||
kTestCdmType, kFileName, read_empty_file_future.GetCallback());
|
||||
EXPECT_THAT(read_empty_file_future.Get().value(), testing::IsEmpty());
|
||||
}
|
||||
|
||||
} // namespace content
|
@ -172,6 +172,7 @@
|
||||
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
|
||||
#include "content/browser/media/cdm_storage_common.h"
|
||||
#include "content/browser/media/cdm_storage_manager.h"
|
||||
#include "content/browser/media/media_license_manager.h"
|
||||
#include "content/public/browser/cdm_storage_data_model.h"
|
||||
#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS)
|
||||
|
||||
@ -788,6 +789,9 @@ storage::QuotaClientTypes StoragePartitionImpl::GenerateQuotaClientTypes(
|
||||
if (remove_mask & StoragePartition::REMOVE_DATA_MASK_BACKGROUND_FETCH) {
|
||||
quota_client_types.insert(storage::QuotaClientType::kBackgroundFetch);
|
||||
}
|
||||
if (remove_mask & StoragePartition::REMOVE_DATA_MASK_MEDIA_LICENSES) {
|
||||
quota_client_types.insert(storage::QuotaClientType::kMediaLicense);
|
||||
}
|
||||
return quota_client_types;
|
||||
}
|
||||
|
||||
@ -1841,6 +1845,11 @@ void StoragePartitionImpl::SetFontAccessManagerForTesting(
|
||||
}
|
||||
|
||||
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
|
||||
MediaLicenseManager* StoragePartitionImpl::GetMediaLicenseManager() {
|
||||
DCHECK(initialized_);
|
||||
return media_license_manager_.get();
|
||||
}
|
||||
|
||||
CdmStorageDataModel* StoragePartitionImpl::GetCdmStorageDataModel() {
|
||||
DCHECK(initialized_);
|
||||
return cdm_storage_manager_.get();
|
||||
@ -2815,7 +2824,7 @@ void StoragePartitionImpl::DataDeletionHelper::ClearDataOnUIThread(
|
||||
}
|
||||
|
||||
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
|
||||
if ((remove_mask_ & REMOVE_DATA_MASK_MEDIA_LICENSES)) {
|
||||
if ((remove_mask_ & REMOVE_DATA_MASK_MEDIA_LICENSES) && cdm_storage_manager) {
|
||||
auto cdm_deletion_callback = base::BindOnce(
|
||||
base::IgnoreArgs<bool>(mojo::WrapCallbackWithDefaultInvokeIfNotRun(
|
||||
CreateTaskCompletionClosure(TracingDataType::kCdmStorage))));
|
||||
@ -2825,11 +2834,14 @@ void StoragePartitionImpl::DataDeletionHelper::ClearDataOnUIThread(
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS)
|
||||
|
||||
// TODO(crbug.com/40272342): Remove REMOVE_DATA_MASK_MEDIA_LICENSES from here
|
||||
// when MediaLicense is removed from Quota types.
|
||||
if (remove_mask_ & REMOVE_DATA_MASK_INDEXEDDB ||
|
||||
remove_mask_ & REMOVE_DATA_MASK_WEBSQL ||
|
||||
remove_mask_ & REMOVE_DATA_MASK_FILE_SYSTEMS ||
|
||||
remove_mask_ & REMOVE_DATA_MASK_SERVICE_WORKERS ||
|
||||
remove_mask_ & REMOVE_DATA_MASK_CACHE_STORAGE) {
|
||||
remove_mask_ & REMOVE_DATA_MASK_CACHE_STORAGE ||
|
||||
remove_mask_ & REMOVE_DATA_MASK_MEDIA_LICENSES) {
|
||||
GetIOThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&DataDeletionHelper::ClearQuotaManagedDataOnIOThread,
|
||||
|
@ -93,6 +93,7 @@ class CacheStorageControlWrapper;
|
||||
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
|
||||
class CdmStorageDataModel;
|
||||
class CdmStorageManager;
|
||||
class MediaLicenseManager;
|
||||
#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS)
|
||||
class CookieDeprecationLabelManagerImpl;
|
||||
class CookieStoreManager;
|
||||
@ -292,6 +293,9 @@ class CONTENT_EXPORT StoragePartitionImpl
|
||||
const std::string& GetPartitionDomain() const;
|
||||
AggregationService* GetAggregationService();
|
||||
FontAccessManager* GetFontAccessManager();
|
||||
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
|
||||
MediaLicenseManager* GetMediaLicenseManager();
|
||||
#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS)
|
||||
|
||||
storage::SharedStorageManager* GetSharedStorageManager() override;
|
||||
PrivateAggregationManager* GetPrivateAggregationManager();
|
||||
@ -778,6 +782,10 @@ class CONTENT_EXPORT StoragePartitionImpl
|
||||
std::unique_ptr<AggregationService> aggregation_service_;
|
||||
#if BUILDFLAG(ENABLE_LIBRARY_CDMS)
|
||||
std::unique_ptr<CdmStorageManager> cdm_storage_manager_;
|
||||
|
||||
// TODO(crbug.com/40272342): Remove MediaLicenseManager once migration has
|
||||
// been completed.
|
||||
std::unique_ptr<MediaLicenseManager> media_license_manager_;
|
||||
#endif // BUILDFLAG(ENABLE_LIBRARY_CDMS)
|
||||
|
||||
// Owning pointer to the SharedStorageManager for this partition.
|
||||
|
@ -3291,6 +3291,8 @@ test("content_unittests") {
|
||||
"../browser/media/cdm_storage_database_unittest.cc",
|
||||
"../browser/media/cdm_storage_manager_unittest.cc",
|
||||
"../browser/media/key_system_support_impl_unittest.cc",
|
||||
"../browser/media/media_license_manager_unittest.cc",
|
||||
"../browser/media/media_license_storage_host_unittest.cc",
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ const QuotaClientTypes& AllQuotaClientTypes() {
|
||||
QuotaClientType::kServiceWorkerCache,
|
||||
QuotaClientType::kServiceWorker,
|
||||
QuotaClientType::kBackgroundFetch,
|
||||
QuotaClientType::kMediaLicense,
|
||||
}};
|
||||
return *all;
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ enum class QuotaClientType {
|
||||
kServiceWorkerCache = 4,
|
||||
kServiceWorker = 5,
|
||||
kBackgroundFetch = 6,
|
||||
kMediaLicense = 7,
|
||||
};
|
||||
|
||||
// Set of QuotaClientType values.
|
||||
|
@ -30,6 +30,8 @@ base::FilePath CreateClientBucketPath(const base::FilePath& profile_path,
|
||||
return bucket_directory.Append(kCacheStorageDirectory);
|
||||
case QuotaClientType::kServiceWorker:
|
||||
return bucket_directory.Append(kScriptCacheDirectory);
|
||||
case QuotaClientType::kMediaLicense:
|
||||
return bucket_directory.Append(kMediaLicenseDirectory);
|
||||
case QuotaClientType::kDatabase:
|
||||
NOTREACHED() << "Unsupported QuotaClientType";
|
||||
}
|
||||
|
@ -305,6 +305,13 @@ void UsageTracker::AccumulateClientUsageWithBreakdown(
|
||||
case QuotaClientType::kBackgroundFetch:
|
||||
info->usage_breakdown->backgroundFetch += total_usage;
|
||||
break;
|
||||
case QuotaClientType::kMediaLicense:
|
||||
// Media license data does not count against quota and should always
|
||||
// report 0 usage.
|
||||
// TODO(crbug.com/40218094): Consider counting media license data against
|
||||
// quota.
|
||||
DCHECK_EQ(total_usage, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
std::move(barrier_callback).Run();
|
||||
|
@ -1191,6 +1191,17 @@ chromium-metrics-reviews@google.com.
|
||||
<int value="12" label="Exit picture in picture"/>
|
||||
</enum>
|
||||
|
||||
<enum name="MediaLicenseStorageHostOpenError">
|
||||
<int value="0" label="InvalidBucket"/>
|
||||
<int value="1" label="kNoFileSpecified"/>
|
||||
<int value="2" label="kInvalidFileName"/>
|
||||
<int value="3" label="kDatabaseOpenError"/>
|
||||
<int value="4" label="kBucketNotFound"/>
|
||||
<int value="5" label="kDatabaseRazeError"/>
|
||||
<int value="6" label="kSQLExecutionError"/>
|
||||
<int value="7" label="kBucketLocatorError"/>
|
||||
</enum>
|
||||
|
||||
<enum name="MediaLoadType">
|
||||
<int value="0" label="URL"/>
|
||||
<int value="1" label="MediaSource"/>
|
||||
|
@ -2916,6 +2916,146 @@ chromium-metrics-reviews@google.com.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram
|
||||
name="Media.EME.MediaLicenseDatabase.WriteFile.FullDiskDataSizeBytes"
|
||||
units="bytes" expires_after="2025-02-27">
|
||||
<owner>vpasupathy@chromium.org</owner>
|
||||
<owner>media-dev-uma@chromium.org</owner>
|
||||
<summary>
|
||||
This UMA records the bytes of the data that fail to be written to the
|
||||
MediaLicenseDatabase during a full disk error. This histogram is logged when
|
||||
a write fails with a full disk error.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Media.EME.MediaLicenseDatabaseCreateDirectoryError"
|
||||
enum="PlatformFileError" expires_after="2025-02-27">
|
||||
<owner>vpasupathy@chromium.org</owner>
|
||||
<owner>media-dev-uma@chromium.org</owner>
|
||||
<summary>
|
||||
Platform File specific errors reported while attempting to open the Media
|
||||
License Database.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Media.EME.MediaLicenseDatabaseOpenQuotaError"
|
||||
enum="QuotaError" expires_after="2025-02-27">
|
||||
<owner>vpasupathy@chromium.org</owner>
|
||||
<owner>media-dev-uma@chromium.org</owner>
|
||||
<summary>
|
||||
Quota errors reported while attempting to unsuccessfully open the Media
|
||||
License Database.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Media.EME.MediaLicenseDatabaseOpenSQLiteError"
|
||||
enum="SqliteLoggedResultCode" expires_after="2025-02-27">
|
||||
<owner>vpasupathy@chromium.org</owner>
|
||||
<owner>media-dev-uma@chromium.org</owner>
|
||||
<summary>
|
||||
SQLite specific errors reported while attempting to unsuccessfully open the
|
||||
Media License Database.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Media.EME.MediaLicenseDatabaseSQLiteError"
|
||||
enum="SqliteLoggedResultCode" expires_after="2025-02-27">
|
||||
<owner>vpasupathy@chromium.org</owner>
|
||||
<owner>media-dev-uma@chromium.org</owner>
|
||||
<summary>
|
||||
SQLite specific errors reported while opening and using the Media License
|
||||
Database.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Media.EME.MediaLicenseDatabaseSQLiteError.DeleteFile"
|
||||
enum="SqliteLoggedResultCode" expires_after="2025-02-27">
|
||||
<owner>vpasupathy@chromium.org</owner>
|
||||
<owner>media-dev-uma@chromium.org</owner>
|
||||
<summary>
|
||||
This UMA records SQLite specific errors that occur when deleting a file from
|
||||
the Media License Database. This UMA is recorded on every error for this
|
||||
operation.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Media.EME.MediaLicenseDatabaseSQLiteError.QueryPageCount"
|
||||
enum="SqliteLoggedResultCode" expires_after="2025-02-27">
|
||||
<owner>vpasupathy@chromium.org</owner>
|
||||
<owner>media-dev-uma@chromium.org</owner>
|
||||
<summary>
|
||||
This UMA records SQLite specific errors that occur when querying for
|
||||
page_count from the Media License Database. This UMA is recorded on every
|
||||
error for this operation.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Media.EME.MediaLicenseDatabaseSQLiteError.QueryPageSize"
|
||||
enum="SqliteLoggedResultCode" expires_after="2025-02-27">
|
||||
<owner>vpasupathy@chromium.org</owner>
|
||||
<owner>media-dev-uma@chromium.org</owner>
|
||||
<summary>
|
||||
This UMA records SQLite specific errors that occur when querying for page
|
||||
size from the Media License Database. This UMA is recorded on every error
|
||||
for this operation.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Media.EME.MediaLicenseDatabaseSQLiteError.ReadFile"
|
||||
enum="SqliteLoggedResultCode" expires_after="2025-02-27">
|
||||
<owner>vpasupathy@chromium.org</owner>
|
||||
<owner>media-dev-uma@chromium.org</owner>
|
||||
<summary>
|
||||
This UMA records SQLite specific errors that occur when reading a file from
|
||||
the Media License Database. This UMA is recorded on every error for this
|
||||
operation.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Media.EME.MediaLicenseDatabaseSQLiteError.WriteFile"
|
||||
enum="SqliteLoggedResultCode" expires_after="2025-02-27">
|
||||
<owner>vpasupathy@chromium.org</owner>
|
||||
<owner>media-dev-uma@chromium.org</owner>
|
||||
<summary>
|
||||
This UMA records SQLite specific errors that occur when writing a file to
|
||||
the Media License Database. This UMA is recorded on every error for this
|
||||
operation.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Media.EME.MediaLicenseStorageHost.CurrentDatabaseUsageKB"
|
||||
units="KB" expires_after="2025-02-27">
|
||||
<owner>vpasupathy@chromium.org</owner>
|
||||
<owner>media-dev-uma@chromium.org</owner>
|
||||
<summary>
|
||||
This UMA records the current MediaLicenseDatabase size in KB. This UMA is
|
||||
recorded only once, on the first read, to retrieve usage levels of the
|
||||
MediaLicenseDatabase.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Media.EME.MediaLicenseStorageHostOpenError.Incognito"
|
||||
enum="MediaLicenseStorageHostOpenError" expires_after="2025-02-27">
|
||||
<owner>vpasupathy@chromium.org</owner>
|
||||
<owner>media-dev-uma@chromium.org</owner>
|
||||
<summary>
|
||||
General errors reported while attempting to open the MediaLicenseDatabase.
|
||||
This UMA further breaks down the UMA above by only reporting errors if the
|
||||
media license storage host error occurs during an incognito process.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Media.EME.MediaLicenseStorageHostOpenError.NotIncognito"
|
||||
enum="MediaLicenseStorageHostOpenError" expires_after="2025-02-27">
|
||||
<owner>vpasupathy@chromium.org</owner>
|
||||
<owner>media-dev-uma@chromium.org</owner>
|
||||
<summary>
|
||||
General errors reported while attempting to open the MediaLicenseDatabase.
|
||||
This UMA further breaks down the UMA above by only reporting errors if the
|
||||
media license storage host error occurs during a non incognito process.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Media.EME.MojoCdm.ConnectionError"
|
||||
enum="BooleanConnectionError" expires_after="2025-02-27">
|
||||
<owner>xhwang@chromium.org</owner>
|
||||
|
Reference in New Issue
Block a user