Commit 13ee72a5 authored by Sam Bowen's avatar Sam Bowen Committed by Commit Bot

[Media Feeds] Add method to fetch top feeds in the background

See the bug description. This is step (1) of the scaled back
requirements. We will post this task in an upcoming CL.

For mojo change, we need to add the reset token to be saved in
the DB so we can bypass an unnecessary DB call in this new code
path.

Bug: 1064751
Change-Id: I31ac1261a5f0e9fccb89787994eba65830cef092
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2243671
Commit-Queue: Sam Bowen <sgbowen@google.com>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarBecca Hughes <beccahughes@chromium.org>
Cr-Commit-Position: refs/heads/master@{#780462}
parent c92a89d5
...@@ -5,12 +5,18 @@ ...@@ -5,12 +5,18 @@
#include "chrome/browser/media/feeds/media_feeds_service.h" #include "chrome/browser/media/feeds/media_feeds_service.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/callback_forward.h"
#include "base/feature_list.h" #include "base/feature_list.h"
#include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_functions.h"
#include "base/optional.h"
#include "base/threading/thread_checker.h" #include "base/threading/thread_checker.h"
#include "base/time/clock.h"
#include "base/time/default_clock.h"
#include "base/time/time_to_iso8601.h"
#include "chrome/browser/media/feeds/media_feeds_converter.h" #include "chrome/browser/media/feeds/media_feeds_converter.h"
#include "chrome/browser/media/feeds/media_feeds_fetcher.h" #include "chrome/browser/media/feeds/media_feeds_fetcher.h"
#include "chrome/browser/media/feeds/media_feeds_service_factory.h" #include "chrome/browser/media/feeds/media_feeds_service_factory.h"
#include "chrome/browser/media/feeds/media_feeds_store.mojom-forward.h"
#include "chrome/browser/media/feeds/media_feeds_store.mojom-shared.h" #include "chrome/browser/media/feeds/media_feeds_store.mojom-shared.h"
#include "chrome/browser/media/feeds/media_feeds_store.mojom.h" #include "chrome/browser/media/feeds/media_feeds_store.mojom.h"
#include "chrome/browser/media/history/media_history_keyed_service.h" #include "chrome/browser/media/history/media_history_keyed_service.h"
...@@ -46,6 +52,15 @@ GURL Normalize(const GURL& url) { ...@@ -46,6 +52,15 @@ GURL Normalize(const GURL& url) {
return url.ReplaceComponents(replacements); return url.ReplaceComponents(replacements);
} }
media_history::MediaHistoryKeyedService::MediaFeedFetchDetails
FetchDetailsFromFeed(const mojom::MediaFeedPtr& media_feed) {
media_history::MediaHistoryKeyedService::MediaFeedFetchDetails details;
details.url = media_feed->url;
details.last_fetch_result = media_feed->last_fetch_result;
details.reset_token = media_feed->reset_token;
return details;
}
class CookieChangeListener : public network::mojom::CookieChangeListener { class CookieChangeListener : public network::mojom::CookieChangeListener {
public: public:
using CookieCallback = using CookieCallback =
...@@ -120,13 +135,21 @@ class CookieChangeListener : public network::mojom::CookieChangeListener { ...@@ -120,13 +135,21 @@ class CookieChangeListener : public network::mojom::CookieChangeListener {
const char MediaFeedsService::kSafeSearchResultHistogramName[] = const char MediaFeedsService::kSafeSearchResultHistogramName[] =
"Media.Feeds.SafeSearch.Result"; "Media.Feeds.SafeSearch.Result";
// The maximum number of feeds to fetch when getting the top feeds.
const int kMaxTopFeedsToFetch = 5;
// The minimum watchtime required on the feed's origin before a feed can be
// considered a top feed.
constexpr base::TimeDelta kTopFeedsMinWatchTime =
base::TimeDelta::FromMinutes(30);
MediaFeedsService::MediaFeedsService(Profile* profile) MediaFeedsService::MediaFeedsService(Profile* profile)
: : cookie_change_listener_(std::make_unique<CookieChangeListener>(
cookie_change_listener_(std::make_unique<CookieChangeListener>(
profile, profile,
base::BindRepeating(&MediaFeedsService::OnResetOriginFromCookie, base::BindRepeating(&MediaFeedsService::OnResetOriginFromCookie,
base::Unretained(this)))), base::Unretained(this)))),
profile_(profile) { profile_(profile),
clock_(base::DefaultClock::GetInstance()) {
DCHECK(!profile->IsOffTheRecord()); DCHECK(!profile->IsOffTheRecord());
pref_change_registrar_.Init(profile_->GetPrefs()); pref_change_registrar_.Init(profile_->GetPrefs());
...@@ -182,7 +205,9 @@ void MediaFeedsService::SetSafeSearchCompletionCallbackForTest( ...@@ -182,7 +205,9 @@ void MediaFeedsService::SetSafeSearchCompletionCallbackForTest(
safe_search_completion_callback_ = std::move(callback); safe_search_completion_callback_ = std::move(callback);
} }
void MediaFeedsService::FetchMediaFeed(int64_t feed_id, void MediaFeedsService::FetchMediaFeed(const int64_t feed_id,
const bool bypass_cache,
media_feeds::mojom::MediaFeedPtr feed,
FetchMediaFeedCallback callback) { FetchMediaFeedCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
...@@ -198,9 +223,19 @@ void MediaFeedsService::FetchMediaFeed(int64_t feed_id, ...@@ -198,9 +223,19 @@ void MediaFeedsService::FetchMediaFeed(int64_t feed_id,
GetURLLoaderFactoryForFetcher()), GetURLLoaderFactoryForFetcher()),
std::move(callback))); std::move(callback)));
GetMediaHistoryService()->GetMediaFeedFetchDetails( if (feed) {
feed_id, base::BindOnce(&MediaFeedsService::OnGotFetchDetails, OnGotFetchDetails(feed_id, bypass_cache, FetchDetailsFromFeed(feed));
weak_factory_.GetWeakPtr(), feed_id)); } else {
GetMediaHistoryService()->GetMediaFeedFetchDetails(
feed_id,
base::BindOnce(&MediaFeedsService::OnGotFetchDetails,
weak_factory_.GetWeakPtr(), feed_id, bypass_cache));
}
}
void MediaFeedsService::FetchMediaFeed(int64_t feed_id,
FetchMediaFeedCallback callback) {
FetchMediaFeed(feed_id, false, nullptr, std::move(callback));
} }
media_history::MediaHistoryKeyedService* media_history::MediaHistoryKeyedService*
...@@ -296,6 +331,45 @@ void MediaFeedsService::ResetMediaFeed(const url::Origin& origin, ...@@ -296,6 +331,45 @@ void MediaFeedsService::ResetMediaFeed(const url::Origin& origin,
GetMediaHistoryService()->ResetMediaFeed(origin, reason); GetMediaHistoryService()->ResetMediaFeed(origin, reason);
} }
void MediaFeedsService::FetchTopMediaFeeds(base::OnceClosure callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!IsBackgroundFetchingEnabled())
return;
GetMediaHistoryService()->GetMediaFeeds(
media_history::MediaHistoryKeyedService::GetMediaFeedsRequest::
CreateTopFeedsForFetch(kMaxTopFeedsToFetch, kTopFeedsMinWatchTime),
base::BindOnce(&MediaFeedsService::OnGotTopFeeds,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void MediaFeedsService::OnGotTopFeeds(
base::OnceClosure callback,
std::vector<media_feeds::mojom::MediaFeedPtr> feeds) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
int feeds_fetched = 0;
auto it = feeds.begin();
while (feeds_fetched < kMaxTopFeedsToFetch && it != feeds.end()) {
auto& feed = *it;
auto background_fetch_feed_settings = GetBackgroundFetchFeedSettings(feed);
if (background_fetch_feed_settings.should_fetch) {
auto feed_id = feed->id;
FetchMediaFeed(
feed_id, /*bypass_cache=*/background_fetch_feed_settings.bypass_cache,
std::move(feed), base::DoNothing());
feeds_fetched++;
}
++it;
}
std::move(callback).Run();
}
void MediaFeedsService::OnCheckURLDone( void MediaFeedsService::OnCheckURLDone(
const media_history::MediaHistoryKeyedService::SafeSearchID id, const media_history::MediaHistoryKeyedService::SafeSearchID id,
const GURL& original_url, const GURL& original_url,
...@@ -388,6 +462,7 @@ MediaFeedsService::InflightSafeSearchCheck::~InflightSafeSearchCheck() = ...@@ -388,6 +462,7 @@ MediaFeedsService::InflightSafeSearchCheck::~InflightSafeSearchCheck() =
void MediaFeedsService::OnGotFetchDetails( void MediaFeedsService::OnGotFetchDetails(
const int64_t feed_id, const int64_t feed_id,
bool bypass_cache,
base::Optional< base::Optional<
media_history::MediaHistoryKeyedService::MediaFeedFetchDetails> media_history::MediaHistoryKeyedService::MediaFeedFetchDetails>
details) { details) {
...@@ -399,11 +474,12 @@ void MediaFeedsService::OnGotFetchDetails( ...@@ -399,11 +474,12 @@ void MediaFeedsService::OnGotFetchDetails(
return; return;
} }
bool should_bypass_cache =
bypass_cache ||
details->last_fetch_result != media_feeds::mojom::FetchResult::kSuccess;
fetches_.at(feed_id).fetcher->FetchFeed( fetches_.at(feed_id).fetcher->FetchFeed(
details->url, details->url, should_bypass_cache,
// If the last fetch result was not successful then we should make sure
// we don't get the bad result from the cache.
details->last_fetch_result != media_feeds::mojom::FetchResult::kSuccess,
// Use of unretained is safe because the callback is owned // Use of unretained is safe because the callback is owned
// by fetcher_, which will not outlive this. // by fetcher_, which will not outlive this.
base::BindOnce(&MediaFeedsService::OnFetchResponse, base::BindOnce(&MediaFeedsService::OnFetchResponse,
...@@ -480,6 +556,48 @@ void MediaFeedsService::OnResetOriginFromCookie( ...@@ -480,6 +556,48 @@ void MediaFeedsService::OnResetOriginFromCookie(
std::move(cookie_change_callback_).Run(); std::move(cookie_change_callback_).Run();
} }
MediaFeedsService::BackgroundFetchFeedSettings
MediaFeedsService::GetBackgroundFetchFeedSettings(
const media_feeds::mojom::MediaFeedPtr& feed) {
BackgroundFetchFeedSettings settings;
settings.should_fetch = false;
settings.bypass_cache = false;
// Fetches should be spaced 15 minutes apart with exponential backoff
// based on how many sequential times the fetch has failed.
if (feed->last_fetch_time.has_value()) {
// TODO(crbug.com/1064751): Consider using net::BackoffEntry for this.
base::Time next_fetch_time =
feed->last_fetch_time.value() +
base::TimeDelta::FromMinutes(15 * pow(2, feed->fetch_failed_count));
settings.should_fetch = next_fetch_time < clock_->Now();
}
// If we haven't gotten a non-cached version of the feed in a while, we should
// fetch.
if (feed->last_fetch_time_not_cache_hit.has_value()) {
base::Time next_fetch_time = feed->last_fetch_time_not_cache_hit.value() +
base::TimeDelta::FromHours(24);
if (next_fetch_time < clock_->Now()) {
settings.should_fetch = true;
settings.bypass_cache = true;
}
}
// If the feed has never been fetched, we should fetch it.
if (!feed->last_fetch_time.has_value()) {
settings.should_fetch = true;
}
// If the feed has been reset, we should fetch and ignore the cache.
if (feed->reset_reason != mojom::ResetReason::kNone) {
settings.should_fetch = true;
settings.bypass_cache = true;
}
return settings;
}
scoped_refptr<::network::SharedURLLoaderFactory> scoped_refptr<::network::SharedURLLoaderFactory>
MediaFeedsService::GetURLLoaderFactoryForFetcher() { MediaFeedsService::GetURLLoaderFactoryForFetcher() {
if (test_url_loader_factory_for_fetcher_) if (test_url_loader_factory_for_fetcher_)
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <memory> #include <memory>
#include <set> #include <set>
#include "base/callback_forward.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h" #include "base/threading/thread_checker.h"
#include "chrome/browser/media/feeds/media_feeds_converter.h" #include "chrome/browser/media/feeds/media_feeds_converter.h"
...@@ -20,6 +21,10 @@ ...@@ -20,6 +21,10 @@
class Profile; class Profile;
class GURL; class GURL;
namespace base {
class Clock;
}
namespace safe_search_api { namespace safe_search_api {
enum class Classification; enum class Classification;
class URLChecker; class URLChecker;
...@@ -70,6 +75,12 @@ class MediaFeedsService : public KeyedService { ...@@ -70,6 +75,12 @@ class MediaFeedsService : public KeyedService {
// Fetches a media feed with the given ID and then store it in the // Fetches a media feed with the given ID and then store it in the
// feeds table in media history. Runs the given callback after storing. The // feeds table in media history. Runs the given callback after storing. The
// fetch will be skipped if another fetch is currently ongoing. // fetch will be skipped if another fetch is currently ongoing.
// If the feed is not supplied, it will be looked up in media history store in
// order to get details related to fetching.
void FetchMediaFeed(const int64_t feed_id,
const bool bypass_cache,
media_feeds::mojom::MediaFeedPtr feed,
FetchMediaFeedCallback callback);
void FetchMediaFeed(int64_t feed_id, FetchMediaFeedCallback callback); void FetchMediaFeed(int64_t feed_id, FetchMediaFeedCallback callback);
// Stores a callback to be called once we have completed all inflight checks. // Stores a callback to be called once we have completed all inflight checks.
...@@ -84,11 +95,17 @@ class MediaFeedsService : public KeyedService { ...@@ -84,11 +95,17 @@ class MediaFeedsService : public KeyedService {
void ResetMediaFeed(const url::Origin& origin, void ResetMediaFeed(const url::Origin& origin,
media_feeds::mojom::ResetReason reason); media_feeds::mojom::ResetReason reason);
// Check the list of discovered feeds and fetch a collection of those with the
// highest watchtime. This should be called periodically in the background.
void FetchTopMediaFeeds(base::OnceClosure callback);
bool HasCookieObserverForTest() const; bool HasCookieObserverForTest() const;
private: private:
friend class MediaFeedsServiceTest; friend class MediaFeedsServiceTest;
void SetClockForTesting(base::Clock* clock) { clock_ = clock; }
bool AddInflightSafeSearchCheck( bool AddInflightSafeSearchCheck(
const media_history::MediaHistoryKeyedService::SafeSearchID id, const media_history::MediaHistoryKeyedService::SafeSearchID id,
const std::set<GURL>& urls); const std::set<GURL>& urls);
...@@ -110,8 +127,12 @@ class MediaFeedsService : public KeyedService { ...@@ -110,8 +127,12 @@ class MediaFeedsService : public KeyedService {
bool IsSafeSearchCheckingEnabled() const; bool IsSafeSearchCheckingEnabled() const;
void OnGotTopFeeds(base::OnceClosure callback,
std::vector<media_feeds::mojom::MediaFeedPtr> feeds);
void OnGotFetchDetails( void OnGotFetchDetails(
const int64_t feed_id, const int64_t feed_id,
bool bypass_cache,
base::Optional< base::Optional<
media_history::MediaHistoryKeyedService::MediaFeedFetchDetails> media_history::MediaHistoryKeyedService::MediaFeedFetchDetails>
details); details);
...@@ -134,6 +155,18 @@ class MediaFeedsService : public KeyedService { ...@@ -134,6 +155,18 @@ class MediaFeedsService : public KeyedService {
void OnDiscoveredFeed(); void OnDiscoveredFeed();
// Settings related to fetching a feed in the background.
struct BackgroundFetchFeedSettings {
// Whether this feed should be fetched now.
bool should_fetch;
// Whether to use cached data (false) or bypass and get fresh data (true).
bool bypass_cache;
};
// Returns whether the feed should be fetched in the background as a top feed.
BackgroundFetchFeedSettings GetBackgroundFetchFeedSettings(
const media_feeds::mojom::MediaFeedPtr& feed);
media_history::MediaHistoryKeyedService* GetMediaHistoryService(); media_history::MediaHistoryKeyedService* GetMediaHistoryService();
scoped_refptr<::network::SharedURLLoaderFactory> scoped_refptr<::network::SharedURLLoaderFactory>
...@@ -185,6 +218,9 @@ class MediaFeedsService : public KeyedService { ...@@ -185,6 +218,9 @@ class MediaFeedsService : public KeyedService {
std::unique_ptr<safe_search_api::URLChecker> safe_search_url_checker_; std::unique_ptr<safe_search_api::URLChecker> safe_search_url_checker_;
Profile* const profile_; Profile* const profile_;
// An internal clock for testing.
base::Clock* clock_;
THREAD_CHECKER(thread_checker_); THREAD_CHECKER(thread_checker_);
base::WeakPtrFactory<MediaFeedsService> weak_factory_{this}; base::WeakPtrFactory<MediaFeedsService> weak_factory_{this};
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
#include "base/test/bind_test_util.h" #include "base/test/bind_test_util.h"
#include "base/test/metrics/histogram_tester.h" #include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/time/time_to_iso8601.h"
#include "chrome/browser/media/feeds/media_feeds_service_factory.h" #include "chrome/browser/media/feeds/media_feeds_service_factory.h"
#include "chrome/browser/media/feeds/media_feeds_store.mojom-shared.h" #include "chrome/browser/media/feeds/media_feeds_store.mojom-shared.h"
#include "chrome/browser/media/history/media_history_keyed_service.h" #include "chrome/browser/media/history/media_history_keyed_service.h"
...@@ -167,12 +169,18 @@ class MediaFeedsServiceTest : public ChromeRenderViewHostTestHarness { ...@@ -167,12 +169,18 @@ class MediaFeedsServiceTest : public ChromeRenderViewHostTestHarness {
void SetUp() override { void SetUp() override {
features_.InitWithFeatures( features_.InitWithFeatures(
{media::kMediaFeeds, media::kMediaFeedsSafeSearch}, {}); {media::kMediaFeeds, media::kMediaFeedsSafeSearch,
media::kMediaFeedsBackgroundFetching},
{});
ChromeRenderViewHostTestHarness::SetUp(); ChromeRenderViewHostTestHarness::SetUp();
stub_url_checker_ = std::make_unique<safe_search_api::StubURLChecker>(); stub_url_checker_ = std::make_unique<safe_search_api::StubURLChecker>();
test_clock_.SetNow(base::Time::Now());
GetMediaFeedsService()->SetClockForTesting(&test_clock_);
GetMediaFeedsService()->SetSafeSearchURLCheckerForTest( GetMediaFeedsService()->SetSafeSearchURLCheckerForTest(
stub_url_checker_->BuildURLChecker(kCacheSize)); stub_url_checker_->BuildURLChecker(kCacheSize));
...@@ -181,6 +189,12 @@ class MediaFeedsServiceTest : public ChromeRenderViewHostTestHarness { ...@@ -181,6 +189,12 @@ class MediaFeedsServiceTest : public ChromeRenderViewHostTestHarness {
&url_loader_factory_); &url_loader_factory_);
} }
void AdvanceClock(base::TimeDelta time_delta) {
test_clock_.SetNow(base::Time::Now() + time_delta);
}
base::Time Now() { return test_clock_.Now(); }
void WaitForDB() { void WaitForDB() {
base::RunLoop run_loop; base::RunLoop run_loop;
GetMediaHistoryService()->PostTaskToDBForTest(run_loop.QuitClosure()); GetMediaHistoryService()->PostTaskToDBForTest(run_loop.QuitClosure());
...@@ -298,6 +312,11 @@ class MediaFeedsServiceTest : public ChromeRenderViewHostTestHarness { ...@@ -298,6 +312,11 @@ class MediaFeedsServiceTest : public ChromeRenderViewHostTestHarness {
enabled); enabled);
} }
void SetBackgroundFetchingEnabled(bool enabled) {
profile()->GetPrefs()->SetBoolean(prefs::kMediaFeedsBackgroundFetching,
enabled);
}
bool RespondToPendingFeedFetch(const GURL& feed_url, bool RespondToPendingFeedFetch(const GURL& feed_url,
bool from_cache = false) { bool from_cache = false) {
return RespondToPendingFeedFetchWithData(feed_url, kTestData, from_cache); return RespondToPendingFeedFetchWithData(feed_url, kTestData, from_cache);
...@@ -465,6 +484,8 @@ class MediaFeedsServiceTest : public ChromeRenderViewHostTestHarness { ...@@ -465,6 +484,8 @@ class MediaFeedsServiceTest : public ChromeRenderViewHostTestHarness {
std::unique_ptr<safe_search_api::StubURLChecker> stub_url_checker_; std::unique_ptr<safe_search_api::StubURLChecker> stub_url_checker_;
data_decoder::test::InProcessDataDecoder data_decoder_; data_decoder::test::InProcessDataDecoder data_decoder_;
base::SimpleTestClock test_clock_;
}; };
TEST_F(MediaFeedsServiceTest, GetForProfile) { TEST_F(MediaFeedsServiceTest, GetForProfile) {
...@@ -2058,4 +2079,277 @@ TEST_P(MediaFeedsSpecTest, RunOpenSourceTest) { ...@@ -2058,4 +2079,277 @@ TEST_P(MediaFeedsSpecTest, RunOpenSourceTest) {
} }
} }
// FetchTopMediaFeeds should fetch a feed with enough watchtime on that origin
// even if it hasn't been fetched before.
TEST_F(MediaFeedsServiceTest, FetchTopMediaFeeds_SuccessNewFetch) {
base::HistogramTester histogram_tester;
const GURL feed_url("https://www.google.com/feed");
SetBackgroundFetchingEnabled(true);
// Store a Media Feed.
GetMediaFeedsService()->DiscoverMediaFeed(feed_url);
WaitForDB();
// FetchTopMediaFeeds should ignore the feed, as the origin does not have
// enough watchtime.
{
base::RunLoop top_feeds_loop;
GetMediaFeedsService()->FetchTopMediaFeeds(top_feeds_loop.QuitClosure());
WaitForDB();
top_feeds_loop.Run();
ASSERT_FALSE(RespondToPendingFeedFetch(feed_url));
}
// Set the watchtime higher than the minimum threshold for top feeds.
auto watchtime = base::TimeDelta::FromMinutes(45);
content::MediaPlayerWatchTime watch_time(
feed_url, feed_url.GetOrigin(), watchtime, base::TimeDelta(), true, true);
GetMediaHistoryService()->SavePlayback(watch_time);
WaitForDB();
// Now that there is high watchtime, the fetch should occur.
{
base::RunLoop top_feeds_loop;
GetMediaFeedsService()->FetchTopMediaFeeds(top_feeds_loop.QuitClosure());
WaitForDB();
top_feeds_loop.Run();
ASSERT_TRUE(RespondToPendingFeedFetch(feed_url));
}
auto feeds = GetMediaFeedsSync();
EXPECT_EQ(1u, feeds.size());
EXPECT_TRUE(feeds[0]->last_fetch_time_not_cache_hit);
EXPECT_EQ(media_feeds::mojom::FetchResult::kSuccess,
feeds[0]->last_fetch_result);
histogram_tester.ExpectUniqueSample(
MediaFeedsFetcher::kFetchSizeKbHistogramName, 15, 1);
}
// Fetch top feeds should periodically fetch the feed from cache if available.
TEST_F(MediaFeedsServiceTest, FetchTopMediaFeeds_SuccessFromCache) {
base::HistogramTester histogram_tester;
const GURL feed_url("https://www.google.com/feed");
SetBackgroundFetchingEnabled(true);
// Store a Media Feed.
GetMediaFeedsService()->DiscoverMediaFeed(feed_url);
WaitForDB();
// Set the watchtime higher than the minimum threshold for top feeds.
auto watchtime = base::TimeDelta::FromMinutes(45);
content::MediaPlayerWatchTime watch_time(
feed_url, feed_url.GetOrigin(), watchtime, base::TimeDelta(), true, true);
GetMediaHistoryService()->SavePlayback(watch_time);
WaitForDB();
// Fetch the Media Feed an initial time.
base::RunLoop run_loop;
GetMediaFeedsService()->FetchMediaFeed(
1, base::BindLambdaForTesting(
[&](const std::string& ignored) { run_loop.Quit(); }));
WaitForDB();
ASSERT_TRUE(RespondToPendingFeedFetch(feed_url));
run_loop.Run();
// After some time, fetch top feeds should refresh the feed.
AdvanceClock(base::TimeDelta::FromHours(1));
base::RunLoop top_feeds_loop;
GetMediaFeedsService()->FetchTopMediaFeeds(top_feeds_loop.QuitClosure());
WaitForDB();
// It has been < 24 hrs, so it should fetch from the cached initial fetch.
EXPECT_FALSE(GetCurrentRequestHasBypassCacheFlag());
ASSERT_TRUE(RespondToPendingFeedFetch(feed_url));
top_feeds_loop.Run();
auto feeds = GetMediaFeedsSync();
EXPECT_EQ(1u, feeds.size());
EXPECT_TRUE(feeds[0]->last_fetch_time_not_cache_hit);
EXPECT_EQ(media_feeds::mojom::FetchResult::kSuccess,
feeds[0]->last_fetch_result);
histogram_tester.ExpectUniqueSample(
MediaFeedsFetcher::kFetchSizeKbHistogramName, 15, 2);
}
// FetchTopMediaFeeds should back off if the feed fails to fetch. But after 24
// hours, it should fetch regardless of failures, bypassing the cache.
TEST_F(MediaFeedsServiceTest, FetchTopMediaFeeds_BacksOffFailedFetches) {
base::HistogramTester histogram_tester;
const int times_to_fail = 10;
const GURL feed_url("https://www.google.com/feed");
SetBackgroundFetchingEnabled(true);
// Store a Media Feed.
GetMediaFeedsService()->DiscoverMediaFeed(feed_url);
WaitForDB();
// Set the watchtime higher than the minimum threshold for top feeds.
auto watchtime = base::TimeDelta::FromMinutes(45);
content::MediaPlayerWatchTime watch_time(
feed_url, feed_url.GetOrigin(), watchtime, base::TimeDelta(), true, true);
GetMediaHistoryService()->SavePlayback(watch_time);
WaitForDB();
// Fetch the Media Feed unsuccessfully several times.
for (int i = 0; i < times_to_fail; i++) {
base::RunLoop run_loop;
GetMediaFeedsService()->FetchMediaFeed(
1, base::BindLambdaForTesting(
[&](const std::string& ignored) { run_loop.Quit(); }));
WaitForDB();
ASSERT_TRUE(RespondToPendingFeedFetchWithStatus(
feed_url, net::HTTP_INTERNAL_SERVER_ERROR));
run_loop.Run();
}
// No fetch should happen because of the backoff from failures.
{
AdvanceClock(base::TimeDelta::FromHours(1));
base::RunLoop top_feeds_loop;
GetMediaFeedsService()->FetchTopMediaFeeds(top_feeds_loop.QuitClosure());
WaitForDB();
ASSERT_FALSE(RespondToPendingFeedFetch(feed_url));
top_feeds_loop.Run();
}
// After 24 hours, the feed should be fetched regardless of failure count.
{
AdvanceClock(base::TimeDelta::FromHours(24));
base::RunLoop top_feeds_loop;
GetMediaFeedsService()->FetchTopMediaFeeds(top_feeds_loop.QuitClosure());
WaitForDB();
// If we bypass failure count, we should also bypass cache.
EXPECT_TRUE(GetCurrentRequestHasBypassCacheFlag());
ASSERT_TRUE(RespondToPendingFeedFetch(feed_url));
top_feeds_loop.Run();
auto feeds = GetMediaFeedsSync();
EXPECT_EQ(1u, feeds.size());
EXPECT_TRUE(feeds[0]->last_fetch_time_not_cache_hit);
EXPECT_EQ(media_feeds::mojom::FetchResult::kSuccess,
feeds[0]->last_fetch_result);
}
histogram_tester.ExpectUniqueSample(
MediaFeedsFetcher::kFetchSizeKbHistogramName, 15, 1);
}
// After 24 hours, FetchTopMediaFeeds should fetch the feed and bypass the
// cache.
TEST_F(MediaFeedsServiceTest, FetchTopMediaFeeds_SuccessBypassCache) {
base::HistogramTester histogram_tester;
const GURL feed_url("https://www.google.com/feed");
SetBackgroundFetchingEnabled(true);
// Store a Media Feed.
GetMediaFeedsService()->DiscoverMediaFeed(feed_url);
WaitForDB();
// Set the watchtime higher than the minimum threshold for top feeds.
auto watchtime = base::TimeDelta::FromMinutes(45);
content::MediaPlayerWatchTime watch_time(
feed_url, feed_url.GetOrigin(), watchtime, base::TimeDelta(), true, true);
GetMediaHistoryService()->SavePlayback(watch_time);
WaitForDB();
// Fetch the Media Feed an initial time.
base::RunLoop run_loop;
GetMediaFeedsService()->FetchMediaFeed(
1, base::BindLambdaForTesting(
[&](const std::string& ignored) { run_loop.Quit(); }));
WaitForDB();
ASSERT_TRUE(RespondToPendingFeedFetch(feed_url));
run_loop.Run();
AdvanceClock(base::TimeDelta::FromHours(24));
base::RunLoop top_feeds_loop;
GetMediaFeedsService()->FetchTopMediaFeeds(top_feeds_loop.QuitClosure());
WaitForDB();
// After a long time between fetches, we should bypass the cache.
EXPECT_TRUE(GetCurrentRequestHasBypassCacheFlag());
ASSERT_TRUE(RespondToPendingFeedFetch(feed_url));
top_feeds_loop.Run();
auto feeds = GetMediaFeedsSync();
EXPECT_EQ(1u, feeds.size());
EXPECT_TRUE(feeds[0]->last_fetch_time_not_cache_hit);
EXPECT_EQ(media_feeds::mojom::FetchResult::kSuccess,
feeds[0]->last_fetch_result);
histogram_tester.ExpectUniqueSample(
MediaFeedsFetcher::kFetchSizeKbHistogramName, 15, 2);
}
// After a feed reset, FetchTopMediaFeeds should fetch anyway.
TEST_F(MediaFeedsServiceTest, FetchTopMediaFeeds_SuccessResetFeed) {
base::HistogramTester histogram_tester;
const GURL feed_url("https://www.google.com/feed");
SetBackgroundFetchingEnabled(true);
// Store a Media Feed.
GetMediaFeedsService()->DiscoverMediaFeed(feed_url);
WaitForDB();
auto watchtime = base::TimeDelta::FromMinutes(45);
content::MediaPlayerWatchTime watch_time(
feed_url, feed_url.GetOrigin(), watchtime, base::TimeDelta(), true, true);
GetMediaHistoryService()->SavePlayback(watch_time);
WaitForDB();
// Fetch the Media Feed.
base::RunLoop run_loop;
GetMediaFeedsService()->FetchMediaFeed(
1, base::BindLambdaForTesting(
[&](const std::string& ignored) { run_loop.Quit(); }));
WaitForDB();
ASSERT_TRUE(RespondToPendingFeedFetch(feed_url));
run_loop.Run();
GetMediaFeedsService()->ResetMediaFeed(url::Origin::Create(feed_url),
mojom::ResetReason::kVisit);
WaitForDB();
base::RunLoop top_feeds_loop;
GetMediaFeedsService()->FetchTopMediaFeeds(top_feeds_loop.QuitClosure());
WaitForDB();
ASSERT_TRUE(RespondToPendingFeedFetch(feed_url));
top_feeds_loop.Run();
auto feeds = GetMediaFeedsSync();
EXPECT_EQ(1u, feeds.size());
EXPECT_TRUE(feeds[0]->last_fetch_time_not_cache_hit);
EXPECT_EQ(media_feeds::mojom::FetchResult::kSuccess,
feeds[0]->last_fetch_result);
histogram_tester.ExpectUniqueSample(
MediaFeedsFetcher::kFetchSizeKbHistogramName, 15, 2);
}
} // namespace media_feeds } // namespace media_feeds
...@@ -10,6 +10,7 @@ import "mojo/public/mojom/base/time.mojom"; ...@@ -10,6 +10,7 @@ import "mojo/public/mojom/base/time.mojom";
import "url/mojom/origin.mojom"; import "url/mojom/origin.mojom";
import "ui/gfx/geometry/mojom/geometry.mojom"; import "ui/gfx/geometry/mojom/geometry.mojom";
import "url/mojom/url.mojom"; import "url/mojom/url.mojom";
import "mojo/public/mojom/base/unguessable_token.mojom";
struct MediaFeed { struct MediaFeed {
// The ID of the field in storage. // The ID of the field in storage.
...@@ -67,6 +68,12 @@ struct MediaFeed { ...@@ -67,6 +68,12 @@ struct MediaFeed {
// have been deleted and the feed status has been reset to default. // have been deleted and the feed status has been reset to default.
ResetReason reset_reason; ResetReason reset_reason;
// Token used to invalidate ongoing fetches for the feed. If there is a token
// mismatch when feed fetch data returns (the reset token when the fetch began
// does not match the feed's current reset token), the data is stale and
// shouldn't be stored because the feed has been reset.
mojo_base.mojom.UnguessableToken? reset_token;
// Contains details about the user signed into the website. // Contains details about the user signed into the website.
UserIdentifier? user_identifier; UserIdentifier? user_identifier;
......
...@@ -197,7 +197,8 @@ std::vector<media_feeds::mojom::MediaFeedPtr> MediaHistoryFeedsTable::GetRows( ...@@ -197,7 +197,8 @@ std::vector<media_feeds::mojom::MediaFeedPtr> MediaHistoryFeedsTable::GetRows(
"mediaFeed.reset_reason, " "mediaFeed.reset_reason, "
"mediaFeed.user_identifier, " "mediaFeed.user_identifier, "
"mediaFeed.cookie_name_filter, " "mediaFeed.cookie_name_filter, "
"mediaFeed.safe_search_result"); "mediaFeed.safe_search_result, "
"mediaFeed.reset_token ");
sql::Statement statement; sql::Statement statement;
...@@ -373,6 +374,12 @@ std::vector<media_feeds::mojom::MediaFeedPtr> MediaHistoryFeedsTable::GetRows( ...@@ -373,6 +374,12 @@ std::vector<media_feeds::mojom::MediaFeedPtr> MediaHistoryFeedsTable::GetRows(
if (statement.GetColumnType(17) == sql::ColumnType::kText) if (statement.GetColumnType(17) == sql::ColumnType::kText)
feed->cookie_name_filter = statement.ColumnString(17); feed->cookie_name_filter = statement.ColumnString(17);
if (statement.GetColumnType(19) == sql::ColumnType::kBlob) {
media_feeds::FeedResetToken token;
if (GetProto(statement, 19, token))
feed->reset_token = ProtoToUnguessableToken(token);
}
feeds.push_back(std::move(feed)); feeds.push_back(std::move(feed));
// If we are returning top feeds then we should apply a limit here. // If we are returning top feeds then we should apply a limit here.
......
...@@ -72,7 +72,8 @@ void MediaFeedsUI::GetItemsForMediaFeed(int64_t feed_id, ...@@ -72,7 +72,8 @@ void MediaFeedsUI::GetItemsForMediaFeed(int64_t feed_id,
void MediaFeedsUI::FetchMediaFeed(int64_t feed_id, void MediaFeedsUI::FetchMediaFeed(int64_t feed_id,
FetchMediaFeedCallback callback) { FetchMediaFeedCallback callback) {
GetMediaFeedsService()->FetchMediaFeed(feed_id, std::move(callback)); GetMediaFeedsService()->FetchMediaFeed(feed_id, /*bypass_cache=*/false,
nullptr, std::move(callback));
} }
void MediaFeedsUI::GetDebugInformation(GetDebugInformationCallback callback) { void MediaFeedsUI::GetDebugInformation(GetDebugInformationCallback callback) {
......
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