Commit d63db359 authored by Becca Hughes's avatar Becca Hughes Committed by Commit Bot

[Media History] Clear history by origin

Add support for clearing media history if we
clear it by origin.

BUG=1024352

Change-Id: I8c053d8cca11b0a63b1e4cc9168acf8fbc3b3e73
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2057634Reviewed-by: default avatarTommy Steimel <steimel@chromium.org>
Commit-Queue: Becca Hughes <beccahughes@chromium.org>
Cr-Commit-Position: refs/heads/master@{#745198}
parent dee355ff
......@@ -26,11 +26,35 @@ sql::InitStatus MediaHistoryImagesTable::CreateTableIfNonExistent() {
bool success =
DB()->Execute(base::StringPrintf("CREATE TABLE IF NOT EXISTS %s("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"url TEXT NOT NULL UNIQUE,"
"mime_type TEXT)",
"playback_origin_id INTEGER NOT NULL,"
"url TEXT NOT NULL,"
"mime_type TEXT, "
"CONSTRAINT fk_origin "
"FOREIGN KEY (playback_origin_id) "
"REFERENCES origin(id) "
"ON DELETE CASCADE"
")",
kTableName)
.c_str());
if (success) {
success = DB()->Execute(
base::StringPrintf(
"CREATE INDEX IF NOT EXISTS playback_origin_id_index ON "
"%s (playback_origin_id)",
kTableName)
.c_str());
}
if (success) {
success = DB()->Execute(
base::StringPrintf(
"CREATE UNIQUE INDEX IF NOT EXISTS image_origin_url_index ON "
"%s (playback_origin_id, url)",
kTableName)
.c_str());
}
if (!success) {
ResetDB();
LOG(ERROR) << "Failed to create media history images table.";
......@@ -42,6 +66,7 @@ sql::InitStatus MediaHistoryImagesTable::CreateTableIfNonExistent() {
base::Optional<int64_t> MediaHistoryImagesTable::SaveOrGetImage(
const GURL& url,
const url::Origin& playback_origin,
const base::string16& mime_type) {
DCHECK_LT(0, DB()->transaction_nesting());
if (!CanAccessDatabase())
......@@ -51,12 +76,16 @@ base::Optional<int64_t> MediaHistoryImagesTable::SaveOrGetImage(
// First we should try and save the image in the database. It will not save
// if we already have this image in the DB.
sql::Statement statement(DB()->GetCachedStatement(
SQL_FROM_HERE, base::StringPrintf("INSERT OR IGNORE INTO %s "
"(url, mime_type) VALUES (?, ?)",
kTableName)
.c_str()));
statement.BindString(0, url.spec());
statement.BindString16(1, mime_type);
SQL_FROM_HERE,
base::StringPrintf("INSERT OR IGNORE INTO %s "
"(playback_origin_id, url, mime_type) VALUES "
"((SELECT id FROM origin WHERE origin = ?), ?, ?)",
kTableName)
.c_str()));
statement.BindString(
0, MediaHistoryOriginTable::GetOriginForStorage(playback_origin));
statement.BindString(1, url.spec());
statement.BindString16(2, mime_type);
if (!statement.Run())
return base::nullopt;
......@@ -78,9 +107,14 @@ base::Optional<int64_t> MediaHistoryImagesTable::SaveOrGetImage(
// If we did not save the image then we need to find the ID of the image.
sql::Statement statement(DB()->GetCachedStatement(
SQL_FROM_HERE,
base::StringPrintf("SELECT id FROM %s WHERE url = ?", kTableName)
base::StringPrintf(
"SELECT id FROM %s WHERE playback_origin_id = (SELECT id FROM "
"origin WHERE origin = ?) AND url = ?",
kTableName)
.c_str()));
statement.BindString(0, url.spec());
statement.BindString(
0, MediaHistoryOriginTable::GetOriginForStorage(playback_origin));
statement.BindString(1, url.spec());
while (statement.Step()) {
return statement.ColumnInt64(0);
......
......@@ -13,6 +13,10 @@ namespace base {
class UpdateableSequencedTaskRunner;
} // namespace base
namespace url {
class Origin;
} // namespace url
namespace media_history {
class MediaHistoryImagesTable : public MediaHistoryTableBase {
......@@ -33,6 +37,7 @@ class MediaHistoryImagesTable : public MediaHistoryTableBase {
// Saves the image or gets the image ID if it is already in the database.
base::Optional<int64_t> SaveOrGetImage(const GURL& url,
const url::Origin& playback_origin,
const base::string16& mime_type);
};
......
......@@ -54,13 +54,35 @@ void MediaHistoryKeyedService::Shutdown() {
void MediaHistoryKeyedService::OnURLsDeleted(
history::HistoryService* history_service,
const history::DeletionInfo& deletion_info) {
if (!deletion_info.IsAllHistory()) {
// TODO(https://crbug.com/1024352): Handle fine-grained history deletion.
if (deletion_info.IsAllHistory()) {
// Destroy the old database and create a new one.
media_history_store_->EraseDatabaseAndCreateNew();
return;
}
// Destroy the old database and create a new one.
media_history_store_->EraseDatabaseAndCreateNew();
// Build a set of all origins in |deleted_rows|.
std::set<url::Origin> origins;
for (const history::URLRow& row : deletion_info.deleted_rows()) {
origins.insert(url::Origin::Create(row.url()));
}
// Find any origins that do not have any more data in the history database.
std::set<url::Origin> no_more_origins;
for (const url::Origin& origin : origins) {
const auto& origin_count =
deletion_info.deleted_urls_origin_map().find(origin.GetURL());
if (origin_count->second.first > 0)
continue;
no_more_origins.insert(origin);
}
if (!no_more_origins.empty())
media_history_store_->DeleteAllOriginData(no_more_origins);
// TODO(https://crbug.com/1024352): For any origins that still have data we
// should remove data by URL.
}
} // namespace media_history
......@@ -9,12 +9,16 @@
#include "base/bind.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_mock_time_task_runner.h"
#include "build/build_config.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/media/history/media_history_images_table.h"
#include "chrome/browser/media/history/media_history_session_images_table.h"
#include "chrome/browser/media/history/media_history_session_table.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/history/core/browser/history_database_params.h"
......@@ -22,6 +26,9 @@
#include "components/history/core/test/test_history_database.h"
#include "content/public/browser/media_player_watch_time.h"
#include "content/public/test/test_utils.h"
#include "services/media_session/public/cpp/media_image.h"
#include "services/media_session/public/cpp/media_metadata.h"
#include "services/media_session/public/cpp/media_position.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media_history {
......@@ -103,9 +110,6 @@ class MediaHistoryKeyedServiceTest : public ChromeRenderViewHostTestHarness {
return count;
}
scoped_refptr<base::TestMockTimeTaskRunner> mock_time_task_runner_;
private:
mojom::MediaHistoryStatsPtr GetStatsSync() {
base::RunLoop run_loop;
mojom::MediaHistoryStatsPtr stats_out;
......@@ -122,6 +126,35 @@ class MediaHistoryKeyedServiceTest : public ChromeRenderViewHostTestHarness {
return stats_out;
}
std::set<GURL> GetURLsInTable(const std::string& table) {
base::RunLoop run_loop;
std::set<GURL> out;
service()->GetMediaHistoryStore()->GetURLsInTableForTest(
table, base::BindLambdaForTesting([&](std::set<GURL> urls) {
out = urls;
run_loop.Quit();
}));
run_loop.Run();
return out;
}
std::vector<media_session::MediaImage> CreateImageVector(const GURL& url) {
std::vector<media_session::MediaImage> images;
media_session::MediaImage image;
image.src = url;
image.sizes.push_back(gfx::Size(10, 10));
image.sizes.push_back(gfx::Size(20, 20));
images.push_back(image);
return images;
}
scoped_refptr<base::TestMockTimeTaskRunner> mock_time_task_runner_;
private:
base::ScopedTempDir temp_dir_;
std::unique_ptr<MediaHistoryKeyedService> service_;
......@@ -168,4 +201,183 @@ TEST_F(MediaHistoryKeyedServiceTest, CleanUpDatabaseWhenHistoryIsDeleted) {
EXPECT_EQ(0, GetUserDataTableRowCount());
}
TEST_F(MediaHistoryKeyedServiceTest, CleanUpDatabaseWhenOriginIsDeleted) {
history::HistoryService* history = HistoryServiceFactory::GetForProfile(
profile(), ServiceAccessType::IMPLICIT_ACCESS);
GURL url1a("https://www.google.com/test1A");
GURL url1b("https://www.google.com/test1B");
GURL url1c("https://www.google.com/test1C");
GURL url2a("https://example.com/test2A");
GURL url2b("https://example.com/test2B");
// Images associated with a media session do not need to be on the same origin
// as where the playback happened.
GURL url1a_image("https://gstatic.com/test1A.png");
GURL url1b_image("https://www.google.com/test1B.png");
GURL url2a_image("https://examplestatic.com/test2B.png");
GURL shared_image("https://gstatic.com/shared.png");
// Create a set that has all the URLs.
std::set<GURL> all_urls;
all_urls.insert(url1a);
all_urls.insert(url1b);
all_urls.insert(url1c);
all_urls.insert(url2a);
all_urls.insert(url2b);
// Create a set that has the URLs that will not be deleted.
std::set<GURL> remaining;
remaining.insert(url2a);
remaining.insert(url2b);
// Create a set that has all the image URLs.
std::set<GURL> images;
images.insert(url1a_image);
images.insert(url1b_image);
images.insert(url2a_image);
images.insert(shared_image);
// Create a set that has the image URLs that will not be deleted.
std::set<GURL> remaining_images;
remaining_images.insert(url2a_image);
remaining_images.insert(shared_image);
// The tables should be empty.
EXPECT_EQ(0, GetUserDataTableRowCount());
// Record a playback in the database for |url1a|.
{
content::MediaPlayerWatchTime watch_time(
url1a, url1a.GetOrigin(), base::TimeDelta::FromMilliseconds(123),
base::TimeDelta::FromMilliseconds(321), true, false);
history->AddPage(url1a, base::Time::Now(), history::SOURCE_BROWSED);
service()->GetMediaHistoryStore()->SavePlayback(watch_time);
service()->GetMediaHistoryStore()->SavePlaybackSession(
url1a, media_session::MediaMetadata(), base::nullopt,
CreateImageVector(url1a_image));
}
// Record a playback in the database for |url1b|.
{
content::MediaPlayerWatchTime watch_time(
url1b, url1b.GetOrigin(), base::TimeDelta::FromMilliseconds(123),
base::TimeDelta::FromMilliseconds(321), true, false);
history->AddPage(url1b, base::Time::Now(), history::SOURCE_BROWSED);
service()->GetMediaHistoryStore()->SavePlayback(watch_time);
service()->GetMediaHistoryStore()->SavePlaybackSession(
url1b, media_session::MediaMetadata(), base::nullopt,
CreateImageVector(url1b_image));
}
// Record a playback in the database for |url1c|. However, we won't store it
// in the history to ensure that the history service is clearing data at
// origin-level.
{
content::MediaPlayerWatchTime watch_time(
url1c, url1c.GetOrigin(), base::TimeDelta::FromMilliseconds(123),
base::TimeDelta::FromMilliseconds(321), true, false);
service()->GetMediaHistoryStore()->SavePlayback(watch_time);
service()->GetMediaHistoryStore()->SavePlaybackSession(
url1c, media_session::MediaMetadata(), base::nullopt,
CreateImageVector(shared_image));
}
// Record a playback in the database for |url2a|.
{
content::MediaPlayerWatchTime watch_time(
url2a, url2a.GetOrigin(), base::TimeDelta::FromMilliseconds(123),
base::TimeDelta::FromMilliseconds(321), true, false);
history->AddPage(url2a, base::Time::Now(), history::SOURCE_BROWSED);
service()->GetMediaHistoryStore()->SavePlayback(watch_time);
service()->GetMediaHistoryStore()->SavePlaybackSession(
url2a, media_session::MediaMetadata(), base::nullopt,
CreateImageVector(url2a_image));
}
// Record a playback in the database for |url2b|.
{
content::MediaPlayerWatchTime watch_time(
url2b, url2b.GetOrigin(), base::TimeDelta::FromMilliseconds(123),
base::TimeDelta::FromMilliseconds(321), true, false);
history->AddPage(url2b, base::Time::Now(), history::SOURCE_BROWSED);
service()->GetMediaHistoryStore()->SavePlayback(watch_time);
service()->GetMediaHistoryStore()->SavePlaybackSession(
url2b, media_session::MediaMetadata(), base::nullopt,
CreateImageVector(shared_image));
}
// Wait until the playbacks have finished saving.
content::RunAllTasksUntilIdle();
{
// Check that the tables have the right count in them.
mojom::MediaHistoryStatsPtr stats = GetStatsSync();
EXPECT_EQ(2, stats->table_row_counts[MediaHistoryOriginTable::kTableName]);
EXPECT_EQ(5,
stats->table_row_counts[MediaHistoryPlaybackTable::kTableName]);
EXPECT_EQ(5, stats->table_row_counts[MediaHistorySessionTable::kTableName]);
// There are 10 session images because each session has an image with two
// sizes.
EXPECT_EQ(
10,
stats->table_row_counts[MediaHistorySessionImagesTable::kTableName]);
EXPECT_EQ(5, stats->table_row_counts[MediaHistoryImagesTable::kTableName]);
}
// Check the URLs are present in the tables.
EXPECT_EQ(all_urls, GetURLsInTable(MediaHistoryPlaybackTable::kTableName));
EXPECT_EQ(all_urls, GetURLsInTable(MediaHistorySessionTable::kTableName));
EXPECT_EQ(images, GetURLsInTable(MediaHistoryImagesTable::kTableName));
{
base::CancelableTaskTracker task_tracker;
// Expire url1a and url1b.
std::vector<history::ExpireHistoryArgs> expire_list;
history::ExpireHistoryArgs args;
args.urls.insert(url1a);
args.urls.insert(url1b);
args.SetTimeRangeForOneDay(base::Time::Now());
expire_list.push_back(args);
history->ExpireHistory(expire_list, base::DoNothing(), &task_tracker);
mock_time_task_runner_->RunUntilIdle();
// Wait for the database to update.
content::RunAllTasksUntilIdle();
}
{
// Check that the tables have the right count in them.
mojom::MediaHistoryStatsPtr stats = GetStatsSync();
EXPECT_EQ(1, stats->table_row_counts[MediaHistoryOriginTable::kTableName]);
EXPECT_EQ(2,
stats->table_row_counts[MediaHistoryPlaybackTable::kTableName]);
EXPECT_EQ(2, stats->table_row_counts[MediaHistorySessionTable::kTableName]);
// There are 4 session images because each session has an image with two
// sizes.
EXPECT_EQ(
4, stats->table_row_counts[MediaHistorySessionImagesTable::kTableName]);
EXPECT_EQ(2, stats->table_row_counts[MediaHistoryImagesTable::kTableName]);
}
// Check we only have the remaining URLs in the tables.
EXPECT_EQ(remaining, GetURLsInTable(MediaHistoryPlaybackTable::kTableName));
EXPECT_EQ(remaining, GetURLsInTable(MediaHistorySessionTable::kTableName));
EXPECT_EQ(remaining_images,
GetURLsInTable(MediaHistoryImagesTable::kTableName));
}
} // namespace media_history
......@@ -107,4 +107,16 @@ bool MediaHistoryOriginTable::IncrementAggregateAudioVideoWatchTime(
return true;
}
bool MediaHistoryOriginTable::Delete(const url::Origin& origin) {
if (!CanAccessDatabase())
return false;
sql::Statement statement(DB()->GetCachedStatement(
SQL_FROM_HERE,
base::StringPrintf("DELETE FROM %s WHERE origin = ?", kTableName)
.c_str()));
statement.BindString(0, GetOriginForStorage(origin));
return statement.Run();
}
} // namespace media_history
......@@ -5,6 +5,8 @@
#ifndef CHROME_BROWSER_MEDIA_HISTORY_MEDIA_HISTORY_ORIGIN_TABLE_H_
#define CHROME_BROWSER_MEDIA_HISTORY_MEDIA_HISTORY_ORIGIN_TABLE_H_
#include <string>
#include "base/updateable_sequenced_task_runner.h"
#include "chrome/browser/media/history/media_history_table_base.h"
#include "sql/init_status.h"
......@@ -39,6 +41,10 @@ class MediaHistoryOriginTable : public MediaHistoryTableBase {
bool IncrementAggregateAudioVideoWatchTime(const url::Origin& origin,
const base::TimeDelta& time);
// Deletes an origin from the database and returns a flag as to whether this
// was successful.
bool Delete(const url::Origin& origin);
DISALLOW_COPY_AND_ASSIGN(MediaHistoryOriginTable);
};
......
......@@ -82,6 +82,9 @@ class MediaHistoryStoreInternal
base::Optional<MediaHistoryStore::GetPlaybackSessionsFilter> filter);
void RazeAndClose();
void DeleteAllOriginData(const std::set<url::Origin>& origins);
std::set<GURL> GetURLsInTableForTest(const std::string& table);
scoped_refptr<base::UpdateableSequencedTaskRunner> db_task_runner_;
const base::FilePath db_path_;
......@@ -135,7 +138,9 @@ void MediaHistoryStoreInternal::SavePlayback(
return;
}
// TODO(https://crbug.com/1052436): Remove the separate origin.
auto origin = url::Origin::Create(watch_time.origin);
CHECK_EQ(origin, url::Origin::Create(watch_time.url));
if (!(CreateOriginId(origin) && playback_table_->SavePlayback(watch_time))) {
DB()->RollbackTransaction();
......@@ -163,16 +168,24 @@ void MediaHistoryStoreInternal::Initialize() {
db_->Preload();
if (!db_->Execute("PRAGMA foreign_keys=1")) {
LOG(ERROR) << "Failed to enable foreign keys on the media history store.";
db_->Poison();
return;
}
meta_table_.Init(db_.get(), GetCurrentVersion(), kCompatibleVersionNumber);
sql::InitStatus status = CreateOrUpgradeIfNeeded();
if (status != sql::INIT_OK) {
LOG(ERROR) << "Failed to create or update the media history store.";
db_->Poison();
return;
}
status = InitializeTables();
if (status != sql::INIT_OK) {
LOG(ERROR) << "Failed to initialize the media history store tables.";
db_->Poison();
return;
}
......@@ -333,7 +346,8 @@ void MediaHistoryStoreInternal::SavePlaybackSession(
}
for (auto& image : artwork) {
auto image_id = images_table_->SaveOrGetImage(image.src, image.type);
auto image_id =
images_table_->SaveOrGetImage(image.src, origin, image.type);
if (!image_id) {
DB()->RollbackTransaction();
return;
......@@ -381,6 +395,46 @@ void MediaHistoryStoreInternal::RazeAndClose() {
sql::Database::Delete(db_path_);
}
void MediaHistoryStoreInternal::DeleteAllOriginData(
const std::set<url::Origin>& origins) {
DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
if (!initialization_successful_)
return;
if (!DB()->BeginTransaction()) {
LOG(ERROR) << "Failed to begin the transaction.";
return;
}
for (auto& origin : origins) {
if (!origin_table_->Delete(origin)) {
DB()->RollbackTransaction();
return;
}
}
DB()->CommitTransaction();
}
std::set<GURL> MediaHistoryStoreInternal::GetURLsInTableForTest(
const std::string& table) {
std::set<GURL> urls;
DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
if (!initialization_successful_)
return urls;
sql::Statement statement(DB()->GetUniqueStatement(
base::StringPrintf("SELECT url from %s", table.c_str()).c_str()));
while (statement.Step()) {
urls.insert(GURL(statement.ColumnString(0)));
}
DCHECK(statement.Succeeded());
return urls;
}
MediaHistoryStore::MediaHistoryStore(
Profile* profile,
scoped_refptr<base::UpdateableSequencedTaskRunner> db_task_runner)
......@@ -480,4 +534,21 @@ void MediaHistoryStore::GetPlaybackSessions(
std::move(callback));
}
void MediaHistoryStore::DeleteAllOriginData(
const std::set<url::Origin>& origins) {
db_->db_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&MediaHistoryStoreInternal::DeleteAllOriginData,
db_, origins));
}
void MediaHistoryStore::GetURLsInTableForTest(
const std::string& table,
base::OnceCallback<void(std::set<GURL>)> callback) {
base::PostTaskAndReplyWithResult(
db_->db_task_runner_.get(), FROM_HERE,
base::BindOnce(&MediaHistoryStoreInternal::GetURLsInTableForTest, db_,
table),
std::move(callback));
}
} // namespace media_history
......@@ -6,6 +6,7 @@
#define CHROME_BROWSER_MEDIA_HISTORY_MEDIA_HISTORY_STORE_H_
#include <memory>
#include <set>
#include <vector>
#include "base/callback_forward.h"
......@@ -33,6 +34,10 @@ namespace sql {
class Database;
} // namespace sql
namespace url {
class Origin;
} // namespace url
namespace media_history {
class MediaHistoryStoreInternal;
......@@ -86,8 +91,13 @@ class MediaHistoryStore {
protected:
friend class MediaHistoryKeyedService;
friend class MediaHistoryKeyedServiceTest;
void EraseDatabaseAndCreateNew();
void DeleteAllOriginData(const std::set<url::Origin>& origins);
void GetURLsInTableForTest(const std::string& table,
base::OnceCallback<void(std::set<GURL>)> callback);
private:
scoped_refptr<MediaHistoryStoreInternal> db_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment