summaryrefslogtreecommitdiff
path: root/chromium/components/feed
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-12 14:27:29 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-10-13 09:35:20 +0000
commitc30a6232df03e1efbd9f3b226777b07e087a1122 (patch)
treee992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/components/feed
parent7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff)
downloadqtwebengine-chromium-85-based.tar.gz
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/components/feed')
-rw-r--r--chromium/components/feed/core/common/pref_names.cc2
-rw-r--r--chromium/components/feed/core/common/pref_names.h2
-rw-r--r--chromium/components/feed/core/proto/BUILD.gn1
-rw-r--r--chromium/components/feed/core/proto/v2/packing.proto19
-rw-r--r--chromium/components/feed/core/proto/v2/wire/capability.proto2
-rw-r--r--chromium/components/feed/core/proto/v2/wire/client_info.proto7
-rw-r--r--chromium/components/feed/core/shared_prefs/pref_names.cc3
-rw-r--r--chromium/components/feed/core/v2/BUILD.gn1
-rw-r--r--chromium/components/feed/core/v2/config.cc5
-rw-r--r--chromium/components/feed/core/v2/config.h3
-rw-r--r--chromium/components/feed/core/v2/enums.cc6
-rw-r--r--chromium/components/feed/core/v2/enums.h7
-rw-r--r--chromium/components/feed/core/v2/feed_network_impl.cc161
-rw-r--r--chromium/components/feed/core/v2/feed_network_impl.h5
-rw-r--r--chromium/components/feed/core/v2/feed_network_impl_unittest.cc6
-rw-r--r--chromium/components/feed/core/v2/feed_store.h9
-rw-r--r--chromium/components/feed/core/v2/feed_stream.cc87
-rw-r--r--chromium/components/feed/core/v2/feed_stream.h24
-rw-r--r--chromium/components/feed/core/v2/feed_stream_unittest.cc144
-rw-r--r--chromium/components/feed/core/v2/metrics_reporter.cc100
-rw-r--r--chromium/components/feed/core/v2/metrics_reporter.h22
-rw-r--r--chromium/components/feed/core/v2/metrics_reporter_unittest.cc190
-rw-r--r--chromium/components/feed/core/v2/prefs.cc44
-rw-r--r--chromium/components/feed/core/v2/prefs.h21
-rw-r--r--chromium/components/feed/core/v2/proto_util.cc27
-rw-r--r--chromium/components/feed/core/v2/proto_util.h12
-rw-r--r--chromium/components/feed/core/v2/proto_util_unittest.cc3
-rw-r--r--chromium/components/feed/core/v2/protocol_translator.cc61
-rw-r--r--chromium/components/feed/core/v2/protocol_translator.h5
-rw-r--r--chromium/components/feed/core/v2/protocol_translator_unittest.cc37
-rw-r--r--chromium/components/feed/core/v2/public/feed_service.cc53
-rw-r--r--chromium/components/feed/core/v2/public/feed_service.h5
-rw-r--r--chromium/components/feed/core/v2/public/feed_stream_api.h12
-rw-r--r--chromium/components/feed/core/v2/public/types.h6
-rw-r--r--chromium/components/feed/core/v2/request_throttler.cc10
-rw-r--r--chromium/components/feed/core/v2/scheduling.cc21
-rw-r--r--chromium/components/feed/core/v2/stream_model.cc9
-rw-r--r--chromium/components/feed/core/v2/stream_model.h4
-rw-r--r--chromium/components/feed/core/v2/stream_model/ephemeral_change.cc2
-rw-r--r--chromium/components/feed/core/v2/stream_model/feature_tree.cc90
-rw-r--r--chromium/components/feed/core/v2/stream_model/feature_tree.h43
-rw-r--r--chromium/components/feed/core/v2/surface_updater.cc3
-rw-r--r--chromium/components/feed/core/v2/tasks/load_stream_from_store_task.cc2
-rw-r--r--chromium/components/feed/core/v2/tasks/load_stream_task.cc15
-rw-r--r--chromium/components/feed/core/v2/tasks/upload_actions_task.cc65
-rw-r--r--chromium/components/feed/core/v2/tasks/upload_actions_task.h1
-rw-r--r--chromium/components/feed/core/v2/types.cc34
-rw-r--r--chromium/components/feed/core/v2/types.h12
-rw-r--r--chromium/components/feed/core/v2/types_unittest.cc32
-rw-r--r--chromium/components/feed/feed_feature_list.cc5
-rw-r--r--chromium/components/feed/feed_feature_list.h2
51 files changed, 1087 insertions, 355 deletions
diff --git a/chromium/components/feed/core/common/pref_names.cc b/chromium/components/feed/core/common/pref_names.cc
index a4736acee86..b3dc860cbb6 100644
--- a/chromium/components/feed/core/common/pref_names.cc
+++ b/chromium/components/feed/core/common/pref_names.cc
@@ -39,6 +39,7 @@ const char kThrottlerLastRequestTime[] =
"feedv2.request_throttler.last_request_time";
const char kDebugStreamData[] = "feedv2.debug_stream_data";
const char kRequestSchedule[] = "feedv2.request_schedule";
+const char kMetricsData[] = "feedv2.metrics_data";
} // namespace prefs
@@ -55,6 +56,7 @@ void RegisterProfilePrefs(PrefRegistrySimple* registry) {
base::Time());
registry->RegisterStringPref(feed::prefs::kDebugStreamData, std::string());
registry->RegisterDictionaryPref(feed::prefs::kRequestSchedule);
+ registry->RegisterDictionaryPref(feed::prefs::kMetricsData);
UserClassifier::RegisterProfilePrefs(registry);
}
diff --git a/chromium/components/feed/core/common/pref_names.h b/chromium/components/feed/core/common/pref_names.h
index b23c1552bde..67a1d1ee502 100644
--- a/chromium/components/feed/core/common/pref_names.h
+++ b/chromium/components/feed/core/common/pref_names.h
@@ -52,6 +52,8 @@ extern const char kThrottlerLastRequestTime[];
extern const char kDebugStreamData[];
// The pref name for storing the request schedule.
extern const char kRequestSchedule[];
+// The pref name for storing the persistent metrics data.
+extern const char kMetricsData[];
} // namespace prefs
diff --git a/chromium/components/feed/core/proto/BUILD.gn b/chromium/components/feed/core/proto/BUILD.gn
index bede4090414..b2b79298464 100644
--- a/chromium/components/feed/core/proto/BUILD.gn
+++ b/chromium/components/feed/core/proto/BUILD.gn
@@ -19,6 +19,7 @@ proto_library("proto") {
proto_library("proto_v2") {
proto_in_dir = "../../../../"
sources = [
+ "v2/packing.proto",
"v2/store.proto",
"v2/ui.proto",
"v2/wire/action_payload.proto",
diff --git a/chromium/components/feed/core/proto/v2/packing.proto b/chromium/components/feed/core/proto/v2/packing.proto
new file mode 100644
index 00000000000..692b15ca41f
--- /dev/null
+++ b/chromium/components/feed/core/proto/v2/packing.proto
@@ -0,0 +1,19 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// These are pseudo-wire types. We need these types to marshal data from the
+// xsurface API. These messages can and should depend on wire types, and the
+// Chrome UI code need not parse these.
+
+syntax = "proto3";
+
+package feedpacking;
+
+import "components/feed/core/proto/v2/wire/data_operation.proto";
+
+option optimize_for = LITE_RUNTIME;
+
+message DismissData {
+ repeated feedwire.DataOperation data_operations = 1;
+}
diff --git a/chromium/components/feed/core/proto/v2/wire/capability.proto b/chromium/components/feed/core/proto/v2/wire/capability.proto
index f9da90c6a64..31dd85c1632 100644
--- a/chromium/components/feed/core/proto/v2/wire/capability.proto
+++ b/chromium/components/feed/core/proto/v2/wire/capability.proto
@@ -36,5 +36,7 @@ enum Capability {
// Enable the card menu.
CARD_MENU = 19;
REQUEST_SCHEDULE = 20;
+ OPEN_IN_TAB = 27;
+ DOWNLOAD_LINK = 28;
reserved 2 to 4, 6 to 8, 12;
}
diff --git a/chromium/components/feed/core/proto/v2/wire/client_info.proto b/chromium/components/feed/core/proto/v2/wire/client_info.proto
index f21907add5c..554fa87711d 100644
--- a/chromium/components/feed/core/proto/v2/wire/client_info.proto
+++ b/chromium/components/feed/core/proto/v2/wire/client_info.proto
@@ -21,10 +21,9 @@ message ClientInfo {
IOS = 2;
}
- enum AppType {
- TEST_APP = 2; // For use with AGA endpoint for testing.
- CHROME = 3;
- }
+ // TODO(harringtond): This should be CLANK = 3, but the server isn't yet
+ // updated to support it.
+ enum AppType { CLANK = 2; }
// The type of OS that the client is running.
optional PlatformType platform_type = 1;
diff --git a/chromium/components/feed/core/shared_prefs/pref_names.cc b/chromium/components/feed/core/shared_prefs/pref_names.cc
index da75d63d4c4..7f4f7a91413 100644
--- a/chromium/components/feed/core/shared_prefs/pref_names.cc
+++ b/chromium/components/feed/core/shared_prefs/pref_names.cc
@@ -13,6 +13,9 @@ namespace feed {
namespace prefs {
const char kEnableSnippets[] = "ntp_snippets.enable";
+// A boolean pref set to true if Feed articles are visible.
+// FEED_ARTICLES_LIST_VISIBLE in ChromePreferenceKeys.java is a pre-native cache
+// and should be consistent with this pref.
const char kArticlesListVisible[] = "ntp_snippets.list_visible";
void RegisterFeedSharedProfilePrefs(PrefRegistrySimple* registry) {
diff --git a/chromium/components/feed/core/v2/BUILD.gn b/chromium/components/feed/core/v2/BUILD.gn
index 929a554bd8d..e27fb66cfa0 100644
--- a/chromium/components/feed/core/v2/BUILD.gn
+++ b/chromium/components/feed/core/v2/BUILD.gn
@@ -82,6 +82,7 @@ source_set("feed_core_v2") {
public_deps = [
"//base",
+ "//base/util/values:values_util",
"//components/feed/core/common:feed_core_common",
"//components/feed/core/proto:proto_v2",
]
diff --git a/chromium/components/feed/core/v2/config.cc b/chromium/components/feed/core/v2/config.cc
index 7f5279286f5..9bf890f534a 100644
--- a/chromium/components/feed/core/v2/config.cc
+++ b/chromium/components/feed/core/v2/config.cc
@@ -52,6 +52,11 @@ void OverrideWithFinch(Config* config) {
config->max_action_upload_bytes = base::GetFieldTrialParamByFeatureAsInt(
kInterestFeedV2, "max_action_upload_bytes",
config->max_action_upload_bytes);
+
+ config->model_unload_timeout =
+ base::TimeDelta::FromSecondsD(base::GetFieldTrialParamByFeatureAsDouble(
+ kInterestFeedV2, "model_unload_timeout_seconds",
+ config->model_unload_timeout.InSecondsF()));
}
} // namespace
diff --git a/chromium/components/feed/core/v2/config.h b/chromium/components/feed/core/v2/config.h
index 82c60a268ff..f4944aaf298 100644
--- a/chromium/components/feed/core/v2/config.h
+++ b/chromium/components/feed/core/v2/config.h
@@ -28,6 +28,9 @@ struct Config {
base::TimeDelta max_action_age = base::TimeDelta::FromHours(24);
// Maximum payload size for one action upload batch.
size_t max_action_upload_bytes = 20000;
+ // If no surfaces are attached, the stream model is unloaded after this
+ // timeout.
+ base::TimeDelta model_unload_timeout = base::TimeDelta::FromSeconds(1);
};
// Gets the current configuration.
diff --git a/chromium/components/feed/core/v2/enums.cc b/chromium/components/feed/core/v2/enums.cc
index bf4a3472b87..a675de4293c 100644
--- a/chromium/components/feed/core/v2/enums.cc
+++ b/chromium/components/feed/core/v2/enums.cc
@@ -47,6 +47,12 @@ std::ostream& operator<<(std::ostream& out, LoadStreamStatus value) {
return out << "kCannotParseNetworkResponseBody";
case LoadStreamStatus::kLoadMoreModelIsNotLoaded:
return out << "kLoadMoreModelIsNotLoaded";
+ case LoadStreamStatus::kLoadNotAllowedDisabledByEnterprisePolicy:
+ return out << "kLoadNotAllowedDisabledByEnterprisePolicy";
+ case LoadStreamStatus::kNetworkFetchFailed:
+ return out << "kNetworkFetchFailed";
+ case LoadStreamStatus::kCannotLoadMoreNoNextPageToken:
+ return out << "kCannotLoadMoreNoNextPageToken";
}
#else
return out << (static_cast<int>(value));
diff --git a/chromium/components/feed/core/v2/enums.h b/chromium/components/feed/core/v2/enums.h
index df2e2861341..ae0c95c5cd2 100644
--- a/chromium/components/feed/core/v2/enums.h
+++ b/chromium/components/feed/core/v2/enums.h
@@ -26,7 +26,6 @@ enum class LoadStreamStatus {
kNoStreamDataInStore = 4,
kModelAlreadyLoaded = 5,
kNoResponseBody = 6,
- // TODO(harringtond): Let's add more specific proto translation errors.
kProtoTranslationFailed = 7,
kDataInStoreIsStale = 8,
// The timestamp for stored data is in the future, so we're treating stored
@@ -37,10 +36,12 @@ enum class LoadStreamStatus {
kCannotLoadFromNetworkThrottled = 12,
kLoadNotAllowedEulaNotAccepted = 13,
kLoadNotAllowedArticlesListHidden = 14,
- // TODO(harringtond): Emit this status value.
kCannotParseNetworkResponseBody = 15,
kLoadMoreModelIsNotLoaded = 16,
- kMaxValue = kLoadMoreModelIsNotLoaded,
+ kLoadNotAllowedDisabledByEnterprisePolicy = 17,
+ kNetworkFetchFailed = 18,
+ kCannotLoadMoreNoNextPageToken = 19,
+ kMaxValue = kCannotLoadMoreNoNextPageToken,
};
std::ostream& operator<<(std::ostream& out, LoadStreamStatus value);
diff --git a/chromium/components/feed/core/v2/feed_network_impl.cc b/chromium/components/feed/core/v2/feed_network_impl.cc
index df1e4874b27..124592c159f 100644
--- a/chromium/components/feed/core/v2/feed_network_impl.cc
+++ b/chromium/components/feed/core/v2/feed_network_impl.cc
@@ -41,20 +41,48 @@
namespace feed {
namespace {
-constexpr char kAuthenticationScope[] =
- "https://www.googleapis.com/auth/googlenow";
constexpr char kApplicationOctetStream[] = "application/octet-stream";
constexpr base::TimeDelta kNetworkTimeout = base::TimeDelta::FromSeconds(30);
-// Add URLs for Bling when it is supported.
-constexpr char kFeedQueryUrl[] =
- "https://www.google.com/httpservice/retry/TrellisClankService/FeedQuery";
-constexpr char kNextPageQueryUrl[] =
- "https://www.google.com/httpservice/retry/TrellisClankService/"
- "NextPageQuery";
-constexpr char kBackgroundQueryUrl[] =
- "https://www.google.com/httpservice/noretry/TrellisClankService/"
- "FeedQuery";
+signin::ScopeSet GetAuthScopes() {
+ return {"https://www.googleapis.com/auth/googlenow"};
+}
+
+GURL GetFeedQueryURL(feedwire::FeedQuery::RequestReason reason) {
+ // Add URLs for Bling when it is supported.
+ switch (reason) {
+ case feedwire::FeedQuery::SCHEDULED_REFRESH:
+ case feedwire::FeedQuery::IN_PLACE_UPDATE:
+ return GURL(
+ "https://www.google.com/httpservice/noretry/TrellisClankService/"
+ "FeedQuery");
+ case feedwire::FeedQuery::NEXT_PAGE_SCROLL:
+ return GURL(
+ "https://www.google.com/httpservice/retry/TrellisClankService/"
+ "NextPageQuery");
+ case feedwire::FeedQuery::MANUAL_REFRESH:
+ return GURL(
+ "https://www.google.com/httpservice/retry/TrellisClankService/"
+ "FeedQuery");
+ default:
+ return GURL();
+ }
+}
+
+GURL GetUploadActionURL(version_info::Channel channel) {
+ switch (channel) {
+ case version_info::Channel::BETA:
+ return GURL(
+ "https://staging-discover-pa.sandbox.googleapis.com/v1/"
+ "actions:upload");
+ case version_info::Channel::STABLE:
+ return GURL("https://discover-pa.googleapis.com/v1/actions:upload");
+ default:
+ return GURL(
+ "https://autopush-discover-pa.sandbox.googleapis.com/v1/"
+ "actions:upload");
+ }
+}
GURL GetUrlWithoutQuery(const GURL& url) {
GURL::Replacements replacements;
@@ -102,12 +130,25 @@ void ParseAndForwardResponse(base::OnceCallback<void(RESULT)> result_callback,
void AddMothershipPayloadQueryParams(bool is_post,
const std::string& payload,
const std::string& language_tag,
- GURL* url) {
+ GURL& url) {
if (!is_post)
- *url = net::AppendQueryParameter(*url, "reqpld", payload);
- *url = net::AppendQueryParameter(*url, "fmt", "bin");
+ url = net::AppendQueryParameter(url, "reqpld", payload);
+ url = net::AppendQueryParameter(url, "fmt", "bin");
if (!language_tag.empty())
- *url = net::AppendQueryParameter(*url, "hl", language_tag);
+ url = net::AppendQueryParameter(url, "hl", language_tag);
+}
+
+// Compresses and attaches |request_body| for upload if it's not empty.
+// Returns the compressed size of the request.
+int PopulateRequestBody(const std::string& request_body,
+ network::SimpleURLLoader* loader) {
+ if (request_body.empty())
+ return 0;
+ std::string compressed_request_body;
+ compression::GzipCompress(request_body, &compressed_request_body);
+ loader->AttachStringForUpload(compressed_request_body,
+ kApplicationOctetStream);
+ return compressed_request_body.size();
}
} // namespace
@@ -167,11 +208,10 @@ class FeedNetworkImpl::NetworkFetch {
private:
void StartAccessTokenFetch() {
- signin::ScopeSet scopes{kAuthenticationScope};
// It's safe to pass base::Unretained(this) since deleting the token fetcher
// will prevent the callback from being completed.
token_fetcher_ = std::make_unique<signin::PrimaryAccountAccessTokenFetcher>(
- "feed", identity_manager_, scopes,
+ "feed", identity_manager_, GetAuthScopes(),
base::BindOnce(&NetworkFetch::AccessTokenFetchFinished,
base::Unretained(this), tick_clock_->NowTicks()),
signin::PrimaryAccountAccessTokenFetcher::Mode::kWaitUntilAvailable);
@@ -250,53 +290,41 @@ class FeedNetworkImpl::NetworkFetch {
resource_request->site_for_cookies = net::SiteForCookies::FromUrl(url);
}
- SetRequestHeaders(!request_body_.empty(), resource_request.get());
+ SetRequestHeaders(!request_body_.empty(), *resource_request);
+ DVLOG(1) << "Feed Request url=" << url;
+ DVLOG(1) << "Feed Request headers=" << resource_request->headers.ToString();
auto simple_loader = network::SimpleURLLoader::Create(
std::move(resource_request), traffic_annotation);
simple_loader->SetAllowHttpErrorResults(true);
simple_loader->SetTimeoutDuration(kNetworkTimeout);
- PopulateRequestBody(simple_loader.get());
+
+ const int compressed_size =
+ PopulateRequestBody(request_body_, simple_loader.get());
+ UMA_HISTOGRAM_COUNTS_1M(
+ "ContentSuggestions.Feed.Network.RequestSizeKB.Compressed",
+ compressed_size / 1024);
return simple_loader;
}
void SetRequestHeaders(bool has_request_body,
- network::ResourceRequest* request) const {
+ network::ResourceRequest& request) const {
if (has_request_body) {
- request->headers.SetHeader(net::HttpRequestHeaders::kContentType,
- kApplicationOctetStream);
- request->headers.SetHeader("Content-Encoding", "gzip");
+ request.headers.SetHeader(net::HttpRequestHeaders::kContentType,
+ kApplicationOctetStream);
+ request.headers.SetHeader("Content-Encoding", "gzip");
}
variations::SignedIn signed_in_status = variations::SignedIn::kNo;
if (!access_token_.empty()) {
- request->headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
- "Bearer " + access_token_);
+ request.headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
+ "Bearer " + access_token_);
signed_in_status = variations::SignedIn::kYes;
}
// Add X-Client-Data header with experiment IDs from field trials.
variations::AppendVariationsHeader(url_, variations::InIncognito::kNo,
- signed_in_status, request);
- }
-
- void PopulateRequestBody(network::SimpleURLLoader* loader) {
- std::string compressed_request_body;
- if (!request_body_.empty()) {
- std::string uncompressed_request_body(
- reinterpret_cast<const char*>(request_body_.data()),
- request_body_.size());
-
- compression::GzipCompress(uncompressed_request_body,
- &compressed_request_body);
-
- loader->AttachStringForUpload(compressed_request_body,
- kApplicationOctetStream);
- }
-
- UMA_HISTOGRAM_COUNTS_1M(
- "ContentSuggestions.Feed.Network.RequestSizeKB.Compressed",
- static_cast<int>(compressed_request_body.size() / 1024));
+ signed_in_status, &request);
}
void OnSimpleLoaderComplete(std::unique_ptr<std::string> response) {
@@ -329,14 +357,15 @@ class FeedNetworkImpl::NetworkFetch {
if (response) {
response_info.status_code =
simple_loader_->ResponseInfo()->headers->response_code();
+ response_info.response_body_bytes = response->size();
+
response_body = std::move(*response);
if (response_info.status_code == net::HTTP_UNAUTHORIZED) {
- signin::ScopeSet scopes{kAuthenticationScope};
CoreAccountId account_id = identity_manager_->GetPrimaryAccountId();
if (!account_id.empty()) {
- identity_manager_->RemoveAccessTokenFromCache(account_id, scopes,
- access_token_);
+ identity_manager_->RemoveAccessTokenFromCache(
+ account_id, GetAuthScopes(), access_token_);
}
}
}
@@ -392,10 +421,12 @@ FeedNetworkImpl::FeedNetworkImpl(
const std::string& api_key,
scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
const base::TickClock* tick_clock,
- PrefService* pref_service)
+ PrefService* pref_service,
+ version_info::Channel chrome_channel)
: delegate_(delegate),
identity_manager_(identity_manager),
api_key_(api_key),
+ chrome_channel_(chrome_channel),
loader_factory_(loader_factory),
tick_clock_(tick_clock),
pref_service_(pref_service) {}
@@ -413,26 +444,13 @@ void FeedNetworkImpl::SendQueryRequest(
// TODO(harringtond): Decide how we want to override these URLs for testing.
// Should probably add a command-line flag.
- GURL url;
- switch (request.feed_request().feed_query().reason()) {
- case feedwire::FeedQuery::SCHEDULED_REFRESH:
- case feedwire::FeedQuery::IN_PLACE_UPDATE:
- url = GURL(kBackgroundQueryUrl);
- break;
- case feedwire::FeedQuery::NEXT_PAGE_SCROLL:
- url = GURL(kNextPageQueryUrl);
- break;
- case feedwire::FeedQuery::MANUAL_REFRESH:
- url = GURL(kFeedQueryUrl);
- break;
- default:
- std::move(callback).Run({});
- return;
- }
+ GURL url = GetFeedQueryURL(request.feed_request().feed_query().reason());
+ if (url.is_empty())
+ return std::move(callback).Run({});
AddMothershipPayloadQueryParams(/*is_post=*/false, base64proto,
- delegate_->GetLanguageTag(), &url);
- Send(url, "GET", /*request_body=*/std::string(),
+ delegate_->GetLanguageTag(), url);
+ Send(url, "GET", /*request_body=*/{},
base::BindOnce(&ParseAndForwardResponse<QueryRequestResult,
NetworkRequestType::kFeedQuery>,
std::move(callback)));
@@ -444,11 +462,10 @@ void FeedNetworkImpl::SendActionRequest(
std::string binary_proto;
request.SerializeToString(&binary_proto);
- GURL url(
- "https://www.google.com/httpservice/retry/ClankActionUploadService/"
- "ClankActionUpload");
- AddMothershipPayloadQueryParams(/*is_post=*/true, /*payload=*/std::string(),
- delegate_->GetLanguageTag(), &url);
+ GURL url = GetUploadActionURL(chrome_channel_);
+ AddMothershipPayloadQueryParams(/*is_post=*/true,
+ /*payload=*/{}, delegate_->GetLanguageTag(),
+ url);
Send(url, "POST", std::move(binary_proto),
base::BindOnce(
&ParseAndForwardResponse<ActionRequestResult,
diff --git a/chromium/components/feed/core/v2/feed_network_impl.h b/chromium/components/feed/core/v2/feed_network_impl.h
index ba6e4dcb96e..2dc8e438d41 100644
--- a/chromium/components/feed/core/v2/feed_network_impl.h
+++ b/chromium/components/feed/core/v2/feed_network_impl.h
@@ -11,6 +11,7 @@
#include "base/containers/unique_ptr_adapters.h"
#include "base/memory/scoped_refptr.h"
#include "components/feed/core/v2/feed_network.h"
+#include "components/version_info/channel.h"
#include "url/gurl.h"
class PrefService;
@@ -43,7 +44,8 @@ class FeedNetworkImpl : public FeedNetwork {
const std::string& api_key,
scoped_refptr<network::SharedURLLoaderFactory> loader_factory,
const base::TickClock* tick_clock,
- PrefService* pref_service);
+ PrefService* pref_service,
+ version_info::Channel chrome_channel);
~FeedNetworkImpl() override;
FeedNetworkImpl(const FeedNetworkImpl&) = delete;
FeedNetworkImpl& operator=(FeedNetworkImpl&) = delete;
@@ -79,6 +81,7 @@ class FeedNetworkImpl : public FeedNetwork {
Delegate* delegate_;
signin::IdentityManager* identity_manager_;
const std::string api_key_;
+ const version_info::Channel chrome_channel_;
scoped_refptr<network::SharedURLLoaderFactory> loader_factory_;
const base::TickClock* tick_clock_;
PrefService* pref_service_;
diff --git a/chromium/components/feed/core/v2/feed_network_impl_unittest.cc b/chromium/components/feed/core/v2/feed_network_impl_unittest.cc
index c931576ae71..990f791debf 100644
--- a/chromium/components/feed/core/v2/feed_network_impl_unittest.cc
+++ b/chromium/components/feed/core/v2/feed_network_impl_unittest.cc
@@ -94,7 +94,7 @@ class FeedNetworkTest : public testing::Test {
feed_network_ = std::make_unique<FeedNetworkImpl>(
&delegate_, identity_test_env_.identity_manager(), "dummy_api_key",
shared_url_loader_factory_, task_environment_.GetMockTickClock(),
- &profile_prefs_);
+ &profile_prefs_, version_info::Channel::STABLE);
}
FeedNetwork* feed_network() { return feed_network_.get(); }
@@ -410,8 +410,8 @@ TEST_F(FeedNetworkTest, SendActionRequestSendsValidRequest) {
RespondToActionRequest(GetTestActionResponse(), net::HTTP_OK);
EXPECT_EQ(
- GURL("https://www.google.com/httpservice/retry/ClankActionUploadService/"
- "ClankActionUpload?fmt=bin&hl=en"),
+ GURL(
+ "https://discover-pa.googleapis.com/v1/actions:upload?fmt=bin&hl=en"),
resource_request.url);
EXPECT_EQ("POST", resource_request.method);
diff --git a/chromium/components/feed/core/v2/feed_store.h b/chromium/components/feed/core/v2/feed_store.h
index e0363db2cf5..c74ab1b4aaf 100644
--- a/chromium/components/feed/core/v2/feed_store.h
+++ b/chromium/components/feed/core/v2/feed_store.h
@@ -95,10 +95,6 @@ class FeedStore {
void WriteMetadata(feedstore::Metadata metadata,
base::OnceCallback<void(bool)> callback);
- // TODO(iwells): implement this
- // Deletes old records that are no longer needed
- // void RemoveOldData(base::OnceCallback<void(bool)> callback);
-
bool IsInitializedForTesting() const;
leveldb_proto::ProtoDatabase<feedstore::Record>* GetDatabaseForTesting() {
@@ -151,11 +147,6 @@ class FeedStore {
bool read_ok,
std::unique_ptr<feedstore::Record> record);
- // TODO(iwells): implement
- // bool OldRecordFilter(const std::string& key);
- // void OnRemoveOldDataFinished(base::OnceCallback<void(bool)> callback,
- // bool success);
-
base::OnceClosure initialize_callback_;
leveldb_proto::Enums::InitStatus database_status_;
std::unique_ptr<leveldb_proto::ProtoDatabase<feedstore::Record>> database_;
diff --git a/chromium/components/feed/core/v2/feed_stream.cc b/chromium/components/feed/core/v2/feed_stream.cc
index 4544d65915c..d6f6ee104a7 100644
--- a/chromium/components/feed/core/v2/feed_stream.cc
+++ b/chromium/components/feed/core/v2/feed_stream.cc
@@ -10,12 +10,15 @@
#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
+#include "base/threading/thread_task_runner_handle.h"
#include "base/time/clock.h"
#include "base/time/tick_clock.h"
#include "components/feed/core/common/pref_names.h"
#include "components/feed/core/proto/v2/store.pb.h"
#include "components/feed/core/proto/v2/ui.pb.h"
+#include "components/feed/core/proto/v2/wire/there_and_back_again_data.pb.h"
#include "components/feed/core/shared_prefs/pref_names.h"
+#include "components/feed/core/v2/config.h"
#include "components/feed/core/v2/enums.h"
#include "components/feed/core/v2/feed_network.h"
#include "components/feed/core/v2/feed_store.h"
@@ -37,7 +40,7 @@ namespace feed {
namespace {
void PopulateDebugStreamData(const LoadStreamTask::Result& load_result,
- PrefService* profile_prefs) {
+ PrefService& profile_prefs) {
DebugStreamData debug_data = ::feed::prefs::GetDebugStreamData(profile_prefs);
std::stringstream ss;
ss << "Code: " << load_result.final_status;
@@ -139,7 +142,7 @@ void FeedStream::TriggerStreamLoad() {
}
void FeedStream::InitialStreamLoadComplete(LoadStreamTask::Result result) {
- PopulateDebugStreamData(result, profile_prefs_);
+ PopulateDebugStreamData(result, *profile_prefs_);
metrics_reporter_->OnLoadStream(result.load_from_store_status,
result.final_status);
@@ -156,11 +159,36 @@ void FeedStream::AttachSurface(SurfaceInterface* surface) {
metrics_reporter_->SurfaceOpened(surface->GetSurfaceId());
TriggerStreamLoad();
surface_updater_->SurfaceAdded(surface);
+ // Cancel any scheduled model unload task.
+ ++unload_on_detach_sequence_number_;
}
void FeedStream::DetachSurface(SurfaceInterface* surface) {
metrics_reporter_->SurfaceClosed(surface->GetSurfaceId());
surface_updater_->SurfaceRemoved(surface);
+ if (!surface_updater_->HasSurfaceAttached()) {
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&FeedStream::AddUnloadModelIfNoSurfacesAttachedTask,
+ GetWeakPtr(), unload_on_detach_sequence_number_),
+ GetFeedConfig().model_unload_timeout);
+ }
+}
+
+void FeedStream::AddUnloadModelIfNoSurfacesAttachedTask(int sequence_number) {
+ // Don't continue if unload_on_detach_sequence_number_ has changed.
+ if (unload_on_detach_sequence_number_ != sequence_number)
+ return;
+
+ task_queue_.AddTask(std::make_unique<offline_pages::ClosureTask>(
+ base::BindOnce(&FeedStream::UnloadModelIfNoSurfacesAttachedTask,
+ base::Unretained(this))));
+}
+
+void FeedStream::UnloadModelIfNoSurfacesAttachedTask() {
+ if (surface_updater_->HasSurfaceAttached())
+ return;
+ UnloadModel();
}
void FeedStream::SetArticlesListVisible(bool is_visible) {
@@ -171,6 +199,10 @@ bool FeedStream::IsArticlesListVisible() {
return profile_prefs_->GetBoolean(prefs::kArticlesListVisible);
}
+bool FeedStream::IsFeedEnabledByEnterprisePolicy() {
+ return profile_prefs_->GetBoolean(prefs::kEnableSnippets);
+}
+
void FeedStream::LoadMore(SurfaceId surface_id,
base::OnceCallback<void(bool)> callback) {
metrics_reporter_->OnLoadMoreBegin(surface_id);
@@ -178,7 +210,16 @@ void FeedStream::LoadMore(SurfaceId surface_id,
DLOG(ERROR) << "Ignoring LoadMore() before the model is loaded";
return std::move(callback).Run(false);
}
+ // We want to abort early to avoid showing a loading spinner if it's not
+ // necessary.
+ if (ShouldMakeFeedQueryRequest(/*is_load_more=*/true,
+ /*consume_quota=*/false) !=
+ LoadStreamStatus::kNoStatus) {
+ return std::move(callback).Run(false);
+ }
+
surface_updater_->SetLoadingMore(true);
+
// Have at most one in-flight LoadMore() request. Send the result to all
// requestors.
load_more_complete_callbacks_.push_back(std::move(callback));
@@ -191,8 +232,6 @@ void FeedStream::LoadMore(SurfaceId surface_id,
void FeedStream::LoadMoreComplete(LoadMoreTask::Result result) {
metrics_reporter_->OnLoadMore(result.final_status);
- // TODO(harringtond): In the case of failure, do we need to load an error
- // message slice?
surface_updater_->SetLoadingMore(false);
std::vector<base::OnceCallback<void(bool)>> moved_callbacks =
std::move(load_more_complete_callbacks_);
@@ -220,6 +259,13 @@ EphemeralChangeId FeedStream::CreateEphemeralChange(
return model_->CreateEphemeralChange(std::move(operations));
}
+EphemeralChangeId FeedStream::CreateEphemeralChangeFromPackedData(
+ base::StringPiece data) {
+ feedpacking::DismissData msg;
+ msg.ParseFromArray(data.data(), data.size());
+ return CreateEphemeralChange(TranslateDismissData(clock_->Now(), msg));
+}
+
bool FeedStream::CommitEphemeralChange(EphemeralChangeId id) {
if (!model_)
return false;
@@ -232,8 +278,18 @@ bool FeedStream::RejectEphemeralChange(EphemeralChangeId id) {
return model_->RejectEphemeralChange(id);
}
+void FeedStream::ProcessThereAndBackAgain(base::StringPiece data) {
+ feedwire::ThereAndBackAgainData msg;
+ msg.ParseFromArray(data.data(), data.size());
+ if (msg.has_action_payload()) {
+ feedwire::FeedAction action_msg;
+ *action_msg.mutable_action_payload() = std::move(msg.action_payload());
+ UploadAction(std::move(action_msg), /*upload_now=*/true, base::DoNothing());
+ }
+}
+
DebugStreamData FeedStream::GetDebugStreamData() {
- return ::feed::prefs::GetDebugStreamData(profile_prefs_);
+ return ::feed::prefs::GetDebugStreamData(*profile_prefs_);
}
void FeedStream::ForceRefreshForDebugging() {
@@ -315,13 +371,17 @@ LoadStreamStatus FeedStream::ShouldAttemptLoad(bool model_loading) {
if (!IsArticlesListVisible())
return LoadStreamStatus::kLoadNotAllowedArticlesListHidden;
+ if (!IsFeedEnabledByEnterprisePolicy())
+ return LoadStreamStatus::kLoadNotAllowedDisabledByEnterprisePolicy;
+
if (!delegate_->IsEulaAccepted())
return LoadStreamStatus::kLoadNotAllowedEulaNotAccepted;
return LoadStreamStatus::kNoStatus;
}
-LoadStreamStatus FeedStream::ShouldMakeFeedQueryRequest(bool is_load_more) {
+LoadStreamStatus FeedStream::ShouldMakeFeedQueryRequest(bool is_load_more,
+ bool consume_quota) {
if (!is_load_more) {
// Time has passed since calling |ShouldAttemptLoad()|, call it again to
// confirm we should still attempt loading.
@@ -330,6 +390,11 @@ LoadStreamStatus FeedStream::ShouldMakeFeedQueryRequest(bool is_load_more) {
if (should_not_attempt_reason != LoadStreamStatus::kNoStatus) {
return should_not_attempt_reason;
}
+ } else {
+ // LoadMore requires a next page token.
+ if (!model_ || model_->GetNextPageToken().empty()) {
+ return LoadStreamStatus::kCannotLoadMoreNoNextPageToken;
+ }
}
// TODO(harringtond): |suppress_refreshes_until_| was historically used
@@ -346,7 +411,8 @@ LoadStreamStatus FeedStream::ShouldMakeFeedQueryRequest(bool is_load_more) {
return LoadStreamStatus::kCannotLoadFromNetworkOffline;
}
- if (!request_throttler_.RequestQuota(NetworkRequestType::kFeedQuery)) {
+ if (consume_quota &&
+ !request_throttler_.RequestQuota(NetworkRequestType::kFeedQuery)) {
return LoadStreamStatus::kCannotLoadFromNetworkThrottled;
}
@@ -390,7 +456,7 @@ void FeedStream::OnSignedOut() {
void FeedStream::ExecuteRefreshTask() {
// Schedule the next refresh attempt. If a new refresh schedule is returned
// through this refresh, it will be overwritten.
- SetRequestSchedule(feed::prefs::GetRequestSchedule(profile_prefs_));
+ SetRequestSchedule(feed::prefs::GetRequestSchedule(*profile_prefs_));
LoadStreamStatus do_not_attempt_reason = ShouldAttemptLoad();
if (do_not_attempt_reason != LoadStreamStatus::kNoStatus) {
@@ -438,7 +504,7 @@ void FeedStream::SetRequestSchedule(RequestSchedule schedule) {
} else {
refresh_task_scheduler_->Cancel();
}
- feed::prefs::SetRequestSchedule(schedule, profile_prefs_);
+ feed::prefs::SetRequestSchedule(schedule, *profile_prefs_);
}
void FeedStream::UnloadModel() {
@@ -500,5 +566,8 @@ void FeedStream::ReportContextMenuOpened() {
void FeedStream::ReportStreamScrolled(int distance_dp) {
metrics_reporter_->StreamScrolled(distance_dp);
}
+void FeedStream::ReportStreamScrollStart() {
+ metrics_reporter_->StreamScrollStart();
+}
} // namespace feed
diff --git a/chromium/components/feed/core/v2/feed_stream.h b/chromium/components/feed/core/v2/feed_stream.h
index d92d889563e..7ddc42d6aa2 100644
--- a/chromium/components/feed/core/v2/feed_stream.h
+++ b/chromium/components/feed/core/v2/feed_stream.h
@@ -119,8 +119,11 @@ class FeedStream : public FeedStreamApi,
std::vector<feedstore::DataOperation> operations) override;
EphemeralChangeId CreateEphemeralChange(
std::vector<feedstore::DataOperation> operations) override;
+ EphemeralChangeId CreateEphemeralChangeFromPackedData(
+ base::StringPiece data) override;
bool CommitEphemeralChange(EphemeralChangeId id) override;
bool RejectEphemeralChange(EphemeralChangeId id) override;
+ void ProcessThereAndBackAgain(base::StringPiece data) override;
DebugStreamData GetDebugStreamData() override;
void ForceRefreshForDebugging() override;
std::string DumpStateForDebugging() override;
@@ -140,6 +143,7 @@ class FeedStream : public FeedStreamApi,
void ReportManageInterestsAction() override;
void ReportContextMenuOpened() override;
void ReportStreamScrolled(int distance_dp) override;
+ void ReportStreamScrollStart() override;
// offline_pages::TaskQueue::Delegate.
void OnTaskQueueIsIdle() override;
@@ -194,8 +198,11 @@ class FeedStream : public FeedStreamApi,
// Determines if a FeedQuery request can be made. If successful,
// returns |LoadStreamStatus::kNoStatus| and acquires throttler quota.
- // Otherwise returns the reason.
- LoadStreamStatus ShouldMakeFeedQueryRequest(bool is_load_more = false);
+ // Otherwise returns the reason. If |consume_quota| is false, no quota is
+ // consumed. This can be used to predict the likely result on a subsequent
+ // call.
+ LoadStreamStatus ShouldMakeFeedQueryRequest(bool is_load_more = false,
+ bool consume_quota = true);
// Unloads the model. Surfaces are not updated, and will remain frozen until a
// model load is requested.
@@ -229,16 +236,26 @@ class FeedStream : public FeedStreamApi,
private:
class ModelStoreChangeMonitor;
+
+ base::WeakPtr<FeedStream> GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
// A single function task to delete stored feed data and force a refresh.
// To only be called from within a |Task|.
void ForceRefreshForDebuggingTask();
+ void AddUnloadModelIfNoSurfacesAttachedTask(int sequence_number);
+ void UnloadModelIfNoSurfacesAttachedTask();
+
void InitialStreamLoadComplete(LoadStreamTask::Result result);
void LoadMoreComplete(LoadMoreTask::Result result);
void BackgroundRefreshComplete(LoadStreamTask::Result result);
void ClearAll();
+ bool IsFeedEnabledByEnterprisePolicy();
+
// Unowned.
RefreshTaskScheduler* refresh_task_scheduler_;
@@ -268,9 +285,12 @@ class FeedStream : public FeedStreamApi,
base::TimeTicks suppress_refreshes_until_;
std::vector<base::OnceCallback<void(bool)>> load_more_complete_callbacks_;
Metadata metadata_;
+ int unload_on_detach_sequence_number_ = 0;
// To allow tests to wait on task queue idle.
base::RepeatingClosure idle_callback_;
+
+ base::WeakPtrFactory<FeedStream> weak_ptr_factory_{this};
};
} // namespace feed
diff --git a/chromium/components/feed/core/v2/feed_stream_unittest.cc b/chromium/components/feed/core/v2/feed_stream_unittest.cc
index d0325fbd69e..46a25b3191c 100644
--- a/chromium/components/feed/core/v2/feed_stream_unittest.cc
+++ b/chromium/components/feed/core/v2/feed_stream_unittest.cc
@@ -28,6 +28,7 @@
#include "components/feed/core/proto/v2/ui.pb.h"
#include "components/feed/core/proto/v2/wire/action_request.pb.h"
#include "components/feed/core/proto/v2/wire/request.pb.h"
+#include "components/feed/core/proto/v2/wire/there_and_back_again_data.pb.h"
#include "components/feed/core/shared_prefs/pref_names.h"
#include "components/feed/core/v2/config.h"
#include "components/feed/core/v2/feed_network.h"
@@ -239,6 +240,8 @@ class TestFeedNetwork : public FeedNetwork {
// time we want to inject a translated response for ease of test-writing.
query_request_sent = request;
QueryRequestResult result;
+ result.response_info.status_code = 200;
+ result.response_info.response_body_bytes = 100;
result.response_info.fetch_duration = base::TimeDelta::FromMilliseconds(42);
if (injected_response_) {
result.response_body = std::make_unique<feedwire::Response>(
@@ -359,8 +362,8 @@ class FakeRefreshTaskScheduler : public RefreshTaskScheduler {
class TestMetricsReporter : public MetricsReporter {
public:
- explicit TestMetricsReporter(const base::TickClock* clock)
- : MetricsReporter(clock) {}
+ explicit TestMetricsReporter(const base::TickClock* clock, PrefService* prefs)
+ : MetricsReporter(clock, prefs) {}
// MetricsReporter.
void ContentSliceViewed(SurfaceId surface_id, int index_in_stream) override {
@@ -400,10 +403,15 @@ class TestMetricsReporter : public MetricsReporter {
class FeedStreamTest : public testing::Test, public FeedStream::Delegate {
public:
void SetUp() override {
+ // Reset to default config, since tests can change it.
+ SetFeedConfigForTesting(Config());
+
feed::prefs::RegisterFeedSharedProfilePrefs(profile_prefs_.registry());
feed::RegisterProfilePrefs(profile_prefs_.registry());
- CHECK_EQ(kTestTimeEpoch, task_environment_.GetMockClock()->Now());
+ metrics_reporter_ = std::make_unique<TestMetricsReporter>(
+ task_environment_.GetMockTickClock(), &profile_prefs_);
+ CHECK_EQ(kTestTimeEpoch, task_environment_.GetMockClock()->Now());
CreateStream();
}
@@ -436,7 +444,7 @@ class FeedStreamTest : public testing::Test, public FeedStream::Delegate {
chrome_info.channel = version_info::Channel::STABLE;
chrome_info.version = base::Version({99, 1, 9911, 2});
stream_ = std::make_unique<FeedStream>(
- &refresh_scheduler_, &metrics_reporter_, this, &profile_prefs_,
+ &refresh_scheduler_, metrics_reporter_.get(), this, &profile_prefs_,
&network_, store_.get(), task_environment_.GetMockClock(),
task_environment_.GetMockTickClock(), chrome_info);
@@ -495,8 +503,8 @@ class FeedStreamTest : public testing::Test, public FeedStream::Delegate {
protected:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
- TestMetricsReporter metrics_reporter_{task_environment_.GetMockTickClock()};
TestingPrefServiceSimple profile_prefs_;
+ std::unique_ptr<TestMetricsReporter> metrics_reporter_;
TestFeedNetwork network_;
TestWireResponseTranslator response_translator_;
@@ -531,7 +539,7 @@ TEST_F(FeedStreamTest, DoNotRefreshIfArticlesListIsHidden) {
stream_->ExecuteRefreshTask();
EXPECT_TRUE(refresh_scheduler_.refresh_task_complete);
EXPECT_EQ(LoadStreamStatus::kLoadNotAllowedArticlesListHidden,
- metrics_reporter_.background_refresh_status);
+ metrics_reporter_->background_refresh_status);
}
TEST_F(FeedStreamTest, BackgroundRefreshSuccess) {
@@ -544,7 +552,7 @@ TEST_F(FeedStreamTest, BackgroundRefreshSuccess) {
// network.
ASSERT_TRUE(refresh_scheduler_.refresh_task_complete);
EXPECT_EQ(LoadStreamStatus::kLoadedFromNetwork,
- metrics_reporter_.background_refresh_status);
+ metrics_reporter_->background_refresh_status);
EXPECT_TRUE(response_translator_.InjectedResponseConsumed());
EXPECT_FALSE(stream_->GetModel());
TestSurface surface(stream_.get());
@@ -558,7 +566,7 @@ TEST_F(FeedStreamTest, BackgroundRefreshNotAttemptedWhenModelIsLoading) {
stream_->ExecuteRefreshTask();
WaitForIdleTaskQueue();
- EXPECT_EQ(metrics_reporter_.background_refresh_status,
+ EXPECT_EQ(metrics_reporter_->background_refresh_status,
LoadStreamStatus::kModelAlreadyLoaded);
}
@@ -570,7 +578,7 @@ TEST_F(FeedStreamTest, BackgroundRefreshNotAttemptedAfterModelIsLoaded) {
stream_->ExecuteRefreshTask();
WaitForIdleTaskQueue();
- EXPECT_EQ(metrics_reporter_.background_refresh_status,
+ EXPECT_EQ(metrics_reporter_->background_refresh_status,
LoadStreamStatus::kModelAlreadyLoaded);
}
@@ -837,7 +845,7 @@ TEST_F(FeedStreamTest, LoadFromNetworkFailsDueToProtoTranslation) {
WaitForIdleTaskQueue();
EXPECT_EQ(LoadStreamStatus::kProtoTranslationFailed,
- metrics_reporter_.load_stream_status);
+ metrics_reporter_->load_stream_status);
}
TEST_F(FeedStreamTest, DoNotLoadFromNetworkWhenOffline) {
@@ -847,7 +855,7 @@ TEST_F(FeedStreamTest, DoNotLoadFromNetworkWhenOffline) {
WaitForIdleTaskQueue();
EXPECT_EQ(LoadStreamStatus::kCannotLoadFromNetworkOffline,
- metrics_reporter_.load_stream_status);
+ metrics_reporter_->load_stream_status);
EXPECT_EQ("loading -> cant-refresh", surface.DescribeUpdates());
}
@@ -858,7 +866,7 @@ TEST_F(FeedStreamTest, DoNotLoadStreamWhenArticleListIsHidden) {
WaitForIdleTaskQueue();
EXPECT_EQ(LoadStreamStatus::kLoadNotAllowedArticlesListHidden,
- metrics_reporter_.load_stream_status);
+ metrics_reporter_->load_stream_status);
EXPECT_EQ("no-cards", surface.DescribeUpdates());
}
@@ -869,7 +877,7 @@ TEST_F(FeedStreamTest, DoNotLoadStreamWhenEulaIsNotAccepted) {
WaitForIdleTaskQueue();
EXPECT_EQ(LoadStreamStatus::kLoadNotAllowedEulaNotAccepted,
- metrics_reporter_.load_stream_status);
+ metrics_reporter_->load_stream_status);
EXPECT_EQ("no-cards", surface.DescribeUpdates());
}
@@ -900,7 +908,7 @@ TEST_F(FeedStreamTest, DoNotLoadFromNetworkAfterHistoryIsDeleted) {
EXPECT_EQ("loading -> no-cards", surface.DescribeUpdates());
EXPECT_EQ(LoadStreamStatus::kCannotLoadFromNetworkSupressedForHistoryDelete,
- metrics_reporter_.load_stream_status);
+ metrics_reporter_->load_stream_status);
surface.Detach();
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(2));
@@ -1028,7 +1036,7 @@ TEST_F(FeedStreamTest, ReportSliceViewedIdentifiesCorrectIndex) {
stream_->ReportSliceViewed(
surface.GetSurfaceId(),
surface.initial_state->updated_slices(1).slice().slice_id());
- EXPECT_EQ(1, metrics_reporter_.slice_viewed_index);
+ EXPECT_EQ(1, metrics_reporter_->slice_viewed_index);
}
TEST_F(FeedStreamTest, LoadMoreAppendsContent) {
@@ -1145,6 +1153,26 @@ TEST_F(FeedStreamTest, LoadMoreSendsTokens) {
.next_page_token());
}
+TEST_F(FeedStreamTest, LoadMoreAbortsIfNoNextPageToken) {
+ {
+ std::unique_ptr<StreamModelUpdateRequest> initial_state =
+ MakeTypicalInitialModelState();
+ initial_state->stream_data.clear_next_page_token();
+ response_translator_.InjectResponse(std::move(initial_state));
+ }
+ TestSurface surface(stream_.get());
+ WaitForIdleTaskQueue();
+
+ CallbackReceiver<bool> callback;
+ stream_->LoadMore(surface.GetSurfaceId(), callback.Bind());
+ WaitForIdleTaskQueue();
+
+ // LoadMore fails, and does not make an additional request.
+ EXPECT_EQ(base::Optional<bool>(false), callback.GetResult());
+ ASSERT_EQ(1, network_.send_query_call_count);
+ EXPECT_EQ("loading -> 2 slices", surface.DescribeUpdates());
+}
+
TEST_F(FeedStreamTest, LoadMoreFail) {
response_translator_.InjectResponse(MakeTypicalInitialModelState());
TestSurface surface(stream_.get());
@@ -1252,15 +1280,17 @@ TEST_F(FeedStreamTest, StorePendingAction) {
TEST_F(FeedStreamTest, StorePendingActionAndUploadNow) {
network_.consistency_token = "token-11";
- CallbackReceiver<UploadActionsTask::Result> cr;
- stream_->UploadAction(MakeFeedAction(42ul), true, cr.Bind());
+ // Call |ProcessThereAndBackAgain()|, which triggers Upload() with
+ // upload_now=true.
+ {
+ feedwire::ThereAndBackAgainData msg;
+ *msg.mutable_action_payload() = MakeFeedAction(42ul).action_payload();
+ stream_->ProcessThereAndBackAgain(msg.SerializeAsString());
+ }
WaitForIdleTaskQueue();
- ASSERT_TRUE(cr.GetResult());
- EXPECT_EQ(1ul, cr.GetResult()->upload_attempt_count);
- EXPECT_EQ(UploadActionsStatus::kUpdatedConsistencyToken,
- cr.GetResult()->status);
-
+ // Verify the action was uploaded.
+ EXPECT_EQ(1, network_.action_request_call_count);
std::vector<feedstore::StoredAction> result =
ReadStoredActions(stream_->GetStore());
ASSERT_EQ(0ul, result.size());
@@ -1425,5 +1455,75 @@ TEST_F(FeedStreamTest, MetadataLoadedWhenDatabaseInitialized) {
EXPECT_EQ(1, stream_->GetMetadata()->GetNextActionId().GetUnsafeValue());
}
+TEST_F(FeedStreamTest, ModelUnloadsAfterTimeout) {
+ Config config;
+ config.model_unload_timeout = base::TimeDelta::FromSeconds(1);
+ SetFeedConfigForTesting(config);
+
+ response_translator_.InjectResponse(MakeTypicalInitialModelState());
+ TestSurface surface(stream_.get());
+ WaitForIdleTaskQueue();
+
+ surface.Detach();
+
+ task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(999));
+ WaitForIdleTaskQueue();
+ EXPECT_TRUE(stream_->GetModel());
+
+ task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(2));
+ WaitForIdleTaskQueue();
+ EXPECT_FALSE(stream_->GetModel());
+}
+
+TEST_F(FeedStreamTest, ModelDoesNotUnloadIfSurfaceIsAttached) {
+ Config config;
+ config.model_unload_timeout = base::TimeDelta::FromSeconds(1);
+ SetFeedConfigForTesting(config);
+
+ response_translator_.InjectResponse(MakeTypicalInitialModelState());
+ TestSurface surface(stream_.get());
+ WaitForIdleTaskQueue();
+
+ surface.Detach();
+
+ task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(999));
+ WaitForIdleTaskQueue();
+ EXPECT_TRUE(stream_->GetModel());
+
+ surface.Attach(stream_.get());
+
+ task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(2));
+ WaitForIdleTaskQueue();
+ EXPECT_TRUE(stream_->GetModel());
+}
+
+TEST_F(FeedStreamTest, ModelUnloadsAfterSecondTimeout) {
+ Config config;
+ config.model_unload_timeout = base::TimeDelta::FromSeconds(1);
+ SetFeedConfigForTesting(config);
+
+ response_translator_.InjectResponse(MakeTypicalInitialModelState());
+ TestSurface surface(stream_.get());
+ WaitForIdleTaskQueue();
+
+ surface.Detach();
+
+ task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(999));
+ WaitForIdleTaskQueue();
+ EXPECT_TRUE(stream_->GetModel());
+
+ // Attaching another surface will prolong the unload time for another second.
+ surface.Attach(stream_.get());
+ surface.Detach();
+
+ task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(999));
+ WaitForIdleTaskQueue();
+ EXPECT_TRUE(stream_->GetModel());
+
+ task_environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(2));
+ WaitForIdleTaskQueue();
+ EXPECT_FALSE(stream_->GetModel());
+}
+
} // namespace
} // namespace feed
diff --git a/chromium/components/feed/core/v2/metrics_reporter.cc b/chromium/components/feed/core/v2/metrics_reporter.cc
index 975db91a92c..8c367a66a18 100644
--- a/chromium/components/feed/core/v2/metrics_reporter.cc
+++ b/chromium/components/feed/core/v2/metrics_reporter.cc
@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "components/feed/core/v2/metrics_reporter.h"
+#include <algorithm>
#include <cmath>
#include "base/metrics/histogram_functions.h"
@@ -10,6 +11,7 @@
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
+#include "components/feed/core/v2/prefs.h"
namespace feed {
namespace {
@@ -23,6 +25,12 @@ constexpr base::TimeDelta kLoadTimeout = base::TimeDelta::FromSeconds(15);
// Maximum time to wait before declaring opening a card a failure.
// For ContentSuggestions.Feed.UserJourney.OpenCard.
constexpr base::TimeDelta kOpenTimeout = base::TimeDelta::FromSeconds(20);
+// For ContentSuggestions.Feed.TimeSpentInFeed, we want to get a measure
+// of how much time the user is spending with the Feed. If the user stops
+// interacting with the Feed, we stop counting it as time spent after this
+// timeout.
+constexpr base::TimeDelta kTimeSpentInFeedInteractionTimeout =
+ base::TimeDelta::FromSeconds(30);
void ReportEngagementTypeHistogram(FeedEngagementType engagement_type) {
base::UmaHistogramEnumeration("ContentSuggestions.Feed.EngagementType",
@@ -41,10 +49,16 @@ void ReportUserActionHistogram(FeedUserActionType action_type) {
} // namespace
-MetricsReporter::MetricsReporter(const base::TickClock* clock)
- : clock_(clock) {}
+MetricsReporter::MetricsReporter(const base::TickClock* clock,
+ PrefService* profile_prefs)
+ : clock_(clock), profile_prefs_(profile_prefs) {
+ persistent_data_ = prefs::GetPersistentMetricsData(*profile_prefs_);
+ ReportPersistentDataIfDayIsDone();
+}
-MetricsReporter::~MetricsReporter() = default;
+MetricsReporter::~MetricsReporter() {
+ FinalizeMetrics();
+}
void MetricsReporter::OnEnterBackground() {
FinalizeMetrics();
@@ -57,6 +71,28 @@ void MetricsReporter::RecordInteraction() {
ReportEngagementTypeHistogram(FeedEngagementType::kFeedInteracted);
}
+void MetricsReporter::TrackTimeSpentInFeed(bool interacted_or_scrolled) {
+ if (time_in_feed_start_) {
+ ReportPersistentDataIfDayIsDone();
+ persistent_data_.accumulated_time_spent_in_feed +=
+ std::min(kTimeSpentInFeedInteractionTimeout,
+ clock_->NowTicks() - *time_in_feed_start_);
+ time_in_feed_start_ = base::nullopt;
+ }
+
+ if (interacted_or_scrolled) {
+ time_in_feed_start_ = clock_->NowTicks();
+ }
+}
+
+void MetricsReporter::FinalizeVisit() {
+ if (!engaged_simple_reported_)
+ return;
+ engaged_reported_ = false;
+ engaged_simple_reported_ = false;
+ TrackTimeSpentInFeed(false);
+}
+
void MetricsReporter::RecordEngagement(int scroll_distance_dp,
bool interacted) {
scroll_distance_dp = std::abs(scroll_distance_dp);
@@ -64,12 +100,13 @@ void MetricsReporter::RecordEngagement(int scroll_distance_dp,
auto now = clock_->NowTicks();
const base::TimeDelta kVisitTimeout = base::TimeDelta::FromMinutes(5);
if (now - visit_start_time_ > kVisitTimeout) {
- engaged_reported_ = false;
- engaged_simple_reported_ = false;
+ FinalizeVisit();
}
// Reset the last active time for session measurement.
visit_start_time_ = now;
+ TrackTimeSpentInFeed(true);
+
// Report the user as engaged-simple if they have scrolled any amount or
// interacted with the card, and we have not already reported it for this
// chrome run.
@@ -89,6 +126,13 @@ void MetricsReporter::RecordEngagement(int scroll_distance_dp,
}
}
+void MetricsReporter::StreamScrollStart() {
+ // Note that |TrackTimeSpentInFeed()| is called as a result of
+ // |StreamScrolled()| as well. Tracking the start of scroll events ensures we
+ // don't miss out on long and slow scrolling.
+ TrackTimeSpentInFeed(true);
+}
+
void MetricsReporter::StreamScrolled(int distance_dp) {
RecordEngagement(distance_dp, /*interacted=*/false);
@@ -188,6 +232,8 @@ void MetricsReporter::ContextMenuOpened() {
}
void MetricsReporter::SurfaceOpened(SurfaceId surface_id) {
+ ReportPersistentDataIfDayIsDone();
+
surfaces_waiting_for_content_.emplace(surface_id, clock_->NowTicks());
ReportUserActionHistogram(FeedUserActionType::kOpenedFeedSurface);
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
@@ -203,6 +249,7 @@ void MetricsReporter::SurfaceClosed(SurfaceId surface_id) {
}
void MetricsReporter::FinalizeMetrics() {
+ FinalizeVisit();
ReportCardOpenEndIfNeeded(false);
for (auto iter = surfaces_waiting_for_content_.begin();
iter != surfaces_waiting_for_content_.end();) {
@@ -212,6 +259,7 @@ void MetricsReporter::FinalizeMetrics() {
iter != surfaces_waiting_for_more_content_.end();) {
ReportGetMoreIfNeeded((iter++)->first, false);
}
+ prefs::SetPersistentMetricsData(persistent_data_, *profile_prefs_);
}
void MetricsReporter::ReportOpenFeedIfNeeded(SurfaceId surface_id,
@@ -332,6 +380,18 @@ void MetricsReporter::OnLoadMore(LoadStreamStatus status) {
"ContentSuggestions.Feed.LoadStreamStatus.LoadMore", status);
}
+void MetricsReporter::OnUploadActionsBatch(UploadActionsBatchStatus status) {
+ DVLOG(1) << "UploadActionsBatchStatus: " << status;
+ base::UmaHistogramEnumeration(
+ "ContentSuggestions.Feed.UploadActionsBatchStatus", status);
+}
+
+void MetricsReporter::OnUploadActions(UploadActionsStatus status) {
+ DVLOG(1) << "UploadActionsTask finished with status " << status;
+ base::UmaHistogramEnumeration("ContentSuggestions.Feed.UploadActionsStatus",
+ status);
+}
+
void MetricsReporter::SurfaceReceivedContent(SurfaceId surface_id) {
ReportGetMoreIfNeeded(surface_id, true);
}
@@ -344,4 +404,34 @@ void MetricsReporter::OnClearAll(base::TimeDelta time_since_last_clear) {
/*bucket_count=*/50);
}
+void MetricsReporter::ReportPersistentDataIfDayIsDone() {
+ // Reset the persistent data if 24 hours have elapsed, or if it has never
+ // been initialized.
+ bool reset_data = false;
+ if (persistent_data_.current_day_start.is_null()) {
+ reset_data = true;
+ } else {
+ // Report metrics if 24 hours have passed since the day started.
+ const base::TimeDelta since_day_start =
+ (base::Time::Now() - persistent_data_.current_day_start);
+ if (since_day_start > base::TimeDelta::FromDays(1)
+ // Allow up to 1 hour of negative delta, for expected clock changes.
+ || since_day_start < -base::TimeDelta::FromHours(1)) {
+ if (persistent_data_.accumulated_time_spent_in_feed > base::TimeDelta()) {
+ base::UmaHistogramLongTimes(
+ "ContentSuggestions.Feed.TimeSpentInFeed",
+ persistent_data_.accumulated_time_spent_in_feed);
+ }
+
+ reset_data = true;
+ }
+ }
+
+ if (reset_data) {
+ persistent_data_ = PersistentMetricsData();
+ persistent_data_.current_day_start = base::Time::Now().LocalMidnight();
+ prefs::SetPersistentMetricsData(persistent_data_, *profile_prefs_);
+ }
+}
+
} // namespace feed
diff --git a/chromium/components/feed/core/v2/metrics_reporter.h b/chromium/components/feed/core/v2/metrics_reporter.h
index d680f1e3916..e42a05e2ce1 100644
--- a/chromium/components/feed/core/v2/metrics_reporter.h
+++ b/chromium/components/feed/core/v2/metrics_reporter.h
@@ -5,6 +5,8 @@
#ifndef COMPONENTS_FEED_CORE_V2_METRICS_REPORTER_H_
#define COMPONENTS_FEED_CORE_V2_METRICS_REPORTER_H_
+#include <map>
+
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/time/time.h"
@@ -54,7 +56,8 @@ enum class FeedUserActionType {
// Note this is inherited only for testing.
class MetricsReporter {
public:
- explicit MetricsReporter(const base::TickClock* clock);
+ explicit MetricsReporter(const base::TickClock* clock,
+ PrefService* profile_prefs);
virtual ~MetricsReporter();
MetricsReporter(const MetricsReporter&) = delete;
MetricsReporter& operator=(const MetricsReporter&) = delete;
@@ -77,6 +80,7 @@ class MetricsReporter {
// Indicates the user scrolled the feed by |distance_dp| and then stopped
// scrolling.
void StreamScrolled(int distance_dp);
+ void StreamScrollStart();
// Called when the Feed surface is opened and closed.
void SurfaceOpened(SurfaceId surface_id);
@@ -100,20 +104,31 @@ class MetricsReporter {
// Called when Chrome is entering the background.
void OnEnterBackground();
+ // Actions upload.
+ static void OnUploadActionsBatch(UploadActionsBatchStatus status);
+ static void OnUploadActions(UploadActionsStatus status);
+
private:
base::WeakPtr<MetricsReporter> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
+ void ReportPersistentDataIfDayIsDone();
void CardOpenBegin();
void CardOpenTimeout(base::TimeTicks start_ticks);
void ReportCardOpenEndIfNeeded(bool success);
void RecordEngagement(int scroll_distance_dp, bool interacted);
+ void TrackTimeSpentInFeed(bool interacted_or_scrolled);
void RecordInteraction();
void ReportOpenFeedIfNeeded(SurfaceId surface_id, bool success);
void ReportGetMoreIfNeeded(SurfaceId surface_id, bool success);
void FinalizeMetrics();
+ void FinalizeVisit();
const base::TickClock* clock_;
+ PrefService* profile_prefs_;
+ // Persistent data stored in prefs. Data is read in the constructor, and then
+ // written back to prefs on backgrounding.
+ PersistentMetricsData persistent_data_;
base::TimeTicks visit_start_time_;
bool engaged_simple_reported_ = false;
@@ -132,6 +147,11 @@ class MetricsReporter {
// loading the page succeeds, the open is considered successful.
base::Optional<base::TimeTicks> pending_open_;
+ // For tracking time spent in the Feed.
+ base::Optional<base::TimeTicks> time_in_feed_start_;
+ // For TimeSpentOnFeed.
+ base::TimeDelta tracked_visit_time_in_feed_;
+
base::WeakPtrFactory<MetricsReporter> weak_ptr_factory_{this};
};
} // namespace feed
diff --git a/chromium/components/feed/core/v2/metrics_reporter_unittest.cc b/chromium/components/feed/core/v2/metrics_reporter_unittest.cc
index 7300a16b8fc..58e9143c091 100644
--- a/chromium/components/feed/core/v2/metrics_reporter_unittest.cc
+++ b/chromium/components/feed/core/v2/metrics_reporter_unittest.cc
@@ -5,10 +5,14 @@
#include "components/feed/core/v2/metrics_reporter.h"
#include <map>
+#include <memory>
#include "base/test/metrics/histogram_tester.h"
#include "base/test/metrics/user_action_tester.h"
#include "base/test/task_environment.h"
+#include "components/feed/core/common/pref_names.h"
+#include "components/feed/core/shared_prefs/pref_names.h"
+#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace feed {
@@ -19,6 +23,17 @@ const base::TimeDelta kEpsilon = base::TimeDelta::FromMilliseconds(1);
class MetricsReporterTest : public testing::Test {
protected:
+ void SetUp() override {
+ feed::prefs::RegisterFeedSharedProfilePrefs(profile_prefs_.registry());
+ feed::RegisterProfilePrefs(profile_prefs_.registry());
+
+ // Tests start at the beginning of a day.
+ task_environment_.AdvanceClock(
+ (base::Time::Now().LocalMidnight() + base::TimeDelta::FromDays(1)) -
+ base::Time::Now() + base::TimeDelta::FromSeconds(1));
+
+ RecreateMetricsReporter();
+ }
std::map<FeedEngagementType, int> ReportedEngagementType() {
std::map<FeedEngagementType, int> result;
for (const auto& bucket :
@@ -28,21 +43,27 @@ class MetricsReporterTest : public testing::Test {
return result;
}
+ void RecreateMetricsReporter() {
+ reporter_ = std::make_unique<MetricsReporter>(
+ task_environment_.GetMockTickClock(), &profile_prefs_);
+ }
+
protected:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
- MetricsReporter reporter_{task_environment_.GetMockTickClock()};
+ TestingPrefServiceSimple profile_prefs_;
+ std::unique_ptr<MetricsReporter> reporter_;
base::HistogramTester histogram_;
base::UserActionTester user_actions_;
};
TEST_F(MetricsReporterTest, SliceViewedReportsSuggestionShown) {
- reporter_.ContentSliceViewed(kSurfaceId, 5);
+ reporter_->ContentSliceViewed(kSurfaceId, 5);
histogram_.ExpectUniqueSample("NewTabPage.ContentSuggestions.Shown", 5, 1);
}
TEST_F(MetricsReporterTest, ScrollingSmall) {
- reporter_.StreamScrolled(100);
+ reporter_->StreamScrolled(100);
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedScrolled, 1},
@@ -52,7 +73,7 @@ TEST_F(MetricsReporterTest, ScrollingSmall) {
}
TEST_F(MetricsReporterTest, ScrollingCanTriggerEngaged) {
- reporter_.StreamScrolled(161);
+ reporter_->StreamScrolled(161);
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedScrolled, 1},
@@ -63,7 +84,7 @@ TEST_F(MetricsReporterTest, ScrollingCanTriggerEngaged) {
}
TEST_F(MetricsReporterTest, OpeningContentIsInteracting) {
- reporter_.OpenAction(5);
+ reporter_->OpenAction(5);
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
@@ -74,7 +95,7 @@ TEST_F(MetricsReporterTest, OpeningContentIsInteracting) {
}
TEST_F(MetricsReporterTest, RemovingContentIsInteracting) {
- reporter_.RemoveAction();
+ reporter_->RemoveAction();
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
@@ -85,7 +106,7 @@ TEST_F(MetricsReporterTest, RemovingContentIsInteracting) {
}
TEST_F(MetricsReporterTest, NotInterestedInIsInteracting) {
- reporter_.NotInterestedInAction();
+ reporter_->NotInterestedInAction();
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
@@ -96,7 +117,7 @@ TEST_F(MetricsReporterTest, NotInterestedInIsInteracting) {
}
TEST_F(MetricsReporterTest, ManageInterestsInIsInteracting) {
- reporter_.ManageInterestsAction();
+ reporter_->ManageInterestsAction();
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
@@ -107,11 +128,11 @@ TEST_F(MetricsReporterTest, ManageInterestsInIsInteracting) {
}
TEST_F(MetricsReporterTest, VisitsCanLastMoreThanFiveMinutes) {
- reporter_.StreamScrolled(1);
+ reporter_->StreamScrolled(1);
task_environment_.FastForwardBy(base::TimeDelta::FromMinutes(5) - kEpsilon);
- reporter_.OpenAction(0);
+ reporter_->OpenAction(0);
task_environment_.FastForwardBy(base::TimeDelta::FromMinutes(5) - kEpsilon);
- reporter_.StreamScrolled(1);
+ reporter_->StreamScrolled(1);
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
@@ -123,11 +144,11 @@ TEST_F(MetricsReporterTest, VisitsCanLastMoreThanFiveMinutes) {
}
TEST_F(MetricsReporterTest, NewVisitAfterInactivity) {
- reporter_.OpenAction(0);
- reporter_.StreamScrolled(1);
+ reporter_->OpenAction(0);
+ reporter_->StreamScrolled(1);
task_environment_.FastForwardBy(base::TimeDelta::FromMinutes(5) + kEpsilon);
- reporter_.OpenAction(0);
- reporter_.StreamScrolled(1);
+ reporter_->OpenAction(0);
+ reporter_->StreamScrolled(1);
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 2},
@@ -139,8 +160,8 @@ TEST_F(MetricsReporterTest, NewVisitAfterInactivity) {
}
TEST_F(MetricsReporterTest, ReportsLoadStreamStatus) {
- reporter_.OnLoadStream(LoadStreamStatus::kDataInStoreIsStale,
- LoadStreamStatus::kLoadedFromNetwork);
+ reporter_->OnLoadStream(LoadStreamStatus::kDataInStoreIsStale,
+ LoadStreamStatus::kLoadedFromNetwork);
histogram_.ExpectUniqueSample(
"ContentSuggestions.Feed.LoadStreamStatus.Initial",
@@ -151,8 +172,8 @@ TEST_F(MetricsReporterTest, ReportsLoadStreamStatus) {
}
TEST_F(MetricsReporterTest, ReportsLoadStreamStatusIgnoresNoStatusFromStore) {
- reporter_.OnLoadStream(LoadStreamStatus::kNoStatus,
- LoadStreamStatus::kLoadedFromNetwork);
+ reporter_->OnLoadStream(LoadStreamStatus::kNoStatus,
+ LoadStreamStatus::kLoadedFromNetwork);
histogram_.ExpectUniqueSample(
"ContentSuggestions.Feed.LoadStreamStatus.Initial",
@@ -162,7 +183,7 @@ TEST_F(MetricsReporterTest, ReportsLoadStreamStatusIgnoresNoStatusFromStore) {
}
TEST_F(MetricsReporterTest, ReportsLoadMoreStatus) {
- reporter_.OnLoadMore(LoadStreamStatus::kLoadedFromNetwork);
+ reporter_->OnLoadMore(LoadStreamStatus::kLoadedFromNetwork);
histogram_.ExpectUniqueSample(
"ContentSuggestions.Feed.LoadStreamStatus.LoadMore",
@@ -170,7 +191,7 @@ TEST_F(MetricsReporterTest, ReportsLoadMoreStatus) {
}
TEST_F(MetricsReporterTest, ReportsBackgroundRefreshStatus) {
- reporter_.OnBackgroundRefresh(LoadStreamStatus::kLoadedFromNetwork);
+ reporter_->OnBackgroundRefresh(LoadStreamStatus::kLoadedFromNetwork);
histogram_.ExpectUniqueSample(
"ContentSuggestions.Feed.LoadStreamStatus.BackgroundRefresh",
@@ -178,7 +199,7 @@ TEST_F(MetricsReporterTest, ReportsBackgroundRefreshStatus) {
}
TEST_F(MetricsReporterTest, OpenAction) {
- reporter_.OpenAction(5);
+ reporter_->OpenAction(5);
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
@@ -194,7 +215,7 @@ TEST_F(MetricsReporterTest, OpenAction) {
}
TEST_F(MetricsReporterTest, OpenInNewTabAction) {
- reporter_.OpenInNewTabAction(5);
+ reporter_->OpenInNewTabAction(5);
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
@@ -210,7 +231,7 @@ TEST_F(MetricsReporterTest, OpenInNewTabAction) {
}
TEST_F(MetricsReporterTest, OpenInNewIncognitoTabAction) {
- reporter_.OpenInNewIncognitoTabAction();
+ reporter_->OpenInNewIncognitoTabAction();
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
@@ -227,7 +248,7 @@ TEST_F(MetricsReporterTest, OpenInNewIncognitoTabAction) {
}
TEST_F(MetricsReporterTest, SendFeedbackAction) {
- reporter_.SendFeedbackAction();
+ reporter_->SendFeedbackAction();
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
@@ -242,7 +263,7 @@ TEST_F(MetricsReporterTest, SendFeedbackAction) {
}
TEST_F(MetricsReporterTest, DownloadAction) {
- reporter_.DownloadAction();
+ reporter_->DownloadAction();
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
@@ -257,7 +278,7 @@ TEST_F(MetricsReporterTest, DownloadAction) {
}
TEST_F(MetricsReporterTest, LearnMoreAction) {
- reporter_.LearnMoreAction();
+ reporter_->LearnMoreAction();
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
@@ -272,7 +293,7 @@ TEST_F(MetricsReporterTest, LearnMoreAction) {
}
TEST_F(MetricsReporterTest, RemoveAction) {
- reporter_.RemoveAction();
+ reporter_->RemoveAction();
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
@@ -287,7 +308,7 @@ TEST_F(MetricsReporterTest, RemoveAction) {
}
TEST_F(MetricsReporterTest, NotInterestedInAction) {
- reporter_.NotInterestedInAction();
+ reporter_->NotInterestedInAction();
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
@@ -302,7 +323,7 @@ TEST_F(MetricsReporterTest, NotInterestedInAction) {
}
TEST_F(MetricsReporterTest, ManageInterestsAction) {
- reporter_.ManageInterestsAction();
+ reporter_->ManageInterestsAction();
std::map<FeedEngagementType, int> want({
{FeedEngagementType::kFeedEngaged, 1},
@@ -317,7 +338,7 @@ TEST_F(MetricsReporterTest, ManageInterestsAction) {
}
TEST_F(MetricsReporterTest, ContextMenuOpened) {
- reporter_.ContextMenuOpened();
+ reporter_->ContextMenuOpened();
std::map<FeedEngagementType, int> want_empty;
EXPECT_EQ(want_empty, ReportedEngagementType());
@@ -328,7 +349,7 @@ TEST_F(MetricsReporterTest, ContextMenuOpened) {
}
TEST_F(MetricsReporterTest, SurfaceOpened) {
- reporter_.SurfaceOpened(kSurfaceId);
+ reporter_->SurfaceOpened(kSurfaceId);
std::map<FeedEngagementType, int> want_empty;
EXPECT_EQ(want_empty, ReportedEngagementType());
@@ -337,9 +358,9 @@ TEST_F(MetricsReporterTest, SurfaceOpened) {
}
TEST_F(MetricsReporterTest, OpenFeedSuccessDuration) {
- reporter_.SurfaceOpened(kSurfaceId);
+ reporter_->SurfaceOpened(kSurfaceId);
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(9));
- reporter_.ContentSliceViewed(kSurfaceId, 0);
+ reporter_->ContentSliceViewed(kSurfaceId, 0);
histogram_.ExpectUniqueTimeSample(
"ContentSuggestions.Feed.UserJourney.OpenFeed.SuccessDuration",
@@ -347,7 +368,7 @@ TEST_F(MetricsReporterTest, OpenFeedSuccessDuration) {
}
TEST_F(MetricsReporterTest, OpenFeedLoadTimeout) {
- reporter_.SurfaceOpened(kSurfaceId);
+ reporter_->SurfaceOpened(kSurfaceId);
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(16));
histogram_.ExpectUniqueTimeSample(
@@ -358,9 +379,9 @@ TEST_F(MetricsReporterTest, OpenFeedLoadTimeout) {
}
TEST_F(MetricsReporterTest, OpenFeedCloseBeforeLoad) {
- reporter_.SurfaceOpened(kSurfaceId);
+ reporter_->SurfaceOpened(kSurfaceId);
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(14));
- reporter_.SurfaceClosed(kSurfaceId);
+ reporter_->SurfaceClosed(kSurfaceId);
histogram_.ExpectUniqueTimeSample(
"ContentSuggestions.Feed.UserJourney.OpenFeed.FailureDuration",
@@ -370,21 +391,19 @@ TEST_F(MetricsReporterTest, OpenFeedCloseBeforeLoad) {
}
TEST_F(MetricsReporterTest, OpenCardSuccessDuration) {
- reporter_.OpenAction(0);
+ reporter_->OpenAction(0);
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(19));
- reporter_.PageLoaded();
+ reporter_->PageLoaded();
- histogram_.ExpectTotalCount(
- "ContentSuggestions.Feed.UserJourney.OpenCard.SuccessDuration", 1);
histogram_.ExpectUniqueTimeSample(
"ContentSuggestions.Feed.UserJourney.OpenCard.SuccessDuration",
base::TimeDelta::FromSeconds(19), 1);
}
TEST_F(MetricsReporterTest, OpenCardTimeout) {
- reporter_.OpenAction(0);
+ reporter_->OpenAction(0);
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(21));
- reporter_.PageLoaded();
+ reporter_->PageLoaded();
histogram_.ExpectUniqueSample(
"ContentSuggestions.Feed.UserJourney.OpenCard.Failure", 1, 1);
@@ -393,10 +412,10 @@ TEST_F(MetricsReporterTest, OpenCardTimeout) {
}
TEST_F(MetricsReporterTest, OpenCardFailureTwiceAndThenSucceed) {
- reporter_.OpenAction(0);
- reporter_.OpenAction(1);
- reporter_.OpenAction(2);
- reporter_.PageLoaded();
+ reporter_->OpenAction(0);
+ reporter_->OpenAction(1);
+ reporter_->OpenAction(2);
+ reporter_->PageLoaded();
histogram_.ExpectUniqueSample(
"ContentSuggestions.Feed.UserJourney.OpenCard.Failure", 1, 2);
@@ -405,8 +424,8 @@ TEST_F(MetricsReporterTest, OpenCardFailureTwiceAndThenSucceed) {
}
TEST_F(MetricsReporterTest, OpenCardCloseChromeFailure) {
- reporter_.OpenAction(0);
- reporter_.OnEnterBackground();
+ reporter_->OpenAction(0);
+ reporter_->OnEnterBackground();
histogram_.ExpectUniqueSample(
"ContentSuggestions.Feed.UserJourney.OpenCard.Failure", 1, 1);
@@ -414,4 +433,77 @@ TEST_F(MetricsReporterTest, OpenCardCloseChromeFailure) {
"ContentSuggestions.Feed.UserJourney.OpenCard.SuccessDuration", 0);
}
+TEST_F(MetricsReporterTest, TimeSpentInFeedCountsOnlyForegroundTime) {
+ reporter_->OpenAction(0);
+ task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+ reporter_->OnEnterBackground();
+ task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(2));
+ reporter_->OpenAction(0);
+ task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(3));
+ reporter_->OnEnterBackground();
+
+ // Trigger reporting the persistent metrics the next day.
+ task_environment_.FastForwardBy(base::TimeDelta::FromDays(1));
+ RecreateMetricsReporter();
+
+ histogram_.ExpectUniqueTimeSample("ContentSuggestions.Feed.TimeSpentInFeed",
+ base::TimeDelta::FromSeconds(4), 1);
+}
+
+TEST_F(MetricsReporterTest, TimeSpentInFeedLimitsIdleTime) {
+ reporter_->OpenAction(0);
+ task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(31));
+ reporter_->OnEnterBackground();
+
+ // Trigger reporting the persistent metrics the next day.
+ task_environment_.FastForwardBy(base::TimeDelta::FromDays(1));
+ RecreateMetricsReporter();
+
+ histogram_.ExpectUniqueTimeSample("ContentSuggestions.Feed.TimeSpentInFeed",
+ base::TimeDelta::FromSeconds(30), 1);
+}
+
+TEST_F(MetricsReporterTest, TimeSpentInFeedIsPerDay) {
+ // One interaction every hour for 2 days. Should be reported at 30 seconds per
+ // interaction due to the interaction timeout. The 49th |OpenAction()| call
+ // triggers reporting the UMA for the previous day.
+ for (int i = 0; i < 49; ++i) {
+ reporter_->OpenAction(0);
+ task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
+ }
+
+ histogram_.ExpectUniqueTimeSample("ContentSuggestions.Feed.TimeSpentInFeed",
+ base::TimeDelta::FromSeconds(30) * 24, 2);
+}
+
+TEST_F(MetricsReporterTest, TimeSpentIsPersisted) {
+ // Verify that the previous test also works when |MetricsReporter| is
+ // destroyed and recreated. The 49th |OpenAction()| call triggers reporting
+ // the UMA for the previous day.
+ for (int i = 0; i < 49; ++i) {
+ reporter_->OpenAction(0);
+ task_environment_.FastForwardBy(base::TimeDelta::FromHours(1));
+ reporter_->OnEnterBackground();
+ RecreateMetricsReporter();
+ }
+
+ histogram_.ExpectUniqueTimeSample("ContentSuggestions.Feed.TimeSpentInFeed",
+ base::TimeDelta::FromSeconds(30) * 24, 2);
+}
+
+TEST_F(MetricsReporterTest, TimeSpentInFeedTracksWholeScrollTime) {
+ reporter_->StreamScrollStart();
+ task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(2));
+ reporter_->StreamScrolled(1);
+ task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+ reporter_->OnEnterBackground();
+
+ // Trigger reporting the persistent metrics the next day.
+ task_environment_.FastForwardBy(base::TimeDelta::FromDays(1));
+ RecreateMetricsReporter();
+
+ histogram_.ExpectUniqueTimeSample("ContentSuggestions.Feed.TimeSpentInFeed",
+ base::TimeDelta::FromSeconds(3), 1);
+}
+
} // namespace feed
diff --git a/chromium/components/feed/core/v2/prefs.cc b/chromium/components/feed/core/v2/prefs.cc
index 589e445686d..41dc55cf43b 100644
--- a/chromium/components/feed/core/v2/prefs.cc
+++ b/chromium/components/feed/core/v2/prefs.cc
@@ -6,7 +6,6 @@
#include <utility>
-#include "base/value_conversions.h"
#include "base/values.h"
#include "components/feed/core/common/pref_names.h"
#include "components/feed/core/v2/scheduling.h"
@@ -16,10 +15,10 @@
namespace feed {
namespace prefs {
-std::vector<int> GetThrottlerRequestCounts(PrefService* pref_service) {
+std::vector<int> GetThrottlerRequestCounts(PrefService& pref_service) {
std::vector<int> result;
const auto& value_list =
- pref_service->GetList(kThrottlerRequestCountListPrefName)->GetList();
+ pref_service.GetList(kThrottlerRequestCountListPrefName)->GetList();
for (const base::Value& value : value_list) {
result.push_back(value.is_int() ? value.GetInt() : 0);
}
@@ -27,41 +26,50 @@ std::vector<int> GetThrottlerRequestCounts(PrefService* pref_service) {
}
void SetThrottlerRequestCounts(std::vector<int> request_counts,
- PrefService* pref_service) {
+ PrefService& pref_service) {
std::vector<base::Value> value_list;
for (int count : request_counts) {
value_list.push_back(base::Value(count));
}
- pref_service->Set(kThrottlerRequestCountListPrefName,
- base::Value(std::move(value_list)));
+ pref_service.Set(kThrottlerRequestCountListPrefName,
+ base::Value(std::move(value_list)));
}
-base::Time GetLastRequestTime(PrefService* pref_service) {
- return pref_service->GetTime(kThrottlerLastRequestTime);
+base::Time GetLastRequestTime(PrefService& pref_service) {
+ return pref_service.GetTime(kThrottlerLastRequestTime);
}
-void SetLastRequestTime(base::Time request_time, PrefService* pref_service) {
- return pref_service->SetTime(kThrottlerLastRequestTime, request_time);
+void SetLastRequestTime(base::Time request_time, PrefService& pref_service) {
+ return pref_service.SetTime(kThrottlerLastRequestTime, request_time);
}
-DebugStreamData GetDebugStreamData(PrefService* pref_service) {
- return DeserializeDebugStreamData(pref_service->GetString(kDebugStreamData))
+DebugStreamData GetDebugStreamData(PrefService& pref_service) {
+ return DeserializeDebugStreamData(pref_service.GetString(kDebugStreamData))
.value_or(DebugStreamData());
}
void SetDebugStreamData(const DebugStreamData& data,
- PrefService* pref_service) {
- pref_service->SetString(kDebugStreamData, SerializeDebugStreamData(data));
+ PrefService& pref_service) {
+ pref_service.SetString(kDebugStreamData, SerializeDebugStreamData(data));
}
void SetRequestSchedule(const RequestSchedule& schedule,
- PrefService* pref_service) {
- pref_service->Set(kRequestSchedule, RequestScheduleToValue(schedule));
+ PrefService& pref_service) {
+ pref_service.Set(kRequestSchedule, RequestScheduleToValue(schedule));
}
-RequestSchedule GetRequestSchedule(PrefService* pref_service) {
- return RequestScheduleFromValue(*pref_service->Get(kRequestSchedule));
+RequestSchedule GetRequestSchedule(PrefService& pref_service) {
+ return RequestScheduleFromValue(*pref_service.Get(kRequestSchedule));
+}
+
+void SetPersistentMetricsData(const PersistentMetricsData& data,
+ PrefService& pref_service) {
+ pref_service.Set(kMetricsData, PersistentMetricsDataToValue(data));
+}
+
+PersistentMetricsData GetPersistentMetricsData(PrefService& pref_service) {
+ return PersistentMetricsDataFromValue(*pref_service.Get(kMetricsData));
}
} // namespace prefs
diff --git a/chromium/components/feed/core/v2/prefs.h b/chromium/components/feed/core/v2/prefs.h
index 334372279ac..f24ab310b1f 100644
--- a/chromium/components/feed/core/v2/prefs.h
+++ b/chromium/components/feed/core/v2/prefs.h
@@ -9,6 +9,7 @@
#include "base/time/time.h"
#include "components/feed/core/v2/public/types.h"
+#include "components/feed/core/v2/types.h"
class PrefService;
@@ -20,21 +21,25 @@ namespace prefs {
// For counting previously made requests, one integer for each
// |NetworkRequestType|.
-std::vector<int> GetThrottlerRequestCounts(PrefService* pref_service);
+std::vector<int> GetThrottlerRequestCounts(PrefService& pref_service);
void SetThrottlerRequestCounts(std::vector<int> request_counts,
- PrefService* pref_service);
+ PrefService& pref_service);
// Time of the last request. For determining whether the next day's quota should
// be released.
-base::Time GetLastRequestTime(PrefService* pref_service);
-void SetLastRequestTime(base::Time request_time, PrefService* pref_service);
+base::Time GetLastRequestTime(PrefService& pref_service);
+void SetLastRequestTime(base::Time request_time, PrefService& pref_service);
-DebugStreamData GetDebugStreamData(PrefService* pref_service);
-void SetDebugStreamData(const DebugStreamData& data, PrefService* pref_service);
+DebugStreamData GetDebugStreamData(PrefService& pref_service);
+void SetDebugStreamData(const DebugStreamData& data, PrefService& pref_service);
void SetRequestSchedule(const RequestSchedule& schedule,
- PrefService* pref_service);
-RequestSchedule GetRequestSchedule(PrefService* pref_service);
+ PrefService& pref_service);
+RequestSchedule GetRequestSchedule(PrefService& pref_service);
+
+PersistentMetricsData GetPersistentMetricsData(PrefService& pref_service);
+void SetPersistentMetricsData(const PersistentMetricsData& data,
+ PrefService& pref_service);
} // namespace prefs
} // namespace feed
diff --git a/chromium/components/feed/core/v2/proto_util.cc b/chromium/components/feed/core/v2/proto_util.cc
index 852666c263a..70ed71a2eb3 100644
--- a/chromium/components/feed/core/v2/proto_util.cc
+++ b/chromium/components/feed/core/v2/proto_util.cc
@@ -116,6 +116,10 @@ feedwire::Request CreateFeedQueryRequest(
feedwire::FeedRequest& feed_request = *request.mutable_feed_request();
feed_request.add_client_capability(feedwire::Capability::BASE_UI);
feed_request.add_client_capability(feedwire::Capability::REQUEST_SCHEDULE);
+ feed_request.add_client_capability(feedwire::Capability::OPEN_IN_TAB);
+ feed_request.add_client_capability(feedwire::Capability::DOWNLOAD_LINK);
+ feed_request.add_client_capability(feedwire::Capability::INFINITE_FEED);
+
*feed_request.mutable_client_info() = CreateClientInfo(request_metadata);
feedwire::FeedQuery& query = *feed_request.mutable_feed_query();
query.set_reason(request_reason);
@@ -127,7 +131,6 @@ feedwire::Request CreateFeedQueryRequest(
query.mutable_next_page_token()
->mutable_next_page_token()
->set_next_page_token(next_page_token);
- feed_request.mutable_consistency_token()->set_token(consistency_token);
}
return request;
}
@@ -156,11 +159,23 @@ bool CompareContentId(const feedwire::ContentId& a,
std::tie(b.content_domain(), b_id, b_type);
}
+bool CompareContent(const feedstore::Content& a, const feedstore::Content& b) {
+ const ContentId& a_id = a.content_id();
+ const ContentId& b_id = b.content_id();
+ if (a_id.id() < b_id.id())
+ return true;
+ if (a_id.id() > b_id.id())
+ return false;
+ if (a_id.type() < b_id.type())
+ return true;
+ if (a_id.type() > b_id.type())
+ return false;
+ return a.frame() < b.frame();
+}
+
feedwire::ClientInfo CreateClientInfo(const RequestMetadata& request_metadata) {
feedwire::ClientInfo client_info;
// TODO(harringtond): Fill out client_instance_id.
- // TODO(harringtond): Fill out advertising_id.
- // TODO(harringtond): Fill out device_country.
feedwire::DisplayInfo& display_info = *client_info.add_display_info();
display_info.set_screen_density(request_metadata.display_metrics.density);
@@ -176,7 +191,7 @@ feedwire::ClientInfo CreateClientInfo(const RequestMetadata& request_metadata) {
#elif defined(OS_IOS)
client_info.set_platform_type(feedwire::ClientInfo::IOS);
#endif
- client_info.set_app_type(feedwire::ClientInfo::TEST_APP);
+ client_info.set_app_type(feedwire::ClientInfo::CLANK);
*client_info.mutable_platform_version() = GetPlatformVersionMessage();
*client_info.mutable_app_version() =
GetAppVersionMessage(request_metadata.chrome_info);
@@ -203,8 +218,8 @@ feedwire::Request CreateFeedQueryLoadMoreRequest(
} // namespace feed
namespace feedstore {
-void SetLastAddedTime(base::Time t, feedstore::StreamData* data) {
- data->set_last_added_time_millis(
+void SetLastAddedTime(base::Time t, feedstore::StreamData& data) {
+ data.set_last_added_time_millis(
(t - base::Time::UnixEpoch()).InMilliseconds());
}
diff --git a/chromium/components/feed/core/v2/proto_util.h b/chromium/components/feed/core/v2/proto_util.h
index 89506b0be82..95fd50dc5fc 100644
--- a/chromium/components/feed/core/v2/proto_util.h
+++ b/chromium/components/feed/core/v2/proto_util.h
@@ -17,6 +17,7 @@ namespace feedwire {
class Request;
} // namespace feedwire
namespace feedstore {
+class Content;
class StreamData;
} // namespace feedstore
@@ -29,6 +30,7 @@ std::string ContentIdString(const feedwire::ContentId&);
bool Equal(const feedwire::ContentId& a, const feedwire::ContentId& b);
bool CompareContentId(const feedwire::ContentId& a,
const feedwire::ContentId& b);
+bool CompareContent(const feedstore::Content& a, const feedstore::Content& b);
class ContentIdCompareFunctor {
public:
@@ -38,6 +40,14 @@ class ContentIdCompareFunctor {
}
};
+class ContentCompareFunctor {
+ public:
+ bool operator()(const feedstore::Content& a,
+ const feedstore::Content& b) const {
+ return CompareContent(a, b);
+ }
+};
+
feedwire::ClientInfo CreateClientInfo(const RequestMetadata& request_metadata);
feedwire::Request CreateFeedQueryRefreshRequest(
@@ -54,7 +64,7 @@ feedwire::Request CreateFeedQueryLoadMoreRequest(
namespace feedstore {
-void SetLastAddedTime(base::Time t, feedstore::StreamData* data);
+void SetLastAddedTime(base::Time t, feedstore::StreamData& data);
base::Time GetLastAddedTime(const feedstore::StreamData& data);
} // namespace feedstore
diff --git a/chromium/components/feed/core/v2/proto_util_unittest.cc b/chromium/components/feed/core/v2/proto_util_unittest.cc
index d405ee94d6d..e02772af600 100644
--- a/chromium/components/feed/core/v2/proto_util_unittest.cc
+++ b/chromium/components/feed/core/v2/proto_util_unittest.cc
@@ -23,8 +23,7 @@ TEST(ProtoUtilTest, CreateClientInfo) {
request_metadata.language_tag = "en-US";
feedwire::ClientInfo result = CreateClientInfo(request_metadata);
- // TODO(harringtond): change back to CHROME when it is supported.
- EXPECT_EQ(feedwire::ClientInfo::TEST_APP, result.app_type());
+ EXPECT_EQ(feedwire::ClientInfo::CLANK, result.app_type());
EXPECT_EQ(feedwire::Version::RELEASE, result.app_version().build_type());
EXPECT_EQ(1, result.app_version().major());
EXPECT_EQ(2, result.app_version().minor());
diff --git a/chromium/components/feed/core/v2/protocol_translator.cc b/chromium/components/feed/core/v2/protocol_translator.cc
index 0b0b691ba98..f9c3c74bb32 100644
--- a/chromium/components/feed/core/v2/protocol_translator.cc
+++ b/chromium/components/feed/core/v2/protocol_translator.cc
@@ -4,10 +4,13 @@
#include "components/feed/core/v2/protocol_translator.h"
+#include <string>
#include <utility>
+#include "base/logging.h"
#include "base/optional.h"
#include "base/time/time.h"
+#include "components/feed/core/proto/v2/packing.pb.h"
#include "components/feed/core/proto/v2/wire/data_operation.pb.h"
#include "components/feed/core/proto/v2/wire/feature.pb.h"
#include "components/feed/core/proto/v2/wire/feed_response.pb.h"
@@ -85,10 +88,10 @@ struct ConvertedDataOperation {
};
bool TranslateFeature(feedwire::Feature* feature,
- ConvertedDataOperation* result) {
+ ConvertedDataOperation& result) {
feedstore::StreamStructure::Type type =
TranslateNodeType(feature->renderable_unit());
- result->stream_structure.set_type(type);
+ result.stream_structure.set_type(type);
if (type == feedstore::StreamStructure::CONTENT) {
feedwire::Content* wire_content = feature->mutable_content_extension();
@@ -99,10 +102,10 @@ bool TranslateFeature(feedwire::Feature* feature,
// TODO(iwells): We still need score, availability_time_seconds,
// offline_metadata, and representation_data to populate content_info.
- result->content.emplace();
- *(result->content->mutable_content_id()) =
- result->stream_structure.content_id();
- result->content->set_allocated_frame(
+ result.content.emplace();
+ *(result.content->mutable_content_id()) =
+ result.stream_structure.content_id();
+ result.content->set_allocated_frame(
wire_content->mutable_xsurface_content()->release_xsurface_output());
}
return true;
@@ -110,26 +113,26 @@ bool TranslateFeature(feedwire::Feature* feature,
base::Optional<feedstore::StreamSharedState> TranslateSharedState(
feedwire::ContentId content_id,
- feedwire::RenderData* wire_shared_state) {
- if (wire_shared_state->render_data_type() != feedwire::RenderData::XSURFACE) {
+ feedwire::RenderData& wire_shared_state) {
+ if (wire_shared_state.render_data_type() != feedwire::RenderData::XSURFACE) {
return base::nullopt;
}
feedstore::StreamSharedState shared_state;
*shared_state.mutable_content_id() = std::move(content_id);
shared_state.set_allocated_shared_state_data(
- wire_shared_state->mutable_xsurface_container()->release_render_data());
+ wire_shared_state.mutable_xsurface_container()->release_render_data());
return shared_state;
}
bool TranslatePayload(base::Time now,
feedwire::DataOperation operation,
ConvertedGlobalData* global_data,
- ConvertedDataOperation* result) {
+ ConvertedDataOperation& result) {
switch (operation.payload_case()) {
case feedwire::DataOperation::kFeature: {
feedwire::Feature* feature = operation.mutable_feature();
- result->stream_structure.set_allocated_parent_id(
+ result.stream_structure.set_allocated_parent_id(
feature->release_parent_id());
if (!TranslateFeature(feature, result))
@@ -137,16 +140,16 @@ bool TranslatePayload(base::Time now,
} break;
case feedwire::DataOperation::kNextPageToken: {
feedwire::Token* token = operation.mutable_next_page_token();
- result->stream_structure.set_allocated_parent_id(
+ result.stream_structure.set_allocated_parent_id(
token->release_parent_id());
- result->next_page_token = std::move(
+ result.next_page_token = std::move(
*token->mutable_next_page_token()->mutable_next_page_token());
} break;
case feedwire::DataOperation::kRenderData: {
- result->shared_state =
- TranslateSharedState(result->stream_structure.content_id(),
- operation.mutable_render_data());
- if (!result->shared_state)
+ result.shared_state =
+ TranslateSharedState(result.stream_structure.content_id(),
+ *operation.mutable_render_data());
+ if (!result.shared_state)
return false;
} break;
case feedwire::DataOperation::kRequestSchedule: {
@@ -184,7 +187,7 @@ base::Optional<ConvertedDataOperation> TranslateDataOperationInternal(
result.stream_structure.set_allocated_content_id(
operation.mutable_metadata()->release_content_id());
- if (!TranslatePayload(now, std::move(operation), global_data, &result))
+ if (!TranslatePayload(now, std::move(operation), global_data, result))
return base::nullopt;
break;
@@ -280,13 +283,15 @@ RefreshResponseData TranslateWireResponse(
}
}
- // TODO(harringtond): If there's more than one shared state, record some
- // sort of error.
if (!result->shared_states.empty()) {
+ if (result->shared_states.size() > 1) {
+ DLOG(ERROR)
+ << "Receieved more than one shared state. Only the first is used.";
+ }
*result->stream_data.mutable_shared_state_id() =
result->shared_states.front().content_id();
}
- feedstore::SetLastAddedTime(current_time, &result->stream_data);
+ feedstore::SetLastAddedTime(current_time, result->stream_data);
RefreshResponseData response_data;
response_data.model_update_request = std::move(result);
@@ -295,4 +300,18 @@ RefreshResponseData TranslateWireResponse(
return response_data;
}
+std::vector<feedstore::DataOperation> TranslateDismissData(
+ base::Time current_time,
+ feedpacking::DismissData data) {
+ std::vector<feedstore::DataOperation> result;
+ for (auto& operation : data.data_operations()) {
+ base::Optional<feedstore::DataOperation> translated_operation =
+ TranslateDataOperation(current_time, operation);
+ if (translated_operation) {
+ result.push_back(std::move(translated_operation.value()));
+ }
+ }
+ return result;
+}
+
} // namespace feed
diff --git a/chromium/components/feed/core/v2/protocol_translator.h b/chromium/components/feed/core/v2/protocol_translator.h
index fdaaa60368c..ca72a108243 100644
--- a/chromium/components/feed/core/v2/protocol_translator.h
+++ b/chromium/components/feed/core/v2/protocol_translator.h
@@ -10,6 +10,7 @@
#include "base/optional.h"
#include "base/time/time.h"
+#include "components/feed/core/proto/v2/packing.pb.h"
#include "components/feed/core/proto/v2/store.pb.h"
#include "components/feed/core/proto/v2/wire/data_operation.pb.h"
#include "components/feed/core/proto/v2/wire/response.pb.h"
@@ -74,6 +75,10 @@ RefreshResponseData TranslateWireResponse(
StreamModelUpdateRequest::Source source,
base::Time current_time);
+std::vector<feedstore::DataOperation> TranslateDismissData(
+ base::Time current_time,
+ feedpacking::DismissData data);
+
} // namespace feed
#endif // COMPONENTS_FEED_CORE_V2_PROTOCOL_TRANSLATOR_H_
diff --git a/chromium/components/feed/core/v2/protocol_translator_unittest.cc b/chromium/components/feed/core/v2/protocol_translator_unittest.cc
index 99a93a3d478..e7256ebdfaa 100644
--- a/chromium/components/feed/core/v2/protocol_translator_unittest.cc
+++ b/chromium/components/feed/core/v2/protocol_translator_unittest.cc
@@ -6,6 +6,7 @@
#include <sstream>
#include <string>
+#include <utility>
#include "base/base_paths.h"
#include "base/files/file_path.h"
@@ -600,4 +601,40 @@ max_structure_sequence_number: 0
EXPECT_EQ(want, ss.str());
}
+TEST(TranslateDismissData, Success) {
+ feedpacking::DismissData input;
+ *input.add_data_operations() =
+ MakeDataOperation(feedwire::DataOperation::CLEAR_ALL);
+ *input.add_data_operations() =
+ MakeDataOperationWithContent(feedwire::DataOperation::UPDATE_OR_APPEND);
+ std::vector<feedstore::DataOperation> result =
+ TranslateDismissData(kCurrentTime, input);
+
+ ASSERT_EQ(2UL, result.size());
+ EXPECT_EQ(R"({
+ structure {
+ operation: 1
+ }
+}
+)",
+ ToTextProto(result[0]));
+ EXPECT_EQ(R"({
+ structure {
+ operation: 2
+ content_id {
+ id: 42
+ }
+ type: 3
+ }
+ content {
+ content_id {
+ id: 42
+ }
+ frame: "content"
+ }
+}
+)",
+ ToTextProto(result[1]));
+}
+
} // namespace feed
diff --git a/chromium/components/feed/core/v2/public/feed_service.cc b/chromium/components/feed/core/v2/public/feed_service.cc
index 67202a0f3dc..7fd7c085842 100644
--- a/chromium/components/feed/core/v2/public/feed_service.cc
+++ b/chromium/components/feed/core/v2/public/feed_service.cc
@@ -9,14 +9,18 @@
#include "base/time/default_clock.h"
#include "base/time/default_tick_clock.h"
#include "build/build_config.h"
+#include "components/feed/core/shared_prefs/pref_names.h"
#include "components/feed/core/v2/feed_network_impl.h"
#include "components/feed/core/v2/feed_store.h"
#include "components/feed/core/v2/feed_stream.h"
#include "components/feed/core/v2/metrics_reporter.h"
#include "components/feed/core/v2/refresh_task_scheduler.h"
+#include "components/feed/feed_feature_list.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/history_service_observer.h"
#include "components/history/core/browser/history_types.h"
+#include "components/prefs/pref_service.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
#include "net/base/network_change_notifier.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -56,7 +60,9 @@ class FeedService::HistoryObserverImpl
HistoryObserverImpl(history::HistoryService* history_service,
FeedStream* feed_stream)
: feed_stream_(feed_stream) {
- history_service->AddObserver(this);
+ // May be null for some profiles.
+ if (history_service)
+ history_service->AddObserver(this);
}
HistoryObserverImpl(const HistoryObserverImpl&) = delete;
HistoryObserverImpl& operator=(const HistoryObserverImpl&) = delete;
@@ -118,6 +124,32 @@ class FeedService::StreamDelegateImpl : public FeedStream::Delegate {
std::unique_ptr<HistoryObserverImpl> history_observer_;
};
+class FeedService::IdentityManagerObserverImpl
+ : public signin::IdentityManager::Observer {
+ public:
+ IdentityManagerObserverImpl(signin::IdentityManager* identity_manager,
+ FeedStream* stream)
+ : identity_manager_(identity_manager), feed_stream_(stream) {}
+ IdentityManagerObserverImpl(const IdentityManagerObserverImpl&) = delete;
+ IdentityManagerObserverImpl& operator=(const IdentityManagerObserverImpl&) =
+ delete;
+ ~IdentityManagerObserverImpl() override {
+ identity_manager_->RemoveObserver(this);
+ }
+ void OnPrimaryAccountSet(
+ const CoreAccountInfo& primary_account_info) override {
+ feed_stream_->OnSignedIn();
+ }
+ void OnPrimaryAccountCleared(
+ const CoreAccountInfo& previous_primary_account_info) override {
+ feed_stream_->OnSignedOut();
+ }
+
+ private:
+ signin::IdentityManager* identity_manager_;
+ FeedStream* feed_stream_;
+};
+
FeedService::FeedService(std::unique_ptr<FeedStream> stream)
: stream_(std::move(stream)) {}
@@ -138,11 +170,12 @@ FeedService::FeedService(
stream_delegate_ =
std::make_unique<StreamDelegateImpl>(local_state, delegate_.get());
network_delegate_ = std::make_unique<NetworkDelegateImpl>(delegate_.get());
- metrics_reporter_ =
- std::make_unique<MetricsReporter>(base::DefaultTickClock::GetInstance());
+ metrics_reporter_ = std::make_unique<MetricsReporter>(
+ base::DefaultTickClock::GetInstance(), profile_prefs);
feed_network_ = std::make_unique<FeedNetworkImpl>(
network_delegate_.get(), identity_manager, api_key, url_loader_factory,
- base::DefaultTickClock::GetInstance(), profile_prefs);
+ base::DefaultTickClock::GetInstance(), profile_prefs,
+ chrome_info.channel);
store_ = std::make_unique<FeedStore>(std::move(database));
stream_ = std::make_unique<FeedStream>(
@@ -155,8 +188,10 @@ FeedService::FeedService(
history_service, static_cast<FeedStream*>(stream_.get()));
stream_delegate_->Initialize(static_cast<FeedStream*>(stream_.get()));
-// TODO(harringtond): Call FeedStream::OnSignedIn()
-// TODO(harringtond): Call FeedStream::OnSignedOut()
+ identity_manager_observer_ = std::make_unique<IdentityManagerObserverImpl>(
+ identity_manager, stream_.get());
+ identity_manager->AddObserver(identity_manager_observer_.get());
+
#if defined(OS_ANDROID)
application_status_listener_ =
base::android::ApplicationStatusListener::New(base::BindRepeating(
@@ -174,6 +209,12 @@ void FeedService::ClearCachedData() {
stream_->OnCacheDataCleared();
}
+// static
+bool FeedService::IsEnabled(const PrefService& pref_service) {
+ return base::FeatureList::IsEnabled(feed::kInterestFeedV2) &&
+ pref_service.GetBoolean(feed::prefs::kEnableSnippets);
+}
+
#if defined(OS_ANDROID)
void FeedService::OnApplicationStateChange(
base::android::ApplicationState state) {
diff --git a/chromium/components/feed/core/v2/public/feed_service.h b/chromium/components/feed/core/v2/public/feed_service.h
index 45fc8848abd..5fc285594ff 100644
--- a/chromium/components/feed/core/v2/public/feed_service.h
+++ b/chromium/components/feed/core/v2/public/feed_service.h
@@ -90,10 +90,14 @@ class FeedService : public KeyedService {
return refresh_task_scheduler_.get();
}
+ // Whether Feedv2 is enabled. If false, the FeedService should not be created.
+ static bool IsEnabled(const PrefService& pref_service);
+
private:
class StreamDelegateImpl;
class NetworkDelegateImpl;
class HistoryObserverImpl;
+ class IdentityManagerObserverImpl;
#if defined(OS_ANDROID)
void OnApplicationStateChange(base::android::ApplicationState state);
#endif
@@ -108,6 +112,7 @@ class FeedService : public KeyedService {
std::unique_ptr<FeedStore> store_;
std::unique_ptr<RefreshTaskScheduler> refresh_task_scheduler_;
std::unique_ptr<HistoryObserverImpl> history_observer_;
+ std::unique_ptr<IdentityManagerObserverImpl> identity_manager_observer_;
#if defined(OS_ANDROID)
bool foregrounded_ = true;
std::unique_ptr<base::android::ApplicationStatusListener>
diff --git a/chromium/components/feed/core/v2/public/feed_stream_api.h b/chromium/components/feed/core/v2/public/feed_stream_api.h
index 6699db20184..9599b82e7e9 100644
--- a/chromium/components/feed/core/v2/public/feed_stream_api.h
+++ b/chromium/components/feed/core/v2/public/feed_stream_api.h
@@ -8,6 +8,7 @@
#include <string>
#include <vector>
+#include "base/callback_forward.h"
#include "base/observer_list_types.h"
#include "components/feed/core/v2/public/types.h"
@@ -69,11 +70,19 @@ class FeedStreamApi {
// nothing if the model is not yet loaded.
virtual EphemeralChangeId CreateEphemeralChange(
std::vector<feedstore::DataOperation> operations) = 0;
+ // Same as |CreateEphemeralChange()|, but data is a serialized
+ // |feedpacking::DismissData| message.
+ virtual EphemeralChangeId CreateEphemeralChangeFromPackedData(
+ base::StringPiece data) = 0;
// Commits a change. Returns false if the change does not exist.
virtual bool CommitEphemeralChange(EphemeralChangeId id) = 0;
// Rejects a change. Returns false if the change does not exist.
virtual bool RejectEphemeralChange(EphemeralChangeId id) = 0;
+ // Sends 'ThereAndBackAgainData' back to the server. |data| is a serialized
+ // |feedwire::ThereAndBackAgainData| message.
+ virtual void ProcessThereAndBackAgain(base::StringPiece data) = 0;
+
// User interaction reporting. These should have no side-effects other than
// reporting metrics.
@@ -109,6 +118,9 @@ class FeedStreamApi {
virtual void ReportContextMenuOpened() = 0;
// The user scrolled the feed by |distance_dp| and then stopped.
virtual void ReportStreamScrolled(int distance_dp) = 0;
+ // The user started scrolling the feed. Typically followed by a call to
+ // |ReportStreamScrolled()|.
+ virtual void ReportStreamScrollStart() = 0;
// The following methods are used for the internals page.
diff --git a/chromium/components/feed/core/v2/public/types.h b/chromium/components/feed/core/v2/public/types.h
index 0f2ec74f574..cffefa301a2 100644
--- a/chromium/components/feed/core/v2/public/types.h
+++ b/chromium/components/feed/core/v2/public/types.h
@@ -34,6 +34,11 @@ using EphemeralChangeId = util::IdTypeU32<class EphemeralChangeIdClass>;
using SurfaceId = util::IdTypeU32<class SurfaceIdClass>;
struct NetworkResponseInfo {
+ NetworkResponseInfo();
+ ~NetworkResponseInfo();
+ NetworkResponseInfo(const NetworkResponseInfo&);
+ NetworkResponseInfo& operator=(const NetworkResponseInfo&);
+
// A union of net::Error (if the request failed) and the http
// status code(if the request succeeded in reaching the server).
int32_t status_code = 0;
@@ -41,6 +46,7 @@ struct NetworkResponseInfo {
base::Time fetch_time;
std::string bless_nonce;
GURL base_request_url;
+ size_t response_body_bytes = 0;
};
// For the snippets-internals page.
diff --git a/chromium/components/feed/core/v2/request_throttler.cc b/chromium/components/feed/core/v2/request_throttler.cc
index bfce9c98317..f45767770db 100644
--- a/chromium/components/feed/core/v2/request_throttler.cc
+++ b/chromium/components/feed/core/v2/request_throttler.cc
@@ -47,7 +47,7 @@ bool RequestThrottler::RequestQuota(NetworkRequestType request_type) {
// Fetch request counts from prefs. There's an entry for each request type.
// We may need to resize the list.
std::vector<int> request_counts =
- feed::prefs::GetThrottlerRequestCounts(pref_service_);
+ feed::prefs::GetThrottlerRequestCounts(*pref_service_);
const size_t request_count_index = static_cast<size_t>(request_type);
if (request_counts.size() <= request_count_index)
request_counts.resize(request_count_index + 1);
@@ -56,7 +56,7 @@ bool RequestThrottler::RequestQuota(NetworkRequestType request_type) {
if (requests_already_made >= max_requests_per_day)
return false;
requests_already_made++;
- feed::prefs::SetThrottlerRequestCounts(request_counts, pref_service_);
+ feed::prefs::SetThrottlerRequestCounts(request_counts, *pref_service_);
return true;
}
@@ -65,12 +65,12 @@ void RequestThrottler::ResetCountersIfDayChanged() {
// making un-throttled requests to server.
const base::Time now = clock_->Now();
const bool day_changed =
- DaysSinceOrigin(feed::prefs::GetLastRequestTime(pref_service_)) !=
+ DaysSinceOrigin(feed::prefs::GetLastRequestTime(*pref_service_)) !=
DaysSinceOrigin(now);
- feed::prefs::SetLastRequestTime(now, pref_service_);
+ feed::prefs::SetLastRequestTime(now, *pref_service_);
if (day_changed)
- feed::prefs::SetThrottlerRequestCounts({}, pref_service_);
+ feed::prefs::SetThrottlerRequestCounts({}, *pref_service_);
}
} // namespace feed
diff --git a/chromium/components/feed/core/v2/scheduling.cc b/chromium/components/feed/core/v2/scheduling.cc
index d65560e15d6..a1980628061 100644
--- a/chromium/components/feed/core/v2/scheduling.cc
+++ b/chromium/components/feed/core/v2/scheduling.cc
@@ -5,7 +5,7 @@
#include "components/feed/core/v2/scheduling.h"
#include "base/time/time.h"
-#include "base/value_conversions.h"
+#include "base/util/values/values_util.h"
#include "base/values.h"
#include "components/feed/core/v2/config.h"
@@ -15,7 +15,7 @@ namespace {
base::Value VectorToValue(const std::vector<base::TimeDelta>& values) {
base::Value result(base::Value::Type::LIST);
for (base::TimeDelta delta : values) {
- result.Append(CreateTimeDeltaValue(delta));
+ result.Append(util::TimeDeltaToValue(delta));
}
return result;
}
@@ -25,10 +25,10 @@ bool ValueToVector(const base::Value& value,
if (!value.is_list())
return false;
for (const base::Value& entry : value.GetList()) {
- base::TimeDelta delta;
- if (!GetValueAsTimeDelta(entry, &delta))
+ base::Optional<base::TimeDelta> delta = util::ValueToTimeDelta(entry);
+ if (!delta)
return false;
- result->push_back(delta);
+ result->push_back(*delta);
}
return true;
}
@@ -41,7 +41,7 @@ RequestSchedule& RequestSchedule::operator=(const RequestSchedule&) = default;
base::Value RequestScheduleToValue(const RequestSchedule& schedule) {
base::Value result(base::Value::Type::DICTIONARY);
- result.SetKey("anchor", CreateTimeValue(schedule.anchor_time));
+ result.SetKey("anchor", util::TimeToValue(schedule.anchor_time));
result.SetKey("offsets", VectorToValue(schedule.refresh_offsets));
return result;
}
@@ -50,14 +50,13 @@ RequestSchedule RequestScheduleFromValue(const base::Value& value) {
if (!value.is_dict())
return {};
RequestSchedule result;
- const base::Value* anchor = value.FindKey("anchor");
+ base::Optional<base::Time> anchor =
+ util::ValueToTime(value.FindKey("anchor"));
const base::Value* offsets =
value.FindKeyOfType("offsets", base::Value::Type::LIST);
- if (!anchor || !offsets)
- return {};
- if (!GetValueAsTime(*anchor, &result.anchor_time) ||
- !ValueToVector(*offsets, &result.refresh_offsets))
+ if (!anchor || !offsets || !ValueToVector(*offsets, &result.refresh_offsets))
return {};
+ result.anchor_time = *anchor;
return result;
}
diff --git a/chromium/components/feed/core/v2/stream_model.cc b/chromium/components/feed/core/v2/stream_model.cc
index eba0ab62f6c..f0645b5b087 100644
--- a/chromium/components/feed/core/v2/stream_model.cc
+++ b/chromium/components/feed/core/v2/stream_model.cc
@@ -116,9 +116,6 @@ void StreamModel::Update(
}
// Update non-tree data.
- // TODO(harringtond): Once we start using StreamData.next_action_id, this line
- // would be problematic. We should probably move next_action_id into a
- // different record in FeedStore.
stream_data_ = update_request->stream_data;
if (has_clear_all) {
@@ -142,11 +139,7 @@ void StreamModel::Update(
}
}
- // TODO(harringtond): Some StreamData fields not yet used.
- // next_action_id - do we need to load the model before uploading
- // actions? If not, we probably will want to move this out of
- // StreamData.
- // content_id - probably just ignore for now
+ // TODO(harringtond): We're not using StreamData's content_id for anything.
UpdateFlattenedTree();
}
diff --git a/chromium/components/feed/core/v2/stream_model.h b/chromium/components/feed/core/v2/stream_model.h
index f3e460b7966..b116eb0af87 100644
--- a/chromium/components/feed/core/v2/stream_model.h
+++ b/chromium/components/feed/core/v2/stream_model.h
@@ -134,8 +134,8 @@ class StreamModel {
Observer* observer_ = nullptr; // Unowned.
StoreObserver* store_observer_ = nullptr; // Unowned.
- stream_model::ContentIdMap id_map_;
- stream_model::FeatureTree base_feature_tree_{&id_map_};
+ stream_model::ContentMap content_map_;
+ stream_model::FeatureTree base_feature_tree_{&content_map_};
// |base_feature_tree_| with |ephemeral_changes_| applied.
// Null if there are no ephemeral changes.
std::unique_ptr<stream_model::FeatureTree> feature_tree_after_changes_;
diff --git a/chromium/components/feed/core/v2/stream_model/ephemeral_change.cc b/chromium/components/feed/core/v2/stream_model/ephemeral_change.cc
index 96e8a65a3ef..91baf5e021c 100644
--- a/chromium/components/feed/core/v2/stream_model/ephemeral_change.cc
+++ b/chromium/components/feed/core/v2/stream_model/ephemeral_change.cc
@@ -41,7 +41,7 @@ std::unique_ptr<FeatureTree> ApplyEphemeralChanges(
tree_with_changes->ApplyStreamStructure(operation.structure());
}
if (operation.has_content()) {
- tree_with_changes->AddContent(operation.content());
+ tree_with_changes->CopyAndAddContent(operation.content());
}
}
}
diff --git a/chromium/components/feed/core/v2/stream_model/feature_tree.cc b/chromium/components/feed/core/v2/stream_model/feature_tree.cc
index 4e6fef564b7..583eac57da0 100644
--- a/chromium/components/feed/core/v2/stream_model/feature_tree.cc
+++ b/chromium/components/feed/core/v2/stream_model/feature_tree.cc
@@ -11,11 +11,22 @@
namespace feed {
namespace stream_model {
+namespace {
+std::string ToAsciiForTesting(const std::string& s) {
+ std::string result = s;
+ for (size_t i = 0; i < result.size(); ++i) {
+ if (result[i] < 32 || result[i] > 126) {
+ result[i] = '?';
+ }
+ }
+ return result;
+}
+} // namespace
-ContentIdMap::ContentIdMap() = default;
-ContentIdMap::~ContentIdMap() = default;
+ContentMap::ContentMap() = default;
+ContentMap::~ContentMap() = default;
-ContentTag ContentIdMap::GetContentTag(const feedwire::ContentId& id) {
+ContentTag ContentMap::GetContentTag(const feedwire::ContentId& id) {
auto iter = mapping_.find(id);
if (iter != mapping_.end())
return iter->second;
@@ -24,8 +35,42 @@ ContentTag ContentIdMap::GetContentTag(const feedwire::ContentId& id) {
return tag;
}
-ContentRevision ContentIdMap::NextContentRevision() {
- return revision_generator_.GenerateNextId();
+const feedstore::Content* ContentMap::FindContent(
+ ContentRevision content_revision) {
+ const size_t index = content_revision.GetUnsafeValue();
+ if (revision_to_content_.size() <= index) {
+ return nullptr;
+ }
+ return revision_to_content_[index];
+}
+
+ContentRevision ContentMap::LookupContentRevision(
+ const feedstore::Content& content) {
+ auto iter = content_.find(content);
+ return (iter != content_.end()) ? iter->second : ContentRevision();
+}
+
+ContentRevision ContentMap::AddContent(feedstore::Content content) {
+ auto result = content_.emplace(std::move(content), ContentRevision());
+ // Already exists
+ if (!result.second)
+ return result.first->second;
+
+ // Newly inserted.
+ const ContentRevision new_revision = revision_generator_.GenerateNextId();
+ result.first->second = new_revision;
+ if (revision_to_content_.size() <= new_revision.GetUnsafeValue()) {
+ revision_to_content_.resize(new_revision.GetUnsafeValue() + 1);
+ }
+ revision_to_content_[new_revision.GetUnsafeValue()] = &result.first->first;
+ return new_revision;
+}
+
+void ContentMap::Clear() {
+ // We don't clear the ID generators, so no IDs are re-used.
+ mapping_.clear();
+ content_.clear();
+ revision_to_content_.clear();
}
StreamNode::StreamNode() = default;
@@ -33,11 +78,11 @@ StreamNode::~StreamNode() = default;
StreamNode::StreamNode(const StreamNode&) = default;
StreamNode& StreamNode::operator=(const StreamNode&) = default;
-FeatureTree::FeatureTree(ContentIdMap* id_map) : id_map_(id_map) {}
+FeatureTree::FeatureTree(ContentMap* content_map) : content_map_(content_map) {}
FeatureTree::FeatureTree(const FeatureTree* base)
: base_(base),
- id_map_(base->id_map_),
+ content_map_(base->content_map_),
computed_root_(base->computed_root_),
root_tag_(base->root_tag_),
nodes_(base->nodes_) {}
@@ -59,10 +104,7 @@ StreamNode* FeatureTree::FindNode(ContentTag id) {
}
const feedstore::Content* FeatureTree::FindContent(ContentRevision id) const {
- auto iter = content_.find(id);
- if (iter != content_.end())
- return &iter->second;
- return base_ ? base_->FindContent(id) : nullptr;
+ return content_map_->FindContent(id);
}
void FeatureTree::ApplyStreamStructure(
@@ -70,7 +112,11 @@ void FeatureTree::ApplyStreamStructure(
switch (structure.operation()) {
case feedstore::StreamStructure::CLEAR_ALL:
nodes_.clear();
- content_.clear();
+ // Clearing content is not required for correctness, but we can do it to
+ // free memory as long as there's no base feature tree that can reference
+ // the content.
+ if (!base_)
+ content_map_->Clear();
computed_root_ = false;
break;
case feedstore::StreamStructure::UPDATE_OR_APPEND: {
@@ -126,19 +172,19 @@ void FeatureTree::ResizeNodesIfNeeded(ContentTag id) {
}
void FeatureTree::AddContent(feedstore::Content content) {
- AddContent(id_map_->NextContentRevision(), std::move(content));
+ const ContentTag tag = GetContentTag(content.content_id());
+ const ContentRevision revision_id =
+ content_map_->AddContent(std::move(content));
+ GetOrMakeNode(tag)->content_revision = revision_id;
}
-void FeatureTree::AddContent(ContentRevision revision_id,
- feedstore::Content content) {
- // TODO(harringtond): Consider de-duping content.
- // Currently, we copy content for ephemeral changes. Both when the ephemeral
- // change is created, and when it is committed. We should consider eliminating
- // these copies.
+void FeatureTree::CopyAndAddContent(const feedstore::Content& content) {
+ ContentRevision revision_id = content_map_->LookupContentRevision(content);
+ if (revision_id.is_null()) {
+ revision_id = content_map_->AddContent(content);
+ }
const ContentTag tag = GetContentTag(content.content_id());
- DCHECK(!content_.count(revision_id));
GetOrMakeNode(tag)->content_revision = revision_id;
- content_[revision_id] = std::move(content);
}
void FeatureTree::ResolveRoot() {
@@ -211,7 +257,7 @@ std::string FeatureTree::DumpStateForTesting() {
}
if (!node->content_revision.is_null()) {
const feedstore::Content* content = FindContent(node->content_revision);
- ss << " content.frame=" << content->frame();
+ ss << " content.frame=" << ToAsciiForTesting(content->frame());
}
ss << '\n';
}
diff --git a/chromium/components/feed/core/v2/stream_model/feature_tree.h b/chromium/components/feed/core/v2/stream_model/feature_tree.h
index 5f5dee45c71..9a9fd2c4a95 100644
--- a/chromium/components/feed/core/v2/stream_model/feature_tree.h
+++ b/chromium/components/feed/core/v2/stream_model/feature_tree.h
@@ -18,25 +18,36 @@
namespace feed {
namespace stream_model {
-// Uniquely identifies a feedwire::ContentId. Provided by |ContentIdMap|.
+// Uniquely identifies a feedwire::ContentId. Provided by |ContentMap|.
using ContentTag = util::IdTypeU32<class ContentTagClass>;
using ContentRevision = feed::ContentRevision;
-// Maps ContentId into ContentTag, and generates ContentRevision IDs.
-class ContentIdMap {
+// Owns instances of feedstore::Content pointed to by the feature tree, and
+// maps ContentId into ContentTag.
+class ContentMap {
public:
- ContentIdMap();
- ~ContentIdMap();
- ContentIdMap(const ContentIdMap&) = delete;
- ContentIdMap& operator=(const ContentIdMap&) = delete;
+ ContentMap();
+ ~ContentMap();
+ ContentMap(const ContentMap&) = delete;
+ ContentMap& operator=(const ContentMap&) = delete;
ContentTag GetContentTag(const feedwire::ContentId& id);
- ContentRevision NextContentRevision();
+
+ const feedstore::Content* FindContent(ContentRevision content_revision);
+ ContentRevision LookupContentRevision(const feedstore::Content& content);
+ ContentRevision AddContent(feedstore::Content content);
+
+ void Clear();
private:
ContentTag::Generator tag_generator_;
ContentRevision::Generator revision_generator_;
std::map<feedwire::ContentId, ContentTag, ContentIdCompareFunctor> mapping_;
+
+ // These two containers work together to store and index content.
+ // Each unique piece of content is stored once, and never removed.
+ std::map<feedstore::Content, ContentRevision, ContentCompareFunctor> content_;
+ std::vector<const feedstore::Content*> revision_to_content_;
};
// A node in FeatureTree.
@@ -61,7 +72,7 @@ struct StreamNode {
};
// The feature tree which underlies StreamModel.
-// This tree is different that most, the rules are as follows:
+// This tree is different than most, the rules are as follows:
// * A node may or may not have a parent, so this is more of a forest than a
// tree.
// * When nodes are removed, their set of children are remembered. If the node
@@ -78,7 +89,7 @@ class FeatureTree {
public:
// Constructor. |id_map| is retained by FeatureTree, and must have a greater
// scope than FeatureTree.
- explicit FeatureTree(ContentIdMap* id_map);
+ explicit FeatureTree(ContentMap* id_map);
// Create a |FeatureTree| which starts as a copy of |base|.
// Copies structure from |base|, and keeps a reference for content access.
explicit FeatureTree(const FeatureTree* base);
@@ -90,8 +101,11 @@ class FeatureTree {
// Mutations.
void ApplyStreamStructure(const feedstore::StreamStructure& structure);
+ // Adds |content| to the tree.
void AddContent(feedstore::Content content);
- void AddContent(ContentRevision revision_id, feedstore::Content content);
+ // Same as |AddContent()|, but can avoid a copy of |content| if it already
+ // exists.
+ void CopyAndAddContent(const feedstore::Content& content);
// Data access.
@@ -99,7 +113,7 @@ class FeatureTree {
StreamNode* FindNode(ContentTag id);
const feedstore::Content* FindContent(ContentRevision id) const;
ContentTag GetContentTag(const feedwire::ContentId& id) {
- return id_map_->GetContentTag(id);
+ return content_map_->GetContentTag(id);
}
// Returns the list of content that should be visible.
@@ -115,7 +129,7 @@ class FeatureTree {
bool RemoveFromParent(StreamNode* parent, ContentTag node_id);
const FeatureTree* base_ = nullptr; // Unowned.
- ContentIdMap* id_map_; // Unowned.
+ ContentMap* content_map_; // Unowned.
// Finding the root:
// We pick the root node as the last STREAM node which has no parent.
// In most cases, we can identify the root as the tree is built.
@@ -126,9 +140,6 @@ class FeatureTree {
// All nodes in the forest, included removed nodes.
// This datastructure was selected to make copies efficient.
std::vector<StreamNode> nodes_;
- // TODO(harringtond): It may be possible to remove old revisions of content
- // to save memory.
- std::map<ContentRevision, feedstore::Content> content_;
};
} // namespace stream_model
diff --git a/chromium/components/feed/core/v2/surface_updater.cc b/chromium/components/feed/core/v2/surface_updater.cc
index 2ac92f10aa2..ff92bd98248 100644
--- a/chromium/components/feed/core/v2/surface_updater.cc
+++ b/chromium/components/feed/core/v2/surface_updater.cc
@@ -134,6 +134,7 @@ feedui::ZeroStateSlice::Type GetZeroStateType(LoadStreamStatus status) {
case LoadStreamStatus::kProtoTranslationFailed:
case LoadStreamStatus::kCannotLoadFromNetworkOffline:
case LoadStreamStatus::kCannotLoadFromNetworkThrottled:
+ case LoadStreamStatus::kNetworkFetchFailed:
return feedui::ZeroStateSlice::CANT_REFRESH;
case LoadStreamStatus::kNoStatus:
case LoadStreamStatus::kLoadedFromStore:
@@ -148,6 +149,8 @@ feedui::ZeroStateSlice::Type GetZeroStateType(LoadStreamStatus status) {
case LoadStreamStatus::kLoadNotAllowedArticlesListHidden:
case LoadStreamStatus::kCannotParseNetworkResponseBody:
case LoadStreamStatus::kLoadMoreModelIsNotLoaded:
+ case LoadStreamStatus::kLoadNotAllowedDisabledByEnterprisePolicy:
+ case LoadStreamStatus::kCannotLoadMoreNoNextPageToken:
break;
}
return feedui::ZeroStateSlice::NO_CARDS_AVAILABLE;
diff --git a/chromium/components/feed/core/v2/tasks/load_stream_from_store_task.cc b/chromium/components/feed/core/v2/tasks/load_stream_from_store_task.cc
index 7cd775c37b4..71fff522a38 100644
--- a/chromium/components/feed/core/v2/tasks/load_stream_from_store_task.cc
+++ b/chromium/components/feed/core/v2/tasks/load_stream_from_store_task.cc
@@ -70,8 +70,6 @@ void LoadStreamFromStoreTask::LoadStreamDone(
}
}
- // TODO(harringtond): Add other failure cases?
-
std::vector<ContentId> referenced_content_ids;
for (const feedstore::StreamStructureSet& structure_set :
result.stream_structures) {
diff --git a/chromium/components/feed/core/v2/tasks/load_stream_task.cc b/chromium/components/feed/core/v2/tasks/load_stream_task.cc
index 72bcb2d9bfd..0ca24120e21 100644
--- a/chromium/components/feed/core/v2/tasks/load_stream_task.cc
+++ b/chromium/components/feed/core/v2/tasks/load_stream_task.cc
@@ -123,9 +123,14 @@ void LoadStreamTask::QueryRequestComplete(
network_response_info_ = result.response_info;
+ if (result.response_info.status_code != 200)
+ return Done(LoadStreamStatus::kNetworkFetchFailed);
+
if (!result.response_body) {
- Done(LoadStreamStatus::kNoResponseBody);
- return;
+ if (result.response_info.response_body_bytes > 0)
+ return Done(LoadStreamStatus::kCannotParseNetworkResponseBody);
+ else
+ return Done(LoadStreamStatus::kNoResponseBody);
}
RefreshResponseData response_data =
@@ -133,10 +138,8 @@ void LoadStreamTask::QueryRequestComplete(
*result.response_body,
StreamModelUpdateRequest::Source::kNetworkUpdate,
stream_->GetClock()->Now());
- if (!response_data.model_update_request) {
- Done(LoadStreamStatus::kProtoTranslationFailed);
- return;
- }
+ if (!response_data.model_update_request)
+ return Done(LoadStreamStatus::kProtoTranslationFailed);
stream_->GetStore()->OverwriteStream(
std::make_unique<StreamModelUpdateRequest>(
diff --git a/chromium/components/feed/core/v2/tasks/upload_actions_task.cc b/chromium/components/feed/core/v2/tasks/upload_actions_task.cc
index adcc5c87fbb..bb479d1947b 100644
--- a/chromium/components/feed/core/v2/tasks/upload_actions_task.cc
+++ b/chromium/components/feed/core/v2/tasks/upload_actions_task.cc
@@ -6,6 +6,7 @@
#include <memory>
#include <vector>
+#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "components/feed/core/proto/v2/store.pb.h"
#include "components/feed/core/proto/v2/wire/action_request.pb.h"
@@ -15,6 +16,7 @@
#include "components/feed/core/v2/feed_network.h"
#include "components/feed/core/v2/feed_store.h"
#include "components/feed/core/v2/feed_stream.h"
+#include "components/feed/core/v2/metrics_reporter.h"
#include "components/feed/core/v2/request_throttler.h"
namespace feed {
@@ -36,11 +38,6 @@ bool ShouldUpload(const StoredAction& action) {
age < GetFeedConfig().max_action_age;
}
-void ReportBatchStatus(UploadActionsBatchStatus status) {
- // TODO(iwells): Get rid of the DVLOG and record status to a histogram.
- DVLOG(1) << "UploadActionsBatchStatus: " << status;
-}
-
} // namespace
class UploadActionsTask::Batch {
@@ -196,19 +193,10 @@ void UploadActionsTask::UploadPendingActions() {
}
void UploadActionsTask::UpdateAndUploadNextBatch() {
- // Finish if all pending actions have been visited.
- if (pending_actions_.empty()) {
- ReportBatchStatus(UploadActionsBatchStatus::kSuccessfullyUploadedBatch);
- UpdateTokenAndFinish();
- return;
- }
-
// Finish if there's no quota remaining for actions uploads.
if (!stream_->GetRequestThrottler()->RequestQuota(
NetworkRequestType::kUploadActions)) {
- ReportBatchStatus(UploadActionsBatchStatus::kExhaustedUploadQuota);
- UpdateTokenAndFinish();
- return;
+ return BatchComplete(UploadActionsBatchStatus::kExhaustedUploadQuota);
}
// Grab a few actions to be processed and erase them from pending_actions_.
@@ -228,18 +216,13 @@ void UploadActionsTask::OnUpdateActionsFinished(
std::unique_ptr<UploadActionsTask::Batch> batch,
bool update_ok) {
// Stop if there are no actions to upload.
- if (batch->UploadCount() == 0ul) {
- ReportBatchStatus(UploadActionsBatchStatus::kAllActionsWereStale);
- UpdateTokenAndFinish();
- return;
- }
+ if (batch->UploadCount() == 0ul)
+ return BatchComplete(UploadActionsBatchStatus::kAllActionsWereStale);
+
+ // Skip uploading batch if these actions couldn't be updated in the store.
+ if (!update_ok)
+ return BatchComplete(UploadActionsBatchStatus::kFailedToUpdateStore);
- // Skip uploading if these actions couldn't be updated in the store.
- if (!update_ok) {
- ReportBatchStatus(UploadActionsBatchStatus::kFailedToUpdateStore);
- UpdateAndUploadNextBatch();
- return;
- }
upload_attempt_count_ += batch->UploadCount();
stale_count_ += batch->StaleCount();
@@ -264,11 +247,8 @@ void UploadActionsTask::OnUpdateActionsFinished(
void UploadActionsTask::OnUploadFinished(
std::unique_ptr<UploadActionsTask::Batch> batch,
FeedNetwork::ActionRequestResult result) {
- if (!result.response_body) {
- ReportBatchStatus(UploadActionsBatchStatus::kFailedToUpload);
- UpdateAndUploadNextBatch();
- return;
- }
+ if (!result.response_body)
+ return BatchComplete(UploadActionsBatchStatus::kFailedToUpload);
consistency_token_ = std::move(result.response_body->feed_response()
.feed_response()
@@ -282,22 +262,33 @@ void UploadActionsTask::OnUploadFinished(
}
void UploadActionsTask::OnUploadedActionsRemoved(bool remove_ok) {
- ReportBatchStatus(UploadActionsBatchStatus::kFailedToRemoveUploadedActions);
+ if (remove_ok)
+ BatchComplete(UploadActionsBatchStatus::kSuccessfullyUploadedBatch);
+ else
+ BatchComplete(UploadActionsBatchStatus::kFailedToRemoveUploadedActions);
+}
+
+void UploadActionsTask::BatchComplete(UploadActionsBatchStatus status) {
+ MetricsReporter::OnUploadActionsBatch(status);
+
+ if (pending_actions_.empty() ||
+ status == UploadActionsBatchStatus::kExhaustedUploadQuota ||
+ status == UploadActionsBatchStatus::kAllActionsWereStale) {
+ return UpdateTokenAndFinish();
+ }
UpdateAndUploadNextBatch();
}
void UploadActionsTask::UpdateTokenAndFinish() {
- if (consistency_token_.empty()) {
- Done(UploadActionsStatus::kFinishedWithoutUpdatingConsistencyToken);
- return;
- }
+ if (consistency_token_.empty())
+ return Done(UploadActionsStatus::kFinishedWithoutUpdatingConsistencyToken);
stream_->GetMetadata()->SetConsistencyToken(consistency_token_);
Done(UploadActionsStatus::kUpdatedConsistencyToken);
}
void UploadActionsTask::Done(UploadActionsStatus status) {
- DVLOG(1) << "UploadActionsTask finished with status " << status;
+ MetricsReporter::OnUploadActions(status);
std::move(callback_).Run({status, upload_attempt_count_, stale_count_});
TaskComplete();
}
diff --git a/chromium/components/feed/core/v2/tasks/upload_actions_task.h b/chromium/components/feed/core/v2/tasks/upload_actions_task.h
index d543a22ec95..00d37ae09e2 100644
--- a/chromium/components/feed/core/v2/tasks/upload_actions_task.h
+++ b/chromium/components/feed/core/v2/tasks/upload_actions_task.h
@@ -87,6 +87,7 @@ class UploadActionsTask : public offline_pages::Task {
FeedNetwork::ActionRequestResult result);
void OnUploadedActionsRemoved(bool remove_ok);
void UpdateTokenAndFinish();
+ void BatchComplete(UploadActionsBatchStatus status);
void Done(UploadActionsStatus status);
FeedStream* stream_;
diff --git a/chromium/components/feed/core/v2/types.cc b/chromium/components/feed/core/v2/types.cc
index b3b0267e3ee..240a78903bb 100644
--- a/chromium/components/feed/core/v2/types.cc
+++ b/chromium/components/feed/core/v2/types.cc
@@ -9,6 +9,8 @@
#include "base/base64.h"
#include "base/pickle.h"
#include "base/strings/string_number_conversions.h"
+#include "base/util/values/values_util.h"
+#include "base/values.h"
#include "components/feed/core/v2/public/types.h"
// Note: This file contains implementation for both types.h and public/types.h.
@@ -66,6 +68,12 @@ base::Optional<DebugStreamData> UnpickleDebugStreamData(
} // namespace
+NetworkResponseInfo::NetworkResponseInfo() = default;
+NetworkResponseInfo::~NetworkResponseInfo() = default;
+NetworkResponseInfo::NetworkResponseInfo(const NetworkResponseInfo&) = default;
+NetworkResponseInfo& NetworkResponseInfo::operator=(
+ const NetworkResponseInfo&) = default;
+
std::string ToString(ContentRevision c) {
return base::NumberToString(c.value());
}
@@ -99,4 +107,30 @@ DebugStreamData::~DebugStreamData() = default;
DebugStreamData::DebugStreamData(const DebugStreamData&) = default;
DebugStreamData& DebugStreamData::operator=(const DebugStreamData&) = default;
+base::Value PersistentMetricsDataToValue(const PersistentMetricsData& data) {
+ base::Value dict(base::Value::Type::DICTIONARY);
+ dict.SetKey("day_start", util::TimeToValue(data.current_day_start));
+ dict.SetKey("time_spent_in_feed",
+ util::TimeDeltaToValue(data.accumulated_time_spent_in_feed));
+ return dict;
+}
+
+PersistentMetricsData PersistentMetricsDataFromValue(const base::Value& value) {
+ PersistentMetricsData result;
+ if (!value.is_dict())
+ return result;
+ base::Optional<base::Time> day_start =
+ util::ValueToTime(value.FindKey("day_start"));
+ if (!day_start)
+ return result;
+ result.current_day_start = *day_start;
+ base::Optional<base::TimeDelta> time_spent_in_feed =
+ util::ValueToTimeDelta(value.FindKey("time_spent_in_feed"));
+ if (time_spent_in_feed) {
+ result.accumulated_time_spent_in_feed = *time_spent_in_feed;
+ }
+
+ return result;
+}
+
} // namespace feed
diff --git a/chromium/components/feed/core/v2/types.h b/chromium/components/feed/core/v2/types.h
index 4b6bf0b0238..02ecc6a2ac2 100644
--- a/chromium/components/feed/core/v2/types.h
+++ b/chromium/components/feed/core/v2/types.h
@@ -8,6 +8,7 @@
#include <string>
#include "base/util/type_safety/id_type.h"
+#include "base/values.h"
#include "components/feed/core/v2/public/types.h"
namespace feed {
@@ -34,6 +35,17 @@ struct RequestMetadata {
DisplayMetrics display_metrics;
};
+// Data internal to MetricsReporter which is persisted to Prefs.
+struct PersistentMetricsData {
+ // The midnight time for the day in which this metric was recorded.
+ base::Time current_day_start;
+ // The total recorded time spent on the Feed for the current day.
+ base::TimeDelta accumulated_time_spent_in_feed;
+};
+
+base::Value PersistentMetricsDataToValue(const PersistentMetricsData& data);
+PersistentMetricsData PersistentMetricsDataFromValue(const base::Value& value);
+
} // namespace feed
#endif // COMPONENTS_FEED_CORE_V2_TYPES_H_
diff --git a/chromium/components/feed/core/v2/types_unittest.cc b/chromium/components/feed/core/v2/types_unittest.cc
index c7dcfd33eba..95938d4f2bf 100644
--- a/chromium/components/feed/core/v2/types_unittest.cc
+++ b/chromium/components/feed/core/v2/types_unittest.cc
@@ -4,10 +4,22 @@
#include "components/feed/core/v2/types.h"
+#include "base/json/json_writer.h"
+#include "base/strings/string_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace feed {
namespace {
+
+std::string ToJSON(const base::Value& value) {
+ std::string json;
+ CHECK(base::JSONWriter::WriteWithOptions(
+ value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json));
+ // Don't use \r\n on windows.
+ base::RemoveChars(json, "\r", &json);
+ return json;
+}
+
DebugStreamData MakeDebugStreamData() {
NetworkResponseInfo fetch_info;
fetch_info.status_code = 200;
@@ -60,4 +72,24 @@ TEST(DebugStreamData, FailsDeserializationGracefully) {
ASSERT_EQ(base::nullopt, DeserializeDebugStreamData({}));
}
+TEST(PersistentMetricsData, SerializesAndDeserializes) {
+ PersistentMetricsData data;
+ data.accumulated_time_spent_in_feed = base::TimeDelta::FromHours(2);
+ data.current_day_start = base::Time::UnixEpoch();
+
+ const base::Value serialized_value = PersistentMetricsDataToValue(data);
+ const PersistentMetricsData deserialized_value =
+ PersistentMetricsDataFromValue(serialized_value);
+
+ EXPECT_EQ(R"({
+ "day_start": "11644473600000000",
+ "time_spent_in_feed": "7200000000"
+}
+)",
+ ToJSON(serialized_value));
+ EXPECT_EQ(data.accumulated_time_spent_in_feed,
+ deserialized_value.accumulated_time_spent_in_feed);
+ EXPECT_EQ(data.current_day_start, deserialized_value.current_day_start);
+}
+
} // namespace feed
diff --git a/chromium/components/feed/feed_feature_list.cc b/chromium/components/feed/feed_feature_list.cc
index f6b51cb172a..f19e413151e 100644
--- a/chromium/components/feed/feed_feature_list.cc
+++ b/chromium/components/feed/feed_feature_list.cc
@@ -8,6 +8,9 @@ namespace feed {
const base::Feature kInterestFeedContentSuggestions{
"InterestFeedContentSuggestions", base::FEATURE_ENABLED_BY_DEFAULT};
+// InterestFeedV2 takes precedence over InterestFeedContentSuggestions.
+const base::Feature kInterestFeedV2{"InterestFeedV2",
+ base::FEATURE_DISABLED_BY_DEFAULT};
const base::FeatureParam<std::string> kDisableTriggerTypes{
&kInterestFeedContentSuggestions, "disable_trigger_types", ""};
@@ -30,8 +33,6 @@ const base::Feature kInterestFeedFeedback{"InterestFeedFeedback",
const base::Feature kReportFeedUserActions{"ReportFeedUserActions",
base::FEATURE_DISABLED_BY_DEFAULT};
-const base::Feature kInterestFeedV2{"InterestFeedV2",
- base::FEATURE_DISABLED_BY_DEFAULT};
} // namespace feed
diff --git a/chromium/components/feed/feed_feature_list.h b/chromium/components/feed/feed_feature_list.h
index f26edc66376..432f56d98de 100644
--- a/chromium/components/feed/feed_feature_list.h
+++ b/chromium/components/feed/feed_feature_list.h
@@ -13,6 +13,7 @@
namespace feed {
extern const base::Feature kInterestFeedContentSuggestions;
+extern const base::Feature kInterestFeedV2;
extern const base::FeatureParam<std::string> kDisableTriggerTypes;
extern const base::FeatureParam<int> kSuppressRefreshDurationMinutes;
@@ -28,7 +29,6 @@ extern const base::Feature kInterestFeedFeedback;
// for personalization. Also enables the feed header menu to manage the feed.
extern const base::Feature kReportFeedUserActions;
-extern const base::Feature kInterestFeedV2;
} // namespace feed