summaryrefslogtreecommitdiff
path: root/chromium/components/feed
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2021-03-12 09:13:00 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2021-03-16 09:58:26 +0000
commit03561cae90f1d99b5c54b1ef3be69f10e882b25e (patch)
treecc5f0958e823c044e7ae51cc0117fe51432abe5e /chromium/components/feed
parentfa98118a45f7e169f8846086dc2c22c49a8ba310 (diff)
downloadqtwebengine-chromium-03561cae90f1d99b5c54b1ef3be69f10e882b25e.tar.gz
BASELINE: Update Chromium to 88.0.4324.208
Change-Id: I3ae87d23e4eff4b4a469685658740a213600c667 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.cc6
-rw-r--r--chromium/components/feed/core/common/pref_names.h14
-rw-r--r--chromium/components/feed/core/feed_networking_host.cc8
-rw-r--r--chromium/components/feed/core/feed_networking_host.h6
-rw-r--r--chromium/components/feed/core/feed_networking_host_unittest.cc17
-rw-r--r--chromium/components/feed/core/proto/BUILD.gn3
-rw-r--r--chromium/components/feed/core/proto/v2/store.proto9
-rw-r--r--chromium/components/feed/core/proto/v2/wire/chrome_client_info.proto17
-rw-r--r--chromium/components/feed/core/proto/v2/wire/chrome_feed_response_metadata.proto1
-rw-r--r--chromium/components/feed/core/proto/v2/wire/chrome_fulfillment_info.proto17
-rw-r--r--chromium/components/feed/core/proto/v2/wire/client_info.proto5
-rw-r--r--chromium/components/feed/core/proto/v2/wire/feed_query.proto4
-rw-r--r--chromium/components/feed/core/proto/wire/chrome_fulfillment_info.proto20
-rw-r--r--chromium/components/feed/core/proto/wire/feed_query.proto5
-rw-r--r--chromium/components/feed/core/v2/BUILD.gn12
-rw-r--r--chromium/components/feed/core/v2/common_enums.h86
-rw-r--r--chromium/components/feed/core/v2/config.cc15
-rw-r--r--chromium/components/feed/core/v2/config.h6
-rw-r--r--chromium/components/feed/core/v2/feed_network.h2
-rw-r--r--chromium/components/feed/core/v2/feed_network_impl.cc11
-rw-r--r--chromium/components/feed/core/v2/feed_network_impl_unittest.cc3
-rw-r--r--chromium/components/feed/core/v2/feed_store.cc4
-rw-r--r--chromium/components/feed/core/v2/feed_store_unittest.cc2
-rw-r--r--chromium/components/feed/core/v2/feed_stream.cc118
-rw-r--r--chromium/components/feed/core/v2/feed_stream.h27
-rw-r--r--chromium/components/feed/core/v2/feed_stream_unittest.cc445
-rw-r--r--chromium/components/feed/core/v2/image_fetcher_unittest.cc2
-rw-r--r--chromium/components/feed/core/v2/metrics_reporter.cc5
-rw-r--r--chromium/components/feed/core/v2/metrics_reporter.h45
-rw-r--r--chromium/components/feed/core/v2/metrics_reporter_unittest.cc3
-rw-r--r--chromium/components/feed/core/v2/notice_card_tracker.cc97
-rw-r--r--chromium/components/feed/core/v2/notice_card_tracker.h47
-rw-r--r--chromium/components/feed/core/v2/notice_card_tracker_unittest.cc151
-rw-r--r--chromium/components/feed/core/v2/prefs.cc18
-rw-r--r--chromium/components/feed/core/v2/prefs.h10
-rw-r--r--chromium/components/feed/core/v2/proto_util.cc40
-rw-r--r--chromium/components/feed/core/v2/proto_util_unittest.cc26
-rw-r--r--chromium/components/feed/core/v2/protocol_translator.cc13
-rw-r--r--chromium/components/feed/core/v2/protocol_translator.h3
-rw-r--r--chromium/components/feed/core/v2/public/feed_service.cc3
-rw-r--r--chromium/components/feed/core/v2/public/feed_service.h2
-rw-r--r--chromium/components/feed/core/v2/public/feed_stream_api.h14
-rw-r--r--chromium/components/feed/core/v2/public/types.h1
-rw-r--r--chromium/components/feed/core/v2/tasks/load_more_task.cc35
-rw-r--r--chromium/components/feed/core/v2/tasks/load_more_task.h3
-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_from_store_task.h1
-rw-r--r--chromium/components/feed/core/v2/tasks/load_stream_task.cc17
-rw-r--r--chromium/components/feed/core/v2/tasks/load_stream_task.h3
-rw-r--r--chromium/components/feed/core/v2/tasks/prefetch_images_task.cc100
-rw-r--r--chromium/components/feed/core/v2/tasks/prefetch_images_task.h51
-rw-r--r--chromium/components/feed/core/v2/types.h2
-rw-r--r--chromium/components/feed/features.gni16
-rw-r--r--chromium/components/feed/feed_feature_list.cc12
-rw-r--r--chromium/components/feed/feed_feature_list.h6
55 files changed, 1469 insertions, 122 deletions
diff --git a/chromium/components/feed/core/common/pref_names.cc b/chromium/components/feed/core/common/pref_names.cc
index a50a96ff852..70cd1594940 100644
--- a/chromium/components/feed/core/common/pref_names.cc
+++ b/chromium/components/feed/core/common/pref_names.cc
@@ -36,6 +36,9 @@ const char kHostOverrideBlessNonce[] = "feed.host_override.bless_nonce";
const char kHasReachedClickAndViewActionsUploadConditions[] =
"feed.clicks_and_views_upload_conditions_reached";
const char kLastFetchHadNoticeCard[] = "feed.last_fetch_had_notice_card";
+const char kLastRefreshWasSignedIn[] = "feed.last_refresh_was_signed_in";
+const char kNoticeCardViewsCount[] = "feed.notice_card_views_count";
+const char kNoticeCardClicksCount[] = "feed.notice_card_clicks_count";
const char kThrottlerRequestCountListPrefName[] =
"feedv2.request_throttler.request_counts";
@@ -68,6 +71,9 @@ void RegisterProfilePrefs(PrefRegistrySimple* registry) {
registry->RegisterBooleanPref(
feed::prefs::kHasReachedClickAndViewActionsUploadConditions, false);
registry->RegisterBooleanPref(feed::prefs::kLastFetchHadNoticeCard, true);
+ registry->RegisterBooleanPref(feed::prefs::kLastRefreshWasSignedIn, false);
+ registry->RegisterIntegerPref(feed::prefs::kNoticeCardViewsCount, 0);
+ registry->RegisterIntegerPref(feed::prefs::kNoticeCardClicksCount, 0);
UserClassifier::RegisterProfilePrefs(registry);
}
diff --git a/chromium/components/feed/core/common/pref_names.h b/chromium/components/feed/core/common/pref_names.h
index 9c381483e65..93116f51d2e 100644
--- a/chromium/components/feed/core/common/pref_names.h
+++ b/chromium/components/feed/core/common/pref_names.h
@@ -46,16 +46,26 @@ extern const char kHostOverrideBlessNonce[];
// to enable the upload of click and view actions in the feed with the notice
// card when using the feature kInterestFeedConditionalClickAndViewActionUpload.
// This is for when the privacy notice card is at the second position in the
-// feed. Currently only used in V1.
+// feed.
extern const char kHasReachedClickAndViewActionsUploadConditions[];
// The pref name for the bit that determines whether the notice card was present
// in the feed in the last fetch of content. The notice card is considered as
// present by default to make sure that the upload of click and view actions
// doesn't take place when the notice card is present but has not yet been
-// detected. Currently only used in V1.
+// detected.
extern const char kLastFetchHadNoticeCard[];
+// The pref name for the bit that determines whether the last refresh request
+// was signed in. Currently only used in V1.
+extern const char kLastRefreshWasSignedIn[];
+
+// The pref name for the counter for the number of views on the notice card.
+extern const char kNoticeCardViewsCount[];
+
+// The pref name for the counter for the number of clicks on the notice card.
+extern const char kNoticeCardClicksCount[];
+
// The following prefs are used only by v2.
// The pref name for the request throttler counts.
diff --git a/chromium/components/feed/core/feed_networking_host.cc b/chromium/components/feed/core/feed_networking_host.cc
index e6ab4ac9321..b6104f523d7 100644
--- a/chromium/components/feed/core/feed_networking_host.cc
+++ b/chromium/components/feed/core/feed_networking_host.cc
@@ -345,7 +345,8 @@ void NetworkFetch::OnSimpleLoaderComplete(
static_cast<int>(response_body.size() / 1024));
}
- std::move(done_callback_).Run(status_code, std::move(response_body));
+ std::move(done_callback_)
+ .Run(status_code, std::move(response_body), !access_token_.empty());
}
FeedNetworkingHost::FeedNetworkingHost(
@@ -388,12 +389,13 @@ void FeedNetworkingHost::NetworkFetchFinished(
NetworkFetch* fetch,
ResponseCallback callback,
int32_t http_code,
- std::vector<uint8_t> response_body) {
+ std::vector<uint8_t> response_body,
+ bool is_signed_in) {
auto fetch_iterator = pending_requests_.find(fetch);
CHECK(fetch_iterator != pending_requests_.end());
pending_requests_.erase(fetch_iterator);
- std::move(callback).Run(http_code, std::move(response_body));
+ std::move(callback).Run(http_code, std::move(response_body), is_signed_in);
}
} // namespace feed
diff --git a/chromium/components/feed/core/feed_networking_host.h b/chromium/components/feed/core/feed_networking_host.h
index f20873554e7..dcfec632efd 100644
--- a/chromium/components/feed/core/feed_networking_host.h
+++ b/chromium/components/feed/core/feed_networking_host.h
@@ -45,7 +45,8 @@ class FeedNetworkingHost {
// status code(if the request succeeded in reaching the server).
using ResponseCallback =
base::OnceCallback<void(int32_t status_code,
- std::vector<uint8_t> response_bytes)>;
+ std::vector<uint8_t> response_bytes,
+ bool is_signed_in)>;
FeedNetworkingHost(
signin::IdentityManager* identity_manager,
@@ -74,7 +75,8 @@ class FeedNetworkingHost {
void NetworkFetchFinished(NetworkFetch* fetch,
ResponseCallback callback,
int32_t http_code,
- std::vector<uint8_t> response_body);
+ std::vector<uint8_t> response_body,
+ bool isSignedIn);
base::flat_set<std::unique_ptr<NetworkFetch>, base::UniquePtrComparator>
pending_requests_;
diff --git a/chromium/components/feed/core/feed_networking_host_unittest.cc b/chromium/components/feed/core/feed_networking_host_unittest.cc
index 9361ec2dd7d..da5af8defdc 100644
--- a/chromium/components/feed/core/feed_networking_host_unittest.cc
+++ b/chromium/components/feed/core/feed_networking_host_unittest.cc
@@ -9,7 +9,7 @@
#include "base/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
-#include "base/test/bind_test_util.h"
+#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_tick_clock.h"
@@ -46,16 +46,20 @@ class MockResponseDoneCallback {
public:
MockResponseDoneCallback() : has_run(false), code(0) {}
- void Done(int32_t http_code, std::vector<uint8_t> response) {
+ void Done(int32_t http_code,
+ std::vector<uint8_t> response,
+ bool is_signed_in) {
EXPECT_FALSE(has_run);
has_run = true;
code = http_code;
response_bytes = std::move(response);
+ is_signed_in_result = is_signed_in;
}
bool has_run;
int32_t code;
std::vector<uint8_t> response_bytes;
+ bool is_signed_in_result;
};
} // namespace
@@ -266,6 +270,15 @@ TEST_F(FeedNetworkingHostTest, ShouldSetHeadersCorrectly) {
EXPECT_EQ(authorization, "Bearer access_token");
}
+TEST_F(FeedNetworkingHostTest, ProvideIsSignedInBitInResult) {
+ MockResponseDoneCallback done_callback;
+ SendRequestAndRespond("http://foobar.com/feed", "POST", "body", "",
+ net::HTTP_OK, network::URLLoaderCompletionStatus(),
+ &done_callback);
+
+ EXPECT_TRUE(done_callback.is_signed_in_result);
+}
+
TEST_F(FeedNetworkingHostTest, ShouldNotSendContentEncodingForEmptyBody) {
MockResponseDoneCallback done_callback;
net::HttpRequestHeaders headers;
diff --git a/chromium/components/feed/core/proto/BUILD.gn b/chromium/components/feed/core/proto/BUILD.gn
index 2ff52bed0fc..9244b9aebc5 100644
--- a/chromium/components/feed/core/proto/BUILD.gn
+++ b/chromium/components/feed/core/proto/BUILD.gn
@@ -28,7 +28,9 @@ proto_library("proto_v2") {
"v2/ui.proto",
"v2/wire/action_payload.proto",
"v2/wire/capability.proto",
+ "v2/wire/chrome_client_info.proto",
"v2/wire/chrome_feed_response_metadata.proto",
+ "v2/wire/chrome_fulfillment_info.proto",
"v2/wire/client_info.proto",
"v2/wire/consistency_token.proto",
"v2/wire/content_id.proto",
@@ -92,6 +94,7 @@ if (is_android) {
"wire/action_request.proto",
"wire/action_type.proto",
"wire/capability.proto",
+ "wire/chrome_fulfillment_info.proto",
"wire/client_info.proto",
"wire/consistency_token.proto",
"wire/content_id.proto",
diff --git a/chromium/components/feed/core/proto/v2/store.proto b/chromium/components/feed/core/proto/v2/store.proto
index 29ba828c4ea..206d90596db 100644
--- a/chromium/components/feed/core/proto/v2/store.proto
+++ b/chromium/components/feed/core/proto/v2/store.proto
@@ -51,15 +51,24 @@ message StreamData {
bool logging_enabled = 8;
// Has the privacy notice been fulfilled?
bool privacy_notice_fulfilled = 9;
+
reserved 3, 5;
}
// Data that doesn't belong to a stream.
message Metadata {
+ // Session identifier used for signed-out feed requests and interactions.
+ message SessionID {
+ string token = 1;
+ int64 expiry_time_ms = 2;
+ }
+
// Token used to read or write to the same storage.
bytes consistency_token = 1;
// ID for the next pending action.
int32 next_action_id = 2;
+ // The most recent session identifier.
+ SessionID session_id = 3;
}
// A set of StreamStructures that should be applied to a stream.
diff --git a/chromium/components/feed/core/proto/v2/wire/chrome_client_info.proto b/chromium/components/feed/core/proto/v2/wire/chrome_client_info.proto
new file mode 100644
index 00000000000..26f71a2b690
--- /dev/null
+++ b/chromium/components/feed/core/proto/v2/wire/chrome_client_info.proto
@@ -0,0 +1,17 @@
+// 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.
+
+syntax = "proto2";
+
+package feedwire;
+
+option optimize_for = LITE_RUNTIME;
+
+// Information about the client performing the request, relevant only to Chrome
+// surfaces.
+message ChromeClientInfo {
+ // The signed-out session identifier (Zwieback) token, for clients which embed
+ // this information in-band instead of using an HTTP Cookie.
+ optional string session_id = 3;
+}
diff --git a/chromium/components/feed/core/proto/v2/wire/chrome_feed_response_metadata.proto b/chromium/components/feed/core/proto/v2/wire/chrome_feed_response_metadata.proto
index c6ec3fcdbe7..2eb9cad7263 100644
--- a/chromium/components/feed/core/proto/v2/wire/chrome_feed_response_metadata.proto
+++ b/chromium/components/feed/core/proto/v2/wire/chrome_feed_response_metadata.proto
@@ -11,4 +11,5 @@ option optimize_for = LITE_RUNTIME;
message ChromeFeedResponseMetadata {
optional bool privacy_notice_fulfilled = 1;
optional bool logging_enabled = 2;
+ optional string session_id = 3;
}
diff --git a/chromium/components/feed/core/proto/v2/wire/chrome_fulfillment_info.proto b/chromium/components/feed/core/proto/v2/wire/chrome_fulfillment_info.proto
new file mode 100644
index 00000000000..22ea6991dcb
--- /dev/null
+++ b/chromium/components/feed/core/proto/v2/wire/chrome_fulfillment_info.proto
@@ -0,0 +1,17 @@
+// 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.
+
+syntax = "proto2";
+
+package feedwire;
+
+option optimize_for = LITE_RUNTIME;
+
+// Information on how to do content fulfillment for Chrome.
+message ChromeFulfillmentInfo {
+ // Whether the notice card has already been acknowledged by the user based on
+ // their views and clicks on the card. This is different from when the user
+ // explicitly dismiss the notice card by touching the button.
+ optional bool notice_card_acknowledged = 1;
+}
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 40eaca72c48..a54ae75ec48 100644
--- a/chromium/components/feed/core/proto/v2/wire/client_info.proto
+++ b/chromium/components/feed/core/proto/v2/wire/client_info.proto
@@ -8,6 +8,7 @@ package feedwire;
option optimize_for = LITE_RUNTIME;
+import "components/feed/core/proto/v2/wire/chrome_client_info.proto";
import "components/feed/core/proto/v2/wire/display_info.proto";
import "components/feed/core/proto/v2/wire/version.proto";
@@ -58,4 +59,8 @@ message ClientInfo {
// this comes from GServices check-in which uses the SIM card MCC (mobile
// country code), with fallback to IP geo lookup.
optional string device_country = 9;
+
+ // Information about the client performing the request, relevant only to
+ // Chrome surfaces.
+ optional ChromeClientInfo chrome_client_info = 338478298;
}
diff --git a/chromium/components/feed/core/proto/v2/wire/feed_query.proto b/chromium/components/feed/core/proto/v2/wire/feed_query.proto
index e69e919fae6..0cc218f07bb 100644
--- a/chromium/components/feed/core/proto/v2/wire/feed_query.proto
+++ b/chromium/components/feed/core/proto/v2/wire/feed_query.proto
@@ -8,6 +8,7 @@ package feedwire;
option optimize_for = LITE_RUNTIME;
+import "components/feed/core/proto/v2/wire/chrome_fulfillment_info.proto";
import "components/feed/core/proto/v2/wire/token.proto";
message FeedQuery {
@@ -48,5 +49,8 @@ message FeedQuery {
Tokens in_place_update_tokens = 5;
}
+ // Information on how to do content fulfillment for Chrome.
+ optional ChromeFulfillmentInfo chrome_fulfillment_info = 341477699;
+
reserved 2;
}
diff --git a/chromium/components/feed/core/proto/wire/chrome_fulfillment_info.proto b/chromium/components/feed/core/proto/wire/chrome_fulfillment_info.proto
new file mode 100644
index 00000000000..e78acbaedcb
--- /dev/null
+++ b/chromium/components/feed/core/proto/wire/chrome_fulfillment_info.proto
@@ -0,0 +1,20 @@
+// 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.
+
+syntax = "proto2";
+
+package feedwire1;
+
+option optimize_for = LITE_RUNTIME;
+
+option java_package = "org.chromium.components.feed.core.proto.wire";
+option java_outer_classname = "ChromeFulfillmentInfoProto";
+
+// Information on how to do content fulfillment for Chrome.
+message ChromeFulfillmentInfo {
+ // Whether the notice card has already been acknowledged by the user based on
+ // their views and clicks on the card. This is different from when the user
+ // explicitly dismiss the notice card by touching the button.
+ optional bool notice_card_acknowledged = 1;
+}
diff --git a/chromium/components/feed/core/proto/wire/feed_query.proto b/chromium/components/feed/core/proto/wire/feed_query.proto
index 2620819c328..e105058af93 100644
--- a/chromium/components/feed/core/proto/wire/feed_query.proto
+++ b/chromium/components/feed/core/proto/wire/feed_query.proto
@@ -11,6 +11,8 @@ option optimize_for = LITE_RUNTIME;
option java_package = "org.chromium.components.feed.core.proto.wire";
option java_outer_classname = "FeedQueryProto";
+import "components/feed/core/proto/wire/chrome_fulfillment_info.proto";
+
message FeedQuery {
enum RequestReason {
// Bucket for any not listed. Should not be used (prefer adding a new
@@ -49,4 +51,7 @@ message FeedQuery {
// Used to fetch the next page when scrolling copied from
// Token.next_page_token
optional bytes page_token = 2;
+
+ // Information on how to do content fulfillment for Chrome.
+ optional ChromeFulfillmentInfo chrome_fulfillment_info = 3;
}
diff --git a/chromium/components/feed/core/v2/BUILD.gn b/chromium/components/feed/core/v2/BUILD.gn
index 3de0962ba05..6331e2eceda 100644
--- a/chromium/components/feed/core/v2/BUILD.gn
+++ b/chromium/components/feed/core/v2/BUILD.gn
@@ -27,6 +27,8 @@ source_set("feed_core_v2") {
"image_fetcher.h",
"metrics_reporter.cc",
"metrics_reporter.h",
+ "notice_card_tracker.cc",
+ "notice_card_tracker.h",
"offline_page_spy.cc",
"offline_page_spy.h",
"prefs.cc",
@@ -63,6 +65,8 @@ source_set("feed_core_v2") {
"tasks/load_stream_from_store_task.h",
"tasks/load_stream_task.cc",
"tasks/load_stream_task.h",
+ "tasks/prefetch_images_task.cc",
+ "tasks/prefetch_images_task.h",
"tasks/upload_actions_task.cc",
"tasks/upload_actions_task.h",
"tasks/wait_for_store_initialize_task.cc",
@@ -71,6 +75,7 @@ source_set("feed_core_v2") {
"types.h",
]
deps = [
+ ":common",
"//components/feed/core:feed_core",
"//components/feed/core/common:feed_core_common",
"//components/history/core/browser",
@@ -97,6 +102,11 @@ source_set("feed_core_v2") {
]
}
+source_set("common") {
+ sources = [ "common_enums.h" ]
+ deps = []
+}
+
source_set("core_unit_tests") {
testonly = true
sources = [
@@ -106,6 +116,7 @@ source_set("core_unit_tests") {
"feed_stream_unittest.cc",
"image_fetcher_unittest.cc",
"metrics_reporter_unittest.cc",
+ "notice_card_tracker_unittest.cc",
"proto_util_unittest.cc",
"protocol_translator_unittest.cc",
"public/feed_service_unittest.cc",
@@ -122,6 +133,7 @@ source_set("core_unit_tests") {
]
deps = [
+ ":common",
":feed_core_v2",
":unit_tests_bundle_data",
"//base",
diff --git a/chromium/components/feed/core/v2/common_enums.h b/chromium/components/feed/core/v2/common_enums.h
new file mode 100644
index 00000000000..070dfc83916
--- /dev/null
+++ b/chromium/components/feed/core/v2/common_enums.h
@@ -0,0 +1,86 @@
+// 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.
+
+#ifndef COMPONENTS_FEED_CORE_V2_COMMON_ENUMS_H_
+#define COMPONENTS_FEED_CORE_V2_COMMON_ENUMS_H_
+
+// Unlike most code from feed/core, these enums are used by both iOS and
+// Android.
+namespace feed {
+
+// Values for the UMA ContentSuggestions.Feed.EngagementType
+// histogram. These values are persisted to logs. Entries should not be
+// renumbered and numeric values should never be reused. This must be kept
+// in sync with FeedEngagementType in enums.xml.
+enum class FeedEngagementType {
+ kFeedEngaged = 0,
+ kFeedEngagedSimple = 1,
+ kFeedInteracted = 2,
+ kDeprecatedFeedScrolled = 3,
+ kFeedScrolled = 4,
+ kMaxValue = kFeedScrolled,
+};
+
+// Values for the UMA ContentSuggestions.Feed.UserActions
+// histogram. These values are persisted to logs. Entries should not be
+// renumbered and numeric values should never be reused. This must be kept
+// in sync with FeedUserActionType in enums.xml.
+// Note: Most of these have a corresponding UserMetricsAction reported here.
+// Exceptions are described below.
+enum class FeedUserActionType {
+ // User tapped on card, opening the article in the same tab.
+ kTappedOnCard = 0,
+ // This is not an actual user action, so there will be no UserMetricsAction
+ // reported for this.
+ kShownCard = 1,
+ // User tapped on 'Send Feedback' in the back of card menu.
+ kTappedSendFeedback = 2,
+ // Discover feed header menu 'Learn More' tapped.
+ kTappedLearnMore = 3,
+ // User Tapped Hide Story in the back of card menu.
+ kTappedHideStory = 4,
+ // User Tapped Not Interested In X in the back of card menu.
+ kTappedNotInterestedIn = 5,
+ // Discover feed header menu 'Manage Interests' tapped.
+ kTappedManageInterests = 6,
+ kTappedDownload = 7,
+ // User opened the article in a new tab from the back of card menu.
+ kTappedOpenInNewTab = 8,
+ // User opened the back of card menu.
+ kOpenedContextMenu = 9,
+ // User action not reported here. See Suggestions.SurfaceVisible.
+ kOpenedFeedSurface = 10,
+ // User opened the article in an incognito tab from the back of card menu.
+ kTappedOpenInNewIncognitoTab = 11,
+ // Ephemeral change, likely due to hide story or not interested in.
+ kEphemeralChange = 12,
+ // Ephemeral change undone, likely due to pressing 'undo' on the snackbar.
+ kEphemeralChangeRejected = 13,
+ // Discover feed visibility toggled from header menu.
+ kTappedTurnOn = 14,
+ kTappedTurnOff = 15,
+ // Discover feed header menu 'Manage Activity' tapped.
+ kTappedManageActivity = 16,
+ // User added article to 'Read Later' list.
+ kAddedToReadLater = 17,
+ // User closed the back of card menu.
+ kClosedContextMenu = 18,
+ // Ephemeral change committed, likely due to dismissing an 'undo' snackbar.
+ kEphemeralChangeCommited = 19,
+ // User opened a Dialog. e.g. Report content Dialog. User action not reported
+ // here.
+ kOpenedDialog = 20,
+ // User closed a Dialog. e.g. Report content Dialog.
+ kClosedDialog = 21,
+ // User action caused a snackbar to be shown. User action not reported here.
+ kShowSnackbar = 22,
+ // User opened the native back of card menu.
+ kOpenedNativeContextMenu = 23,
+ // Highest enumerator. Recommended by Histogram metrics best practices.
+ kMaxValue = kOpenedNativeContextMenu,
+};
+
+} // namespace feed
+
+#endif // COMPONENTS_FEED_CORE_V2_COMMON_ENUMS_H_
diff --git a/chromium/components/feed/core/v2/config.cc b/chromium/components/feed/core/v2/config.cc
index 753f275f00b..309e4583b89 100644
--- a/chromium/components/feed/core/v2/config.cc
+++ b/chromium/components/feed/core/v2/config.cc
@@ -76,6 +76,21 @@ void OverrideWithFinch(Config* config) {
kInterestFeedV2, "upload_actions_on_enter_background",
config->upload_actions_on_enter_background);
+ config->send_signed_out_session_logs =
+ base::GetFieldTrialParamByFeatureAsBool(
+ kInterestFeedV2, "send_signed_out_session_logs",
+ config->send_signed_out_session_logs);
+
+ config->session_id_max_age =
+ base::TimeDelta::FromDays(base::GetFieldTrialParamByFeatureAsInt(
+ kInterestFeedV2, "session_id_max_age_days",
+ config->session_id_max_age.InDays()));
+
+ config->max_prefetch_image_requests_per_refresh =
+ base::GetFieldTrialParamByFeatureAsInt(
+ kInterestFeedV2, "max_prefetch_image_requests_per_refresh",
+ config->max_prefetch_image_requests_per_refresh);
+
// Erase any capabilities with "enable_CAPABILITY = false" set.
base::EraseIf(config->experimental_capabilities, CapabilityDisabled);
}
diff --git a/chromium/components/feed/core/v2/config.h b/chromium/components/feed/core/v2/config.h
index 2789d0cd219..1eb0d612c12 100644
--- a/chromium/components/feed/core/v2/config.h
+++ b/chromium/components/feed/core/v2/config.h
@@ -38,6 +38,12 @@ struct Config {
int load_more_trigger_lookahead = 5;
// Whether to attempt uploading actions when Chrome is hidden.
bool upload_actions_on_enter_background = true;
+ // Whether to send (pseudonymous) logs for signed-out sessions.
+ bool send_signed_out_session_logs = false;
+ // The max age of a signed-out session token.
+ base::TimeDelta session_id_max_age = base::TimeDelta::FromDays(30);
+ // Maximum number of images prefetched per refresh.
+ int max_prefetch_image_requests_per_refresh = 50;
// Set of optional capabilities included in requests. See
// CreateFeedQueryRequest() for required capabilities.
base::flat_set<feedwire::Capability> experimental_capabilities = {
diff --git a/chromium/components/feed/core/v2/feed_network.h b/chromium/components/feed/core/v2/feed_network.h
index b0836218f4f..a5676de45a4 100644
--- a/chromium/components/feed/core/v2/feed_network.h
+++ b/chromium/components/feed/core/v2/feed_network.h
@@ -30,6 +30,8 @@ class FeedNetwork {
NetworkResponseInfo response_info;
// Response body if one was received.
std::unique_ptr<feedwire::Response> response_body;
+ // Whether the request was signed in.
+ bool was_signed_in;
};
// Result of SendActionRequest.
diff --git a/chromium/components/feed/core/v2/feed_network_impl.cc b/chromium/components/feed/core/v2/feed_network_impl.cc
index 4cc86a94c45..5f2c68ad471 100644
--- a/chromium/components/feed/core/v2/feed_network_impl.cc
+++ b/chromium/components/feed/core/v2/feed_network_impl.cc
@@ -12,6 +12,7 @@
#include "base/containers/flat_set.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
+#include "base/strings/strcat.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "components/feed/core/common/pref_names.h"
@@ -39,6 +40,9 @@
#include "third_party/protobuf/src/google/protobuf/io/coded_stream.h"
#include "third_party/zlib/google/compression_utils.h"
+// Token override for Feedv2NewTabPageCardInstrumentationTest.java:
+// #define TOKEN_OVERRIDE_FOR_TESTING "put-test-token-here"
+
namespace feed {
namespace {
constexpr char kApplicationXProtobuf[] = "application/x-protobuf";
@@ -312,8 +316,12 @@ class FeedNetworkImpl::NetworkFetch {
variations::SignedIn signed_in_status = variations::SignedIn::kNo;
if (!access_token_.empty()) {
+ base::StringPiece token = access_token_;
+#ifdef TOKEN_OVERRIDE_FOR_TESTING
+ token = TOKEN_OVERRIDE_FOR_TESTING;
+#endif
request.headers.SetHeader(net::HttpRequestHeaders::kAuthorization,
- "Bearer " + access_token_);
+ base::StrCat({"Bearer ", token}));
signed_in_status = variations::SignedIn::kYes;
}
@@ -329,6 +337,7 @@ class FeedNetworkImpl::NetworkFetch {
tick_clock_->NowTicks() - entire_send_start_ticks_;
response_info.fetch_time = base::Time::Now();
response_info.base_request_url = GetUrlWithoutQuery(url_);
+ response_info.was_signed_in = !access_token_.empty();
// If overriding the feed host, try to grab the Bless nonce. This is
// strictly informational, and only displayed in snippets-internals.
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 6d759773629..c6ad6e97a6e 100644
--- a/chromium/components/feed/core/v2/feed_network_impl_unittest.cc
+++ b/chromium/components/feed/core/v2/feed_network_impl_unittest.cc
@@ -10,7 +10,7 @@
#include "base/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
-#include "base/test/bind_test_util.h"
+#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/task_environment.h"
@@ -277,6 +277,7 @@ TEST_F(FeedNetworkTest, SendQueryRequestReceivesResponse) {
"https://www.google.com/httpservice/retry/TrellisClankService/FeedQuery",
result.response_info.base_request_url);
EXPECT_NE(base::Time(), result.response_info.fetch_time);
+ EXPECT_TRUE(result.response_info.was_signed_in);
EXPECT_EQ(GetTestFeedResponse().response_version(),
result.response_body->response_version());
}
diff --git a/chromium/components/feed/core/v2/feed_store.cc b/chromium/components/feed/core/v2/feed_store.cc
index bb0add49a8e..65eafb55d54 100644
--- a/chromium/components/feed/core/v2/feed_store.cc
+++ b/chromium/components/feed/core/v2/feed_store.cc
@@ -7,7 +7,7 @@
#include <utility>
#include "base/bind.h"
-#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
#include "base/containers/flat_set.h"
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
@@ -501,7 +501,7 @@ void FeedStore::RemoveActions(std::vector<LocalActionId> ids,
database_->UpdateEntries(
/*entries_to_save=*/std::make_unique<
std::vector<std::pair<std::string, feedstore::Record>>>(),
- /*key_to_remove=*/std::move(keys), std::move(callback));
+ /*keys_to_remove=*/std::move(keys), std::move(callback));
}
void FeedStore::Write(std::vector<feedstore::Record> records,
diff --git a/chromium/components/feed/core/v2/feed_store_unittest.cc b/chromium/components/feed/core/v2/feed_store_unittest.cc
index db7473c2d58..fe2a1ecd19c 100644
--- a/chromium/components/feed/core/v2/feed_store_unittest.cc
+++ b/chromium/components/feed/core/v2/feed_store_unittest.cc
@@ -11,7 +11,7 @@
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
-#include "base/test/bind_test_util.h"
+#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "components/feed/core/proto/v2/wire/content_id.pb.h"
#include "components/feed/core/v2/protocol_translator.h"
diff --git a/chromium/components/feed/core/v2/feed_stream.cc b/chromium/components/feed/core/v2/feed_stream.cc
index 022579bcb33..a2794afa866 100644
--- a/chromium/components/feed/core/v2/feed_stream.cc
+++ b/chromium/components/feed/core/v2/feed_stream.cc
@@ -34,6 +34,7 @@
#include "components/feed/core/v2/tasks/clear_all_task.h"
#include "components/feed/core/v2/tasks/get_prefetch_suggestions_task.h"
#include "components/feed/core/v2/tasks/load_stream_task.h"
+#include "components/feed/core/v2/tasks/prefetch_images_task.h"
#include "components/feed/core/v2/tasks/upload_actions_task.h"
#include "components/feed/core/v2/tasks/wait_for_store_initialize_task.h"
#include "components/feed/feed_feature_list.h"
@@ -113,7 +114,7 @@ void FeedStream::Metadata::Populate(feedstore::Metadata metadata) {
metadata_ = std::move(metadata);
}
-std::string FeedStream::Metadata::GetConsistencyToken() const {
+const std::string& FeedStream::Metadata::GetConsistencyToken() const {
return metadata_.consistency_token();
}
@@ -122,6 +123,36 @@ void FeedStream::Metadata::SetConsistencyToken(std::string consistency_token) {
store_->WriteMetadata(metadata_, base::DoNothing());
}
+const std::string& FeedStream::Metadata::GetSessionIdToken() const {
+ return metadata_.session_id().token();
+}
+
+base::Time FeedStream::Metadata::GetSessionIdExpiryTime() const {
+ return base::Time::FromDeltaSinceWindowsEpoch(
+ base::TimeDelta::FromMilliseconds(
+ metadata_.session_id().expiry_time_ms()));
+}
+
+void FeedStream::Metadata::SetSessionId(std::string token,
+ base::Time expiry_time) {
+ feedstore::Metadata::SessionID* session_id = metadata_.mutable_session_id();
+ session_id->set_token(std::move(token));
+ session_id->set_expiry_time_ms(
+ expiry_time.ToDeltaSinceWindowsEpoch().InMilliseconds());
+ store_->WriteMetadata(metadata_, base::DoNothing());
+}
+
+void FeedStream::Metadata::MaybeUpdateSessionId(
+ base::Optional<std::string> token,
+ const base::Clock* clock) {
+ if (token && metadata_.session_id().token() != *token) {
+ base::Time expiry_time =
+ token->empty() ? base::Time()
+ : clock->Now() + GetFeedConfig().session_id_max_age;
+ SetSessionId(*token, expiry_time);
+ }
+}
+
LocalActionId FeedStream::Metadata::GetNextActionId() {
uint32_t id = metadata_.next_action_id();
// Never use 0, as that's an invalid LocalActionId.
@@ -157,7 +188,8 @@ FeedStream::FeedStream(RefreshTaskScheduler* refresh_task_scheduler,
chrome_info_(chrome_info),
task_queue_(this),
request_throttler_(profile_prefs, clock),
- metadata_(feed_store) {
+ metadata_(feed_store),
+ notice_card_tracker_(profile_prefs) {
static WireResponseTranslator default_translator;
wire_response_translator_ = &default_translator;
@@ -238,13 +270,32 @@ bool FeedStream::IsActivityLoggingEnabled() const {
void FeedStream::UpdateIsActivityLoggingEnabled() {
is_activity_logging_enabled_ =
- model_ && model_->signed_in() && model_->logging_enabled();
+ model_ &&
+ ((model_->signed_in() && model_->logging_enabled()) ||
+ (!model_->signed_in() && GetFeedConfig().send_signed_out_session_logs));
+}
+
+std::string FeedStream::GetSessionId() const {
+ return GetMetadata()->GetSessionIdToken();
+}
+
+void FeedStream::PrefetchImage(const GURL& url) {
+ delegate_->PrefetchImage(url);
}
void FeedStream::AttachSurface(SurfaceInterface* surface) {
metrics_reporter_->SurfaceOpened(surface->GetSurfaceId());
+
+ // Skip normal processing when overriding stream data from the internals page.
+ if (forced_stream_update_for_debugging_.updated_slices_size() > 0) {
+ surface_updater_->SurfaceAdded(surface);
+ surface->StreamUpdate(forced_stream_update_for_debugging_);
+ return;
+ }
+
TriggerStreamLoad();
surface_updater_->SurfaceAdded(surface);
+
// Cancel any scheduled model unload task.
++unload_on_detach_sequence_number_;
UpdateCanUploadActionsWithNoticeCard();
@@ -292,7 +343,7 @@ bool FeedStream::IsArticlesListVisible() {
return profile_prefs_->GetBoolean(prefs::kArticlesListVisible);
}
-std::string FeedStream::GetClientInstanceId() {
+std::string FeedStream::GetClientInstanceId() const {
return prefs::GetClientInstanceId(*profile_prefs_);
}
@@ -455,6 +506,11 @@ std::string FeedStream::DumpStateForDebugging() {
return ss.str();
}
+void FeedStream::SetForcedStreamUpdateForDebugging(
+ const feedui::StreamUpdate& stream_update) {
+ forced_stream_update_for_debugging_ = stream_update;
+}
+
base::Time FeedStream::GetLastFetchTime() {
const base::Time fetch_time =
profile_prefs_->GetTime(feed::prefs::kLastFetchAttemptTime);
@@ -556,12 +612,38 @@ bool FeedStream::ShouldForceSignedOutFeedQueryRequest() const {
return base::TimeTicks::Now() < signed_out_refreshes_until_;
}
-RequestMetadata FeedStream::GetRequestMetadata() {
+RequestMetadata FeedStream::GetRequestMetadata(bool is_for_next_page) const {
RequestMetadata result;
result.chrome_info = chrome_info_;
result.display_metrics = delegate_->GetDisplayMetrics();
result.language_tag = delegate_->GetLanguageTag();
- result.client_instance_id = GetClientInstanceId();
+ result.notice_card_acknowledged =
+ notice_card_tracker_.HasAcknowledgedNoticeCard();
+
+ if (is_for_next_page) {
+ // If we are continuing an existing feed, use whatever session continuity
+ // mechanism is currently associated with the stream: client-instance-id
+ // for signed-in feed, session_id token for signed-out.
+ DCHECK(model_);
+ if (model_->signed_in()) {
+ result.client_instance_id = GetClientInstanceId();
+ } else {
+ result.session_id = GetMetadata()->GetSessionIdToken();
+ }
+ } else {
+ // The request is for the first page of the feed. Use client_instance_id
+ // for signed in requests and session_id token (if any, and not expired)
+ // for signed-out.
+ if (delegate_->IsSignedIn() && !ShouldForceSignedOutFeedQueryRequest()) {
+ result.client_instance_id = GetClientInstanceId();
+ } else if (!GetMetadata()->GetSessionIdToken().empty() &&
+ GetMetadata()->GetSessionIdExpiryTime() > clock_->Now()) {
+ result.session_id = GetMetadata()->GetSessionIdToken();
+ }
+ }
+
+ DCHECK(result.session_id.empty() || result.client_instance_id.empty());
+
return result;
}
@@ -626,6 +708,10 @@ void FeedStream::BackgroundRefreshComplete(LoadStreamTask::Result result) {
if (result.loaded_new_content_from_network && prefetch_service_)
prefetch_service_->NewSuggestionsAvailable();
+ // Add prefetch images to task queue without waiting to finish
+ // since we treat them as best-effort.
+ task_queue_.AddTask(std::make_unique<PrefetchImagesTask>(this));
+
refresh_task_scheduler_->RefreshTaskComplete();
}
@@ -693,19 +779,22 @@ void FeedStream::UnloadModel() {
surface_updater_->SetModel(nullptr);
model_.reset();
}
-
void FeedStream::ReportOpenAction(const std::string& slice_id) {
int index = surface_updater_->GetSliceIndexFromSliceId(slice_id);
- if (index >= 0)
- metrics_reporter_->OpenAction(index);
+ if (index < 0)
+ index = MetricsReporter::kUnknownCardIndex;
+ metrics_reporter_->OpenAction(index);
+ notice_card_tracker_.OnOpenAction(index);
}
void FeedStream::ReportOpenVisitComplete(base::TimeDelta visit_time) {
metrics_reporter_->OpenVisitComplete(visit_time);
}
void FeedStream::ReportOpenInNewTabAction(const std::string& slice_id) {
int index = surface_updater_->GetSliceIndexFromSliceId(slice_id);
- if (index >= 0)
- metrics_reporter_->OpenInNewTabAction(index);
+ if (index < 0)
+ index = MetricsReporter::kUnknownCardIndex;
+ metrics_reporter_->OpenInNewTabAction(index);
+ notice_card_tracker_.OnOpenAction(index);
}
void FeedStream::ReportOpenInNewIncognitoTabAction() {
metrics_reporter_->OpenInNewIncognitoTabAction();
@@ -715,9 +804,11 @@ void FeedStream::ReportSliceViewed(SurfaceId surface_id,
int index = surface_updater_->GetSliceIndexFromSliceId(slice_id);
if (index >= 0) {
UpdateShownSlicesUploadCondition(index);
+ notice_card_tracker_.OnSliceViewed(index);
metrics_reporter_->ContentSliceViewed(surface_id, index);
}
}
+// TODO(crbug/1147237): Rename this method and related members?
bool FeedStream::CanUploadActions() const {
return can_upload_actions_with_notice_card_ ||
!prefs::GetLastFetchHadNoticeCard(*profile_prefs_);
@@ -746,11 +837,16 @@ void FeedStream::DeclareHasReachedConditionsToUploadActionsWithNoticeCard() {
void FeedStream::UpdateShownSlicesUploadCondition(int viewed_slice_index) {
constexpr int kShownSlicesThreshold = 2;
+ DCHECK(model_) << "Model was unloaded while handling a viewed slice";
+
// Don't take shown slices into consideration when the upload conditions has
// already been reached.
if (HasReachedConditionsToUploadActionsWithNoticeCard())
return;
+ if (!model_->signed_in())
+ return;
+
if (viewed_slice_index + 1 >= kShownSlicesThreshold)
DeclareHasReachedConditionsToUploadActionsWithNoticeCard();
}
diff --git a/chromium/components/feed/core/v2/feed_stream.h b/chromium/components/feed/core/v2/feed_stream.h
index 0b3082a4211..7b7fd04e6e7 100644
--- a/chromium/components/feed/core/v2/feed_stream.h
+++ b/chromium/components/feed/core/v2/feed_stream.h
@@ -16,8 +16,10 @@
#include "base/version.h"
#include "components/feed/core/common/enums.h"
#include "components/feed/core/common/user_classifier.h"
+#include "components/feed/core/proto/v2/ui.pb.h"
#include "components/feed/core/proto/v2/wire/response.pb.h"
#include "components/feed/core/v2/enums.h"
+#include "components/feed/core/v2/notice_card_tracker.h"
#include "components/feed/core/v2/protocol_translator.h"
#include "components/feed/core/v2/public/feed_stream_api.h"
#include "components/feed/core/v2/request_throttler.h"
@@ -68,6 +70,7 @@ class FeedStream : public FeedStreamApi,
virtual std::string GetLanguageTag() = 0;
virtual void ClearAll() = 0;
virtual bool IsSignedIn() = 0;
+ virtual void PrefetchImage(const GURL& url) = 0;
};
// Forwards to |feed::TranslateWireResponse()| by default. Can be overridden
@@ -90,9 +93,15 @@ class FeedStream : public FeedStreamApi,
void Populate(feedstore::Metadata metadata);
- std::string GetConsistencyToken() const;
+ const std::string& GetConsistencyToken() const;
void SetConsistencyToken(std::string consistency_token);
+ const std::string& GetSessionIdToken() const;
+ base::Time GetSessionIdExpiryTime() const;
+ void SetSessionId(std::string token, base::Time expiry_time);
+ void MaybeUpdateSessionId(base::Optional<std::string> token,
+ const base::Clock* clock);
+
LocalActionId GetNextActionId();
private:
@@ -123,11 +132,12 @@ class FeedStream : public FeedStreamApi,
// FeedStreamApi.
bool IsActivityLoggingEnabled() const override;
+ std::string GetSessionId() const override;
void AttachSurface(SurfaceInterface*) override;
void DetachSurface(SurfaceInterface*) override;
void SetArticlesListVisible(bool is_visible) override;
bool IsArticlesListVisible() override;
- std::string GetClientInstanceId() override;
+ std::string GetClientInstanceId() const override;
void ExecuteRefreshTask() override;
ImageFetchId FetchImage(
const GURL& url,
@@ -148,6 +158,8 @@ class FeedStream : public FeedStreamApi,
DebugStreamData GetDebugStreamData() override;
void ForceRefreshForDebugging() override;
std::string DumpStateForDebugging() override;
+ void SetForcedStreamUpdateForDebugging(
+ const feedui::StreamUpdate& stream_update) override;
void ReportSliceViewed(SurfaceId surface_id,
const std::string& slice_id) override;
@@ -211,8 +223,11 @@ class FeedStream : public FeedStreamApi,
FeedStore* GetStore() { return store_; }
RequestThrottler* GetRequestThrottler() { return &request_throttler_; }
Metadata* GetMetadata() { return &metadata_; }
+ const Metadata* GetMetadata() const { return &metadata_; }
MetricsReporter* GetMetricsReporter() const { return metrics_reporter_; }
+ void PrefetchImage(const GURL& url);
+
// Returns the time of the last content fetch.
base::Time GetLastFetchTime();
@@ -252,7 +267,7 @@ class FeedStream : public FeedStreamApi,
const base::Clock* GetClock() const { return clock_; }
const base::TickClock* GetTickClock() const { return tick_clock_; }
- RequestMetadata GetRequestMetadata();
+ RequestMetadata GetRequestMetadata(bool is_for_next_page) const;
const WireResponseTranslator* GetWireResponseTranslator() const {
return wire_response_translator_;
@@ -354,6 +369,12 @@ class FeedStream : public FeedStreamApi,
// To allow tests to wait on task queue idle.
base::RepeatingClosure idle_callback_;
+ // Stream update forced to use for new surfaces. This is provided in feed
+ // internals page for debugging purpose.
+ feedui::StreamUpdate forced_stream_update_for_debugging_;
+
+ NoticeCardTracker notice_card_tracker_;
+
base::WeakPtrFactory<FeedStream> weak_ptr_factory_{this};
};
diff --git a/chromium/components/feed/core/v2/feed_stream_unittest.cc b/chromium/components/feed/core/v2/feed_stream_unittest.cc
index ffbb96cc550..43831c0aa58 100644
--- a/chromium/components/feed/core/v2/feed_stream_unittest.cc
+++ b/chromium/components/feed/core/v2/feed_stream_unittest.cc
@@ -18,7 +18,7 @@
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
-#include "base/test/bind_test_util.h"
+#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/scoped_run_loop_timeout.h"
@@ -29,6 +29,7 @@
#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/chrome_client_info.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/proto/v2/xsurface.pb.h"
@@ -303,6 +304,7 @@ class TestFeedNetwork : public FeedNetwork {
result.response_info.status_code = 200;
result.response_info.response_body_bytes = 100;
result.response_info.fetch_duration = base::TimeDelta::FromMilliseconds(42);
+ result.response_info.was_signed_in = true;
if (injected_response_) {
result.response_body = std::make_unique<feedwire::Response>(
std::move(injected_response_.value()));
@@ -387,9 +389,12 @@ class TestWireResponseTranslator : public FeedStream::WireResponseTranslator {
return FeedStream::WireResponseTranslator::TranslateWireResponse(
std::move(response), source, was_signed_in_request, current_time);
}
- void InjectResponse(std::unique_ptr<StreamModelUpdateRequest> response) {
+ void InjectResponse(std::unique_ptr<StreamModelUpdateRequest> response,
+ base::Optional<std::string> session_id = base::nullopt) {
+ DCHECK(!response->stream_data.signed_in() || !session_id);
RefreshResponseData data;
data.model_update_request = std::move(response);
+ data.session_id = std::move(session_id);
InjectResponse(std::move(data));
}
void InjectResponse(RefreshResponseData response_data) {
@@ -573,10 +578,7 @@ class FeedStreamTest : public testing::Test, public FeedStream::Delegate {
CreateStream();
}
- virtual void SetupFeatures() {
- scoped_feature_list_.InitAndDisableFeature(
- feed::kInterestFeedV2ClicksAndViewsConditionalUpload);
- }
+ virtual void SetupFeatures() {}
void TearDown() override {
// Ensure the task queue can return to idle. Failure to do so may be due
@@ -600,6 +602,10 @@ class FeedStreamTest : public testing::Test, public FeedStream::Delegate {
std::string GetLanguageTag() override { return "en-US"; }
void ClearAll() override {}
bool IsSignedIn() override { return is_signed_in_; }
+ void PrefetchImage(const GURL& url) override {
+ prefetched_images_.push_back(url);
+ prefetch_image_call_count_++;
+ }
// For tests.
@@ -690,6 +696,8 @@ class FeedStreamTest : public testing::Test, public FeedStream::Delegate {
bool is_offline_ = false;
bool is_signed_in_ = true;
base::test::ScopedFeatureList scoped_feature_list_;
+ int prefetch_image_call_count_ = 0;
+ std::vector<GURL> prefetched_images_;
};
class FeedStreamConditionalActionsUploadTest : public FeedStreamTest {
@@ -742,6 +750,21 @@ TEST_F(FeedStreamTest, BackgroundRefreshSuccess) {
EXPECT_EQ(1, prefetch_service_.NewSuggestionsAvailableCallCount());
}
+TEST_F(FeedStreamTest, BackgroundRefreshPrefetchesImages) {
+ // Trigger a background refresh.
+ response_translator_.InjectResponse(MakeTypicalInitialModelState());
+ stream_->ExecuteRefreshTask();
+ EXPECT_EQ(0, prefetch_image_call_count_);
+ WaitForIdleTaskQueue();
+
+ std::vector<GURL> expected_fetches(
+ {GURL("http://image0/"), GURL("http://favicon0/"), GURL("http://image1/"),
+ GURL("http://favicon1/")});
+ // Verify that images were prefetched.
+ EXPECT_EQ(4, prefetch_image_call_count_);
+ EXPECT_EQ(expected_fetches, prefetched_images_);
+}
+
TEST_F(FeedStreamTest, BackgroundRefreshNotAttemptedWhenModelIsLoading) {
response_translator_.InjectResponse(MakeTypicalInitialModelState());
TestSurface surface(stream_.get());
@@ -1089,14 +1112,78 @@ TEST_F(FeedStreamTest, LoadStreamAfterEulaIsAccepted) {
TEST_F(FeedStreamTest, ForceSignedOutRequestAfterHistoryIsDeleted) {
stream_->OnAllHistoryDeleted();
+
+ const std::string kSessionId = "session-id";
+
+ // This test injects response post translation/parsing. We have to configure
+ // the response data that should come out of the translator, which should
+ // mark the request/response as having been made from the signed-out state.
+ StreamModelUpdateRequestGenerator model_generator;
+ model_generator.signed_in = false;
+
+ // Advance the clock, but not past the end of the forced-signed-out period.
task_environment_.FastForwardBy(kSuppressRefreshDuration -
base::TimeDelta::FromSeconds(1));
- response_translator_.InjectResponse(MakeTypicalInitialModelState());
+
+ // Refresh the feed, queuing up a signed-out response.
+ response_translator_.InjectResponse(model_generator.MakeFirstPage(),
+ kSessionId);
TestSurface surface(stream_.get());
WaitForIdleTaskQueue();
+ // Validate that the network request was sent as signed out.
+ ASSERT_EQ(1, network_.send_query_call_count);
+ EXPECT_TRUE(network_.forced_signed_out_request);
+ EXPECT_TRUE(network_.query_request_sent->feed_request()
+ .client_info()
+ .chrome_client_info()
+ .session_id()
+ .empty());
+
+ // Validate the downstream consumption of the response.
EXPECT_EQ("loading -> 2 slices", surface.DescribeUpdates());
+ EXPECT_EQ(kSessionId, stream_->GetMetadata()->GetSessionIdToken());
+ EXPECT_FALSE(stream_->GetModel()->signed_in());
+
+ // Advance the clock beyond the forced signed out period.
+ task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(2));
+ EXPECT_FALSE(stream_->GetModel()->signed_in());
+
+ // Requests for subsequent pages continue the use existing session.
+ // Subsequent responses may omit the session id.
+ response_translator_.InjectResponse(model_generator.MakeNextPage());
+ stream_->LoadMore(surface.GetSurfaceId(), base::DoNothing());
+ WaitForIdleTaskQueue();
+
+ // Validate that the network request was sent as signed out and
+ // contained the session id.
+ ASSERT_EQ(2, network_.send_query_call_count);
EXPECT_TRUE(network_.forced_signed_out_request);
+ EXPECT_EQ(kSessionId, stream_->GetMetadata()->GetSessionIdToken());
+ EXPECT_EQ(network_.query_request_sent->feed_request()
+ .client_info()
+ .chrome_client_info()
+ .session_id(),
+ kSessionId);
+
+ // The model should still be in the signed-out state.
+ EXPECT_FALSE(stream_->GetModel()->signed_in());
+
+ // Force a refresh of the feed by clearing the cache. The request for the
+ // first page should revert back to signed-in. The response data will denote
+ // a signed-in response with no session_id.
+ model_generator.signed_in = true;
+ response_translator_.InjectResponse(model_generator.MakeFirstPage());
+ stream_->OnCacheDataCleared();
+ WaitForIdleTaskQueue();
+
+ // Validate that a signed-in request was sent.
+ ASSERT_EQ(3, network_.send_query_call_count);
+ EXPECT_FALSE(network_.forced_signed_out_request);
+
+ // The model should now be in the signed-in state.
+ EXPECT_TRUE(stream_->GetModel()->signed_in());
+ EXPECT_TRUE(stream_->GetMetadata()->GetSessionIdToken().empty());
}
TEST_F(FeedStreamTest, AllowSignedInRequestAfterHistoryIsDeletedAfterDelay) {
@@ -1109,6 +1196,7 @@ TEST_F(FeedStreamTest, AllowSignedInRequestAfterHistoryIsDeletedAfterDelay) {
EXPECT_EQ("loading -> 2 slices", surface.DescribeUpdates());
EXPECT_FALSE(network_.forced_signed_out_request);
+ EXPECT_TRUE(stream_->GetMetadata()->GetSessionIdToken().empty());
}
TEST_F(FeedStreamTest, ShouldMakeFeedQueryRequestConsumesQuota) {
@@ -1809,6 +1897,29 @@ TEST_F(FeedStreamTest, LoadStreamUpdateNoticeCardFulfillmentHistogram) {
1, 1);
}
+TEST_F(FeedStreamConditionalActionsUploadTest,
+ DontTriggerActionsUploadWhenWasNotSignedIn) {
+ auto update_request = MakeTypicalInitialModelState();
+ update_request->stream_data.set_signed_in(false);
+ response_translator_.InjectResponse(std::move(update_request));
+ TestSurface surface(stream_.get());
+ WaitForIdleTaskQueue();
+
+ // Try to reach conditions.
+ stream_->ReportSliceViewed(
+ surface.GetSurfaceId(),
+ surface.initial_state->updated_slices(1).slice().slice_id());
+
+ // Try to trigger an upload through a query.
+ stream_->OnEnterBackground();
+ WaitForIdleTaskQueue();
+
+ // Verify that even if the conditions were reached, the pref that enables the
+ // upload wasn't set to true because the latest refresh request wasn't signed
+ // in.
+ ASSERT_FALSE(stream_->CanUploadActions());
+}
+
TEST_F(FeedStreamTest, LoadStreamFromNetworkUploadsActions) {
stream_->UploadAction(MakeFeedAction(99ul), false, base::DoNothing());
WaitForIdleTaskQueue();
@@ -1893,7 +2004,14 @@ TEST_F(FeedStreamTest, LoadMoreUpdatesIsActivityLoggingEnabled) {
CallbackReceiver<bool> callback;
stream_->LoadMore(surface.GetSurfaceId(), callback.Bind());
WaitForIdleTaskQueue();
- EXPECT_EQ(stream_->IsActivityLoggingEnabled(), signed_in && waa_on);
+ EXPECT_EQ(
+ stream_->IsActivityLoggingEnabled(),
+ (signed_in && waa_on) ||
+ (!signed_in && GetFeedConfig().send_signed_out_session_logs))
+ << "signed_in=" << signed_in << " waa_on=" << waa_on
+ << " privacy_notice_fulfilled=" << privacy_notice_fulfilled
+ << " send_signed_out_session_logs="
+ << GetFeedConfig().send_signed_out_session_logs;
}
}
}
@@ -1911,7 +2029,6 @@ TEST_F(FeedStreamConditionalActionsUploadTest,
response_translator_.InjectResponse(MakeTypicalNextPageState(
/* first_cluster_id= */ 0,
/* last_added_time= */ kTestTimeEpoch,
- /* signed_in= */ true,
/* logging_enabled= */ true,
/* privacy_notice_fulfilled= */ false));
@@ -2055,7 +2172,8 @@ TEST_F(FeedStreamTest, UploadActionsErasesStaleActionsByAttempts) {
TEST_F(FeedStreamTest, MetadataLoadedWhenDatabaseInitialized) {
ASSERT_TRUE(stream_->GetMetadata());
- // Set the token and increment next action ID.
+ const auto kExpiry = kTestTimeEpoch + base::TimeDelta::FromDays(1234);
+ stream_->GetMetadata()->SetSessionId("session-id", kExpiry);
stream_->GetMetadata()->SetConsistencyToken("token");
EXPECT_EQ(1, stream_->GetMetadata()->GetNextActionId().GetUnsafeValue());
@@ -2063,6 +2181,8 @@ TEST_F(FeedStreamTest, MetadataLoadedWhenDatabaseInitialized) {
CreateStream();
ASSERT_TRUE(stream_->GetMetadata());
+ EXPECT_EQ("session-id", stream_->GetMetadata()->GetSessionIdToken());
+ EXPECT_EQ(kExpiry, stream_->GetMetadata()->GetSessionIdExpiryTime());
EXPECT_EQ("token", stream_->GetMetadata()->GetConsistencyToken());
EXPECT_EQ(2, stream_->GetMetadata()->GetNextActionId().GetUnsafeValue());
}
@@ -2335,6 +2455,13 @@ TEST_F(FeedStreamTest, SendsClientInstanceId) {
.client_instance_id();
EXPECT_NE("", first_instance_id);
+ // No signed-out session id was in the request.
+ EXPECT_TRUE(network_.query_request_sent->feed_request()
+ .client_info()
+ .chrome_client_info()
+ .session_id()
+ .empty());
+
// LoadMore, and verify the same token is used.
response_translator_.InjectResponse(MakeTypicalNextPageState(2));
stream_->LoadMore(surface.GetSurfaceId(), base::DoNothing());
@@ -2345,15 +2472,311 @@ TEST_F(FeedStreamTest, SendsClientInstanceId) {
.client_info()
.client_instance_id());
+ // No signed-out session id was in the request.
+ EXPECT_TRUE(network_.query_request_sent->feed_request()
+ .client_info()
+ .chrome_client_info()
+ .session_id()
+ .empty());
+
// Trigger a ClearAll to verify the instance ID changes.
stream_->OnSignedOut();
WaitForIdleTaskQueue();
+ EXPECT_FALSE(stream_->GetModel());
+ const bool is_for_next_page = false; // No model so no first page yet.
const std::string new_instance_id =
- stream_->GetRequestMetadata().client_instance_id;
+ stream_->GetRequestMetadata(is_for_next_page).client_instance_id;
ASSERT_NE("", new_instance_id);
ASSERT_NE(first_instance_id, new_instance_id);
}
+TEST_F(FeedStreamTest, LoadStreamSendsNoticeCardAcknowledgement) {
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeature(
+ feed::kInterestFeedNoticeCardAutoDismiss);
+
+ store_->OverwriteStream(MakeTypicalInitialModelState(), base::DoNothing());
+ TestSurface surface(stream_.get());
+ WaitForIdleTaskQueue();
+
+ // Generate 3 view actions and 1 click action to trigger the acknowledgement
+ // of the notice card.
+ const int notice_card_index = 0;
+ std::string slice_id =
+ surface.initial_state->updated_slices(notice_card_index)
+ .slice()
+ .slice_id();
+ stream_->ReportSliceViewed(surface.GetSurfaceId(), slice_id);
+ stream_->ReportSliceViewed(surface.GetSurfaceId(), slice_id);
+ stream_->ReportSliceViewed(surface.GetSurfaceId(), slice_id);
+ stream_->ReportOpenAction(slice_id);
+
+ response_translator_.InjectResponse(MakeTypicalInitialModelState());
+ refresh_scheduler_.Clear();
+ stream_->UnloadModel();
+ stream_->ExecuteRefreshTask();
+ WaitForIdleTaskQueue();
+
+ EXPECT_TRUE(network_.query_request_sent->feed_request()
+ .feed_query()
+ .chrome_fulfillment_info()
+ .notice_card_acknowledged());
+}
+
+TEST_F(FeedStreamTest, GetSetAndUpdateSessionId) {
+ const std::string kToken1 = "token1";
+ const std::string kToken2 = "token2";
+ const base::Time kExpiryTime1 =
+ kTestTimeEpoch + base::TimeDelta::FromHours(2);
+ const base::Time kExpiryTime2 =
+ kTestTimeEpoch + GetFeedConfig().session_id_max_age;
+ ASSERT_NE(kExpiryTime1, kExpiryTime2);
+
+ // The stream metadata is initialized with an empty token and expiry time.
+ EXPECT_TRUE(stream_->GetMetadata()->GetSessionIdToken().empty());
+ EXPECT_TRUE(stream_->GetMetadata()->GetSessionIdExpiryTime().is_null());
+
+ // Verify that directly calling SetSessionId works as expected.
+ stream_->GetMetadata()->SetSessionId(kToken1, kExpiryTime1);
+ EXPECT_EQ(kToken1, stream_->GetMetadata()->GetSessionIdToken());
+ EXPECT_EQ(kExpiryTime1, stream_->GetMetadata()->GetSessionIdExpiryTime());
+
+ // Updating the token with nullopt is a NOP.
+ stream_->GetMetadata()->MaybeUpdateSessionId(base::nullopt,
+ stream_->GetClock());
+ EXPECT_EQ(kToken1, stream_->GetMetadata()->GetSessionIdToken());
+ EXPECT_EQ(kExpiryTime1, stream_->GetMetadata()->GetSessionIdExpiryTime());
+
+ // Updating the token with the same value is a NOP.
+ stream_->GetMetadata()->MaybeUpdateSessionId(kToken1, stream_->GetClock());
+ EXPECT_EQ(kToken1, stream_->GetMetadata()->GetSessionIdToken());
+ EXPECT_EQ(kExpiryTime1, stream_->GetMetadata()->GetSessionIdExpiryTime());
+
+ // Updating the token with a different value resets the token and assigns a
+ // new expiry time.
+ stream_->GetMetadata()->MaybeUpdateSessionId(kToken2, stream_->GetClock());
+ EXPECT_EQ(kToken2, stream_->GetMetadata()->GetSessionIdToken());
+ EXPECT_EQ(kExpiryTime2, stream_->GetMetadata()->GetSessionIdExpiryTime());
+
+ // Updating the token with the empty string clears its value.
+ stream_->GetMetadata()->MaybeUpdateSessionId("", stream_->GetClock());
+ EXPECT_TRUE(stream_->GetMetadata()->GetSessionIdToken().empty());
+ EXPECT_TRUE(stream_->GetMetadata()->GetSessionIdExpiryTime().is_null());
+}
+
+TEST_F(FeedStreamTest, SignedOutSessionIdConsistency) {
+ const std::string kSessionToken1("session-token-1");
+ const std::string kSessionToken2("session-token-2");
+
+ is_signed_in_ = false;
+
+ StreamModelUpdateRequestGenerator model_generator;
+ model_generator.signed_in = false;
+
+ // (1) Do an initial load of the store
+ // - this should trigger a network request
+ // - the request should not include client-instance-id
+ // - the request should not include a session-id
+ // - the stream should capture the session-id token from the response
+ response_translator_.InjectResponse(model_generator.MakeFirstPage(),
+ kSessionToken1);
+ TestSurface surface(stream_.get());
+ WaitForIdleTaskQueue();
+ ASSERT_EQ(1, network_.send_query_call_count);
+ EXPECT_TRUE(network_.query_request_sent->feed_request()
+ .client_info()
+ .client_instance_id()
+ .empty());
+ EXPECT_FALSE(network_.query_request_sent->feed_request()
+ .client_info()
+ .has_chrome_client_info());
+ EXPECT_EQ(kSessionToken1, stream_->GetMetadata()->GetSessionIdToken());
+ const base::Time kSessionToken1ExpiryTime =
+ stream_->GetMetadata()->GetSessionIdExpiryTime();
+
+ // (2) LoadMore: the server returns the same session-id token
+ // - this should trigger a network request
+ // - the request should not include client-instance-id
+ // - the request should include the first session-id
+ // - the stream should retain the first session-id
+ // - the session-id's expiry time should be unchanged
+ task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+ response_translator_.InjectResponse(model_generator.MakeNextPage(2),
+ kSessionToken1);
+ stream_->LoadMore(surface.GetSurfaceId(), base::DoNothing());
+ WaitForIdleTaskQueue();
+ ASSERT_EQ(2, network_.send_query_call_count);
+ EXPECT_TRUE(network_.query_request_sent->feed_request()
+ .client_info()
+ .client_instance_id()
+ .empty());
+ EXPECT_EQ(kSessionToken1, network_.query_request_sent->feed_request()
+ .client_info()
+ .chrome_client_info()
+ .session_id());
+ EXPECT_EQ(kSessionToken1, stream_->GetMetadata()->GetSessionIdToken());
+ EXPECT_EQ(kSessionToken1ExpiryTime,
+ stream_->GetMetadata()->GetSessionIdExpiryTime());
+
+ // (3) LoadMore: the server omits returning a session-id token
+ // - this should trigger a network request
+ // - the request should not include client-instance-id
+ // - the request should include the first session-id
+ // - the stream should retain the first session-id
+ // - the session-id's expiry time should be unchanged
+ task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+ response_translator_.InjectResponse(model_generator.MakeNextPage(3));
+ stream_->LoadMore(surface.GetSurfaceId(), base::DoNothing());
+ WaitForIdleTaskQueue();
+ ASSERT_EQ(3, network_.send_query_call_count);
+ EXPECT_TRUE(network_.query_request_sent->feed_request()
+ .client_info()
+ .client_instance_id()
+ .empty());
+ EXPECT_EQ(kSessionToken1, network_.query_request_sent->feed_request()
+ .client_info()
+ .chrome_client_info()
+ .session_id());
+ EXPECT_EQ(kSessionToken1, stream_->GetMetadata()->GetSessionIdToken());
+ EXPECT_EQ(kSessionToken1ExpiryTime,
+ stream_->GetMetadata()->GetSessionIdExpiryTime());
+
+ // (4) LoadMore: the server returns new session id.
+ // - this should trigger a network request
+ // - the request should not include client-instance-id
+ // - the request should include the first session-id
+ // - the stream should retain the second session-id
+ // - the new session-id's expiry time should be updated
+ task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
+ response_translator_.InjectResponse(model_generator.MakeNextPage(4),
+ kSessionToken2);
+ stream_->LoadMore(surface.GetSurfaceId(), base::DoNothing());
+ WaitForIdleTaskQueue();
+ ASSERT_EQ(4, network_.send_query_call_count);
+ EXPECT_TRUE(network_.query_request_sent->feed_request()
+ .client_info()
+ .client_instance_id()
+ .empty());
+ EXPECT_EQ(kSessionToken1, network_.query_request_sent->feed_request()
+ .client_info()
+ .chrome_client_info()
+ .session_id());
+ EXPECT_EQ(kSessionToken2, stream_->GetMetadata()->GetSessionIdToken());
+ EXPECT_EQ(kSessionToken1ExpiryTime + base::TimeDelta::FromSeconds(3),
+ stream_->GetMetadata()->GetSessionIdExpiryTime());
+}
+
+TEST_F(FeedStreamTest, ClearAllResetsSessionId) {
+ is_signed_in_ = false;
+
+ // Initialize a session id.
+ stream_->GetMetadata()->MaybeUpdateSessionId("session-id",
+ stream_->GetClock());
+ ASSERT_FALSE(stream_->GetMetadata()->GetSessionIdToken().empty());
+ ASSERT_FALSE(stream_->GetMetadata()->GetSessionIdExpiryTime().is_null());
+
+ // Trigger a ClearAll.
+ stream_->OnCacheDataCleared();
+ WaitForIdleTaskQueue();
+
+ // Session-ID should be wiped.
+ EXPECT_TRUE(stream_->GetMetadata()->GetSessionIdToken().empty());
+ EXPECT_TRUE(stream_->GetMetadata()->GetSessionIdExpiryTime().is_null());
+}
+
+TEST_F(FeedStreamTest, SignedOutSessionIdExpiry) {
+ const std::string kSessionToken1("session-token-1");
+ const std::string kSessionToken2("session-token-2");
+
+ is_signed_in_ = false;
+
+ StreamModelUpdateRequestGenerator model_generator;
+ model_generator.signed_in = false;
+
+ // (1) Do an initial load of the store
+ // - this should trigger a network request
+ // - the request should not include a session-id
+ // - the stream should capture the session-id token from the response
+ response_translator_.InjectResponse(model_generator.MakeFirstPage(),
+ kSessionToken1);
+ TestSurface surface(stream_.get());
+ WaitForIdleTaskQueue();
+ ASSERT_EQ(1, network_.send_query_call_count);
+ EXPECT_FALSE(network_.query_request_sent->feed_request()
+ .client_info()
+ .has_chrome_client_info());
+ EXPECT_EQ(kSessionToken1, stream_->GetMetadata()->GetSessionIdToken());
+
+ // (2) Reload the stream from the network:
+ // - Detach the surface, advance the clock beyond the stale content
+ // threshold, re-attach the surface.
+ // - this should trigger a network request
+ // - the request should include kSessionToken1
+ // - the stream should retain the original session-id
+ surface.Detach();
+ task_environment_.FastForwardBy(GetFeedConfig().stale_content_threshold +
+ base::TimeDelta::FromSeconds(1));
+ response_translator_.InjectResponse(model_generator.MakeFirstPage());
+ surface.Attach(stream_.get());
+ WaitForIdleTaskQueue();
+ ASSERT_EQ(2, network_.send_query_call_count);
+ EXPECT_EQ(kSessionToken1, network_.query_request_sent->feed_request()
+ .client_info()
+ .chrome_client_info()
+ .session_id());
+ EXPECT_EQ(kSessionToken1, stream_->GetMetadata()->GetSessionIdToken());
+
+ // (3) Reload the stream from the network:
+ // - Detach the surface, advance the clock beyond the session id max age
+ // threshold, re-attach the surface.
+ // - this should trigger a network request
+ // - the request should not include a session-id
+ // - the stream should get a new session-id
+ surface.Detach();
+ task_environment_.FastForwardBy(GetFeedConfig().session_id_max_age -
+ GetFeedConfig().stale_content_threshold);
+ ASSERT_LT(stream_->GetMetadata()->GetSessionIdExpiryTime(),
+ task_environment_.GetMockClock()->Now());
+ response_translator_.InjectResponse(model_generator.MakeFirstPage(),
+ kSessionToken2);
+ surface.Attach(stream_.get());
+ WaitForIdleTaskQueue();
+ ASSERT_EQ(3, network_.send_query_call_count);
+ EXPECT_FALSE(network_.query_request_sent->feed_request()
+ .client_info()
+ .has_chrome_client_info());
+ EXPECT_EQ(kSessionToken2, stream_->GetMetadata()->GetSessionIdToken());
+}
+
+TEST_F(FeedStreamTest, SessionIdPersistsAcrossStreamLoads) {
+ const std::string kSessionToken("session-token-ftw");
+ const base::Time kExpiryTime =
+ kTestTimeEpoch + GetFeedConfig().session_id_max_age;
+
+ StreamModelUpdateRequestGenerator model_generator;
+ model_generator.signed_in = false;
+ is_signed_in_ = false;
+
+ // (1) Do an initial load of the store
+ // - this should trigger a network request
+ // - the stream should capture the session-id token from the response
+ response_translator_.InjectResponse(model_generator.MakeFirstPage(),
+ kSessionToken);
+ TestSurface surface(stream_.get());
+ WaitForIdleTaskQueue();
+ ASSERT_EQ(1, network_.send_query_call_count);
+
+ // (2) Reload the metadata from disk.
+ // - the network query call count should be unchanged
+ // - the session token and expiry time should have been reloaded.
+ surface.Detach();
+ CreateStream();
+ WaitForIdleTaskQueue();
+ ASSERT_EQ(1, network_.send_query_call_count);
+ EXPECT_EQ(kSessionToken, stream_->GetMetadata()->GetSessionIdToken());
+ EXPECT_EQ(kExpiryTime, stream_->GetMetadata()->GetSessionIdExpiryTime());
+}
+
} // namespace
} // namespace feed
diff --git a/chromium/components/feed/core/v2/image_fetcher_unittest.cc b/chromium/components/feed/core/v2/image_fetcher_unittest.cc
index ba2dbe4f548..185945c7d89 100644
--- a/chromium/components/feed/core/v2/image_fetcher_unittest.cc
+++ b/chromium/components/feed/core/v2/image_fetcher_unittest.cc
@@ -10,7 +10,7 @@
#include "base/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
-#include "base/test/bind_test_util.h"
+#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "components/feed/core/v2/public/types.h"
diff --git a/chromium/components/feed/core/v2/metrics_reporter.cc b/chromium/components/feed/core/v2/metrics_reporter.cc
index d23a99f17b9..df136e67a40 100644
--- a/chromium/components/feed/core/v2/metrics_reporter.cc
+++ b/chromium/components/feed/core/v2/metrics_reporter.cc
@@ -11,12 +11,13 @@
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
+#include "components/feed/core/v2/common_enums.h"
#include "components/feed/core/v2/prefs.h"
namespace feed {
namespace {
-using feed::internal::FeedEngagementType;
-using feed::internal::FeedUserActionType;
+using feed::FeedEngagementType;
+using feed::FeedUserActionType;
const int kMaxSuggestionsTotal = 50;
// Maximum time to wait before declaring a load operation failed.
// For both ContentSuggestions.Feed.UserJourney.OpenFeed
diff --git a/chromium/components/feed/core/v2/metrics_reporter.h b/chromium/components/feed/core/v2/metrics_reporter.h
index 31489456ead..21515c8697f 100644
--- a/chromium/components/feed/core/v2/metrics_reporter.h
+++ b/chromium/components/feed/core/v2/metrics_reporter.h
@@ -5,6 +5,7 @@
#ifndef COMPONENTS_FEED_CORE_V2_METRICS_REPORTER_H_
#define COMPONENTS_FEED_CORE_V2_METRICS_REPORTER_H_
+#include <climits>
#include <map>
#include "base/memory/weak_ptr.h"
@@ -17,50 +18,16 @@ namespace base {
class TickClock;
} // namespace base
namespace feed {
-namespace internal {
-// This enum is used for a UMA histogram. Keep in sync with FeedEngagementType
-// in enums.xml.
-enum class FeedEngagementType {
- kFeedEngaged = 0,
- kFeedEngagedSimple = 1,
- kFeedInteracted = 2,
- kDeprecatedFeedScrolled = 3,
- kFeedScrolled = 4,
- kMaxValue = FeedEngagementType::kFeedScrolled,
-};
-
-// This enum must match FeedUserActionType in enums.xml.
-// Note that most of these have a corresponding UserMetricsAction reported here.
-// Exceptions are described below.
-enum class FeedUserActionType {
- kTappedOnCard = 0,
- // This is not an actual user action, so there will be no UserMetricsAction
- // reported for this.
- kShownCard = 1,
- kTappedSendFeedback = 2,
- kTappedLearnMore = 3,
- kTappedHideStory = 4,
- kTappedNotInterestedIn = 5,
- kTappedManageInterests = 6,
- kTappedDownload = 7,
- kTappedOpenInNewTab = 8,
- kOpenedContextMenu = 9,
- // User action not reported here. See Suggestions.SurfaceVisible.
- kOpenedFeedSurface = 10,
- kTappedOpenInNewIncognitoTab = 11,
- kEphemeralChange = 12,
- kEphemeralChangeRejected = 13,
- kTappedTurnOn = 14,
- kTappedTurnOff = 15,
- kMaxValue = kTappedTurnOff,
-};
-
-} // namespace internal
// Reports UMA metrics for feed.
// Note this is inherited only for testing.
class MetricsReporter {
public:
+ // For 'index_in_stream' parameters, when the card index is unknown.
+ // This is most likely to happen when the action originates from the bottom
+ // sheet.
+ static const int kUnknownCardIndex = INT_MAX;
+
explicit MetricsReporter(const base::TickClock* clock,
PrefService* profile_prefs);
virtual ~MetricsReporter();
diff --git a/chromium/components/feed/core/v2/metrics_reporter_unittest.cc b/chromium/components/feed/core/v2/metrics_reporter_unittest.cc
index c38bdb43cad..e768420fa4c 100644
--- a/chromium/components/feed/core/v2/metrics_reporter_unittest.cc
+++ b/chromium/components/feed/core/v2/metrics_reporter_unittest.cc
@@ -12,12 +12,11 @@
#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/feed/core/v2/common_enums.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace feed {
-using feed::internal::FeedEngagementType;
-using feed::internal::FeedUserActionType;
constexpr SurfaceId kSurfaceId = SurfaceId(5);
const base::TimeDelta kEpsilon = base::TimeDelta::FromMilliseconds(1);
diff --git a/chromium/components/feed/core/v2/notice_card_tracker.cc b/chromium/components/feed/core/v2/notice_card_tracker.cc
new file mode 100644
index 00000000000..cb318c8678e
--- /dev/null
+++ b/chromium/components/feed/core/v2/notice_card_tracker.cc
@@ -0,0 +1,97 @@
+// 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.
+
+#include "components/feed/core/v2/notice_card_tracker.h"
+
+#include "components/feed/core/v2/prefs.h"
+#include "components/feed/feed_feature_list.h"
+#include "components/prefs/pref_service.h"
+
+namespace feed {
+namespace {
+
+int GetNoticeCardIndex() {
+ // Infer that the notice card is at the 2nd position when the feature related
+ // to putting the notice card at the second position is enabled.
+ if (base::FeatureList::IsEnabled(
+ feed::kInterestFeedV2ClicksAndViewsConditionalUpload)) {
+ return 1;
+ }
+ return 0;
+}
+
+} // namespace
+
+NoticeCardTracker::NoticeCardTracker(PrefService* profile_prefs)
+ : profile_prefs_(profile_prefs) {
+ DCHECK(profile_prefs_);
+}
+
+void NoticeCardTracker::OnSliceViewed(int index) {
+ MaybeUpdateNoticeCardViewsCount(index);
+}
+
+void NoticeCardTracker::OnOpenAction(int index) {
+ MaybeUpdateNoticeCardClicksCount(index);
+}
+
+bool NoticeCardTracker::HasAcknowledgedNoticeCard() const {
+ if (!base::FeatureList::IsEnabled(feed::kInterestFeedNoticeCardAutoDismiss))
+ return false;
+
+ int views_count_threshold = base::GetFieldTrialParamByFeatureAsInt(
+ feed::kInterestFeedNoticeCardAutoDismiss,
+ kNoticeCardViewsCountThresholdParamName, 3);
+ DCHECK(views_count_threshold >= 0);
+ int clicks_count_threshold = base::GetFieldTrialParamByFeatureAsInt(
+ feed::kInterestFeedNoticeCardAutoDismiss,
+ kNoticeCardClicksCountThresholdParamName, 1);
+ DCHECK(clicks_count_threshold >= 0);
+
+ DCHECK(views_count_threshold > 0 || clicks_count_threshold > 0)
+ << "all notice card auto-dismiss thresholds are set to 0 when there "
+ "should be at least one threshold above 0";
+
+ if (views_count_threshold > 0 &&
+ prefs::GetNoticeCardViewsCount(*profile_prefs_) >=
+ views_count_threshold) {
+ return true;
+ }
+
+ if (clicks_count_threshold > 0 &&
+ prefs::GetNoticeCardClicksCount(*profile_prefs_) >=
+ clicks_count_threshold) {
+ return true;
+ }
+
+ return false;
+}
+
+bool NoticeCardTracker::HasNoticeCardActionsCountPrerequisites(int index) {
+ if (!base::FeatureList::IsEnabled(feed::kInterestFeedNoticeCardAutoDismiss))
+ return false;
+
+ if (!prefs::GetLastFetchHadNoticeCard(*profile_prefs_)) {
+ return false;
+ }
+
+ if (index != GetNoticeCardIndex()) {
+ return false;
+ }
+ return true;
+}
+void NoticeCardTracker::MaybeUpdateNoticeCardViewsCount(int index) {
+ if (!HasNoticeCardActionsCountPrerequisites(index))
+ return;
+
+ prefs::IncrementNoticeCardViewsCount(*profile_prefs_);
+}
+void NoticeCardTracker::MaybeUpdateNoticeCardClicksCount(int index) {
+ if (!HasNoticeCardActionsCountPrerequisites(index))
+ return;
+
+ prefs::IncrementNoticeCardClicksCount(*profile_prefs_);
+}
+
+} // namespace feed \ No newline at end of file
diff --git a/chromium/components/feed/core/v2/notice_card_tracker.h b/chromium/components/feed/core/v2/notice_card_tracker.h
new file mode 100644
index 00000000000..db9f71815d1
--- /dev/null
+++ b/chromium/components/feed/core/v2/notice_card_tracker.h
@@ -0,0 +1,47 @@
+// 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.
+
+#ifndef COMPONENTS_FEED_CORE_V2_NOTICE_CARD_TRACKER_H_
+#define COMPONENTS_FEED_CORE_V2_NOTICE_CARD_TRACKER_H_
+
+class PrefService;
+
+namespace feed {
+
+constexpr char kNoticeCardViewsCountThresholdParamName[] =
+ "notice-card-views-count-threshold";
+constexpr char kNoticeCardClicksCountThresholdParamName[] =
+ "notice-card-clicks-count-threshold";
+
+// Tracker for the notice card related actions that also provide signals based
+// on those.
+class NoticeCardTracker {
+ public:
+ explicit NoticeCardTracker(PrefService* profile_prefs);
+
+ NoticeCardTracker(const NoticeCardTracker&) = delete;
+ NoticeCardTracker& operator=(const NoticeCardTracker&) = delete;
+
+ // Capture the actions.
+
+ void OnSliceViewed(int index);
+ void OnOpenAction(int index);
+
+ // Get signals based on the actions.
+
+ // Indicates whether there were enough views or clicks done on the notice
+ // card to consider it as acknowledged by the user.
+ bool HasAcknowledgedNoticeCard() const;
+
+ private:
+ bool HasNoticeCardActionsCountPrerequisites(int index);
+ void MaybeUpdateNoticeCardViewsCount(int index);
+ void MaybeUpdateNoticeCardClicksCount(int index);
+
+ PrefService* profile_prefs_;
+};
+
+} // namespace feed
+
+#endif // COMPONENTS_FEED_CORE_V2_NOTICE_CARD_TRACKER_H_ \ No newline at end of file
diff --git a/chromium/components/feed/core/v2/notice_card_tracker_unittest.cc b/chromium/components/feed/core/v2/notice_card_tracker_unittest.cc
new file mode 100644
index 00000000000..60efb9d10aa
--- /dev/null
+++ b/chromium/components/feed/core/v2/notice_card_tracker_unittest.cc
@@ -0,0 +1,151 @@
+// 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.
+
+#include "components/feed/core/v2/notice_card_tracker.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "components/feed/core/common/pref_names.h"
+#include "components/feed/core/v2/prefs.h"
+#include "components/feed/feed_feature_list.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace feed {
+namespace {
+
+class NoticeCardTrackerTest : public testing::Test {
+ public:
+ void SetUp() override {
+ feed::RegisterProfilePrefs(profile_prefs_.registry());
+ ;
+ }
+
+ protected:
+ TestingPrefServiceSimple profile_prefs_;
+};
+
+TEST_F(NoticeCardTrackerTest,
+ TrackingNoticeCardActionsDoesntUpdateCountsWhenNoNoticeCard) {
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeature(
+ feed::kInterestFeedNoticeCardAutoDismiss);
+ NoticeCardTracker tracker(&profile_prefs_);
+
+ prefs::SetLastFetchHadNoticeCard(profile_prefs_, false);
+
+ // Generate enough views to reach the acknowlegement threshold, but there was
+ // no notice card in the feed.
+ const int notice_card_index = 0;
+ tracker.OnSliceViewed(notice_card_index);
+ tracker.OnSliceViewed(notice_card_index);
+ tracker.OnSliceViewed(notice_card_index);
+
+ EXPECT_FALSE(tracker.HasAcknowledgedNoticeCard());
+}
+
+TEST_F(NoticeCardTrackerTest,
+ TrackingNoticeCardActionsDoesntUpdateCountsForNonNoticeCard) {
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeature(
+ feed::kInterestFeedNoticeCardAutoDismiss);
+ NoticeCardTracker tracker(&profile_prefs_);
+
+ // Generate enough views to reach the acknowlegement threshold, but the views
+ // were not on the notice card.
+ const int non_notice_card_index = 1;
+ tracker.OnSliceViewed(non_notice_card_index);
+ tracker.OnSliceViewed(non_notice_card_index);
+ tracker.OnSliceViewed(non_notice_card_index);
+
+ EXPECT_FALSE(tracker.HasAcknowledgedNoticeCard());
+}
+
+TEST_F(NoticeCardTrackerTest,
+ AcknowledgedNoticeCardWhenEnoughViewsAndNoticeCardAt1stPos) {
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeature(
+ feed::kInterestFeedNoticeCardAutoDismiss);
+ NoticeCardTracker tracker(&profile_prefs_);
+
+ const int notice_card_index = 0;
+ tracker.OnSliceViewed(notice_card_index);
+ tracker.OnSliceViewed(notice_card_index);
+ tracker.OnSliceViewed(notice_card_index);
+
+ EXPECT_TRUE(tracker.HasAcknowledgedNoticeCard());
+}
+
+TEST_F(NoticeCardTrackerTest,
+ AcknowledgedNoticeCardWhenEnoughViewsAndNoticeCardAt2ndPos) {
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitWithFeatures(
+ /*enabled_features=*/{feed::kInterestFeedNoticeCardAutoDismiss,
+ feed::
+ kInterestFeedV2ClicksAndViewsConditionalUpload},
+ /*disabled_features=*/{});
+ NoticeCardTracker tracker(&profile_prefs_);
+
+ const int notice_card_index = 1;
+ tracker.OnSliceViewed(notice_card_index);
+ tracker.OnSliceViewed(notice_card_index);
+ tracker.OnSliceViewed(notice_card_index);
+
+ EXPECT_TRUE(tracker.HasAcknowledgedNoticeCard());
+}
+
+TEST_F(NoticeCardTrackerTest,
+ DontAcknowledgedNoticeCardWhenNotEnoughViewsNorClicks) {
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeature(
+ feed::kInterestFeedNoticeCardAutoDismiss);
+ NoticeCardTracker tracker(&profile_prefs_);
+
+ // Generate views but not enough to reach the threshold.
+ const int notice_card_index = 0;
+ tracker.OnSliceViewed(notice_card_index);
+ tracker.OnSliceViewed(notice_card_index);
+
+ EXPECT_FALSE(tracker.HasAcknowledgedNoticeCard());
+}
+
+TEST_F(NoticeCardTrackerTest, DontAcknowledgedNoticeCardWhenFeatureDisabled) {
+ // Generate enough views and clicks on the notice card to reach the threshold,
+ // but the feature is disabled.
+ prefs::IncrementNoticeCardClicksCount(profile_prefs_);
+ prefs::IncrementNoticeCardViewsCount(profile_prefs_);
+ prefs::IncrementNoticeCardViewsCount(profile_prefs_);
+ prefs::IncrementNoticeCardViewsCount(profile_prefs_);
+
+ NoticeCardTracker tracker(&profile_prefs_);
+ EXPECT_FALSE(tracker.HasAcknowledgedNoticeCard());
+}
+
+TEST_F(NoticeCardTrackerTest,
+ DontAcknowledgedNoticeCardFromViewsCountWhenThresholdIsZero) {
+ base::FieldTrialParams params;
+ params[kNoticeCardViewsCountThresholdParamName] = "0";
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeatureWithParameters(
+ feed::kInterestFeedNoticeCardAutoDismiss, params);
+
+ NoticeCardTracker tracker(&profile_prefs_);
+ EXPECT_FALSE(tracker.HasAcknowledgedNoticeCard());
+}
+
+TEST_F(NoticeCardTrackerTest,
+ DontAcknowledgedNoticeCardFromClicksCountWhenThresholdIsZero) {
+ base::FieldTrialParams params;
+ params[kNoticeCardClicksCountThresholdParamName] = "0";
+ base::test::ScopedFeatureList scoped_feature_list;
+ scoped_feature_list.InitAndEnableFeatureWithParameters(
+ feed::kInterestFeedNoticeCardAutoDismiss, params);
+
+ NoticeCardTracker tracker(&profile_prefs_);
+ EXPECT_FALSE(tracker.HasAcknowledgedNoticeCard());
+}
+
+} // namespace
+} // namespace feed \ No newline at end of file
diff --git a/chromium/components/feed/core/v2/prefs.cc b/chromium/components/feed/core/v2/prefs.cc
index 562ff7d9c4f..49ef37fd3fe 100644
--- a/chromium/components/feed/core/v2/prefs.cc
+++ b/chromium/components/feed/core/v2/prefs.cc
@@ -106,6 +106,24 @@ bool GetHasReachedClickAndViewActionsUploadConditions(
feed::prefs::kHasReachedClickAndViewActionsUploadConditions);
}
+void IncrementNoticeCardViewsCount(PrefService& pref_service) {
+ int count = pref_service.GetInteger(feed::prefs::kNoticeCardViewsCount);
+ pref_service.SetInteger(feed::prefs::kNoticeCardViewsCount, count + 1);
+}
+
+int GetNoticeCardViewsCount(const PrefService& pref_service) {
+ return pref_service.GetInteger(feed::prefs::kNoticeCardViewsCount);
+}
+
+void IncrementNoticeCardClicksCount(PrefService& pref_service) {
+ int count = pref_service.GetInteger(feed::prefs::kNoticeCardClicksCount);
+ pref_service.SetInteger(feed::prefs::kNoticeCardClicksCount, count + 1);
+}
+
+int GetNoticeCardClicksCount(const PrefService& pref_service) {
+ return pref_service.GetInteger(feed::prefs::kNoticeCardClicksCount);
+}
+
} // namespace prefs
} // namespace feed
diff --git a/chromium/components/feed/core/v2/prefs.h b/chromium/components/feed/core/v2/prefs.h
index 634bddb16c6..e16befa3e47 100644
--- a/chromium/components/feed/core/v2/prefs.h
+++ b/chromium/components/feed/core/v2/prefs.h
@@ -52,6 +52,16 @@ void SetHasReachedClickAndViewActionsUploadConditions(PrefService& pref_service,
bool GetHasReachedClickAndViewActionsUploadConditions(
const PrefService& pref_service);
+// Increment the stored notice card views count by 1.
+void IncrementNoticeCardViewsCount(PrefService& pref_service);
+
+int GetNoticeCardViewsCount(const PrefService& pref_service);
+
+// Increment the stored notice card clicks count by 1.
+void IncrementNoticeCardClicksCount(PrefService& pref_service);
+
+int GetNoticeCardClicksCount(const 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 aa21f4a1b93..d680ecfcf3f 100644
--- a/chromium/components/feed/core/v2/proto_util.cc
+++ b/chromium/components/feed/core/v2/proto_util.cc
@@ -13,10 +13,12 @@
#include "build/build_config.h"
#include "components/feed/core/proto/v2/store.pb.h"
#include "components/feed/core/proto/v2/wire/capability.pb.h"
+#include "components/feed/core/proto/v2/wire/chrome_client_info.pb.h"
#include "components/feed/core/proto/v2/wire/feed_request.pb.h"
#include "components/feed/core/proto/v2/wire/request.pb.h"
#include "components/feed/core/v2/config.h"
#include "components/feed/core/v2/feed_stream.h"
+#include "components/feed/feed_feature_list.h"
#if defined(OS_ANDROID)
#include "base/android/build_info.h"
@@ -124,9 +126,15 @@ feedwire::Request CreateFeedQueryRequest(
*feed_request.mutable_client_info() = CreateClientInfo(request_metadata);
feedwire::FeedQuery& query = *feed_request.mutable_feed_query();
query.set_reason(request_reason);
- if (!consistency_token.empty()) {
+
+ // |consistency_token|, for action reporting, is only applicable to signed-in
+ // requests. The presence of |client_instance_id|, also signed-in only, can be
+ // used a proxy for checking if we're creating a signed-in request.
+ if (!consistency_token.empty() &&
+ !request_metadata.client_instance_id.empty()) {
feed_request.mutable_consistency_token()->set_token(consistency_token);
}
+
if (!next_page_token.empty()) {
DCHECK_EQ(request_reason, feedwire::FeedQuery::NEXT_PAGE_SCROLL);
query.mutable_next_page_token()
@@ -136,6 +144,16 @@ feedwire::Request CreateFeedQueryRequest(
return request;
}
+void SetNoticeCardAcknowledged(feedwire::Request* request,
+ const RequestMetadata& request_metadata) {
+ if (request_metadata.notice_card_acknowledged) {
+ request->mutable_feed_request()
+ ->mutable_feed_query()
+ ->mutable_chrome_fulfillment_info()
+ ->set_notice_card_acknowledged(true);
+ }
+}
+
} // namespace
std::string ContentIdString(const feedwire::ContentId& content_id) {
@@ -176,7 +194,6 @@ bool CompareContent(const feedstore::Content& a, const feedstore::Content& b) {
feedwire::ClientInfo CreateClientInfo(const RequestMetadata& request_metadata) {
feedwire::ClientInfo client_info;
- client_info.set_client_instance_id(request_metadata.client_instance_id);
feedwire::DisplayInfo& display_info = *client_info.add_display_info();
display_info.set_screen_density(request_metadata.display_metrics.density);
@@ -196,6 +213,19 @@ feedwire::ClientInfo CreateClientInfo(const RequestMetadata& request_metadata) {
*client_info.mutable_platform_version() = GetPlatformVersionMessage();
*client_info.mutable_app_version() =
GetAppVersionMessage(request_metadata.chrome_info);
+
+ // client_instance_id and session_id should not both be set at the same time.
+ DCHECK(request_metadata.client_instance_id.empty() ||
+ request_metadata.session_id.empty());
+
+ // Populate client_instance_id, session_id, or neither.
+ if (!request_metadata.client_instance_id.empty()) {
+ client_info.set_client_instance_id(request_metadata.client_instance_id);
+ } else if (!request_metadata.session_id.empty()) {
+ client_info.mutable_chrome_client_info()->set_session_id(
+ request_metadata.session_id);
+ }
+
return client_info;
}
@@ -203,8 +233,10 @@ feedwire::Request CreateFeedQueryRefreshRequest(
feedwire::FeedQuery::RequestReason request_reason,
const RequestMetadata& request_metadata,
const std::string& consistency_token) {
- return CreateFeedQueryRequest(request_reason, request_metadata,
- consistency_token, std::string());
+ feedwire::Request request = CreateFeedQueryRequest(
+ request_reason, request_metadata, consistency_token, std::string());
+ SetNoticeCardAcknowledged(&request, request_metadata);
+ return request;
}
feedwire::Request CreateFeedQueryLoadMoreRequest(
diff --git a/chromium/components/feed/core/v2/proto_util_unittest.cc b/chromium/components/feed/core/v2/proto_util_unittest.cc
index 53eecb56451..8d53ebe00fa 100644
--- a/chromium/components/feed/core/v2/proto_util_unittest.cc
+++ b/chromium/components/feed/core/v2/proto_util_unittest.cc
@@ -108,5 +108,31 @@ TEST(ProtoUtilTest, DisableCapabilitiesWithFinch) {
EXPECT_TRUE(HasCapability(request, feedwire::Capability::PREFETCH_METADATA));
}
+TEST(ProtoUtilTest, NoticeCardAcknowledged) {
+ RequestMetadata request_metadata;
+ request_metadata.notice_card_acknowledged = true;
+ feedwire::Request request = CreateFeedQueryRefreshRequest(
+ feedwire::FeedQuery::MANUAL_REFRESH, request_metadata,
+ /*consistency_token=*/std::string());
+
+ EXPECT_TRUE(request.feed_request()
+ .feed_query()
+ .chrome_fulfillment_info()
+ .notice_card_acknowledged());
+}
+
+TEST(ProtoUtilTest, NoticeCardNotAcknowledged) {
+ RequestMetadata request_metadata;
+ request_metadata.notice_card_acknowledged = false;
+ feedwire::Request request = CreateFeedQueryRefreshRequest(
+ feedwire::FeedQuery::MANUAL_REFRESH, request_metadata,
+ /*consistency_token=*/std::string());
+
+ EXPECT_FALSE(request.feed_request()
+ .feed_query()
+ .chrome_fulfillment_info()
+ .notice_card_acknowledged());
+}
+
} // namespace
} // namespace feed
diff --git a/chromium/components/feed/core/v2/protocol_translator.cc b/chromium/components/feed/core/v2/protocol_translator.cc
index db128746859..ea69442f531 100644
--- a/chromium/components/feed/core/v2/protocol_translator.cc
+++ b/chromium/components/feed/core/v2/protocol_translator.cc
@@ -306,6 +306,18 @@ RefreshResponseData TranslateWireResponse(
result->stream_data.set_privacy_notice_fulfilled(
response_metadata.privacy_notice_fulfilled());
+ base::Optional<std::string> session_id = base::nullopt;
+ if (was_signed_in_request) {
+ // Signed-in requests don't use session_id tokens; set an empty value to
+ // ensure that there are no old session_id tokens left hanging around.
+ session_id = std::string();
+ } else if (response_metadata.has_session_id()) {
+ // Signed-out requests can set a new session token; otherwise, we leave
+ // the default base::nullopt value to keep whatever token is already in
+ // play.
+ session_id = response_metadata.session_id();
+ }
+
MetricsReporter::ActivityLoggingEnabled(response_metadata.logging_enabled());
MetricsReporter::NoticeCardFulfilledObsolete(
response_metadata.privacy_notice_fulfilled());
@@ -313,6 +325,7 @@ RefreshResponseData TranslateWireResponse(
RefreshResponseData response_data;
response_data.model_update_request = std::move(result);
response_data.request_schedule = std::move(global_data.request_schedule);
+ response_data.session_id = std::move(session_id);
return response_data;
}
diff --git a/chromium/components/feed/core/v2/protocol_translator.h b/chromium/components/feed/core/v2/protocol_translator.h
index 3728e1e5dc4..b83a23fdecf 100644
--- a/chromium/components/feed/core/v2/protocol_translator.h
+++ b/chromium/components/feed/core/v2/protocol_translator.h
@@ -64,6 +64,9 @@ struct RefreshResponseData {
// Server-defined request schedule, if provided.
base::Optional<RequestSchedule> request_schedule;
+
+ // Server-defined session id token, if provided.
+ base::Optional<std::string> session_id;
};
base::Optional<feedstore::DataOperation> TranslateDataOperation(
diff --git a/chromium/components/feed/core/v2/public/feed_service.cc b/chromium/components/feed/core/v2/public/feed_service.cc
index 7e7a59776ee..b5836074240 100644
--- a/chromium/components/feed/core/v2/public/feed_service.cc
+++ b/chromium/components/feed/core/v2/public/feed_service.cc
@@ -121,6 +121,9 @@ class FeedService::StreamDelegateImpl : public FeedStream::Delegate {
return service_delegate_->GetLanguageTag();
}
void ClearAll() override { service_delegate_->ClearAll(); }
+ void PrefetchImage(const GURL& url) override {
+ service_delegate_->PrefetchImage(url);
+ }
bool IsSignedIn() override { return identity_manager_->HasPrimaryAccount(); }
private:
diff --git a/chromium/components/feed/core/v2/public/feed_service.h b/chromium/components/feed/core/v2/public/feed_service.h
index ae3b5ddefb0..d8c1f2aa222 100644
--- a/chromium/components/feed/core/v2/public/feed_service.h
+++ b/chromium/components/feed/core/v2/public/feed_service.h
@@ -67,6 +67,8 @@ class FeedService : public KeyedService {
virtual DisplayMetrics GetDisplayMetrics() = 0;
// Clear all stored data.
virtual void ClearAll() = 0;
+ // Fetch the image and store it in the disk cache.
+ virtual void PrefetchImage(const GURL& url) = 0;
};
// Construct a FeedService given an already constructed FeedStream.
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 d3bfabc34de..b33d9e87239 100644
--- a/chromium/components/feed/core/v2/public/feed_stream_api.h
+++ b/chromium/components/feed/core/v2/public/feed_stream_api.h
@@ -63,9 +63,14 @@ class FeedStreamApi {
// as the feed is refreshed or the user signs in/out.
virtual bool IsActivityLoggingEnabled() const = 0;
- // Returns the client_instance_id. This value is reset whenever the feed
- // stream is cleared (on sign-in, sign-out, and some data clear events).
- virtual std::string GetClientInstanceId() = 0;
+ // Returns the signed-in client_instance_id. This value is reset whenever the
+ // feed stream is cleared (on sign-in, sign-out, and some data clear events).
+ virtual std::string GetClientInstanceId() const = 0;
+
+ // Returns the client's signed-out session id. This value is reset whenever
+ // the feed stream is cleared (on sign-in, sign-out, and some data clear
+ // events).
+ virtual std::string GetSessionId() const = 0;
// Invoked by RefreshTaskScheduler's scheduled task.
virtual void ExecuteRefreshTask() = 0;
@@ -170,6 +175,9 @@ class FeedStreamApi {
virtual void ForceRefreshForDebugging() = 0;
// Dumps some state information for debugging.
virtual std::string DumpStateForDebugging() = 0;
+ // Forces to render a StreamUpdate on all subsequent surface attaches.
+ virtual void SetForcedStreamUpdateForDebugging(
+ const feedui::StreamUpdate& stream_update) = 0;
};
} // namespace feed
diff --git a/chromium/components/feed/core/v2/public/types.h b/chromium/components/feed/core/v2/public/types.h
index 7680ef75813..0bdc72e7f07 100644
--- a/chromium/components/feed/core/v2/public/types.h
+++ b/chromium/components/feed/core/v2/public/types.h
@@ -48,6 +48,7 @@ struct NetworkResponseInfo {
std::string bless_nonce;
GURL base_request_url;
size_t response_body_bytes = 0;
+ bool was_signed_in = false;
};
struct NetworkResponse {
diff --git a/chromium/components/feed/core/v2/tasks/load_more_task.cc b/chromium/components/feed/core/v2/tasks/load_more_task.cc
index 8c6dc6370aa..6afb90d4780 100644
--- a/chromium/components/feed/core/v2/tasks/load_more_task.cc
+++ b/chromium/components/feed/core/v2/tasks/load_more_task.cc
@@ -7,7 +7,7 @@
#include <memory>
#include <utility>
-#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
#include "base/check.h"
#include "base/time/clock.h"
#include "base/time/tick_clock.h"
@@ -48,22 +48,34 @@ void LoadMoreTask::Run() {
}
void LoadMoreTask::UploadActionsComplete(UploadActionsTask::Result result) {
+ StreamModel* model = stream_->GetModel();
+ DCHECK(model) << "Model was unloaded outside of a Task";
+
+ // Determine whether the load more request should be forced signed-out
+ // regardless of the live sign-in state of the client.
+ //
+ // The signed-in state of the model is used instead of using
+ // FeedStream#ShouldForceSignedOutFeedQueryRequest because the load more
+ // requests should be in the same signed-in state as the prior requests that
+ // filled the model to have consistent data.
+ //
+ // The sign-in state of the load stream request that brings the initial
+ // content determines the sign-in state of the subsequent load more requests.
+ // This avoids a possible situation where there would be a mix of signed-in
+ // and signed-out content, which we don't want.
+ bool force_signed_out_request = !model->signed_in();
// Send network request.
- bool force_signed_out_request =
- stream_->ShouldForceSignedOutFeedQueryRequest();
fetch_start_time_ = stream_->GetTickClock()->NowTicks();
stream_->GetNetwork()->SendQueryRequest(
CreateFeedQueryLoadMoreRequest(
- stream_->GetRequestMetadata(),
+ stream_->GetRequestMetadata(/*is_for_next_page=*/true),
stream_->GetMetadata()->GetConsistencyToken(),
stream_->GetModel()->GetNextPageToken()),
force_signed_out_request,
- base::BindOnce(&LoadMoreTask::QueryRequestComplete, GetWeakPtr(),
- force_signed_out_request));
+ base::BindOnce(&LoadMoreTask::QueryRequestComplete, GetWeakPtr()));
}
void LoadMoreTask::QueryRequestComplete(
- bool was_forced_signed_out_request,
FeedNetwork::QueryRequestResult result) {
StreamModel* model = stream_->GetModel();
DCHECK(model) << "Model was unloaded outside of a Task";
@@ -71,20 +83,21 @@ void LoadMoreTask::QueryRequestComplete(
if (!result.response_body)
return Done(LoadStreamStatus::kNoResponseBody);
- bool was_signed_in_request =
- !was_forced_signed_out_request && stream_->IsSignedIn();
-
RefreshResponseData translated_response =
stream_->GetWireResponseTranslator()->TranslateWireResponse(
*result.response_body,
StreamModelUpdateRequest::Source::kNetworkLoadMore,
- was_signed_in_request, stream_->GetClock()->Now());
+ result.response_info.was_signed_in, stream_->GetClock()->Now());
if (!translated_response.model_update_request)
return Done(LoadStreamStatus::kProtoTranslationFailed);
loaded_new_content_from_network_ =
!translated_response.model_update_request->stream_structures.empty();
+
+ stream_->GetMetadata()->MaybeUpdateSessionId(translated_response.session_id,
+ stream_->GetClock());
+
model->Update(std::move(translated_response.model_update_request));
if (translated_response.request_schedule)
diff --git a/chromium/components/feed/core/v2/tasks/load_more_task.h b/chromium/components/feed/core/v2/tasks/load_more_task.h
index 397c4eb1cab..538fcf9c75d 100644
--- a/chromium/components/feed/core/v2/tasks/load_more_task.h
+++ b/chromium/components/feed/core/v2/tasks/load_more_task.h
@@ -44,8 +44,7 @@ class LoadMoreTask : public offline_pages::Task {
}
void UploadActionsComplete(UploadActionsTask::Result result);
- void QueryRequestComplete(bool was_signed_in_request,
- FeedNetwork::QueryRequestResult result);
+ void QueryRequestComplete(FeedNetwork::QueryRequestResult result);
void Done(LoadStreamStatus status);
FeedStream* stream_; // Unowned.
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 71fff522a38..999d5f6fae9 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
@@ -126,8 +126,6 @@ void LoadStreamFromStoreTask::Complete(LoadStreamStatus status) {
if (status == LoadStreamStatus::kLoadedFromStore &&
load_type_ == LoadType::kFullLoad) {
task_result.update_request = std::move(update_request_);
- } else {
- task_result.consistency_token = consistency_token_;
}
std::move(result_callback_).Run(std::move(task_result));
TaskComplete();
diff --git a/chromium/components/feed/core/v2/tasks/load_stream_from_store_task.h b/chromium/components/feed/core/v2/tasks/load_stream_from_store_task.h
index 61dee09d676..fef672dc9ac 100644
--- a/chromium/components/feed/core/v2/tasks/load_stream_from_store_task.h
+++ b/chromium/components/feed/core/v2/tasks/load_stream_from_store_task.h
@@ -76,7 +76,6 @@ class LoadStreamFromStoreTask : public offline_pages::Task {
// Data to be stuffed into the Result when the task is complete.
std::unique_ptr<StreamModelUpdateRequest> update_request_;
- std::string consistency_token_;
std::vector<feedstore::StoredAction> pending_actions_;
base::WeakPtrFactory<LoadStreamFromStoreTask> weak_ptr_factory_{this};
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 c061f9d91bf..9b4f04b1e69 100644
--- a/chromium/components/feed/core/v2/tasks/load_stream_task.cc
+++ b/chromium/components/feed/core/v2/tasks/load_stream_task.cc
@@ -7,7 +7,7 @@
#include <memory>
#include <utility>
-#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
#include "base/check.h"
#include "base/time/clock.h"
#include "base/time/tick_clock.h"
@@ -123,15 +123,14 @@ void LoadStreamTask::UploadActionsComplete(UploadActionsTask::Result result) {
latencies_->StepComplete(LoadLatencyTimes::kUploadActions);
stream_->GetNetwork()->SendQueryRequest(
CreateFeedQueryRefreshRequest(
- GetRequestReason(load_type_), stream_->GetRequestMetadata(),
+ GetRequestReason(load_type_),
+ stream_->GetRequestMetadata(/*is_for_next_page=*/false),
stream_->GetMetadata()->GetConsistencyToken()),
force_signed_out_request,
- base::BindOnce(&LoadStreamTask::QueryRequestComplete, GetWeakPtr(),
- force_signed_out_request));
+ base::BindOnce(&LoadStreamTask::QueryRequestComplete, GetWeakPtr()));
}
void LoadStreamTask::QueryRequestComplete(
- bool was_forced_signed_out_request,
FeedNetwork::QueryRequestResult result) {
latencies_->StepComplete(LoadLatencyTimes::kQueryRequest);
@@ -149,14 +148,11 @@ void LoadStreamTask::QueryRequestComplete(
return Done(LoadStreamStatus::kNoResponseBody);
}
- bool was_signed_in_request =
- !was_forced_signed_out_request && stream_->IsSignedIn();
-
RefreshResponseData response_data =
stream_->GetWireResponseTranslator()->TranslateWireResponse(
*result.response_body,
StreamModelUpdateRequest::Source::kNetworkUpdate,
- was_signed_in_request, stream_->GetClock()->Now());
+ result.response_info.was_signed_in, stream_->GetClock()->Now());
if (!response_data.model_update_request)
return Done(LoadStreamStatus::kProtoTranslationFailed);
@@ -172,6 +168,9 @@ void LoadStreamTask::QueryRequestComplete(
stream_->SetLastStreamLoadHadNoticeCard(isNoticeCardFulfilled);
MetricsReporter::NoticeCardFulfilled(isNoticeCardFulfilled);
+ stream_->GetMetadata()->MaybeUpdateSessionId(response_data.session_id,
+ stream_->GetClock());
+
if (load_type_ != LoadType::kBackgroundRefresh) {
auto model = std::make_unique<StreamModel>();
model->Update(std::move(response_data.model_update_request));
diff --git a/chromium/components/feed/core/v2/tasks/load_stream_task.h b/chromium/components/feed/core/v2/tasks/load_stream_task.h
index 6e8993959ac..dd4c210db73 100644
--- a/chromium/components/feed/core/v2/tasks/load_stream_task.h
+++ b/chromium/components/feed/core/v2/tasks/load_stream_task.h
@@ -74,8 +74,7 @@ class LoadStreamTask : public offline_pages::Task {
void LoadFromStoreComplete(LoadStreamFromStoreTask::Result result);
void UploadActionsComplete(UploadActionsTask::Result result);
- void QueryRequestComplete(bool was_signed_in_request,
- FeedNetwork::QueryRequestResult result);
+ void QueryRequestComplete(FeedNetwork::QueryRequestResult result);
void Done(LoadStreamStatus status);
LoadType load_type_;
diff --git a/chromium/components/feed/core/v2/tasks/prefetch_images_task.cc b/chromium/components/feed/core/v2/tasks/prefetch_images_task.cc
new file mode 100644
index 00000000000..f8a927ba6af
--- /dev/null
+++ b/chromium/components/feed/core/v2/tasks/prefetch_images_task.cc
@@ -0,0 +1,100 @@
+// 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.
+
+#include "components/feed/core/v2/tasks/prefetch_images_task.h"
+
+#include <utility>
+
+#include "base/callback.h"
+#include "base/logging.h"
+#include "components/feed/core/proto/v2/wire/stream_structure.pb.h"
+#include "components/feed/core/v2/config.h"
+#include "components/feed/core/v2/feed_store.h"
+#include "components/feed/core/v2/feed_stream.h"
+#include "components/feed/core/v2/stream_model.h"
+#include "components/feed/core/v2/tasks/load_stream_from_store_task.h"
+
+namespace feed {
+namespace {
+
+// Converts a URL string into a GURL. If the string is not a valid URL, returns
+// an empty GURL. Since GURL::spec() asserts on invalid URLs, this is necessary
+// to scrub the incoming data from the wire.
+GURL SpecToGURL(const std::string& url_string) {
+ GURL url(url_string);
+ if (!url.is_valid())
+ url = GURL();
+ return url;
+}
+
+} // namespace
+
+PrefetchImagesTask::PrefetchImagesTask(FeedStream* stream) : stream_(stream) {
+ max_images_per_refresh_ =
+ GetFeedConfig().max_prefetch_image_requests_per_refresh;
+}
+
+PrefetchImagesTask::~PrefetchImagesTask() = default;
+
+void PrefetchImagesTask::Run() {
+ if (stream_->GetModel()) {
+ PrefetchImagesFromModel(*stream_->GetModel());
+ return;
+ }
+
+ load_from_store_task_ = std::make_unique<LoadStreamFromStoreTask>(
+ LoadStreamFromStoreTask::LoadType::kFullLoad, stream_->GetStore(),
+ stream_->GetClock(),
+ base::BindOnce(&PrefetchImagesTask::LoadStreamComplete,
+ base::Unretained(this)));
+
+ load_from_store_task_->Execute(base::DoNothing());
+}
+
+void PrefetchImagesTask::LoadStreamComplete(
+ LoadStreamFromStoreTask::Result result) {
+ if (!result.update_request) {
+ TaskComplete();
+ return;
+ }
+
+ // It is a bit dangerous to retain the model loaded here. The normal
+ // LoadStreamTask flow has various considerations for metrics and signalling
+ // surfaces to update. For this reason, we're not going to retain the loaded
+ // model for use outside of this task.
+ StreamModel model;
+ model.Update(std::move(result.update_request));
+ PrefetchImagesFromModel(model);
+}
+
+void PrefetchImagesTask::PrefetchImagesFromModel(const StreamModel& model) {
+ for (ContentRevision rev : model.GetContentList()) {
+ const feedstore::Content* content = model.FindContent(rev);
+ if (!content)
+ continue;
+ for (const feedwire::PrefetchMetadata& metadata :
+ content->prefetch_metadata()) {
+ MaybePrefetchImage(SpecToGURL(metadata.image_url()));
+ MaybePrefetchImage(SpecToGURL(metadata.favicon_url()));
+ for (const std::string& url : metadata.additional_image_urls()) {
+ MaybePrefetchImage(SpecToGURL(url));
+ }
+ }
+ }
+
+ TaskComplete();
+}
+
+void PrefetchImagesTask::MaybePrefetchImage(const GURL& gurl) {
+ // If we've already fetched this url, or we've hit the max number of fetches,
+ // then don't send a fetch request.
+ if (!gurl.is_valid() ||
+ (previously_fetched_.find(gurl.spec()) != previously_fetched_.end()) ||
+ previously_fetched_.size() >= max_images_per_refresh_)
+ return;
+ previously_fetched_.insert(gurl.spec());
+ stream_->PrefetchImage(gurl);
+}
+
+} // namespace feed
diff --git a/chromium/components/feed/core/v2/tasks/prefetch_images_task.h b/chromium/components/feed/core/v2/tasks/prefetch_images_task.h
new file mode 100644
index 00000000000..ddc24a6cc76
--- /dev/null
+++ b/chromium/components/feed/core/v2/tasks/prefetch_images_task.h
@@ -0,0 +1,51 @@
+// 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.
+
+#ifndef COMPONENTS_FEED_CORE_V2_TASKS_PREFETCH_IMAGES_TASK_H_
+#define COMPONENTS_FEED_CORE_V2_TASKS_PREFETCH_IMAGES_TASK_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "components/feed/core/v2/tasks/load_stream_from_store_task.h"
+#include "components/offline_pages/task/task.h"
+
+namespace feed {
+class FeedStream;
+class StreamModel;
+
+// Prefetch the images in the model.
+class PrefetchImagesTask : public offline_pages::Task {
+ public:
+ explicit PrefetchImagesTask(FeedStream* stream);
+ ~PrefetchImagesTask() override;
+ PrefetchImagesTask(const PrefetchImagesTask&) = delete;
+ PrefetchImagesTask& operator=(const PrefetchImagesTask&) = delete;
+
+ private:
+ base::WeakPtr<PrefetchImagesTask> GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
+ // offline_pages::Task.
+ void Run() override;
+
+ void LoadStreamComplete(LoadStreamFromStoreTask::Result result);
+
+ void PrefetchImagesFromModel(const StreamModel& model);
+
+ void MaybePrefetchImage(const GURL& gurl);
+
+ FeedStream* stream_;
+ std::unordered_set<std::string> previously_fetched_;
+ unsigned long max_images_per_refresh_;
+
+ std::unique_ptr<LoadStreamFromStoreTask> load_from_store_task_;
+
+ base::WeakPtrFactory<PrefetchImagesTask> weak_ptr_factory_{this};
+};
+} // namespace feed
+
+#endif // COMPONENTS_FEED_CORE_V2_TASKS_PREFETCH_IMAGES_TASK_H_
diff --git a/chromium/components/feed/core/v2/types.h b/chromium/components/feed/core/v2/types.h
index 883effa2bfb..508e56c98e4 100644
--- a/chromium/components/feed/core/v2/types.h
+++ b/chromium/components/feed/core/v2/types.h
@@ -39,7 +39,9 @@ struct RequestMetadata {
ChromeInfo chrome_info;
std::string language_tag;
std::string client_instance_id;
+ std::string session_id;
DisplayMetrics display_metrics;
+ bool notice_card_acknowledged = false;
};
// Data internal to MetricsReporter which is persisted to Prefs.
diff --git a/chromium/components/feed/features.gni b/chromium/components/feed/features.gni
index 022f876896e..627f7d7bf52 100644
--- a/chromium/components/feed/features.gni
+++ b/chromium/components/feed/features.gni
@@ -2,12 +2,28 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import("//chrome/android/channel.gni")
+
# Chrome can be built with any combination of v1/v2 enabled.
# When both are enabled, we use the InterestFeedV2 feature to select which
# one is used at runtime.
# If both are false, some of the Feed V2 UI classes are still used to display
# the NTP.
declare_args() {
+ # Whether version 1 of the NTP feed is enabled in the build.
enable_feed_v1 = is_android
+
+ # Whether version 2 of the NTP feed is enabled in the build.
enable_feed_v2 = is_android
+
+ # Whether to include Feed V2 in ChromeModern builds.
+ enable_feed_v2_modern = true
+
+ # Whether to include Feed V2 as a DFM in ChromeModern builds.
+ dfmify_feed_v2_modern = false
+}
+
+if (is_android && android_channel != "default") {
+ # You probably don't want to build without either version in a Clank release.
+ assert(enable_feed_v1 || enable_feed_v2)
}
diff --git a/chromium/components/feed/feed_feature_list.cc b/chromium/components/feed/feed_feature_list.cc
index 8065e083e04..f9cf11aba5e 100644
--- a/chromium/components/feed/feed_feature_list.cc
+++ b/chromium/components/feed/feed_feature_list.cc
@@ -30,9 +30,6 @@ const base::FeatureParam<bool> kOnlySetLastRefreshAttemptOnSuccess{
&kInterestFeedContentSuggestions,
"only_set_last_refresh_attempt_on_success", true};
-const base::Feature kInterestFeedFeedback{"InterestFeedFeedback",
- base::FEATURE_DISABLED_BY_DEFAULT};
-
const base::Feature kReportFeedUserActions{"ReportFeedUserActions",
base::FEATURE_DISABLED_BY_DEFAULT};
@@ -47,6 +44,15 @@ const base::Feature kInterestFeedV2ClicksAndViewsConditionalUpload{
"InterestFeedV2ClickAndViewActionsConditionalUpload",
base::FEATURE_DISABLED_BY_DEFAULT};
+// Feature that allows the client to automatically dismiss the notice card based
+// on the clicks and views on the notice card.
+const base::Feature kInterestFeedNoticeCardAutoDismiss{
+ "InterestFeedNoticeCardAutoDismiss", base::FEATURE_DISABLED_BY_DEFAULT};
+
+// Used for A:B testing of a bug fix (crbug.com/1151391).
+const base::Feature kInterestFeedSpinnerAlwaysAnimate{
+ "InterestFeedSpinnerAlwaysAnimate", base::FEATURE_DISABLED_BY_DEFAULT};
+
const char kDefaultReferrerUrl[] =
"https://www.googleapis.com/auth/chrome-content-suggestions";
diff --git a/chromium/components/feed/feed_feature_list.h b/chromium/components/feed/feed_feature_list.h
index 3fd0e3cb300..432ba988ff8 100644
--- a/chromium/components/feed/feed_feature_list.h
+++ b/chromium/components/feed/feed_feature_list.h
@@ -24,8 +24,6 @@ extern const base::FeatureParam<int> kTimeoutDurationSeconds;
extern const base::FeatureParam<bool> kThrottleBackgroundFetches;
extern const base::FeatureParam<bool> kOnlySetLastRefreshAttemptOnSuccess;
-extern const base::Feature kInterestFeedFeedback;
-
// Indicates if user card clicks and views in Chrome's feed should be reported
// for personalization. Also enables the feed header menu to manage the feed.
extern const base::Feature kReportFeedUserActions;
@@ -33,6 +31,10 @@ extern const base::Feature kReportFeedUserActions;
extern const base::Feature kInterestFeedV1ClicksAndViewsConditionalUpload;
extern const base::Feature kInterestFeedV2ClicksAndViewsConditionalUpload;
+extern const base::Feature kInterestFeedNoticeCardAutoDismiss;
+
+extern const base::Feature kInterestFeedSpinnerAlwaysAnimate;
+
std::string GetFeedReferrerUrl();
} // namespace feed