summaryrefslogtreecommitdiff
path: root/chromium/components/ntp_snippets
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:20:33 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:28:57 +0000
commitd17ea114e5ef69ad5d5d7413280a13e6428098aa (patch)
tree2c01a75df69f30d27b1432467cfe7c1467a498da /chromium/components/ntp_snippets
parent8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (diff)
downloadqtwebengine-chromium-d17ea114e5ef69ad5d5d7413280a13e6428098aa.tar.gz
BASELINE: Update Chromium to 67.0.3396.47
Change-Id: Idcb1341782e417561a2473eeecc82642dafda5b7 Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'chromium/components/ntp_snippets')
-rw-r--r--chromium/components/ntp_snippets/BUILD.gn40
-rw-r--r--chromium/components/ntp_snippets/DEPS6
-rw-r--r--chromium/components/ntp_snippets/OWNERS3
-rw-r--r--chromium/components/ntp_snippets/bookmarks/bookmark_suggestions_provider.cc7
-rw-r--r--chromium/components/ntp_snippets/bookmarks/bookmark_suggestions_provider.h2
-rw-r--r--chromium/components/ntp_snippets/breaking_news/breaking_news_gcm_app_handler_unittest.cc33
-rw-r--r--chromium/components/ntp_snippets/breaking_news/breaking_news_metrics.h2
-rw-r--r--chromium/components/ntp_snippets/breaking_news/subscription_json_request_unittest.cc1
-rw-r--r--chromium/components/ntp_snippets/callbacks.h3
-rw-r--r--chromium/components/ntp_snippets/content_suggestions_metrics.cc2
-rw-r--r--chromium/components/ntp_snippets/content_suggestions_provider.h9
-rw-r--r--chromium/components/ntp_snippets/content_suggestions_service.cc15
-rw-r--r--chromium/components/ntp_snippets/content_suggestions_service.h9
-rw-r--r--chromium/components/ntp_snippets/contextual/cluster.cc46
-rw-r--r--chromium/components/ntp_snippets/contextual/cluster.h49
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service.cc79
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service.h50
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_proxy.cc140
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_proxy.h89
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_proxy_unittest.cc116
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_unittest.cc186
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_json_request.cc252
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_json_request.h114
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_json_request_unittest.cc90
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_suggestion.cc114
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_suggestion.h86
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_suggestions_fetch.cc282
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_suggestions_fetch.h71
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher.h31
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl.cc310
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl.h94
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl_unittest.cc661
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.cc110
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.h163
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter_unittest.cc90
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_suggestions_ukm_entry.cc146
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_suggestions_ukm_entry.h99
-rw-r--r--chromium/components/ntp_snippets/contextual/contextual_suggestions_ukm_entry_unittest.cc104
-rw-r--r--chromium/components/ntp_snippets/contextual/proto/chrome_search_api_request_context.proto38
-rw-r--r--chromium/components/ntp_snippets/contextual/proto/get_pivots_request.proto86
-rw-r--r--chromium/components/ntp_snippets/contextual/proto/get_pivots_response.proto125
-rw-r--r--chromium/components/ntp_snippets/mock_content_suggestions_provider.cc6
-rw-r--r--chromium/components/ntp_snippets/mock_content_suggestions_provider.h4
-rw-r--r--chromium/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.cc9
-rw-r--r--chromium/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.h2
-rw-r--r--chromium/components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.cc7
-rw-r--r--chromium/components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.h2
-rw-r--r--chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.cc9
-rw-r--r--chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.h2
-rw-r--r--chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider_unittest.cc17
-rw-r--r--chromium/components/ntp_snippets/remote/cached_image_fetcher.cc139
-rw-r--r--chromium/components/ntp_snippets/remote/cached_image_fetcher.h31
-rw-r--r--chromium/components/ntp_snippets/remote/cached_image_fetcher_unittest.cc103
-rw-r--r--chromium/components/ntp_snippets/remote/json_request_unittest.cc4
-rw-r--r--chromium/components/ntp_snippets/remote/remote_suggestion.h2
-rw-r--r--chromium/components/ntp_snippets/remote/remote_suggestion_builder.cc1
-rw-r--r--chromium/components/ntp_snippets/remote/remote_suggestions_database.cc61
-rw-r--r--chromium/components/ntp_snippets/remote/remote_suggestions_database.h18
-rw-r--r--chromium/components/ntp_snippets/remote/remote_suggestions_fetcher_impl_unittest.cc7
-rw-r--r--chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc35
-rw-r--r--chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl.h4
-rw-r--r--chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc101
-rw-r--r--chromium/components/ntp_snippets/remote/remote_suggestions_scheduler_impl_unittest.cc8
-rw-r--r--chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.cc7
-rw-r--r--chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.h2
-rw-r--r--chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider_unittest.cc7
-rw-r--r--chromium/components/ntp_snippets/sessions/tab_delegate_sync_adapter_unittest.cc4
67 files changed, 2963 insertions, 1582 deletions
diff --git a/chromium/components/ntp_snippets/BUILD.gn b/chromium/components/ntp_snippets/BUILD.gn
index eb88be97120..88f8092c998 100644
--- a/chromium/components/ntp_snippets/BUILD.gn
+++ b/chromium/components/ntp_snippets/BUILD.gn
@@ -4,6 +4,8 @@
import("//build/config/ui.gni")
+import("//third_party/protobuf/proto_library.gni")
+
if (is_android) {
import("//build/config/android/config.gni")
import("//build/config/android/rules.gni")
@@ -44,15 +46,23 @@ static_library("ntp_snippets") {
"content_suggestions_provider.h",
"content_suggestions_service.cc",
"content_suggestions_service.h",
+ "contextual/cluster.cc",
+ "contextual/cluster.h",
"contextual/contextual_content_suggestions_service.cc",
"contextual/contextual_content_suggestions_service.h",
- "contextual/contextual_json_request.cc",
- "contextual/contextual_json_request.h",
+ "contextual/contextual_content_suggestions_service_proxy.cc",
+ "contextual/contextual_content_suggestions_service_proxy.h",
"contextual/contextual_suggestion.cc",
"contextual/contextual_suggestion.h",
+ "contextual/contextual_suggestions_fetch.cc",
+ "contextual/contextual_suggestions_fetch.h",
"contextual/contextual_suggestions_fetcher.h",
"contextual/contextual_suggestions_fetcher_impl.cc",
"contextual/contextual_suggestions_fetcher_impl.h",
+ "contextual/contextual_suggestions_metrics_reporter.cc",
+ "contextual/contextual_suggestions_metrics_reporter.h",
+ "contextual/contextual_suggestions_ukm_entry.cc",
+ "contextual/contextual_suggestions_ukm_entry.h",
"features.cc",
"features.h",
"logger.cc",
@@ -136,6 +146,7 @@ static_library("ntp_snippets") {
]
deps = [
+ ":explore_protos",
"//components/bookmarks/browser",
"//components/data_use_measurement/core",
"//components/favicon/core",
@@ -154,13 +165,19 @@ static_library("ntp_snippets") {
"//components/sessions",
"//components/strings",
"//components/sync_sessions",
+
+ # "//components/ukm/content",
"//components/url_formatter",
"//components/variations",
"//components/variations/net",
"//components/variations/service",
"//components/web_resource",
"//services/identity/public/cpp",
+ "//services/metrics/public/cpp:metrics_cpp",
+ "//services/network/public/cpp",
+ "//services/network/public/mojom",
"//third_party/icu/",
+ "//third_party/protobuf:protobuf_lite",
"//ui/gfx",
]
}
@@ -172,10 +189,19 @@ if (is_android) {
"category_info.h",
"category_status.h",
"content_suggestions_service.cc",
+ "contextual/contextual_suggestions_metrics_reporter.h",
]
}
}
+proto_library("explore_protos") {
+ sources = [
+ "contextual/proto/chrome_search_api_request_context.proto",
+ "contextual/proto/get_pivots_request.proto",
+ "contextual/proto/get_pivots_response.proto",
+ ]
+}
+
source_set("unit_tests") {
testonly = true
sources = [
@@ -188,9 +214,11 @@ source_set("unit_tests") {
"category_unittest.cc",
"content_suggestions_metrics_unittest.cc",
"content_suggestions_service_unittest.cc",
+ "contextual/contextual_content_suggestions_service_proxy_unittest.cc",
"contextual/contextual_content_suggestions_service_unittest.cc",
- "contextual/contextual_json_request_unittest.cc",
"contextual/contextual_suggestions_fetcher_impl_unittest.cc",
+ "contextual/contextual_suggestions_metrics_reporter_unittest.cc",
+ "contextual/contextual_suggestions_ukm_entry_unittest.cc",
"logger_unittest.cc",
"offline_pages/recent_tab_suggestions_provider_unittest.cc",
"physical_web_pages/physical_web_page_suggestions_provider_unittest.cc",
@@ -218,6 +246,7 @@ source_set("unit_tests") {
}
deps = [
+ ":explore_protos",
":ntp_snippets",
":test_support",
"//base",
@@ -227,6 +256,7 @@ source_set("unit_tests") {
"//components/gcm_driver",
"//components/gcm_driver/instance_id",
"//components/image_fetcher/core",
+ "//components/image_fetcher/core:test_support",
"//components/leveldb_proto:test_support",
"//components/ntp_snippets/remote/proto",
"//components/offline_pages/core",
@@ -243,12 +273,16 @@ source_set("unit_tests") {
"//components/sync:test_support_driver",
"//components/sync_preferences:test_support",
"//components/sync_sessions",
+ "//components/ukm:test_support",
"//components/variations:test_support",
"//components/web_resource:web_resource",
"//google_apis/gcm",
"//net:test_support",
"//services/identity/public/cpp",
"//services/identity/public/cpp:test_support",
+ "//services/network:test_support",
+ "//services/network/public/cpp",
+ "//services/network/public/mojom",
"//testing/gtest",
"//third_party/icu/",
"//ui/gfx:test_support",
diff --git a/chromium/components/ntp_snippets/DEPS b/chromium/components/ntp_snippets/DEPS
index 1fbc017dcb0..33dd6c93fa2 100644
--- a/chromium/components/ntp_snippets/DEPS
+++ b/chromium/components/ntp_snippets/DEPS
@@ -15,6 +15,7 @@ include_rules = [
"+components/strings/grit/components_strings.h",
"+components/sync_preferences/testing_pref_service_syncable.h",
"+components/sync/driver",
+ "+components/ukm",
"+components/url_formatter",
"+components/variations",
"+components/version_info",
@@ -26,6 +27,11 @@ include_rules = [
"+net/url_request",
"+google_apis",
"+services/identity/public/cpp",
+ "+services/metrics/public/cpp",
+ "+services/network/public/cpp",
+ "+services/network/public/mojom",
+ "+services/network/test",
+ "+third_party/protobuf",
"+ui/base",
"+ui/gfx",
]
diff --git a/chromium/components/ntp_snippets/OWNERS b/chromium/components/ntp_snippets/OWNERS
index 3ac3779dd87..ed0519eb118 100644
--- a/chromium/components/ntp_snippets/OWNERS
+++ b/chromium/components/ntp_snippets/OWNERS
@@ -1,7 +1,10 @@
bauerb@chromium.org
+fgorski@chromium.org
jkrcal@chromium.org
+pnoland@chromium.org
treib@chromium.org
tschumann@chromium.org
+zea@chromium.org
# Unsure who to ask? Choose from the above.
markusheintz@chromium.org
diff --git a/chromium/components/ntp_snippets/bookmarks/bookmark_suggestions_provider.cc b/chromium/components/ntp_snippets/bookmarks/bookmark_suggestions_provider.cc
index c0be92cb0aa..8ecaa37a68e 100644
--- a/chromium/components/ntp_snippets/bookmarks/bookmark_suggestions_provider.cc
+++ b/chromium/components/ntp_snippets/bookmarks/bookmark_suggestions_provider.cc
@@ -116,6 +116,13 @@ void BookmarkSuggestionsProvider::FetchSuggestionImage(
FROM_HERE, base::BindOnce(std::move(callback), gfx::Image()));
}
+void BookmarkSuggestionsProvider::FetchSuggestionImageData(
+ const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), std::string()));
+}
+
void BookmarkSuggestionsProvider::Fetch(
const Category& category,
const std::set<std::string>& known_suggestion_ids,
diff --git a/chromium/components/ntp_snippets/bookmarks/bookmark_suggestions_provider.h b/chromium/components/ntp_snippets/bookmarks/bookmark_suggestions_provider.h
index ad2aca0b300..095d2616465 100644
--- a/chromium/components/ntp_snippets/bookmarks/bookmark_suggestions_provider.h
+++ b/chromium/components/ntp_snippets/bookmarks/bookmark_suggestions_provider.h
@@ -33,6 +33,8 @@ class BookmarkSuggestionsProvider : public ContentSuggestionsProvider,
void DismissSuggestion(const ContentSuggestion::ID& suggestion_id) override;
void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
ImageFetchedCallback callback) override;
+ void FetchSuggestionImageData(const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) override;
void Fetch(const Category& category,
const std::set<std::string>& known_suggestion_ids,
FetchDoneCallback callback) override;
diff --git a/chromium/components/ntp_snippets/breaking_news/breaking_news_gcm_app_handler_unittest.cc b/chromium/components/ntp_snippets/breaking_news/breaking_news_gcm_app_handler_unittest.cc
index b262a3ff873..87daac8bfdb 100644
--- a/chromium/components/ntp_snippets/breaking_news/breaking_news_gcm_app_handler_unittest.cc
+++ b/chromium/components/ntp_snippets/breaking_news/breaking_news_gcm_app_handler_unittest.cc
@@ -204,13 +204,13 @@ void ParseJson(
ACTION_TEMPLATE(InvokeCallbackArgument,
HAS_1_TEMPLATE_PARAMS(int, k),
AND_1_VALUE_PARAMS(p0)) {
- ::std::tr1::get<k>(args).Run(p0);
+ std::get<k>(args).Run(p0);
}
ACTION_TEMPLATE(InvokeCallbackArgument,
HAS_1_TEMPLATE_PARAMS(int, k),
AND_2_VALUE_PARAMS(p0, p1)) {
- ::std::tr1::get<k>(args).Run(p0, p1);
+ std::get<k>(args).Run(p0, p1);
}
base::Time GetDummyNow() {
@@ -254,9 +254,6 @@ class BreakingNewsGCMAppHandlerTest : public testing::Test {
std::unique_ptr<BreakingNewsGCMAppHandler> MakeHandler(
scoped_refptr<TestMockTimeTaskRunner> timer_mock_task_runner) {
- tick_clock_ = timer_mock_task_runner->GetMockTickClock();
- clock_ = timer_mock_task_runner->GetMockClock();
-
message_loop_.SetTaskRunner(timer_mock_task_runner);
// TODO(vitaliii): Initialize MockSubscriptionManager in the constructor, so
@@ -265,18 +262,19 @@ class BreakingNewsGCMAppHandlerTest : public testing::Test {
std::make_unique<NiceMock<MockSubscriptionManager>>();
mock_subscription_manager_ = wrapped_mock_subscription_manager.get();
- auto token_validation_timer =
- std::make_unique<base::OneShotTimer>(tick_clock_.get());
+ auto token_validation_timer = std::make_unique<base::OneShotTimer>(
+ timer_mock_task_runner->GetMockTickClock());
token_validation_timer->SetTaskRunner(timer_mock_task_runner);
- auto forced_subscription_timer =
- std::make_unique<base::OneShotTimer>(tick_clock_.get());
+ auto forced_subscription_timer = std::make_unique<base::OneShotTimer>(
+ timer_mock_task_runner->GetMockTickClock());
forced_subscription_timer->SetTaskRunner(timer_mock_task_runner);
return std::make_unique<BreakingNewsGCMAppHandler>(
mock_gcm_driver_.get(), mock_instance_id_driver_.get(), pref_service(),
std::move(wrapped_mock_subscription_manager), base::Bind(&ParseJson),
- clock_.get(), std::move(token_validation_timer),
+ timer_mock_task_runner->GetMockClock(),
+ std::move(token_validation_timer),
std::move(forced_subscription_timer));
}
@@ -312,8 +310,6 @@ class BreakingNewsGCMAppHandlerTest : public testing::Test {
std::unique_ptr<StrictMock<MockGCMDriver>> mock_gcm_driver_;
std::unique_ptr<StrictMock<MockInstanceIDDriver>> mock_instance_id_driver_;
std::unique_ptr<StrictMock<MockInstanceID>> mock_instance_id_;
- std::unique_ptr<TickClock> tick_clock_;
- std::unique_ptr<Clock> clock_;
};
TEST_F(BreakingNewsGCMAppHandlerTest,
@@ -931,7 +927,8 @@ TEST_F(BreakingNewsGCMAppHandlerTest, ShouldReportMissingAction) {
histogram_tester.GetAllSamples(
"NewTabPage.ContentSuggestions.BreakingNews.ReceivedMessageAction"),
ElementsAre(base::Bucket(
- /*min=*/metrics::ReceivedMessageAction::NO_ACTION, /*count=*/1)));
+ /*min=*/static_cast<int>(metrics::ReceivedMessageAction::NO_ACTION),
+ /*count=*/1)));
}
TEST_F(BreakingNewsGCMAppHandlerTest, ShouldReportInvalidAction) {
@@ -957,7 +954,8 @@ TEST_F(BreakingNewsGCMAppHandlerTest, ShouldReportInvalidAction) {
histogram_tester.GetAllSamples(
"NewTabPage.ContentSuggestions.BreakingNews.ReceivedMessageAction"),
ElementsAre(
- base::Bucket(/*min=*/metrics::ReceivedMessageAction::INVALID_ACTION,
+ base::Bucket(/*min=*/static_cast<int>(
+ metrics::ReceivedMessageAction::INVALID_ACTION),
/*count=*/1)));
}
@@ -984,7 +982,8 @@ TEST_F(BreakingNewsGCMAppHandlerTest, ShouldReportPushToRefreshAction) {
histogram_tester.GetAllSamples(
"NewTabPage.ContentSuggestions.BreakingNews.ReceivedMessageAction"),
ElementsAre(
- base::Bucket(/*min=*/metrics::ReceivedMessageAction::PUSH_TO_REFRESH,
+ base::Bucket(/*min=*/static_cast<int>(
+ metrics::ReceivedMessageAction::PUSH_TO_REFRESH),
/*count=*/1)));
}
@@ -1012,7 +1011,9 @@ TEST_F(BreakingNewsGCMAppHandlerTest, ShouldReportPushByValueAction) {
histogram_tester.GetAllSamples(
"NewTabPage.ContentSuggestions.BreakingNews.ReceivedMessageAction"),
ElementsAre(base::Bucket(
- /*min=*/metrics::ReceivedMessageAction::PUSH_BY_VALUE, /*count=*/1)));
+ /*min=*/static_cast<int>(
+ metrics::ReceivedMessageAction::PUSH_BY_VALUE),
+ /*count=*/1)));
}
TEST_F(BreakingNewsGCMAppHandlerTest,
diff --git a/chromium/components/ntp_snippets/breaking_news/breaking_news_metrics.h b/chromium/components/ntp_snippets/breaking_news/breaking_news_metrics.h
index 1b787ed677c..a80e78aef83 100644
--- a/chromium/components/ntp_snippets/breaking_news/breaking_news_metrics.h
+++ b/chromium/components/ntp_snippets/breaking_news/breaking_news_metrics.h
@@ -20,7 +20,7 @@ void OnUnsubscriptionRequestCompleted(const Status& status);
// don't remove or reorder elements, only add new ones at the end (before
// COUNT), and keep in sync with ContentSuggestionsBreakingNewsMessageAction in
// enums.xml.
-enum ReceivedMessageAction {
+enum class ReceivedMessageAction {
NO_ACTION = 0,
INVALID_ACTION = 1,
PUSH_BY_VALUE = 2,
diff --git a/chromium/components/ntp_snippets/breaking_news/subscription_json_request_unittest.cc b/chromium/components/ntp_snippets/breaking_news/subscription_json_request_unittest.cc
index 2625529a0d0..8ff89d1a5b6 100644
--- a/chromium/components/ntp_snippets/breaking_news/subscription_json_request_unittest.cc
+++ b/chromium/components/ntp_snippets/breaking_news/subscription_json_request_unittest.cc
@@ -5,7 +5,6 @@
#include "components/ntp_snippets/breaking_news/subscription_json_request.h"
#include "base/json/json_reader.h"
-#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/test/gtest_util.h"
#include "base/test/mock_callback.h"
diff --git a/chromium/components/ntp_snippets/callbacks.h b/chromium/components/ntp_snippets/callbacks.h
index 2d9efcec22a..16d417af4dd 100644
--- a/chromium/components/ntp_snippets/callbacks.h
+++ b/chromium/components/ntp_snippets/callbacks.h
@@ -29,6 +29,9 @@ using FetchDoneCallback =
// ContentSuggestionsProvider.
using ImageFetchedCallback = base::OnceCallback<void(const gfx::Image&)>;
+using ImageDataFetchedCallback =
+ base::OnceCallback<void(const std::string& image_data)>;
+
// Returns the list of dismissed suggestions when invoked. Currently only used
// for debugging methods to check the internal state of a provider.
using DismissedSuggestionsCallback = base::OnceCallback<void(
diff --git a/chromium/components/ntp_snippets/content_suggestions_metrics.cc b/chromium/components/ntp_snippets/content_suggestions_metrics.cc
index feec62e796a..5c6746ce293 100644
--- a/chromium/components/ntp_snippets/content_suggestions_metrics.cc
+++ b/chromium/components/ntp_snippets/content_suggestions_metrics.cc
@@ -79,7 +79,7 @@ const char kPerCategoryHistogramFormat[] = "%s.%s";
// and contains exactly the values to be recorded in UMA. Don't remove or
// reorder elements, only add new ones at the end (before COUNT), and keep in
// sync with ContentSuggestionsCategory in histograms.xml.
-enum HistogramCategories {
+enum class HistogramCategories {
EXPERIMENTAL,
RECENT_TABS,
DOWNLOADS,
diff --git a/chromium/components/ntp_snippets/content_suggestions_provider.h b/chromium/components/ntp_snippets/content_suggestions_provider.h
index c22826f8880..aa67c151bba 100644
--- a/chromium/components/ntp_snippets/content_suggestions_provider.h
+++ b/chromium/components/ntp_snippets/content_suggestions_provider.h
@@ -95,6 +95,15 @@ class ContentSuggestionsProvider {
virtual void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
ImageFetchedCallback callback) = 0;
+ // Fetches the image data for the suggestion with the given ID and returns it
+ // through the callback. This fetch may occur locally or from the internet.
+ // If that suggestion doesn't exist, doesn't have an image or if the fetch
+ // fails, the callback gets empty data. The callback will not be called
+ // synchronously.
+ virtual void FetchSuggestionImageData(
+ const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) = 0;
+
// Fetches more suggestions for the given category. The new suggestions
// will not include any suggestion of the |known_suggestion_ids| sets.
// As a result of this call, the provider:
diff --git a/chromium/components/ntp_snippets/content_suggestions_service.cc b/chromium/components/ntp_snippets/content_suggestions_service.cc
index 0cefd1e1cb7..0fc12b6b729 100644
--- a/chromium/components/ntp_snippets/content_suggestions_service.cc
+++ b/chromium/components/ntp_snippets/content_suggestions_service.cc
@@ -11,7 +11,6 @@
#include "base/bind.h"
#include "base/location.h"
-#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
@@ -163,6 +162,20 @@ void ContentSuggestionsService::FetchSuggestionImage(
suggestion_id, std::move(callback));
}
+void ContentSuggestionsService::FetchSuggestionImageData(
+ const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) {
+ if (!providers_by_category_.count(suggestion_id.category())) {
+ LOG(WARNING) << "Requested image for suggestion " << suggestion_id
+ << " for unavailable category " << suggestion_id.category();
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), std::string()));
+ return;
+ }
+ providers_by_category_[suggestion_id.category()]->FetchSuggestionImageData(
+ suggestion_id, std::move(callback));
+}
+
// TODO(jkrcal): Split the favicon fetching into a separate class.
void ContentSuggestionsService::FetchSuggestionFavicon(
const ContentSuggestion::ID& suggestion_id,
diff --git a/chromium/components/ntp_snippets/content_suggestions_service.h b/chromium/components/ntp_snippets/content_suggestions_service.h
index 0a0c4c49170..6cc866c2933 100644
--- a/chromium/components/ntp_snippets/content_suggestions_service.h
+++ b/chromium/components/ntp_snippets/content_suggestions_service.h
@@ -92,7 +92,7 @@ class ContentSuggestionsService : public KeyedService,
virtual ~Observer() = default;
};
- enum State {
+ enum class State {
ENABLED,
DISABLED,
};
@@ -143,6 +143,13 @@ class ContentSuggestionsService : public KeyedService,
void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
ImageFetchedCallback callback);
+ // Fetches the image data for the suggestion with the given |suggestion_id|
+ // and runs the |callback|. If that suggestion doesn't exist or the fetch
+ // fails, the callback gets empty data. The callback will not be called
+ // synchronously.
+ void FetchSuggestionImageData(const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback);
+
// Fetches the favicon from local cache (if larger than or equal to
// |minimum_size_in_pixel|) or from Google server (if there is no icon in the
// cache) and returns the results in the callback. If that suggestion doesn't
diff --git a/chromium/components/ntp_snippets/contextual/cluster.cc b/chromium/components/ntp_snippets/contextual/cluster.cc
new file mode 100644
index 00000000000..b706a794850
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/cluster.cc
@@ -0,0 +1,46 @@
+// Copyright 2018 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/ntp_snippets/contextual/cluster.h"
+
+#include "components/ntp_snippets/contextual/contextual_suggestion.h"
+
+namespace contextual_suggestions {
+
+Cluster::Cluster() = default;
+
+// MSVC doesn't support defaulted move constructors, so we have to define it
+// ourselves.
+Cluster::Cluster(Cluster&& other) noexcept
+ : title(std::move(other.title)),
+ suggestions(std::move(other.suggestions)) {}
+
+Cluster::~Cluster() = default;
+
+ClusterBuilder::ClusterBuilder(const std::string& title) {
+ cluster_.title = title;
+}
+
+ClusterBuilder::ClusterBuilder(const ClusterBuilder& other) {
+ cluster_.title = other.cluster_.title;
+ for (const auto& suggestion : other.cluster_.suggestions) {
+ AddSuggestion(SuggestionBuilder(suggestion.url)
+ .Title(suggestion.title)
+ .PublisherName(suggestion.publisher_name)
+ .Snippet(suggestion.snippet)
+ .ImageId(suggestion.image_id)
+ .Build());
+ }
+}
+
+ClusterBuilder& ClusterBuilder::AddSuggestion(ContextualSuggestion suggestion) {
+ cluster_.suggestions.emplace_back(std::move(suggestion));
+ return *this;
+}
+
+Cluster ClusterBuilder::Build() {
+ return std::move(cluster_);
+}
+
+} // namespace contextual_suggestions \ No newline at end of file
diff --git a/chromium/components/ntp_snippets/contextual/cluster.h b/chromium/components/ntp_snippets/contextual/cluster.h
new file mode 100644
index 00000000000..621253867c1
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/cluster.h
@@ -0,0 +1,49 @@
+// Copyright 2018 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_NTP_SNIPPETS_CONTEXTUAL_CLUSTER_H_
+#define COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CLUSTER_H_
+
+#include "components/ntp_snippets/contextual/contextual_suggestion.h"
+
+using ntp_snippets::ContextualSuggestion;
+using ntp_snippets::SuggestionBuilder;
+
+namespace contextual_suggestions {
+
+// A structure representing a suggestion cluster.
+struct Cluster {
+ Cluster();
+ Cluster(Cluster&&) noexcept;
+ ~Cluster();
+
+ std::string title;
+ std::vector<ContextualSuggestion> suggestions;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Cluster);
+};
+
+// Allows concise construction of a cluster and copying, which cluster
+// doesn't allow.
+class ClusterBuilder {
+ public:
+ ClusterBuilder(const std::string& title);
+
+ // Allow copying for ease of validation when testing.
+ ClusterBuilder(const ClusterBuilder& other);
+ ClusterBuilder& AddSuggestion(ContextualSuggestion suggestion);
+ Cluster Build();
+
+ private:
+ Cluster cluster_;
+};
+
+using FetchClustersCallback =
+ base::OnceCallback<void(std::string peek_text,
+ std::vector<Cluster> clusters)>;
+
+} // namespace contextual_suggestions
+
+#endif // COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CLUSTER_H_ \ No newline at end of file
diff --git a/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service.cc b/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service.cc
index dd12e915788..d7bdc7ac3ba 100644
--- a/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service.cc
+++ b/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service.cc
@@ -11,70 +11,87 @@
#include "base/bind.h"
#include "base/memory/ptr_util.h"
+#include "components/ntp_snippets/contextual/cluster.h"
#include "components/ntp_snippets/remote/cached_image_fetcher.h"
#include "components/ntp_snippets/remote/remote_suggestions_database.h"
#include "components/ntp_snippets/remote/remote_suggestions_provider_impl.h"
+#include "contextual_content_suggestions_service_proxy.h"
#include "ui/gfx/image/image.h"
namespace ntp_snippets {
+using contextual_suggestions::Cluster;
+using contextual_suggestions::ContextualSuggestionsMetricsReporterProvider;
+using contextual_suggestions::FetchClustersCallback;
+
+namespace {
+bool IsEligibleURL(const GURL& url) {
+ return url.is_valid() && url.SchemeIsHTTPOrHTTPS() && !url.HostIsIPAddress();
+}
+
+} // namespace
+
ContextualContentSuggestionsService::ContextualContentSuggestionsService(
std::unique_ptr<ContextualSuggestionsFetcher>
contextual_suggestions_fetcher,
std::unique_ptr<CachedImageFetcher> image_fetcher,
- std::unique_ptr<RemoteSuggestionsDatabase> contextual_suggestions_database)
+ std::unique_ptr<RemoteSuggestionsDatabase> contextual_suggestions_database,
+ std::unique_ptr<ContextualSuggestionsMetricsReporterProvider>
+ metrics_reporter_provider)
: contextual_suggestions_database_(
std::move(contextual_suggestions_database)),
contextual_suggestions_fetcher_(
std::move(contextual_suggestions_fetcher)),
- image_fetcher_(std::move(image_fetcher)) {}
+ image_fetcher_(std::move(image_fetcher)),
+ metrics_reporter_provider_(std::move(metrics_reporter_provider)) {}
ContextualContentSuggestionsService::~ContextualContentSuggestionsService() =
default;
-void ContextualContentSuggestionsService::FetchContextualSuggestions(
+void ContextualContentSuggestionsService::FetchContextualSuggestionClusters(
const GURL& url,
- FetchContextualSuggestionsCallback callback) {
- contextual_suggestions_fetcher_->FetchContextualSuggestions(
- url,
- base::BindOnce(
- &ContextualContentSuggestionsService::DidFetchContextualSuggestions,
- base::Unretained(this), url, std::move(callback)));
+ FetchClustersCallback callback,
+ ReportFetchMetricsCallback metrics_callback) {
+ // TODO(pnoland): Also check that the url is safe.
+ if (IsEligibleURL(url)) {
+ contextual_suggestions_fetcher_->FetchContextualSuggestionsClusters(
+ url, std::move(callback), std::move(metrics_callback));
+ } else {
+ std::move(callback).Run("", {});
+ }
}
void ContextualContentSuggestionsService::FetchContextualSuggestionImage(
const ContentSuggestion::ID& suggestion_id,
+ const GURL& image_url,
+ ImageFetchedCallback callback) {
+ image_fetcher_->FetchSuggestionImage(suggestion_id, image_url,
+ ImageDataFetchedCallback(),
+ std::move(callback));
+}
+
+void ContextualContentSuggestionsService::FetchContextualSuggestionImageLegacy(
+ const ContentSuggestion::ID& suggestion_id,
ImageFetchedCallback callback) {
const std::string& id_within_category = suggestion_id.id_within_category();
auto image_url_iterator = image_url_by_id_.find(id_within_category);
- if (image_url_iterator != image_url_by_id_.end()) {
- GURL image_url = image_url_iterator->second;
- image_fetcher_->FetchSuggestionImage(suggestion_id, image_url,
- std::move(callback));
- } else {
+ if (image_url_iterator == image_url_by_id_.end()) {
DVLOG(1) << "FetchContextualSuggestionImage unknown image"
<< " id_within_category: " << id_within_category;
std::move(callback).Run(gfx::Image());
+ return;
}
+
+ GURL image_url = image_url_iterator->second;
+ FetchContextualSuggestionImage(suggestion_id, image_url, std::move(callback));
}
-// TODO(gaschler): Cache contextual suggestions at run-time.
-void ContextualContentSuggestionsService::DidFetchContextualSuggestions(
- const GURL& url,
- FetchContextualSuggestionsCallback callback,
- Status status,
- ContextualSuggestionsFetcher::OptionalSuggestions fetched_suggestions) {
- std::vector<ContentSuggestion> suggestions;
- if (fetched_suggestions.has_value()) {
- for (const std::unique_ptr<ContextualSuggestion>& suggestion :
- fetched_suggestions.value()) {
- suggestions.emplace_back(suggestion->ToContentSuggestion());
- ContentSuggestion::ID id = suggestions.back().id();
- GURL image_url = suggestion->salient_image_url();
- image_url_by_id_[id.id_within_category()] = image_url;
- }
- }
- std::move(callback).Run(status, url, std::move(suggestions));
+std::unique_ptr<
+ contextual_suggestions::ContextualContentSuggestionsServiceProxy>
+ContextualContentSuggestionsService::CreateProxy() {
+ return std::make_unique<
+ contextual_suggestions::ContextualContentSuggestionsServiceProxy>(
+ this, metrics_reporter_provider_->CreateMetricsReporter());
}
} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service.h b/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service.h
index e24fa661c24..7f40448378d 100644
--- a/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service.h
+++ b/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service.h
@@ -15,10 +15,21 @@
#include "components/image_fetcher/core/image_fetcher.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/ntp_snippets/callbacks.h"
+#include "components/ntp_snippets/content_suggestion.h"
#include "components/ntp_snippets/contextual/contextual_suggestions_fetcher.h"
+#include "components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
+
+namespace contextual_suggestions {
+class ContextualContentSuggestionsServiceProxy;
+}
namespace ntp_snippets {
+using contextual_suggestions::Cluster;
+using contextual_suggestions::FetchClustersCallback;
+using contextual_suggestions::ReportFetchMetricsCallback;
+
class CachedImageFetcher;
class RemoteSuggestionsDatabase;
@@ -31,31 +42,36 @@ class ContextualContentSuggestionsService : public KeyedService {
contextual_suggestions_fetcher,
std::unique_ptr<CachedImageFetcher> image_fetcher,
std::unique_ptr<RemoteSuggestionsDatabase>
- contextual_suggestions_database);
+ contextual_suggestions_database,
+ std::unique_ptr<
+ contextual_suggestions::ContextualSuggestionsMetricsReporterProvider>
+ metrics_reporter_provider);
~ContextualContentSuggestionsService() override;
- using FetchContextualSuggestionsCallback =
- base::OnceCallback<void(Status status_code,
- const GURL& url,
- std::vector<ContentSuggestion> suggestions)>;
-
// Asynchronously fetches contextual suggestions for the given URL.
- void FetchContextualSuggestions(const GURL& url,
- FetchContextualSuggestionsCallback callback);
+ virtual void FetchContextualSuggestionClusters(
+ const GURL& url,
+ FetchClustersCallback callback,
+ ReportFetchMetricsCallback metrics_callback);
+
+ // Fetches an image pointed to by |url| and internally caches it using
+ // |suggestion_id|. Asynchronous if cache or network is queried.
+ virtual void FetchContextualSuggestionImage(
+ const ContentSuggestion::ID& suggestion_id,
+ const GURL& url,
+ ImageFetchedCallback callback);
// Fetches an image for a given contextual suggestion ID.
// Asynchronous if cache or network is queried.
- void FetchContextualSuggestionImage(
+ virtual void FetchContextualSuggestionImageLegacy(
const ContentSuggestion::ID& suggestion_id,
ImageFetchedCallback callback);
- private:
- void DidFetchContextualSuggestions(
- const GURL& url,
- FetchContextualSuggestionsCallback callback,
- Status status,
- ContextualSuggestionsFetcher::OptionalSuggestions fetched_suggestions);
+ std::unique_ptr<
+ contextual_suggestions::ContextualContentSuggestionsServiceProxy>
+ CreateProxy();
+ private:
// Cache for images of contextual suggestions, needed by CachedImageFetcher.
std::unique_ptr<RemoteSuggestionsDatabase> contextual_suggestions_database_;
@@ -64,6 +80,10 @@ class ContextualContentSuggestionsService : public KeyedService {
std::unique_ptr<CachedImageFetcher> image_fetcher_;
+ std::unique_ptr<
+ contextual_suggestions::ContextualSuggestionsMetricsReporterProvider>
+ metrics_reporter_provider_;
+
// Look up by ContentSuggestion::ID::id_within_category() aka std::string to
// get image URL.
std::map<std::string, GURL> image_url_by_id_;
diff --git a/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_proxy.cc b/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_proxy.cc
new file mode 100644
index 00000000000..9f3a2e3fb66
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_proxy.cc
@@ -0,0 +1,140 @@
+// Copyright 2018 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/ntp_snippets/contextual/contextual_content_suggestions_service_proxy.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "ui/gfx/image/image.h"
+#include "url/gurl.h"
+
+namespace contextual_suggestions {
+
+namespace {
+// TODO(pnoland): check if this is the correct base URL for all images.
+static constexpr char kImageURLFormat[] =
+ "http://www.google.com/images?q=tbn:%s";
+
+GURL ImageUrlFromId(const std::string& image_id) {
+ return GURL(base::StringPrintf(kImageURLFormat, image_id.c_str()));
+}
+} // namespace
+
+ContextualContentSuggestionsServiceProxy::
+ ContextualContentSuggestionsServiceProxy(
+ ntp_snippets::ContextualContentSuggestionsService* service,
+ std::unique_ptr<ContextualSuggestionsMetricsReporter> metrics_reporter)
+ : service_(service),
+ metrics_reporter_(std::move(metrics_reporter)),
+ last_ukm_source_id_(ukm::kInvalidSourceId),
+ weak_ptr_factory_(this) {}
+
+ContextualContentSuggestionsServiceProxy::
+ ~ContextualContentSuggestionsServiceProxy() {}
+
+void ContextualContentSuggestionsServiceProxy::FetchContextualSuggestions(
+ const GURL& url,
+ ClustersCallback callback) {
+ service_->FetchContextualSuggestionClusters(
+ url,
+ base::BindOnce(
+ &ContextualContentSuggestionsServiceProxy::CacheSuggestions,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
+ base::BindRepeating(
+ &ContextualContentSuggestionsServiceProxy::ReportEvent,
+ weak_ptr_factory_.GetWeakPtr(), last_ukm_source_id_));
+}
+
+void ContextualContentSuggestionsServiceProxy::FetchContextualSuggestionImage(
+ const std::string& suggestion_id,
+ ntp_snippets::ImageFetchedCallback callback) {
+ auto suggestion_iter = suggestions_.find(suggestion_id);
+ if (suggestion_iter == suggestions_.end()) {
+ DVLOG(1) << "Unkown suggestion ID: " << suggestion_id;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), gfx::Image()));
+ return;
+ }
+
+ FetchImageImpl(suggestion_iter->second.image_id, std::move(callback));
+}
+
+void ContextualContentSuggestionsServiceProxy::FetchContextualSuggestionFavicon(
+ const std::string& suggestion_id,
+ ntp_snippets::ImageFetchedCallback callback) {
+ auto suggestion_iter = suggestions_.find(suggestion_id);
+ if (suggestion_iter == suggestions_.end()) {
+ DVLOG(1) << "Unkown suggestion ID: " << suggestion_id;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), gfx::Image()));
+ return;
+ }
+
+ FetchImageImpl(suggestion_iter->second.favicon_image_id, std::move(callback));
+}
+
+void ContextualContentSuggestionsServiceProxy::ClearState() {
+ suggestions_.clear();
+ weak_ptr_factory_.InvalidateWeakPtrs();
+}
+
+void ContextualContentSuggestionsServiceProxy::ReportEvent(
+ ukm::SourceId ukm_source_id,
+ ContextualSuggestionsEvent event) {
+ // TODO(pnoland): investigate how we can get into this state(one known
+ // example is if we switch tabs and there's no committed navigation in the new
+ // tab) and prevent it from happening. Replace the early return with a DCHECK
+ // once this is done.
+ if (ukm_source_id == ukm::kInvalidSourceId) {
+ return;
+ }
+
+ // Flush the previous page (if any) and setup the new page.
+ if (ukm_source_id != last_ukm_source_id_) {
+ if (last_ukm_source_id_ != ukm::kInvalidSourceId)
+ metrics_reporter_->Flush();
+ last_ukm_source_id_ = ukm_source_id;
+ metrics_reporter_->SetupForPage(ukm_source_id);
+ }
+
+ metrics_reporter_->RecordEvent(event);
+}
+
+void ContextualContentSuggestionsServiceProxy::FlushMetrics() {
+ if (last_ukm_source_id_ != ukm::kInvalidSourceId)
+ metrics_reporter_->Flush();
+ last_ukm_source_id_ = ukm::kInvalidSourceId;
+}
+
+void ContextualContentSuggestionsServiceProxy::FetchImageImpl(
+ const std::string& image_id,
+ ntp_snippets::ImageFetchedCallback callback) {
+ GURL image_url = ImageUrlFromId(image_id);
+
+ ntp_snippets::ContentSuggestion::ID synthetic_cache_id(
+ ntp_snippets::Category::FromKnownCategory(
+ ntp_snippets::KnownCategories::CONTEXTUAL),
+ image_id);
+
+ service_->FetchContextualSuggestionImage(synthetic_cache_id, image_url,
+ std::move(callback));
+}
+
+void ContextualContentSuggestionsServiceProxy::CacheSuggestions(
+ ClustersCallback callback,
+ std::string peek_text,
+ std::vector<Cluster> clusters) {
+ suggestions_.clear();
+ for (auto& cluster : clusters) {
+ for (auto& suggestion : cluster.suggestions) {
+ suggestions_.emplace(std::make_pair(suggestion.id, suggestion));
+ }
+ }
+ std::move(callback).Run(peek_text, std::move(clusters));
+}
+
+} // namespace contextual_suggestions
diff --git a/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_proxy.h b/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_proxy.h
new file mode 100644
index 00000000000..e448659558a
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_proxy.h
@@ -0,0 +1,89 @@
+// Copyright 2018 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_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_CONTENT_SUGGESTIONS_SERVICE_PROXY_H_
+#define COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_CONTENT_SUGGESTIONS_SERVICE_PROXY_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "components/ntp_snippets/contextual/cluster.h"
+#include "components/ntp_snippets/contextual/contextual_content_suggestions_service.h"
+
+class GURL;
+
+namespace contextual_suggestions {
+
+// Class providing access to the Contextual Content Suggestions Service and
+// caching the list of current suggestions shown in a tab. It is owned by a UI
+// object. There could be multiple instances of serivce proxy. Either can be
+// torn down with a part of UI that owns it, which doesn't affect other proxies.
+class ContextualContentSuggestionsServiceProxy {
+ public:
+ using ClustersCallback = ntp_snippets::FetchClustersCallback;
+ using Cluster = ntp_snippets::Cluster;
+
+ ContextualContentSuggestionsServiceProxy(
+ ntp_snippets::ContextualContentSuggestionsService* service,
+ std::unique_ptr<ContextualSuggestionsMetricsReporter> metrics_reporter);
+ ~ContextualContentSuggestionsServiceProxy();
+
+ // Fetches contextual suggestions for a given |url|.
+ void FetchContextualSuggestions(const GURL& url, ClustersCallback callback);
+
+ // Fetches an image for a contextual suggestion with specified
+ // |suggestion_id|.
+ void FetchContextualSuggestionImage(
+ const std::string& suggestion_id,
+ ntp_snippets::ImageFetchedCallback callback);
+
+ // Fetches a favicon for a contextual suggestion with specified
+ // |suggestion_id|.
+ void FetchContextualSuggestionFavicon(
+ const std::string& suggestion_id,
+ ntp_snippets::ImageFetchedCallback callback);
+
+ // Clears the state of the proxy.
+ void ClearState();
+
+ // Reports user interface event to the service.
+ void ReportEvent(ukm::SourceId, ContextualSuggestionsEvent event);
+
+ // Ensures that all metrics are properly flushed.
+ void FlushMetrics();
+
+ private:
+ void FetchImageImpl(const std::string& image_id,
+ ntp_snippets::ImageFetchedCallback callback);
+
+ void CacheSuggestions(ClustersCallback callback,
+ std::string peek_text,
+ std::vector<Cluster> clusters);
+ // Pointer to the service.
+ ntp_snippets::ContextualContentSuggestionsService* service_;
+ // Cache of contextual suggestions.
+ std::map<std::string, ntp_snippets::ContextualSuggestion> suggestions_;
+
+ // Sink for reporting metrics for this proxy.
+ std::unique_ptr<contextual_suggestions::ContextualSuggestionsMetricsReporter>
+ metrics_reporter_;
+
+ // The most recent SourceId in use by metrics_reporter_, or
+ // ukm::kInvalidSourceId.
+ ukm::SourceId last_ukm_source_id_;
+
+ // Weak pointer factory to cancel pending callbacks.
+ base::WeakPtrFactory<ContextualContentSuggestionsServiceProxy>
+ weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContextualContentSuggestionsServiceProxy);
+};
+
+} // namespace contextual_suggestions
+
+#endif // COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_CONTENT_SUGGESTIONS_SERVICE_PROXY_H_
diff --git a/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_proxy_unittest.cc b/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_proxy_unittest.cc
new file mode 100644
index 00000000000..24d1bafe033
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_proxy_unittest.cc
@@ -0,0 +1,116 @@
+// Copyright 2018 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/ntp_snippets/contextual/contextual_content_suggestions_service_proxy.h"
+
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "components/ntp_snippets/contextual/contextual_content_suggestions_service.h"
+#include "components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.h"
+#include "components/ntp_snippets/remote/cached_image_fetcher.h"
+#include "components/ntp_snippets/remote/remote_suggestions_database.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::IsEmpty;
+using testing::Pointee;
+
+namespace contextual_suggestions {
+
+using Cluster = ntp_snippets::Cluster;
+using ClustersCallback = ntp_snippets::FetchClustersCallback;
+
+namespace {
+
+static const std::string kTestPeekText("Test peek test");
+static const std::string kValidFromUrl = "http://some.url";
+
+class FakeContextualContentSuggestionsService
+ : public ntp_snippets::ContextualContentSuggestionsService {
+ public:
+ FakeContextualContentSuggestionsService();
+ ~FakeContextualContentSuggestionsService() override;
+
+ void FetchContextualSuggestionClusters(
+ const GURL& url,
+ ClustersCallback callback,
+ ReportFetchMetricsCallback metrics_callback) override {
+ clusters_callback_ = std::move(callback);
+ }
+
+ void RunClustersCallback(std::string peek_text,
+ std::vector<Cluster> clusters) {
+ std::move(clusters_callback_)
+ .Run(std::move(peek_text), std::move(clusters));
+ }
+
+ private:
+ ClustersCallback clusters_callback_;
+};
+
+FakeContextualContentSuggestionsService::
+ FakeContextualContentSuggestionsService()
+ : ContextualContentSuggestionsService(nullptr, nullptr, nullptr, nullptr) {}
+
+FakeContextualContentSuggestionsService::
+ ~FakeContextualContentSuggestionsService() {}
+
+// GMock does not support movable-only types (Cluster).
+// Instead WrappedRun is used as callback and it redirects the call to a
+// method without movable-only types, which is then mocked.
+class MockClustersCallback {
+ public:
+ void WrappedRun(std::string peek_text, std::vector<Cluster> clusters) {
+ Run(peek_text, &clusters);
+ }
+
+ ClustersCallback ToOnceCallback() {
+ return base::BindOnce(&MockClustersCallback::WrappedRun,
+ base::Unretained(this));
+ }
+
+ MOCK_METHOD2(Run,
+ void(const std::string& peek_text,
+ std::vector<Cluster>* clusters));
+};
+
+} // namespace
+
+class ContextualContentSuggestionsServiceProxyTest : public testing::Test {
+ public:
+ void SetUp() override;
+
+ FakeContextualContentSuggestionsService* service() { return service_.get(); }
+
+ ContextualContentSuggestionsServiceProxy* proxy() { return proxy_.get(); }
+
+ private:
+ std::unique_ptr<FakeContextualContentSuggestionsService> service_;
+ std::unique_ptr<ContextualContentSuggestionsServiceProxy> proxy_;
+};
+
+void ContextualContentSuggestionsServiceProxyTest::SetUp() {
+ service_ = std::make_unique<FakeContextualContentSuggestionsService>();
+ auto metrics_reporter =
+ std::make_unique<ContextualSuggestionsMetricsReporter>();
+ proxy_ = std::make_unique<ContextualContentSuggestionsServiceProxy>(
+ service_.get(), std::move(metrics_reporter));
+}
+
+TEST_F(ContextualContentSuggestionsServiceProxyTest,
+ FetchSuggestionsWhenEmpty) {
+ MockClustersCallback mock_cluster_callback;
+
+ proxy()->FetchContextualSuggestions(GURL(kValidFromUrl),
+ mock_cluster_callback.ToOnceCallback());
+ EXPECT_CALL(mock_cluster_callback, Run(kTestPeekText, Pointee(IsEmpty())));
+ service()->RunClustersCallback(kTestPeekText, std::vector<Cluster>());
+}
+
+// TODO(fgorski): More tests will be added, once we have the suggestions
+// restructured.
+
+} // namespace contextual_suggestions
diff --git a/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_unittest.cc b/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_unittest.cc
index 9c3e7dc67d4..65553517b75 100644
--- a/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_unittest.cc
+++ b/chromium/components/ntp_snippets/contextual/contextual_content_suggestions_service_unittest.cc
@@ -9,6 +9,7 @@
#include <vector>
#include "base/bind.h"
+#include "base/bind_helpers.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
@@ -18,6 +19,7 @@
#include "components/ntp_snippets/content_suggestion.h"
#include "components/ntp_snippets/contextual/contextual_suggestion.h"
#include "components/ntp_snippets/contextual/contextual_suggestions_fetcher.h"
+#include "components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.h"
#include "components/ntp_snippets/remote/cached_image_fetcher.h"
#include "components/ntp_snippets/remote/json_to_categories.h"
#include "components/ntp_snippets/remote/remote_suggestions_database.h"
@@ -28,6 +30,8 @@
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_unittest_util.h"
+using contextual_suggestions::ClusterBuilder;
+using contextual_suggestions::ReportFetchMetricsCallback;
using testing::_;
using testing::AllOf;
using testing::ElementsAre;
@@ -40,37 +44,37 @@ namespace ntp_snippets {
namespace {
-ACTION_TEMPLATE(MoveArg,
- HAS_1_TEMPLATE_PARAMS(int, k),
- AND_1_VALUE_PARAMS(out)) {
- *out = std::move(*::testing::get<k>(args));
+class MockClustersCallback {
+ public:
+ void Done(std::string peek_text, std::vector<Cluster> clusters) {
+ EXPECT_FALSE(has_run);
+ has_run = true;
+ response_peek_text = peek_text;
+ response_clusters = std::move(clusters);
+ }
+
+ bool has_run = false;
+ std::string response_peek_text;
+ std::vector<Cluster> response_clusters;
};
// Always fetches the result that was set by SetFakeResponse.
class FakeContextualSuggestionsFetcher : public ContextualSuggestionsFetcher {
public:
- void FetchContextualSuggestions(
+ void FetchContextualSuggestionsClusters(
const GURL& url,
- SuggestionsAvailableCallback callback) override {
- std::move(callback).Run(fake_status_, std::move(fake_suggestions_));
- fake_suggestions_ = base::nullopt;
+ FetchClustersCallback callback,
+ ReportFetchMetricsCallback metrics_callback) override {
+ std::move(callback).Run("peek text", std::move(fake_suggestions_));
+ fake_suggestions_.clear();
}
- void SetFakeResponse(Status fake_status,
- OptionalSuggestions fake_suggestions) {
- fake_status_ = fake_status;
+ void SetFakeResponse(std::vector<Cluster> fake_suggestions) {
fake_suggestions_ = std::move(fake_suggestions);
}
- const std::string& GetLastStatusForTesting() const override { return empty_; }
- const std::string& GetLastJsonForTesting() const override { return empty_; }
- const GURL& GetFetchUrlForTesting() const override { return empty_url_; }
-
private:
- std::string empty_;
- GURL empty_url_;
- Status fake_status_ = Status::Success();
- OptionalSuggestions fake_suggestions_;
+ std::vector<Cluster> fake_suggestions_;
};
// Always fetches a fake image if the given URL is valid.
@@ -83,6 +87,7 @@ class FakeCachedImageFetcher : public CachedImageFetcher {
void FetchSuggestionImage(const ContentSuggestion::ID&,
const GURL& image_url,
+ ImageDataFetchedCallback image_data_callback,
ImageFetchedCallback callback) override {
gfx::Image image;
if (image_url.is_valid()) {
@@ -92,29 +97,6 @@ class FakeCachedImageFetcher : public CachedImageFetcher {
}
};
-// GMock does not support movable-only types (ContentSuggestion).
-// Instead WrappedRun is used as callback and it redirects the call to a
-// method without movable-only types, which is then mocked.
-class MockFetchContextualSuggestionsCallback {
- public:
- void WrappedRun(Status status,
- const GURL& url,
- std::vector<ContentSuggestion> suggestions) {
- Run(status, url, &suggestions);
- }
-
- ContextualContentSuggestionsService::FetchContextualSuggestionsCallback
- ToOnceCallback() {
- return base::BindOnce(&MockFetchContextualSuggestionsCallback::WrappedRun,
- base::Unretained(this));
- }
-
- MOCK_METHOD3(Run,
- void(Status status_code,
- const GURL& url,
- std::vector<ContentSuggestion>* suggestions));
-};
-
} // namespace
class ContextualContentSuggestionsServiceTest : public testing::Test {
@@ -124,10 +106,13 @@ class ContextualContentSuggestionsServiceTest : public testing::Test {
std::unique_ptr<FakeContextualSuggestionsFetcher> fetcher =
std::make_unique<FakeContextualSuggestionsFetcher>();
fetcher_ = fetcher.get();
+ auto metrics_reporter_provider = std::make_unique<
+ contextual_suggestions::ContextualSuggestionsMetricsReporterProvider>();
source_ = std::make_unique<ContextualContentSuggestionsService>(
std::move(fetcher),
std::make_unique<FakeCachedImageFetcher>(&pref_service_),
- std::unique_ptr<RemoteSuggestionsDatabase>());
+ std::unique_ptr<RemoteSuggestionsDatabase>(),
+ std::move(metrics_reporter_provider));
}
FakeContextualSuggestionsFetcher* fetcher() { return fetcher_; }
@@ -142,55 +127,6 @@ class ContextualContentSuggestionsServiceTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(ContextualContentSuggestionsServiceTest);
};
-TEST_F(ContextualContentSuggestionsServiceTest,
- ShouldFetchContextualSuggestion) {
- MockFetchContextualSuggestionsCallback mock_suggestions_callback;
- const std::string kValidFromUrl = "http://some.url";
- const std::string kToUrl = "http://another.url";
- ContextualSuggestionsFetcher::OptionalSuggestions contextual_suggestions =
- ContextualSuggestion::PtrVector();
- contextual_suggestions->push_back(
- ContextualSuggestion::CreateForTesting(kToUrl, ""));
- fetcher()->SetFakeResponse(Status::Success(),
- std::move(contextual_suggestions));
- EXPECT_CALL(mock_suggestions_callback,
- Run(Property(&Status::IsSuccess, true), GURL(kValidFromUrl),
- Pointee(ElementsAre(AllOf(
- Property(&ContentSuggestion::id,
- Property(&ContentSuggestion::ID::category,
- Category::FromKnownCategory(
- KnownCategories::CONTEXTUAL))),
- Property(&ContentSuggestion::url, GURL(kToUrl)))))));
- source()->FetchContextualSuggestions(
- GURL(kValidFromUrl), mock_suggestions_callback.ToOnceCallback());
- base::RunLoop().RunUntilIdle();
-}
-
-TEST_F(ContextualContentSuggestionsServiceTest,
- ShouldRunCallbackOnEmptyResults) {
- MockFetchContextualSuggestionsCallback mock_suggestions_callback;
- const std::string kEmpty;
- fetcher()->SetFakeResponse(Status::Success(),
- ContextualSuggestion::PtrVector());
- EXPECT_CALL(mock_suggestions_callback, Run(Property(&Status::IsSuccess, true),
- GURL(kEmpty), Pointee(IsEmpty())));
- source()->FetchContextualSuggestions(
- GURL(kEmpty), mock_suggestions_callback.ToOnceCallback());
- base::RunLoop().RunUntilIdle();
-}
-
-TEST_F(ContextualContentSuggestionsServiceTest, ShouldRunCallbackOnError) {
- MockFetchContextualSuggestionsCallback mock_suggestions_callback;
- const std::string kEmpty;
- fetcher()->SetFakeResponse(Status(StatusCode::TEMPORARY_ERROR, ""),
- ContextualSuggestion::PtrVector());
- EXPECT_CALL(mock_suggestions_callback,
- Run(Property(&Status::IsSuccess, false), GURL(kEmpty),
- Pointee(IsEmpty())));
- source()->FetchContextualSuggestions(
- GURL(kEmpty), mock_suggestions_callback.ToOnceCallback());
- base::RunLoop().RunUntilIdle();
-}
TEST_F(ContextualContentSuggestionsServiceTest,
ShouldFetchEmptyImageIfNotFound) {
@@ -200,39 +136,55 @@ TEST_F(ContextualContentSuggestionsServiceTest,
Category::FromKnownCategory(KnownCategories::CONTEXTUAL), kEmpty);
EXPECT_CALL(mock_image_fetched_callback,
Run(Property(&gfx::Image::IsEmpty, true)));
- source()->FetchContextualSuggestionImage(id,
- mock_image_fetched_callback.Get());
+ source()->FetchContextualSuggestionImageLegacy(
+ id, mock_image_fetched_callback.Get());
// TODO(gaschler): Verify with a mock that the image fetcher is not called if
// the id is unknown.
base::RunLoop().RunUntilIdle();
}
TEST_F(ContextualContentSuggestionsServiceTest,
- ShouldFetchImageForPreviouslyFetchedSuggestion) {
- const std::string kValidFromUrl = "http://some.url";
- const std::string kToUrl = "http://another.url";
- const std::string kValidImageUrl = "http://some.url/image.png";
- ContextualSuggestionsFetcher::OptionalSuggestions contextual_suggestions =
- ContextualSuggestion::PtrVector();
- contextual_suggestions->push_back(
- ContextualSuggestion::CreateForTesting(kToUrl, kValidImageUrl));
- fetcher()->SetFakeResponse(Status::Success(),
- std::move(contextual_suggestions));
- MockFetchContextualSuggestionsCallback mock_suggestions_callback;
- std::vector<ContentSuggestion> suggestions;
- EXPECT_CALL(mock_suggestions_callback, Run(_, _, _))
- .WillOnce(MoveArg<2>(&suggestions));
- source()->FetchContextualSuggestions(
- GURL(kValidFromUrl), mock_suggestions_callback.ToOnceCallback());
+ ShouldFetchContextualSuggestionsClusters) {
+ MockClustersCallback mock_callback;
+ std::vector<Cluster> clusters;
+ GURL context_url("http://www.from.url");
+
+ clusters.emplace_back(ClusterBuilder("Title")
+ .AddSuggestion(SuggestionBuilder(context_url)
+ .Title("Title1")
+ .PublisherName("from.url")
+ .Snippet("Summary")
+ .ImageId("abc")
+ .Build())
+ .Build());
+
+ fetcher()->SetFakeResponse(std::move(clusters));
+ source()->FetchContextualSuggestionClusters(
+ context_url,
+ base::BindOnce(&MockClustersCallback::Done,
+ base::Unretained(&mock_callback)),
+ base::DoNothing());
base::RunLoop().RunUntilIdle();
- ASSERT_THAT(suggestions, Not(IsEmpty()));
- base::MockCallback<ImageFetchedCallback> mock_image_fetched_callback;
- EXPECT_CALL(mock_image_fetched_callback,
- Run(Property(&gfx::Image::IsEmpty, false)));
- source()->FetchContextualSuggestionImage(suggestions[0].id(),
- mock_image_fetched_callback.Get());
- base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(mock_callback.has_run);
+}
+
+TEST_F(ContextualContentSuggestionsServiceTest, ShouldRejectInvalidUrls) {
+ std::vector<Cluster> clusters;
+ for (GURL invalid_url :
+ {GURL("htp:/"), GURL("www.foobar"), GURL("http://127.0.0.1/"),
+ GURL("file://some.file"), GURL("chrome://settings"), GURL("")}) {
+ MockClustersCallback mock_callback;
+ source()->FetchContextualSuggestionClusters(
+ invalid_url,
+ base::BindOnce(&MockClustersCallback::Done,
+ base::Unretained(&mock_callback)),
+ base::DoNothing());
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(mock_callback.has_run);
+ EXPECT_EQ(mock_callback.response_peek_text, "");
+ EXPECT_EQ(mock_callback.response_clusters.size(), 0u);
+ }
}
} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/contextual/contextual_json_request.cc b/chromium/components/ntp_snippets/contextual/contextual_json_request.cc
deleted file mode 100644
index cd7d29cfd8f..00000000000
--- a/chromium/components/ntp_snippets/contextual/contextual_json_request.cc
+++ /dev/null
@@ -1,252 +0,0 @@
-// Copyright 2016 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/ntp_snippets/contextual/contextual_json_request.h"
-
-#include <algorithm>
-#include <utility>
-#include <vector>
-
-#include "base/json/json_writer.h"
-#include "base/strings/stringprintf.h"
-#include "base/values.h"
-#include "components/data_use_measurement/core/data_use_user_data.h"
-#include "components/ntp_snippets/category_info.h"
-#include "components/ntp_snippets/features.h"
-#include "components/strings/grit/components_strings.h"
-#include "components/variations/net/variations_http_headers.h"
-#include "components/variations/variations_associated_data.h"
-#include "net/base/load_flags.h"
-#include "net/http/http_response_headers.h"
-#include "net/http/http_status_code.h"
-#include "net/traffic_annotation/network_traffic_annotation.h"
-#include "net/url_request/url_fetcher.h"
-#include "net/url_request/url_request_context_getter.h"
-#include "third_party/icu/source/common/unicode/uloc.h"
-#include "third_party/icu/source/common/unicode/utypes.h"
-
-using net::URLFetcher;
-using net::URLRequestContextGetter;
-using net::HttpRequestHeaders;
-using net::URLRequestStatus;
-
-namespace ntp_snippets {
-
-namespace internal {
-
-namespace {
-
-const int k5xxRetries = 2;
-
-} // namespace
-
-ContextualJsonRequest::ContextualJsonRequest(const ParseJSONCallback& callback)
- : parse_json_callback_(callback), weak_ptr_factory_(this) {}
-
-ContextualJsonRequest::~ContextualJsonRequest() {
- DLOG_IF(ERROR, !request_completed_callback_.is_null())
- << "The CompletionCallback was never called!";
-}
-
-void ContextualJsonRequest::Start(CompletedCallback callback) {
- request_completed_callback_ = std::move(callback);
- url_fetcher_->Start();
-}
-
-std::string ContextualJsonRequest::GetResponseString() const {
- std::string response;
- url_fetcher_->GetResponseAsString(&response);
- return response;
-}
-
-// URLFetcherDelegate overrides
-void ContextualJsonRequest::OnURLFetchComplete(const net::URLFetcher* source) {
- DCHECK_EQ(url_fetcher_.get(), source);
- const URLRequestStatus& status = url_fetcher_->GetStatus();
- int response = url_fetcher_->GetResponseCode();
- // TODO(gaschler): Add UMA metrics for response status code
-
- if (!status.is_success()) {
- std::move(request_completed_callback_)
- .Run(/*result=*/nullptr, FetchResult::URL_REQUEST_STATUS_ERROR,
- /*error_details=*/base::StringPrintf(" %d", status.error()));
- } else if (response != net::HTTP_OK) {
- // TODO(jkrcal): https://crbug.com/609084
- // We need to deal with the edge case again where the auth
- // token expires just before we send the request (in which case we need to
- // fetch a new auth token). We should extract that into a common class
- // instead of adding it to every single class that uses auth tokens.
- std::move(request_completed_callback_)
- .Run(/*result=*/nullptr, FetchResult::HTTP_ERROR,
- /*error_details=*/base::StringPrintf(" %d", response));
- } else {
- ParseJsonResponse();
- }
-}
-
-void ContextualJsonRequest::ParseJsonResponse() {
- std::string json_string;
- bool stores_result_to_string =
- url_fetcher_->GetResponseAsString(&json_string);
- DCHECK(stores_result_to_string);
-
- parse_json_callback_.Run(json_string,
- base::Bind(&ContextualJsonRequest::OnJsonParsed,
- weak_ptr_factory_.GetWeakPtr()),
- base::Bind(&ContextualJsonRequest::OnJsonError,
- weak_ptr_factory_.GetWeakPtr()));
-}
-
-void ContextualJsonRequest::OnJsonParsed(std::unique_ptr<base::Value> result) {
- std::move(request_completed_callback_)
- .Run(std::move(result), FetchResult::SUCCESS,
- /*error_details=*/std::string());
-}
-
-void ContextualJsonRequest::OnJsonError(const std::string& error) {
- std::string json_string;
- url_fetcher_->GetResponseAsString(&json_string);
- LOG(WARNING) << "Received invalid JSON (" << error << "): " << json_string;
- std::move(request_completed_callback_)
- .Run(/*result=*/nullptr, FetchResult::JSON_PARSE_ERROR,
- /*error_details=*/base::StringPrintf(" (error %s)", error.c_str()));
-}
-
-ContextualJsonRequest::Builder::Builder() = default;
-ContextualJsonRequest::Builder::Builder(ContextualJsonRequest::Builder&&) =
- default;
-ContextualJsonRequest::Builder::~Builder() = default;
-
-std::unique_ptr<ContextualJsonRequest> ContextualJsonRequest::Builder::Build()
- const {
- DCHECK(url_request_context_getter_);
- auto request = std::make_unique<ContextualJsonRequest>(parse_json_callback_);
- std::string body = BuildBody();
- std::string headers = BuildHeaders();
- request->url_fetcher_ = BuildURLFetcher(request.get(), headers, body);
-
- // Log the request for debugging network issues.
- VLOG(1) << "Sending a NTP snippets request to " << url_ << ":\n"
- << headers << "\n"
- << body;
-
- return request;
-}
-
-ContextualJsonRequest::Builder&
-ContextualJsonRequest::Builder::SetAuthentication(
- const std::string& account_id,
- const std::string& auth_header) {
- auth_header_ = auth_header;
- return *this;
-}
-
-ContextualJsonRequest::Builder&
-ContextualJsonRequest::Builder::SetParseJsonCallback(
- ParseJSONCallback callback) {
- parse_json_callback_ = callback;
- return *this;
-}
-
-ContextualJsonRequest::Builder& ContextualJsonRequest::Builder::SetUrl(
- const GURL& url) {
- url_ = url;
- return *this;
-}
-
-ContextualJsonRequest::Builder&
-ContextualJsonRequest::Builder::SetUrlRequestContextGetter(
- const scoped_refptr<net::URLRequestContextGetter>& context_getter) {
- url_request_context_getter_ = context_getter;
- return *this;
-}
-
-ContextualJsonRequest::Builder& ContextualJsonRequest::Builder::SetContentUrl(
- const GURL& url) {
- content_url_ = url;
- return *this;
-}
-
-std::string ContextualJsonRequest::Builder::BuildHeaders() const {
- net::HttpRequestHeaders headers;
- headers.SetHeader("Content-Type", "application/json; charset=UTF-8");
- if (!auth_header_.empty()) {
- headers.SetHeader("Authorization", auth_header_);
- }
- // Add X-Client-Data header with experiment IDs from field trials.
- // Note: It's OK to pass SignedIn::kNo if it's unknown, as it does not affect
- // transmission of experiments coming from the variations server.
- variations::AppendVariationHeaders(url_, variations::InIncognito::kNo,
- variations::SignedIn::kNo, &headers);
- return headers.ToString();
-}
-
-std::string ContextualJsonRequest::Builder::BuildBody() const {
- auto request = std::make_unique<base::DictionaryValue>();
-
- request->SetString("url", content_url_.spec());
- auto categories = std::make_unique<base::ListValue>();
- categories->AppendString("RELATED_ARTICLES");
- categories->AppendString("PUBLIC_DEBATE");
- request->Set("categories", std::move(categories));
-
- std::string request_json;
- bool success = base::JSONWriter::WriteWithOptions(
- *request, base::JSONWriter::OPTIONS_PRETTY_PRINT, &request_json);
- DCHECK(success);
- return request_json;
-}
-
-std::unique_ptr<net::URLFetcher>
-ContextualJsonRequest::Builder::BuildURLFetcher(
- net::URLFetcherDelegate* delegate,
- const std::string& headers,
- const std::string& body) const {
- net::NetworkTrafficAnnotationTag traffic_annotation =
- net::DefineNetworkTrafficAnnotation("ntp_contextual_suggestions_fetch",
- R"(
- semantics {
- sender: "New Tab Page Contextual Suggestions Fetch"
- description:
- "Chromium can show contextual suggestions that are related to the "
- "currently visited page on the New Tab page. "
- trigger:
- "Triggered when Home sheet is pulled up."
- data:
- "Only for a white-listed signed-in test user, the URL of the "
- "current tab."
- destination: GOOGLE_OWNED_SERVICE
- }
- policy {
- cookies_allowed: NO
- setting:
- "This feature can be disabled by the flag "
- "contextual-suggestions-carousel."
- chrome_policy {
- NTPContentSuggestionsEnabled {
- NTPContentSuggestionsEnabled: False
- }
- }
- })");
- std::unique_ptr<net::URLFetcher> url_fetcher = net::URLFetcher::Create(
- url_, net::URLFetcher::POST, delegate, traffic_annotation);
- url_fetcher->SetRequestContext(url_request_context_getter_.get());
- url_fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
- net::LOAD_DO_NOT_SAVE_COOKIES);
- data_use_measurement::DataUseUserData::AttachToFetcher(
- url_fetcher.get(),
- data_use_measurement::DataUseUserData::NTP_SNIPPETS_SUGGESTIONS);
-
- url_fetcher->SetExtraRequestHeaders(headers);
- url_fetcher->SetUploadData("application/json", body);
-
- // Fetchers are sometimes cancelled because a network change was detected.
- url_fetcher->SetAutomaticallyRetryOnNetworkChanges(3);
- url_fetcher->SetMaxRetriesOn5xx(k5xxRetries);
- return url_fetcher;
-}
-
-} // namespace internal
-
-} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/contextual/contextual_json_request.h b/chromium/components/ntp_snippets/contextual/contextual_json_request.h
deleted file mode 100644
index c62ccd203cf..00000000000
--- a/chromium/components/ntp_snippets/contextual/contextual_json_request.h
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2016 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_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_JSON_REQUEST_H_
-#define COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_JSON_REQUEST_H_
-
-#include <memory>
-#include <string>
-#include <utility>
-
-#include "base/callback.h"
-#include "base/memory/weak_ptr.h"
-#include "components/ntp_snippets/remote/json_request.h"
-#include "components/ntp_snippets/status.h"
-#include "net/http/http_request_headers.h"
-
-namespace base {
-class Value;
-} // namespace base
-
-namespace ntp_snippets {
-
-namespace internal {
-
-// A request to query contextual suggestions.
-class ContextualJsonRequest : public net::URLFetcherDelegate {
- public:
- // A client can expect error_details only, if there was any error during the
- // fetching or parsing. In successful cases, it will be an empty string.
- using CompletedCallback =
- base::OnceCallback<void(std::unique_ptr<base::Value> result,
- FetchResult result_code,
- const std::string& error_details)>;
-
- // Builds authenticated and non-authenticated ContextualJsonRequests.
- class Builder {
- public:
- Builder();
- Builder(Builder&&);
- ~Builder();
-
- // Builds a Request object that contains all data to fetch new snippets.
- std::unique_ptr<ContextualJsonRequest> Build() const;
-
- Builder& SetAuthentication(const std::string& account_id,
- const std::string& auth_header);
- Builder& SetParseJsonCallback(ParseJSONCallback callback);
- Builder& SetUrl(const GURL& url);
- Builder& SetUrlRequestContextGetter(
- const scoped_refptr<net::URLRequestContextGetter>& context_getter);
- Builder& SetContentUrl(const GURL& url);
-
- // These preview methods allow to inspect the Request without exposing it
- // publicly.
- std::string PreviewRequestBodyForTesting() { return BuildBody(); }
- std::string PreviewRequestHeadersForTesting() { return BuildHeaders(); }
-
- private:
- std::string BuildHeaders() const;
- std::string BuildBody() const;
- std::unique_ptr<net::URLFetcher> BuildURLFetcher(
- net::URLFetcherDelegate* request,
- const std::string& headers,
- const std::string& body) const;
-
- std::string auth_header_;
- ParseJSONCallback parse_json_callback_;
- GURL url_;
- scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
-
- // The URL for which to fetch contextual suggestions for.
- GURL content_url_;
-
- DISALLOW_COPY_AND_ASSIGN(Builder);
- };
-
- ContextualJsonRequest(const ParseJSONCallback& callback);
- ContextualJsonRequest(ContextualJsonRequest&&);
- ~ContextualJsonRequest() override;
-
- void Start(CompletedCallback callback);
-
- std::string GetResponseString() const;
-
- private:
- // URLFetcherDelegate implementation.
- void OnURLFetchComplete(const net::URLFetcher* source) override;
-
- void ParseJsonResponse();
- void OnJsonParsed(std::unique_ptr<base::Value> result);
- void OnJsonError(const std::string& error);
-
- // The fetcher for downloading the snippets. Only non-null if a fetch is
- // currently ongoing.
- std::unique_ptr<net::URLFetcher> url_fetcher_;
-
- // This callback is called to parse a json string. It contains callbacks for
- // error and success cases.
- ParseJSONCallback parse_json_callback_;
-
- // The callback to notify when URLFetcher finished and results are available.
- CompletedCallback request_completed_callback_;
-
- base::WeakPtrFactory<ContextualJsonRequest> weak_ptr_factory_;
-
- DISALLOW_COPY_AND_ASSIGN(ContextualJsonRequest);
-};
-
-} // namespace internal
-
-} // namespace ntp_snippets
-
-#endif // COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_JSON_REQUEST_H_
diff --git a/chromium/components/ntp_snippets/contextual/contextual_json_request_unittest.cc b/chromium/components/ntp_snippets/contextual/contextual_json_request_unittest.cc
deleted file mode 100644
index d501dd15656..00000000000
--- a/chromium/components/ntp_snippets/contextual/contextual_json_request_unittest.cc
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2017 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/ntp_snippets/contextual/contextual_json_request.h"
-
-#include <utility>
-
-#include "base/json/json_reader.h"
-#include "base/memory/ptr_util.h"
-#include "base/message_loop/message_loop.h"
-#include "base/test/test_mock_time_task_runner.h"
-#include "base/values.h"
-#include "components/ntp_snippets/features.h"
-#include "net/url_request/url_request_test_util.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace ntp_snippets {
-
-namespace internal {
-
-namespace {
-
-using testing::_;
-using testing::Eq;
-using testing::StrEq;
-
-MATCHER_P(EqualsJSON, json, "equals JSON") {
- std::unique_ptr<base::Value> expected = base::JSONReader::Read(json);
- if (!expected) {
- *result_listener << "INTERNAL ERROR: couldn't parse expected JSON";
- return false;
- }
-
- std::string err_msg;
- int err_line, err_col;
- std::unique_ptr<base::Value> actual = base::JSONReader::ReadAndReturnError(
- arg, base::JSON_PARSE_RFC, nullptr, &err_msg, &err_line, &err_col);
- if (!actual) {
- *result_listener << "input:" << err_line << ":" << err_col << ": "
- << "parse error: " << err_msg;
- return false;
- }
- return *expected == *actual;
-}
-
-} // namespace
-
-class ContextualJsonRequestTest : public testing::Test {
- public:
- ContextualJsonRequestTest()
- : request_context_getter_(
- new net::TestURLRequestContextGetter(loop_.task_runner())) {}
-
- ContextualJsonRequest::Builder CreateDefaultBuilder() {
- ContextualJsonRequest::Builder builder;
- builder.SetUrl(GURL("http://valid-url.test"))
- .SetUrlRequestContextGetter(request_context_getter_.get());
- return builder;
- }
-
- private:
- base::MessageLoop loop_;
- scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
-
- DISALLOW_COPY_AND_ASSIGN(ContextualJsonRequestTest);
-};
-
-TEST_F(ContextualJsonRequestTest, AuthenticatedRequest) {
- ContextualJsonRequest::Builder builder = CreateDefaultBuilder();
- builder.SetAuthentication("0BFUSGAIA", "headerstuff")
- .SetContentUrl(GURL("http://my-url.test"))
- .Build();
-
- EXPECT_THAT(builder.PreviewRequestHeadersForTesting(),
- StrEq("Content-Type: application/json; charset=UTF-8\r\n"
- "Authorization: headerstuff\r\n"
- "\r\n"));
- EXPECT_THAT(builder.PreviewRequestBodyForTesting(),
- EqualsJSON("{"
- " \"categories\": [ \"RELATED_ARTICLES\","
- " \"PUBLIC_DEBATE\" ],"
- " \"url\": \"http://my-url.test/\""
- "}"));
-}
-
-} // namespace internal
-
-} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/contextual/contextual_suggestion.cc b/chromium/components/ntp_snippets/contextual/contextual_suggestion.cc
index 546d7f13419..20cb47ca881 100644
--- a/chromium/components/ntp_snippets/contextual/contextual_suggestion.cc
+++ b/chromium/components/ntp_snippets/contextual/contextual_suggestion.cc
@@ -4,91 +4,61 @@
#include "components/ntp_snippets/contextual/contextual_suggestion.h"
-#include "base/memory/ptr_util.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/stringprintf.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/values.h"
-#include "components/ntp_snippets/category.h"
+namespace ntp_snippets {
-namespace {
+ContextualSuggestion::ContextualSuggestion() = default;
-// dict.Get() specialization for base::Time values
-bool GetTimeValue(const base::DictionaryValue& dict,
- const std::string& key,
- base::Time* time) {
- // TODO(gaschler): Replace all usages of GetString(key, &str) by
- // FindKey(key)->GetString().
- std::string time_value;
- return dict.GetString(key, &time_value) &&
- base::Time::FromString(time_value.c_str(), time);
-}
+ContextualSuggestion::ContextualSuggestion(const ContextualSuggestion& other) =
+ default;
-// dict.Get() specialization for GURL values
-bool GetURLValue(const base::DictionaryValue& dict,
- const std::string& key,
- GURL* url) {
- std::string spec;
- if (!dict.GetString(key, &spec)) {
- return false;
- }
- *url = GURL(spec);
- return url->is_valid();
-}
+// MSVC doesn't support defaulted move constructors, so we have to define it
+// ourselves.
+ContextualSuggestion::ContextualSuggestion(
+ ContextualSuggestion&& other) noexcept
+ : id(std::move(other.id)),
+ title(std::move(other.title)),
+ url(std::move(other.url)),
+ publisher_name(std::move(other.publisher_name)),
+ snippet(std::move(other.snippet)),
+ image_id(std::move(other.image_id)),
+ favicon_image_id(std::move(other.favicon_image_id)) {}
-} // namespace
+ContextualSuggestion::~ContextualSuggestion() = default;
-namespace ntp_snippets {
+SuggestionBuilder::SuggestionBuilder(const GURL& url) {
+ suggestion_.url = url;
+ suggestion_.id = url.spec();
+}
-ContextualSuggestion::ContextualSuggestion(const std::string& id) : id_(id) {}
+SuggestionBuilder& SuggestionBuilder::Title(const std::string& title) {
+ suggestion_.title = title;
+ return *this;
+}
-ContextualSuggestion::~ContextualSuggestion() = default;
+SuggestionBuilder& SuggestionBuilder::PublisherName(
+ const std::string& publisher_name) {
+ suggestion_.publisher_name = publisher_name;
+ return *this;
+}
-// static
-std::unique_ptr<ContextualSuggestion>
-ContextualSuggestion::CreateFromDictionary(const base::DictionaryValue& dict) {
- std::string id;
- if (!dict.GetString("url", &id) || id.empty()) {
- return nullptr;
- }
- auto suggestion = MakeUnique(id);
- GetURLValue(dict, "url", &suggestion->url_);
- if (!dict.GetString("title", &suggestion->title_)) {
- dict.GetString("source", &suggestion->title_);
- }
- dict.GetString("snippet", &suggestion->snippet_);
- GetTimeValue(dict, "creationTime", &suggestion->publish_date_);
- GetURLValue(dict, "imageUrl", &suggestion->salient_image_url_);
- if (!dict.GetString("attribution", &suggestion->publisher_name_)) {
- dict.GetString("source", &suggestion->publisher_name_);
- }
- return suggestion;
+SuggestionBuilder& SuggestionBuilder::Snippet(const std::string& snippet) {
+ suggestion_.snippet = snippet;
+ return *this;
}
-ContentSuggestion ContextualSuggestion::ToContentSuggestion() const {
- ContentSuggestion suggestion(
- Category::FromKnownCategory(KnownCategories::CONTEXTUAL), id_, url_);
- suggestion.set_title(base::UTF8ToUTF16(title_));
- suggestion.set_snippet_text(base::UTF8ToUTF16(snippet_));
- suggestion.set_publish_date(publish_date_);
- suggestion.set_publisher_name(base::UTF8ToUTF16(publisher_name_));
- return suggestion;
+SuggestionBuilder& SuggestionBuilder::ImageId(const std::string& image_id) {
+ suggestion_.image_id = image_id;
+ return *this;
}
-// static
-std::unique_ptr<ContextualSuggestion> ContextualSuggestion::CreateForTesting(
- const std::string& to_url,
- const std::string& image_url) {
- auto suggestion = MakeUnique({to_url});
- suggestion->url_ = GURL(to_url);
- suggestion->salient_image_url_ = GURL(image_url);
- return suggestion;
+SuggestionBuilder& SuggestionBuilder::FaviconImageId(
+ const std::string& favicon_image_id) {
+ suggestion_.favicon_image_id = favicon_image_id;
+ return *this;
}
-// static
-std::unique_ptr<ContextualSuggestion> ContextualSuggestion::MakeUnique(
- const std::string& id) {
- return base::WrapUnique(new ContextualSuggestion(id));
+ContextualSuggestion SuggestionBuilder::Build() {
+ return std::move(suggestion_);
}
-} // namespace ntp_snippets
+} // namespace ntp_snippets \ No newline at end of file
diff --git a/chromium/components/ntp_snippets/contextual/contextual_suggestion.h b/chromium/components/ntp_snippets/contextual/contextual_suggestion.h
index eb48dbe8fd0..c13753653df 100644
--- a/chromium/components/ntp_snippets/contextual/contextual_suggestion.h
+++ b/chromium/components/ntp_snippets/contextual/contextual_suggestion.h
@@ -5,75 +5,57 @@
#ifndef COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_SUGGESTION_H_
#define COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_SUGGESTION_H_
-#include <cstdint>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "base/macros.h"
-#include "base/optional.h"
-#include "base/time/time.h"
-#include "components/ntp_snippets/content_suggestion.h"
#include "url/gurl.h"
-namespace base {
-class DictionaryValue;
-} // namespace base
-
namespace ntp_snippets {
-class ContextualSuggestion {
- public:
- using PtrVector = std::vector<std::unique_ptr<ContextualSuggestion>>;
-
+// Struct containing the data for a single contextual content suggestion.
+struct ContextualSuggestion {
+ ContextualSuggestion();
+ ContextualSuggestion(const ContextualSuggestion&);
+ ContextualSuggestion(ContextualSuggestion&&) noexcept;
~ContextualSuggestion();
- // Creates a ContextualSuggestion from a dictionary. Returns a null pointer if
- // the dictionary doesn't correspond to a valid suggestion.
- static std::unique_ptr<ContextualSuggestion> CreateFromDictionary(
- const base::DictionaryValue& dict);
-
- // Converts to general content suggestion form.
- ContentSuggestion ToContentSuggestion() const;
-
- // The ID for identifying the suggestion.
- const std::string& id() const { return id_; }
+ // The ID identifying the suggestion.
+ std::string id;
// Title of the suggestion.
- const std::string& title() const { return title_; }
+ std::string title;
- // The main URL pointing to the content web page.
- const GURL& url() const { return url_; }
+ // The URL of the suggested page.
+ GURL url;
// The name of the content's publisher.
- const std::string& publisher_name() const { return publisher_name_; }
+ std::string publisher_name;
// Summary or relevant extract from the content.
- const std::string& snippet() const { return snippet_; }
+ std::string snippet;
- // Link to an image representative of the content. Do not fetch directly,
- // but through the service, which uses caching, to avoid multiple
- // network requests.
- const GURL& salient_image_url() const { return salient_image_url_; }
+ // Identifier of the image to display alongside the suggestion. This id can
+ // be used to construct a URL to the image.
+ std::string image_id;
+
+ // As above, but for identifying the favicon for the site the suggestion
+ // resides on.
+ std::string favicon_image_id;
+};
+
+// Allows compact, precise construction of a ContextualSuggestion. Its main
+// purpose is to avoid using a confusing 6 param helper whose argument
+// order has to be guessed at.
+class SuggestionBuilder {
+ public:
+ SuggestionBuilder(const GURL& url);
- static std::unique_ptr<ContextualSuggestion> CreateForTesting(
- const std::string& to_url,
- const std::string& image_url);
+ SuggestionBuilder& Title(const std::string& title);
+ SuggestionBuilder& PublisherName(const std::string& publisher_name);
+ SuggestionBuilder& Snippet(const std::string& snippet);
+ SuggestionBuilder& ImageId(const std::string& image_id);
+ SuggestionBuilder& FaviconImageId(const std::string& favicon_image_id);
+ ContextualSuggestion Build();
private:
- ContextualSuggestion(const std::string& id);
-
- // std::make_unique doesn't work if the ctor is private.
- static std::unique_ptr<ContextualSuggestion> MakeUnique(
- const std::string& id);
-
- std::string id_;
- std::string title_;
- GURL url_;
- std::string publisher_name_;
- GURL salient_image_url_;
- std::string snippet_;
- base::Time publish_date_;
+ ContextualSuggestion suggestion_;
};
} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetch.cc b/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetch.cc
new file mode 100644
index 00000000000..0e42aa0b048
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetch.cc
@@ -0,0 +1,282 @@
+// Copyright 2018 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/ntp_snippets/contextual/contextual_suggestions_fetch.h"
+
+#include "base/base64.h"
+#include "base/base64url.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "components/ntp_snippets/contextual/contextual_suggestion.h"
+#include "components/ntp_snippets/contextual/proto/chrome_search_api_request_context.pb.h"
+#include "components/ntp_snippets/contextual/proto/get_pivots_request.pb.h"
+#include "components/ntp_snippets/contextual/proto/get_pivots_response.pb.h"
+#include "components/variations/net/variations_http_headers.h"
+#include "net/base/escape.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "third_party/protobuf/src/google/protobuf/io/coded_stream.h"
+
+using base::Base64Encode;
+
+namespace contextual_suggestions {
+
+namespace {
+
+static constexpr char kFetchEndpoint[] =
+ "https://www.google.com/httpservice/web/ExploreService/GetPivots/";
+
+static constexpr int kNumberOfSuggestionsToFetch = 10;
+static constexpr int kMinNumberOfClusters = 1;
+static constexpr int kMaxNumberOfClusters = 5;
+
+// Wrapper for net::UnescapeForHTML that also handles utf8<->16 conversion.
+std::string Unescape(const std::string& encoded_text) {
+ return base::UTF16ToUTF8(
+ net::UnescapeForHTML(base::UTF8ToUTF16(encoded_text)));
+}
+
+// Converts |item| to a ContextualSuggestion, un-escaping any HTML entities
+// encountered.
+ContextualSuggestion ItemToSuggestion(const PivotItem& item) {
+ PivotDocument document = item.document();
+
+ return SuggestionBuilder(GURL(document.url().raw_url()))
+ .Title(Unescape(document.title()))
+ .Snippet(Unescape(document.summary()))
+ .PublisherName(Unescape(document.site_name()))
+ .ImageId(document.image().id().encrypted_docid())
+ .FaviconImageId(document.favicon_image().id().encrypted_docid())
+ .Build();
+}
+
+Cluster PivotToCluster(const PivotCluster& pivot) {
+ ClusterBuilder cluster_builder(pivot.label().label());
+ for (PivotItem item : pivot.item()) {
+ cluster_builder.AddSuggestion(ItemToSuggestion(item));
+ }
+
+ return cluster_builder.Build();
+}
+
+std::vector<Cluster> ClustersFromResponse(
+ const GetPivotsResponse& response_proto) {
+ std::vector<Cluster> clusters;
+ Pivots pivots = response_proto.pivots();
+
+ if (pivots.item_size() > 0) {
+ // If the first item is a cluster, we can assume they all are.
+ if (pivots.item()[0].has_cluster()) {
+ clusters.reserve(response_proto.pivots().item_size());
+ for (auto item : response_proto.pivots().item()) {
+ if (item.has_cluster()) {
+ PivotCluster pivot_cluster = item.cluster();
+ clusters.emplace_back(PivotToCluster(pivot_cluster));
+ }
+ }
+ } else if (pivots.item()[0].has_document()) {
+ Cluster single_cluster;
+ for (auto item : pivots.item()) {
+ single_cluster.suggestions.emplace_back(ItemToSuggestion(item));
+ }
+ clusters.emplace_back(std::move(single_cluster));
+ }
+ }
+
+ return clusters;
+}
+
+std::string PeekTextFromResponse(const GetPivotsResponse& response_proto) {
+ return response_proto.pivots().peek_text().text();
+}
+
+const std::string SerializedPivotsRequest(const std::string& url,
+ const std::string& bcp_language) {
+ GetPivotsRequest pivot_request;
+
+ SearchApiRequestContext* search_context = pivot_request.mutable_context();
+ search_context->mutable_localization_context()->set_language_code(
+ bcp_language);
+
+ GetPivotsQuery* query = pivot_request.mutable_query();
+ query->add_context()->set_url(url);
+
+ PivotDocumentParams* params = query->mutable_document_params();
+ params->set_enabled(true);
+ params->set_num(kNumberOfSuggestionsToFetch);
+ params->set_enable_images(true);
+ params->set_image_aspect_ratio(PivotDocumentParams::SQUARE);
+
+ PivotClusteringParams* cluster_params = query->mutable_clustering_params();
+ cluster_params->set_enabled(true);
+ cluster_params->set_min(kMinNumberOfClusters);
+ cluster_params->set_max(kMaxNumberOfClusters);
+
+ query->mutable_peek_text_params()->set_enabled(true);
+
+ return pivot_request.SerializeAsString();
+}
+
+} // namespace
+
+ContextualSuggestionsFetch::ContextualSuggestionsFetch(
+ const GURL& url,
+ const std::string& bcp_language_code)
+ : url_(url), bcp_language_code_(bcp_language_code) {}
+
+ContextualSuggestionsFetch::~ContextualSuggestionsFetch() = default;
+
+// static
+const std::string ContextualSuggestionsFetch::GetFetchEndpoint() {
+ return kFetchEndpoint;
+}
+
+void ContextualSuggestionsFetch::Start(
+ FetchClustersCallback callback,
+ ReportFetchMetricsCallback metrics_callback,
+ const scoped_refptr<network::SharedURLLoaderFactory>& loader_factory) {
+ request_completed_callback_ = std::move(callback);
+ url_loader_ = MakeURLLoader();
+ url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
+ loader_factory.get(),
+ base::BindOnce(&ContextualSuggestionsFetch::OnURLLoaderComplete,
+ base::Unretained(this), std::move(metrics_callback)));
+}
+
+std::unique_ptr<network::SimpleURLLoader>
+ContextualSuggestionsFetch::MakeURLLoader() const {
+ // TODO(pnoland, https://crbug.com/831693): Update this once there's an
+ // opt-out setting.
+ net::NetworkTrafficAnnotationTag traffic_annotation =
+ net::DefineNetworkTrafficAnnotation("ntp_contextual_suggestions_fetch",
+ R"(
+ semantics {
+ sender: "Contextual Suggestions Fetch"
+ description:
+ "Chromium can show contextual suggestions that are related to the "
+ "currently visited page."
+ trigger:
+ "Triggered when a page is visited and stayed on for more than a "
+ "few seconds."
+ data:
+ "The URL of the current tab and the ui language."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting:
+ "This feature can be disabled by the flag "
+ "enable-contextual-suggestions-bottom-sheet."
+ policy_exception_justification: "Not implemented. The feature is "
+ "currently Android-only and disabled for all enterprise users. "
+ "A policy will be added before enabling for enterprise users."
+ })");
+
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+
+ resource_request->url = GURL(GetFetchEndpoint());
+ // TODO(pnoland): include cookies once the suggestions endpoint can make use
+ // of them.
+ resource_request->load_flags =
+ net::LOAD_BYPASS_CACHE | net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SEND_AUTH_DATA;
+ resource_request->headers = MakeHeaders();
+ resource_request->method = "GET";
+
+ auto simple_loader = network::SimpleURLLoader::Create(
+ std::move(resource_request), traffic_annotation);
+ simple_loader->SetAllowHttpErrorResults(true);
+ return simple_loader;
+}
+
+net::HttpRequestHeaders ContextualSuggestionsFetch::MakeHeaders() const {
+ net::HttpRequestHeaders headers;
+ std::string serialized_request_body =
+ SerializedPivotsRequest(url_.spec(), bcp_language_code_);
+ std::string base64_encoded_body;
+ base::Base64UrlEncode(serialized_request_body,
+ base::Base64UrlEncodePolicy::INCLUDE_PADDING,
+ &base64_encoded_body);
+ headers.SetHeader("X-Protobuffer-Request-Payload", base64_encoded_body);
+ variations::AppendVariationHeaders(url_, variations::InIncognito::kNo,
+ variations::SignedIn::kNo, &headers);
+
+ UMA_HISTOGRAM_COUNTS_1M(
+ "ContextualSuggestions.FetchRequestProtoSizeKB",
+ static_cast<int>(base64_encoded_body.length() / 1024));
+
+ return headers;
+}
+
+void ContextualSuggestionsFetch::OnURLLoaderComplete(
+ ReportFetchMetricsCallback metrics_callback,
+ std::unique_ptr<std::string> result) {
+ std::vector<Cluster> clusters;
+ std::string peek_text;
+
+ int32_t response_code = 0;
+ int32_t error_code = url_loader_->NetError();
+ if (result) {
+ response_code = url_loader_->ResponseInfo()->headers->response_code();
+
+ if (response_code == net::HTTP_OK) {
+ // The response comes in the format (length, bytes) where length is a
+ // varint32 encoded int. Rather than hand-rolling logic to skip the
+ // length(which we don't actually need), we use EncodedInputStream to
+ // skip past it, then parse the remainder of the stream.
+ google::protobuf::io::CodedInputStream coded_stream(
+ reinterpret_cast<const uint8_t*>(result->data()), result->length());
+ uint32_t response_size;
+ if (coded_stream.ReadVarint32(&response_size) && response_size != 0) {
+ GetPivotsResponse response_proto;
+ if (response_proto.MergePartialFromCodedStream(&coded_stream)) {
+ clusters = ClustersFromResponse(response_proto);
+ peek_text = PeekTextFromResponse(response_proto);
+ }
+ }
+ }
+
+ UMA_HISTOGRAM_COUNTS_1M("ContextualSuggestions.FetchResponseSizeKB",
+ static_cast<int>(result->length() / 1024));
+ }
+
+ ReportFetchMetrics(error_code, response_code, clusters.size(),
+ std::move(metrics_callback));
+
+ std::move(request_completed_callback_).Run(peek_text, std::move(clusters));
+}
+
+void ContextualSuggestionsFetch::ReportFetchMetrics(
+ int32_t error_code,
+ int32_t response_code,
+ size_t clusters_size,
+ ReportFetchMetricsCallback metrics_callback) {
+ ContextualSuggestionsEvent event;
+ if (error_code != net::OK) {
+ event = FETCH_ERROR;
+ } else if (response_code == net::HTTP_SERVICE_UNAVAILABLE) {
+ event = FETCH_SERVER_BUSY;
+ } else if (response_code != net::HTTP_OK) {
+ event = FETCH_ERROR;
+ } else if (clusters_size == 0) {
+ event = FETCH_EMPTY;
+ } else {
+ event = FETCH_COMPLETED;
+ }
+
+ base::UmaHistogramSparse("ContextualSuggestions.FetchErrorCode", error_code);
+ if (response_code > 0) {
+ base::UmaHistogramSparse("ContextualSuggestions.FetchResponseCode",
+ response_code);
+ }
+
+ std::move(metrics_callback).Run(event);
+}
+
+} // namespace contextual_suggestions \ No newline at end of file
diff --git a/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetch.h b/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetch.h
new file mode 100644
index 00000000000..7ac12818187
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetch.h
@@ -0,0 +1,71 @@
+// Copyright 2018 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_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_SUGGESTIONS_FETCH_H_
+#define COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_SUGGESTIONS_FETCH_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/callback.h"
+#include "components/ntp_snippets/contextual/cluster.h"
+#include "components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.h"
+#include "net/http/http_request_headers.h"
+#include "url/gurl.h"
+
+namespace network {
+class SimpleURLLoader;
+class SharedURLLoaderFactory;
+} // namespace network
+
+namespace contextual_suggestions {
+
+// A fetch of contextual suggestions. This encapsulates the request-response
+// lifecycle. It is also responsible for building and serializing the request
+// body protos and parsing the response body protos.
+class ContextualSuggestionsFetch {
+ public:
+ ContextualSuggestionsFetch(const GURL& url, const std::string& bcp_language);
+ ~ContextualSuggestionsFetch();
+
+ // Get the url used to fetch suggestions.
+ static const std::string GetFetchEndpoint();
+
+ // Start fetching suggestions using |loader_factory| to construct a
+ // URLLoader, and calling |callback| when finished.
+ void Start(
+ FetchClustersCallback callback,
+ ReportFetchMetricsCallback metrics_callback,
+ const scoped_refptr<network::SharedURLLoaderFactory>& loader_factory);
+
+ private:
+ std::unique_ptr<network::SimpleURLLoader> MakeURLLoader() const;
+ net::HttpRequestHeaders MakeHeaders() const;
+ void OnURLLoaderComplete(ReportFetchMetricsCallback metrics_callback,
+ std::unique_ptr<std::string> result);
+ void ReportFetchMetrics(int32_t error_code,
+ int32_t response_code,
+ size_t clusters_size,
+ ReportFetchMetricsCallback metrics_callback);
+
+ // The url for which we're fetching suggestions.
+ const GURL url_;
+
+ // Identifier for the spoken language in BCP47 format.
+ const std::string bcp_language_code_;
+
+ // The loader for downloading the suggestions. Only non-null if a fetch is
+ // currently ongoing.
+ std::unique_ptr<network::SimpleURLLoader> url_loader_;
+
+ // The callback to notify when results are available.
+ FetchClustersCallback request_completed_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContextualSuggestionsFetch);
+};
+
+} // namespace contextual_suggestions
+
+#endif // COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_SUGGESTIONS_FETCH_H_
diff --git a/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher.h b/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher.h
index ea408067cd1..8fbdcf2f767 100644
--- a/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher.h
+++ b/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher.h
@@ -5,38 +5,27 @@
#ifndef COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_SUGGESTIONS_FETCHER_H_
#define COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_SUGGESTIONS_FETCHER_H_
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "base/callback.h"
-#include "base/optional.h"
-#include "components/ntp_snippets/category.h"
-#include "components/ntp_snippets/category_info.h"
+#include "components/ntp_snippets/contextual/cluster.h"
#include "components/ntp_snippets/contextual/contextual_suggestion.h"
-#include "components/ntp_snippets/status.h"
+#include "components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.h"
+
+using contextual_suggestions::FetchClustersCallback;
+using contextual_suggestions::ReportFetchMetricsCallback;
namespace ntp_snippets {
// Fetches contextual suggestions from the server.
class ContextualSuggestionsFetcher {
public:
- using OptionalSuggestions = base::Optional<ContextualSuggestion::PtrVector>;
-
- // If fetching fails, the optional will be empty.
- using SuggestionsAvailableCallback =
- base::OnceCallback<void(Status status,
- OptionalSuggestions fetched_suggestions)>;
virtual ~ContextualSuggestionsFetcher() = default;
- virtual void FetchContextualSuggestions(
+ // Fetch clusters of suggestions for |url|, calling back to |callback| when
+ // complete.
+ virtual void FetchContextualSuggestionsClusters(
const GURL& url,
- SuggestionsAvailableCallback callback) = 0;
-
- virtual const std::string& GetLastStatusForTesting() const = 0;
- virtual const std::string& GetLastJsonForTesting() const = 0;
- virtual const GURL& GetFetchUrlForTesting() const = 0;
+ FetchClustersCallback callback,
+ ReportFetchMetricsCallback metrics_callback) = 0;
};
} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl.cc b/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl.cc
index 7eb6228cb67..bdafe0b669f 100644
--- a/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl.cc
+++ b/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl.cc
@@ -4,302 +4,44 @@
#include "components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl.h"
-#include "base/strings/stringprintf.h"
-#include "base/strings/utf_string_conversions.h"
-#include "base/time/default_clock.h"
-#include "base/time/time.h"
-#include "base/values.h"
-#include "components/ntp_snippets/category.h"
-#include "components/strings/grit/components_strings.h"
-#include "net/url_request/url_fetcher.h"
-#include "net/url_request/url_request_status.h"
-#include "services/identity/public/cpp/identity_manager.h"
-#include "ui/base/l10n/l10n_util.h"
-
-using net::HttpRequestHeaders;
-using net::URLFetcher;
-using net::URLRequestContextGetter;
+#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace ntp_snippets {
-using internal::ContextualJsonRequest;
-using internal::FetchResult;
-
-namespace {
-
-const char kContentSuggestionsApiScope[] =
- "https://www.googleapis.com/auth/chrome-content-suggestions";
-const char kAuthorizationRequestHeaderFormat[] = "Bearer %s";
-
-std::string FetchResultToString(FetchResult result) {
- switch (result) {
- case FetchResult::SUCCESS:
- return "OK";
- case FetchResult::URL_REQUEST_STATUS_ERROR:
- return "URLRequestStatus error";
- case FetchResult::HTTP_ERROR:
- return "HTTP error";
- case FetchResult::JSON_PARSE_ERROR:
- return "Received invalid JSON";
- case FetchResult::INVALID_SNIPPET_CONTENT_ERROR:
- return "Invalid / empty list.";
- case FetchResult::OAUTH_TOKEN_ERROR:
- return "Error in obtaining an OAuth2 access token.";
- case FetchResult::MISSING_API_KEY:
- return "No API key available.";
- case FetchResult::RESULT_MAX:
- break;
- }
- NOTREACHED();
- return "Unknown error";
-}
-
-Status FetchResultToStatus(FetchResult result) {
- switch (result) {
- case FetchResult::SUCCESS:
- return Status::Success();
- // Permanent errors occur if it is more likely that the error originated
- // from the client.
- case FetchResult::OAUTH_TOKEN_ERROR:
- case FetchResult::MISSING_API_KEY:
- return Status(StatusCode::PERMANENT_ERROR, FetchResultToString(result));
- // Temporary errors occur if it's more likely that the client behaved
- // correctly but the server failed to respond as expected.
- // TODO(fhorschig): Revisit HTTP_ERROR once the rescheduling was reworked.
- case FetchResult::HTTP_ERROR:
- case FetchResult::URL_REQUEST_STATUS_ERROR:
- case FetchResult::INVALID_SNIPPET_CONTENT_ERROR:
- case FetchResult::JSON_PARSE_ERROR:
- return Status(StatusCode::TEMPORARY_ERROR, FetchResultToString(result));
- case FetchResult::RESULT_MAX:
- break;
- }
- NOTREACHED();
- return Status(StatusCode::PERMANENT_ERROR, std::string());
-}
-
-std::string GetFetchEndpoint() {
- return "https://alpha-chromecontentsuggestions-pa.sandbox.googleapis.com/v1"
- "/publicdebate"
- "/getsuggestions";
-}
-
-// Creates suggestions from dictionary values in |list| and adds them to
-// |suggestions|. Returns true on success, false if anything went wrong.
-bool AddSuggestionsFromListValue(bool content_suggestions_api,
- const base::ListValue& list,
- ContextualSuggestion::PtrVector* suggestions) {
- for (const auto& value : list) {
- const base::DictionaryValue* dict = nullptr;
- if (!value.GetAsDictionary(&dict)) {
- return false;
- }
-
- if (DLOG_IS_ON(INFO)) {
- std::string s;
- dict->GetAsString(&s);
- DVLOG(1) << "AddSuggestionsFromListValue " << s;
- }
- std::unique_ptr<ContextualSuggestion> suggestion =
- ContextualSuggestion::CreateFromDictionary(*dict);
- suggestions->push_back(std::move(suggestion));
- }
- return true;
-}
-
-} // namespace
-
ContextualSuggestionsFetcherImpl::ContextualSuggestionsFetcherImpl(
- identity::IdentityManager* identity_manager,
- scoped_refptr<URLRequestContextGetter> url_request_context_getter,
- PrefService* pref_service,
- const ParseJSONCallback& parse_json_callback)
- : identity_manager_(identity_manager),
- url_request_context_getter_(std::move(url_request_context_getter)),
- parse_json_callback_(parse_json_callback),
- fetch_url_(GetFetchEndpoint()) {}
+ const scoped_refptr<network::SharedURLLoaderFactory>& loader_factory,
+ const std::string& application_language_code)
+ : loader_factory_(loader_factory),
+ bcp_language_code_(application_language_code) {}
ContextualSuggestionsFetcherImpl::~ContextualSuggestionsFetcherImpl() = default;
-const std::string& ContextualSuggestionsFetcherImpl::GetLastStatusForTesting()
- const {
- return last_status_;
-}
-const std::string& ContextualSuggestionsFetcherImpl::GetLastJsonForTesting()
- const {
- return last_fetch_json_;
-}
-const GURL& ContextualSuggestionsFetcherImpl::GetFetchUrlForTesting() const {
- return fetch_url_;
-}
-
-void ContextualSuggestionsFetcherImpl::FetchContextualSuggestions(
+void ContextualSuggestionsFetcherImpl::FetchContextualSuggestionsClusters(
const GURL& url,
- SuggestionsAvailableCallback callback) {
- ContextualJsonRequest::Builder builder;
- builder.SetParseJsonCallback(parse_json_callback_)
- .SetUrlRequestContextGetter(url_request_context_getter_)
- .SetContentUrl(url);
-
- pending_requests_.emplace(std::move(builder), std::move(callback));
- StartTokenRequest();
-}
-
-void ContextualSuggestionsFetcherImpl::StartRequest(
- ContextualJsonRequest::Builder builder,
- SuggestionsAvailableCallback callback,
- const std::string& oauth_access_token) {
- builder.SetUrl(fetch_url_)
- .SetAuthentication(identity_manager_->GetPrimaryAccountInfo().account_id,
- base::StringPrintf(kAuthorizationRequestHeaderFormat,
- oauth_access_token.c_str()));
- DVLOG(1) << "ContextualSuggestionsFetcherImpl::StartRequest";
- std::unique_ptr<ContextualJsonRequest> request = builder.Build();
- ContextualJsonRequest* raw_request = request.get();
- raw_request->Start(base::BindOnce(
- &ContextualSuggestionsFetcherImpl::JsonRequestDone,
- base::Unretained(this), std::move(request), std::move(callback)));
-}
+ FetchClustersCallback callback,
+ ReportFetchMetricsCallback metrics_callback) {
+ auto fetch =
+ std::make_unique<ContextualSuggestionsFetch>(url, bcp_language_code_);
+ ContextualSuggestionsFetch* fetch_unowned = fetch.get();
+ pending_requests_.emplace(std::move(fetch));
-void ContextualSuggestionsFetcherImpl::StartTokenRequest() {
- // If there is already an ongoing token request, just wait for that.
- if (token_fetcher_) {
- return;
- }
-
- OAuth2TokenService::ScopeSet scopes{kContentSuggestionsApiScope};
- token_fetcher_ = identity_manager_->CreateAccessTokenFetcherForPrimaryAccount(
- "ntp_snippets", scopes,
- base::BindOnce(
- &ContextualSuggestionsFetcherImpl::AccessTokenFetchFinished,
- base::Unretained(this)),
- identity::PrimaryAccountAccessTokenFetcher::Mode::kWaitUntilAvailable);
-}
-
-void ContextualSuggestionsFetcherImpl::AccessTokenFetchFinished(
- const GoogleServiceAuthError& error,
- const std::string& access_token) {
- // Delete the fetcher only after we leave this method (which is called from
- // the fetcher itself).
- DCHECK(token_fetcher_);
- std::unique_ptr<identity::PrimaryAccountAccessTokenFetcher>
- token_fetcher_deleter(std::move(token_fetcher_));
-
- if (error.state() != GoogleServiceAuthError::NONE) {
- AccessTokenError(error);
- return;
- }
-
- DCHECK(!access_token.empty());
-
- while (!pending_requests_.empty()) {
- std::pair<ContextualJsonRequest::Builder, SuggestionsAvailableCallback>
- builder_and_callback = std::move(pending_requests_.front());
- pending_requests_.pop();
- StartRequest(std::move(builder_and_callback.first),
- std::move(builder_and_callback.second), access_token);
- }
-}
-
-void ContextualSuggestionsFetcherImpl::AccessTokenError(
- const GoogleServiceAuthError& error) {
- DCHECK_NE(error.state(), GoogleServiceAuthError::NONE);
-
- LOG(WARNING) << "ContextualSuggestionsFetcherImpl::AccessTokenError "
- "Unable to get token: "
- << error.ToString();
-
- while (!pending_requests_.empty()) {
- std::pair<ContextualJsonRequest::Builder, SuggestionsAvailableCallback>
- builder_and_callback = std::move(pending_requests_.front());
-
- FetchFinished(OptionalSuggestions(), std::move(builder_and_callback.second),
- FetchResult::OAUTH_TOKEN_ERROR,
- /*error_details=*/
- base::StringPrintf(" (%s)", error.ToString().c_str()));
- pending_requests_.pop();
- }
-}
-
-void ContextualSuggestionsFetcherImpl::JsonRequestDone(
- std::unique_ptr<ContextualJsonRequest> request,
- SuggestionsAvailableCallback callback,
- std::unique_ptr<base::Value> result,
- FetchResult status_code,
- const std::string& error_details) {
- DCHECK(request);
-
- DVLOG(1) << "ContextualSuggestionsFetcherImpl::JsonRequestDone status_code="
- << static_cast<int>(status_code)
- << " error_details=" << error_details;
- last_fetch_json_ = request->GetResponseString();
-
- // TODO(gaschler): Add UMA metrics for fetch duration of the request
-
- if (!result) {
- FetchFinished(OptionalSuggestions(), std::move(callback), status_code,
- error_details);
- return;
- }
-
- OptionalSuggestions optional_suggestions = ContextualSuggestion::PtrVector();
- if (!JsonToSuggestions(*result, &optional_suggestions.value())) {
- DLOG(WARNING) << "Received invalid suggestions: " << last_fetch_json_;
- FetchFinished(OptionalSuggestions(), std::move(callback),
- FetchResult::INVALID_SNIPPET_CONTENT_ERROR, std::string());
- return;
- }
-
- FetchFinished(std::move(optional_suggestions), std::move(callback),
- FetchResult::SUCCESS, std::string());
+ FetchClustersCallback internal_callback = base::BindOnce(
+ &ContextualSuggestionsFetcherImpl::FetchFinished, base::Unretained(this),
+ fetch_unowned, std::move(callback));
+ fetch_unowned->Start(std::move(internal_callback),
+ std::move(metrics_callback), loader_factory_);
}
void ContextualSuggestionsFetcherImpl::FetchFinished(
- OptionalSuggestions optional_suggestions,
- SuggestionsAvailableCallback callback,
- FetchResult fetch_result,
- const std::string& error_details) {
- DCHECK(fetch_result == FetchResult::SUCCESS ||
- !optional_suggestions.has_value());
-
- last_status_ = FetchResultToString(fetch_result) + error_details;
-
- DVLOG(1) << "Fetch finished: " << last_status_;
-
- std::move(callback).Run(FetchResultToStatus(fetch_result),
- std::move(optional_suggestions));
-}
-
-bool ContextualSuggestionsFetcherImpl::JsonToSuggestions(
- const base::Value& parsed,
- ContextualSuggestion::PtrVector* suggestions) {
- const base::DictionaryValue* top_dict = nullptr;
- if (!parsed.GetAsDictionary(&top_dict)) {
- return false;
- }
-
- const base::ListValue* categories_value = nullptr;
- if (!top_dict->GetList("categories", &categories_value)) {
- return false;
- }
-
- for (const auto& v : *categories_value) {
- const base::DictionaryValue* category_value = nullptr;
- if (!(v.GetAsDictionary(&category_value))) {
- return false;
- }
-
- const base::ListValue* suggestions_list = nullptr;
- // Absence of a list of suggestions is treated as an empty list, which
- // is permissible.
- if (category_value->GetList("suggestions", &suggestions_list)) {
- if (!AddSuggestionsFromListValue(true, *suggestions_list, suggestions)) {
- return false;
- }
- }
- }
-
- return true;
+ ContextualSuggestionsFetch* fetch,
+ FetchClustersCallback callback,
+ std::string peek_text,
+ std::vector<Cluster> clusters) {
+ auto fetch_iterator = pending_requests_.find(fetch);
+ CHECK(fetch_iterator != pending_requests_.end());
+ pending_requests_.erase(fetch_iterator);
+
+ std::move(callback).Run(peek_text, std::move(clusters));
}
} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl.h b/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl.h
index 3a7a7fc236f..bb1041b9464 100644
--- a/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl.h
+++ b/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl.h
@@ -10,93 +10,49 @@
#include <vector>
#include "base/callback.h"
-#include "base/containers/queue.h"
-#include "base/optional.h"
-#include "base/time/clock.h"
-#include "components/ntp_snippets/category.h"
-#include "components/ntp_snippets/category_info.h"
-#include "components/ntp_snippets/contextual/contextual_json_request.h"
+#include "base/containers/flat_set.h"
+#include "base/containers/unique_ptr_adapters.h"
#include "components/ntp_snippets/contextual/contextual_suggestion.h"
+#include "components/ntp_snippets/contextual/contextual_suggestions_fetch.h"
#include "components/ntp_snippets/contextual/contextual_suggestions_fetcher.h"
-#include "components/ntp_snippets/status.h"
-#include "net/url_request/url_request_context_getter.h"
-#include "services/identity/public/cpp/primary_account_access_token_fetcher.h"
-class PrefService;
+namespace network {
+class SharedURLLoaderFactory;
+} // namespace network
-namespace identity {
-class IdentityManager;
-class PrimaryAccountAccessTokenFetcher;
-}
+using contextual_suggestions::Cluster;
+using contextual_suggestions::ContextualSuggestionsFetch;
namespace ntp_snippets {
-// TODO(gaschler): Move authentication that is in common with
-// RemoteSuggestionsFetcher to a helper class.
class ContextualSuggestionsFetcherImpl : public ContextualSuggestionsFetcher {
public:
ContextualSuggestionsFetcherImpl(
- identity::IdentityManager* identity_manager,
- scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
- PrefService* pref_service,
- const ParseJSONCallback& parse_json_callback);
+ const scoped_refptr<network::SharedURLLoaderFactory>& loader_factory,
+ const std::string& application_language_code);
~ContextualSuggestionsFetcherImpl() override;
// ContextualSuggestionsFetcher implementation.
- void FetchContextualSuggestions(
+ void FetchContextualSuggestionsClusters(
const GURL& url,
- SuggestionsAvailableCallback callback) override;
-
- const std::string& GetLastStatusForTesting() const override;
- const std::string& GetLastJsonForTesting() const override;
- const GURL& GetFetchUrlForTesting() const override;
+ FetchClustersCallback callback,
+ ReportFetchMetricsCallback metrics_callback) override;
private:
- void StartRequest(internal::ContextualJsonRequest::Builder builder,
- SuggestionsAvailableCallback callback,
- const std::string& oauth_access_token);
-
- void StartTokenRequest();
-
- void AccessTokenFetchFinished(const GoogleServiceAuthError& error,
- const std::string& access_token);
- void AccessTokenError(const GoogleServiceAuthError& error);
-
- void JsonRequestDone(std::unique_ptr<internal::ContextualJsonRequest> request,
- SuggestionsAvailableCallback callback,
- std::unique_ptr<base::Value> result,
- internal::FetchResult status_code,
- const std::string& error_details);
- void FetchFinished(OptionalSuggestions optional_suggestions,
- SuggestionsAvailableCallback callback,
- internal::FetchResult status_code,
- const std::string& error_details);
-
- bool JsonToSuggestions(const base::Value& parsed,
- ContextualSuggestion::PtrVector* suggestions);
-
- // Authentication for signed-in users.
- identity::IdentityManager* identity_manager_;
-
- std::unique_ptr<identity::PrimaryAccountAccessTokenFetcher> token_fetcher_;
-
- // Holds the URL request context.
- scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
-
- // Stores requests that wait for an access token.
- base::queue<std::pair<internal::ContextualJsonRequest::Builder,
- SuggestionsAvailableCallback>>
+ void FetchFinished(ContextualSuggestionsFetch* fetch,
+ FetchClustersCallback callback,
+ std::string peek_text,
+ std::vector<Cluster> clusters);
+
+ const scoped_refptr<network::SharedURLLoaderFactory> loader_factory_;
+ /// BCP47 formatted language code to use.
+ const std::string bcp_language_code_;
+
+ // Stores requests that are inflight.
+ base::flat_set<std::unique_ptr<ContextualSuggestionsFetch>,
+ base::UniquePtrComparator>
pending_requests_;
- const ParseJSONCallback parse_json_callback_;
-
- // API endpoint for fetching contextual suggestions.
- const GURL fetch_url_;
-
- // Info on the last finished fetch.
- std::string last_status_;
- std::string last_fetch_json_;
-
DISALLOW_COPY_AND_ASSIGN(ContextualSuggestionsFetcherImpl);
};
diff --git a/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl_unittest.cc b/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl_unittest.cc
index 77ba581069d..b9de9cde2d5 100644
--- a/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl_unittest.cc
+++ b/chromium/components/ntp_snippets/contextual/contextual_suggestions_fetcher_impl_unittest.cc
@@ -7,96 +7,195 @@
#include <utility>
#include <vector>
-#include "base/json/json_reader.h"
-#include "base/optional.h"
-#include "base/test/test_mock_time_task_runner.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "base/values.h"
-#include "components/ntp_snippets/category.h"
-#include "components/ntp_snippets/remote/test_utils.h"
-#include "components/prefs/testing_pref_service.h"
-#include "net/url_request/test_url_fetcher_factory.h"
-#include "net/url_request/url_request_test_util.h"
-#include "services/identity/public/cpp/identity_test_environment.h"
+#include "base/base64.h"
+#include "base/bind_helpers.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/histogram_tester.h"
+#include "base/test/scoped_task_environment.h"
+#include "components/ntp_snippets/contextual/proto/chrome_search_api_request_context.pb.h"
+#include "components/ntp_snippets/contextual/proto/get_pivots_request.pb.h"
+#include "components/ntp_snippets/contextual/proto/get_pivots_response.pb.h"
+#include "net/http/http_status_code.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/url_loader_completion_status.h"
+#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ntp_snippets {
+using contextual_suggestions::ClusterBuilder;
+using contextual_suggestions::ContextualSuggestionsEvent;
+using contextual_suggestions::ExploreContext;
+using contextual_suggestions::GetPivotsQuery;
+using contextual_suggestions::GetPivotsRequest;
+using contextual_suggestions::GetPivotsResponse;
+using contextual_suggestions::ImageId;
+using contextual_suggestions::PivotCluster;
+using contextual_suggestions::PivotClusteringParams;
+using contextual_suggestions::PivotDocument;
+using contextual_suggestions::PivotDocumentParams;
+using contextual_suggestions::PivotItem;
+using contextual_suggestions::Pivots;
+using network::SharedURLLoaderFactory;
+using network::TestURLLoaderFactory;
+using testing::ElementsAre;
+
namespace {
-using testing::_;
-using testing::AllOf;
-using testing::Eq;
-using testing::Field;
-using testing::IsEmpty;
-using testing::Property;
-using testing::StartsWith;
-
-const char kTestEmail[] = "foo@bar.com";
-const char kValidURL[] = "http://valid-url.test";
-
-MATCHER(IsEmptySuggestionsList, "is an empty list of suggestions") {
- ContextualSuggestionsFetcher::OptionalSuggestions& optional_suggestions =
- *arg;
- if (!optional_suggestions) {
- *result_listener << "expected a suggestion list.";
- return false;
- }
- if (optional_suggestions->size() != 0) {
- *result_listener << "expected empty suggestions, got: "
- << optional_suggestions->size();
- return false;
+class MockClustersCallback {
+ public:
+ void Done(std::string peek_text, std::vector<Cluster> clusters) {
+ EXPECT_FALSE(has_run);
+ has_run = true;
+ response_peek_text = peek_text;
+ response_clusters = std::move(clusters);
}
- return true;
-}
-MATCHER_P(IsSingleSuggestion,
- url,
- "is a list with the single suggestion %(url)s") {
- ContextualSuggestionsFetcher::OptionalSuggestions& optional_suggestions =
- *arg;
- if (!optional_suggestions) {
- *result_listener << "expected a suggestion list.";
- return false;
+ bool has_run = false;
+ std::string response_peek_text;
+ std::vector<Cluster> response_clusters;
+};
+
+class MockMetricsCallback {
+ public:
+ void Report(ContextualSuggestionsEvent event) { events.push_back(event); }
+
+ std::vector<ContextualSuggestionsEvent> events;
+};
+
+// TODO(pnoland): de-dupe this and the identical class in
+// feed_networking_host_unittest.cc
+class TestSharedURLLoaderFactory : public SharedURLLoaderFactory {
+ public:
+ void CreateLoaderAndStart(network::mojom::URLLoaderRequest request,
+ int32_t routing_id,
+ int32_t request_id,
+ uint32_t options,
+ const network::ResourceRequest& url_request,
+ network::mojom::URLLoaderClientPtr client,
+ const net::MutableNetworkTrafficAnnotationTag&
+ traffic_annotation) override {
+ test_factory_.CreateLoaderAndStart(std::move(request), routing_id,
+ request_id, options, url_request,
+ std::move(client), traffic_annotation);
}
- if (optional_suggestions->size() != 1) {
- *result_listener << "expected single suggestion, got: "
- << optional_suggestions->size();
- return false;
+
+ std::unique_ptr<network::SharedURLLoaderFactoryInfo> Clone() override {
+ NOTREACHED();
+ return nullptr;
}
- if (optional_suggestions.value()[0]->url().spec() != url) {
- *result_listener << "unexpected url, got: "
- << optional_suggestions.value()[0]->url().spec();
- return false;
+
+ TestURLLoaderFactory* test_factory() { return &test_factory_; }
+
+ protected:
+ ~TestSharedURLLoaderFactory() override = default;
+
+ private:
+ TestURLLoaderFactory test_factory_;
+};
+
+Cluster DefaultCluster() {
+ return ClusterBuilder("Articles")
+ .AddSuggestion(SuggestionBuilder(GURL("http://www.foobar.com"))
+ .Title("Title")
+ .PublisherName("cnn.com")
+ .Snippet("Summary")
+ .ImageId("abcdef")
+ .Build())
+ .Build();
+}
+
+// Returns a single cluster with a single suggestion with preset values.
+std::vector<Cluster> DefaultClusters() {
+ std::vector<Cluster> clusters;
+
+ clusters.emplace_back(DefaultCluster());
+ return clusters;
+}
+
+void PopulateDocument(PivotDocument* document,
+ const ContextualSuggestion& suggestion) {
+ document->mutable_url()->set_raw_url(suggestion.url.spec());
+ document->set_title(suggestion.title);
+ document->set_summary(suggestion.snippet);
+ document->set_site_name(suggestion.publisher_name);
+
+ ImageId* image_id = document->mutable_image()->mutable_id();
+ image_id->set_encrypted_docid(suggestion.image_id);
+}
+
+// Populates a GetPivotsResponse proto using |peek_text| and |clusters| and
+// returns that proto as a serialized string.
+std::string SerializedResponseProto(const std::string& peek_text,
+ std::vector<Cluster> clusters) {
+ GetPivotsResponse response_proto;
+ Pivots* pivots = response_proto.mutable_pivots();
+ pivots->mutable_peek_text()->set_text(peek_text);
+
+ for (const auto& cluster : clusters) {
+ PivotItem* root_item = pivots->add_item();
+ PivotCluster* pivot_cluster = root_item->mutable_cluster();
+ pivot_cluster->mutable_label()->set_label(cluster.title);
+ for (const ContextualSuggestion& suggestion : cluster.suggestions) {
+ PopulateDocument(pivot_cluster->add_item()->mutable_document(),
+ suggestion);
+ }
}
- return true;
+
+ // The fetch parsing logic expects the response to come as (length, bytes)
+ // where length is varint32 encoded, but ignores the actual length read.
+ // " " is a valid varint32(32) so this works for now.
+ // TODO(pnoland): Use a CodedOutputStream to prepend the actual size so that
+ // we're not relying on implementation details.
+ return " " + response_proto.SerializeAsString();
}
-class MockSuggestionsAvailableCallback {
- public:
- // Workaround for gMock's lack of support for movable arguments.
- void WrappedRun(
- Status status,
- ContextualSuggestionsFetcher::OptionalSuggestions optional_suggestions) {
- Run(status, &optional_suggestions);
+// Populates a GetPivotsResponse proto with a single, flat list of suggestions
+// from |single_cluster| and returns that proto as a serialized string.
+std::string SerializedResponseProto(const std::string& peek_text,
+ Cluster single_cluster) {
+ GetPivotsResponse response_proto;
+ Pivots* pivots = response_proto.mutable_pivots();
+ pivots->mutable_peek_text()->set_text(peek_text);
+
+ for (const ContextualSuggestion& suggestion : single_cluster.suggestions) {
+ PopulateDocument(pivots->add_item()->mutable_document(), suggestion);
}
- MOCK_METHOD2(Run,
- void(Status status,
- ContextualSuggestionsFetcher::OptionalSuggestions*
- optional_suggestions));
-};
+ // See explanation above for why we prepend " ".
+ return " " + response_proto.SerializeAsString();
+}
+
+void ExpectSuggestionsMatch(ContextualSuggestion actual,
+ ContextualSuggestion expected) {
+ EXPECT_EQ(actual.id, expected.id);
+ EXPECT_EQ(actual.title, expected.title);
+ EXPECT_EQ(actual.url, expected.url);
+ EXPECT_EQ(actual.snippet, expected.snippet);
+ EXPECT_EQ(actual.publisher_name, expected.publisher_name);
+ EXPECT_EQ(actual.image_id, expected.image_id);
+ EXPECT_EQ(actual.favicon_image_id, expected.favicon_image_id);
+}
+
+void ExpectClustersMatch(Cluster actual, Cluster expected) {
+ EXPECT_EQ(actual.title, expected.title);
+ for (size_t i = 0; i < actual.suggestions.size(); i++) {
+ ExpectSuggestionsMatch(std::move(actual.suggestions[i]),
+ std::move(expected.suggestions[i]));
+ }
+}
-void ParseJson(const std::string& json,
- const SuccessCallback& success_callback,
- const ErrorCallback& error_callback) {
- base::JSONReader json_reader;
- std::unique_ptr<base::Value> value = json_reader.ReadToValue(json);
- if (value) {
- success_callback.Run(std::move(value));
- } else {
- error_callback.Run(json_reader.GetErrorMessage());
+void ExpectResponsesMatch(MockClustersCallback callback,
+ std::string expected_peek_text,
+ std::vector<Cluster> expected_clusters) {
+ EXPECT_EQ(callback.response_peek_text, expected_peek_text);
+ ASSERT_EQ(callback.response_clusters.size(), expected_clusters.size());
+ for (size_t i = 0; i < callback.response_clusters.size(); i++) {
+ ExpectClustersMatch(std::move(callback.response_clusters[i]),
+ std::move(expected_clusters[i]));
}
}
@@ -104,166 +203,322 @@ void ParseJson(const std::string& json,
class ContextualSuggestionsFetcherTest : public testing::Test {
public:
- ContextualSuggestionsFetcherTest()
- : fake_url_fetcher_factory_(new net::FakeURLFetcherFactory(nullptr)),
- mock_task_runner_(new base::TestMockTimeTaskRunner(
- base::TestMockTimeTaskRunner::Type::kBoundToThread)) {
- scoped_refptr<net::TestURLRequestContextGetter> request_context_getter =
- new net::TestURLRequestContextGetter(mock_task_runner_.get());
+ ContextualSuggestionsFetcherTest() {
+ loader_factory_ = base::MakeRefCounted<TestSharedURLLoaderFactory>();
fetcher_ = std::make_unique<ContextualSuggestionsFetcherImpl>(
- identity_test_env_.identity_manager(),
- std::move(request_context_getter), test_utils_.pref_service(),
- base::BindRepeating(&ParseJson));
+ loader_factory_, "en");
}
- ~ContextualSuggestionsFetcherTest() override {
- }
-
- void FastForwardUntilNoTasksRemain() {
- mock_task_runner_->FastForwardUntilNoTasksRemain();
- }
-
- void InitializeFakeCredentials() {
- identity_test_env()->MakePrimaryAccountAvailable(kTestEmail);
- }
+ ~ContextualSuggestionsFetcherTest() override {}
void SetFakeResponse(const std::string& response_data,
- net::HttpStatusCode response_code,
- net::URLRequestStatus::Status status) {
- fake_url_fetcher_factory_->SetFakeResponse(
- fetcher_->GetFetchUrlForTesting(), response_data, response_code,
- status);
+ net::HttpStatusCode response_code = net::HTTP_OK,
+ network::URLLoaderCompletionStatus status =
+ network::URLLoaderCompletionStatus()) {
+ GURL fetch_url(ContextualSuggestionsFetch::GetFetchEndpoint());
+ network::ResourceResponseHead head;
+ if (response_code >= 0) {
+ head.headers = base::MakeRefCounted<net::HttpResponseHeaders>(
+ "HTTP/1.1 " + base::NumberToString(response_code));
+ status.decoded_body_length = response_data.length();
+ }
+
+ loader_factory_->test_factory()->AddResponse(fetch_url, head, response_data,
+ status);
}
- ContextualSuggestionsFetcher::SuggestionsAvailableCallback
- ToSuggestionsAvailableCallback(MockSuggestionsAvailableCallback* callback) {
- return base::BindOnce(&MockSuggestionsAvailableCallback::WrappedRun,
- base::Unretained(callback));
+ void SendAndAwaitResponse(
+ const GURL& context_url,
+ MockClustersCallback* callback,
+ MockMetricsCallback* mock_metrics_callback = nullptr) {
+ ReportFetchMetricsCallback metrics_callback =
+ mock_metrics_callback
+ ? base::BindRepeating(&MockMetricsCallback::Report,
+ base::Unretained(mock_metrics_callback))
+ : base::DoNothing();
+ fetcher().FetchContextualSuggestionsClusters(
+ context_url,
+ base::BindOnce(&MockClustersCallback::Done, base::Unretained(callback)),
+ metrics_callback);
+ base::RunLoop().RunUntilIdle();
}
ContextualSuggestionsFetcher& fetcher() { return *fetcher_; }
- MockSuggestionsAvailableCallback& mock_suggestions_available_callback() {
- return mock_suggestions_available_callback_;
- }
- identity::IdentityTestEnvironment* identity_test_env() {
- return &identity_test_env_;
+ TestURLLoaderFactory* test_factory() {
+ return loader_factory_->test_factory();
}
private:
- identity::IdentityTestEnvironment identity_test_env_;
- std::unique_ptr<net::FakeURLFetcherFactory> fake_url_fetcher_factory_;
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ scoped_refptr<TestSharedURLLoaderFactory> loader_factory_;
std::unique_ptr<ContextualSuggestionsFetcherImpl> fetcher_;
- MockSuggestionsAvailableCallback mock_suggestions_available_callback_;
- scoped_refptr<base::TestMockTimeTaskRunner> mock_task_runner_;
- test::RemoteSuggestionsTestUtils test_utils_;
DISALLOW_COPY_AND_ASSIGN(ContextualSuggestionsFetcherTest);
};
-TEST_F(ContextualSuggestionsFetcherTest, ShouldCreateFetcher) {
- FastForwardUntilNoTasksRemain();
- EXPECT_THAT(fetcher().GetLastStatusForTesting(), IsEmpty());
+TEST_F(ContextualSuggestionsFetcherTest, SingleSuggestionResponse) {
+ MockClustersCallback callback;
+ MockMetricsCallback metrics_callback;
+ SetFakeResponse(SerializedResponseProto("Peek Text", DefaultClusters()));
+
+ SendAndAwaitResponse(GURL("http://www.article.com"), &callback,
+ &metrics_callback);
+
+ EXPECT_TRUE(callback.has_run);
+ ExpectResponsesMatch(std::move(callback), "Peek Text", DefaultClusters());
+ EXPECT_EQ(metrics_callback.events,
+ std::vector<ContextualSuggestionsEvent>(
+ {contextual_suggestions::FETCH_COMPLETED}));
}
-TEST_F(ContextualSuggestionsFetcherTest, ShouldFetchSuggestion) {
- InitializeFakeCredentials();
- const std::string kValidResponseData =
- "{\"categories\" : [{"
- " \"id\": 0,"
- " \"suggestions\" : [{"
- " \"title\" : \"Title\","
- " \"summary\" : \"...\","
- " \"url\" : \"http://localhost/foobar\","
- " \"imageUrl\" : \"http://localhost/foobar.jpg\""
- " }]"
- "}]}";
- SetFakeResponse(/*response_data=*/kValidResponseData, net::HTTP_OK,
- net::URLRequestStatus::SUCCESS);
- EXPECT_CALL(mock_suggestions_available_callback(),
- Run(Property(&Status::IsSuccess, true),
- IsSingleSuggestion("http://localhost/foobar")));
-
- fetcher().FetchContextualSuggestions(
- GURL(kValidURL),
- ToSuggestionsAvailableCallback(&mock_suggestions_available_callback()));
-
- identity_test_env()->WaitForAccessTokenRequestAndRespondWithToken(
- "access_token", base::Time::Max());
-
- FastForwardUntilNoTasksRemain();
- EXPECT_THAT(fetcher().GetLastStatusForTesting(), Eq("OK"));
- EXPECT_THAT(fetcher().GetLastJsonForTesting(), Eq(kValidResponseData));
+TEST_F(ContextualSuggestionsFetcherTest,
+ MultipleSuggestionMultipleClusterResponse) {
+ std::vector<Cluster> clusters;
+ std::vector<Cluster> clusters_copy;
+ base::HistogramTester histogram_tester;
+
+ ClusterBuilder cluster1_builder("Category 1");
+ cluster1_builder
+ .AddSuggestion(SuggestionBuilder(GURL("http://www.test.com"))
+ .Title("Title1")
+ .PublisherName("test.com")
+ .Snippet("Summary 1")
+ .ImageId("abc")
+ .Build())
+ .AddSuggestion(SuggestionBuilder(GURL("http://www.foobar.com"))
+ .Title("Title2")
+ .PublisherName("foobar.com")
+ .Snippet("Summary 2")
+ .ImageId("def")
+ .Build());
+
+ ClusterBuilder cluster2_builder("Category 2");
+ cluster2_builder
+ .AddSuggestion(SuggestionBuilder(GURL("http://www.barbaz.com"))
+ .Title("Title3")
+ .PublisherName("barbaz.com")
+ .Snippet("Summary 3")
+ .ImageId("ghi")
+ .Build())
+ .AddSuggestion(SuggestionBuilder(GURL("http://www.cnn.com"))
+ .Title("Title4")
+ .PublisherName("cnn.com")
+ .Snippet("Summary 4")
+ .ImageId("jkl")
+ .Build())
+ .AddSuggestion(SuggestionBuilder(GURL("http://www.slate.com"))
+ .Title("Title5")
+ .PublisherName("slate.com")
+ .Snippet("Summary 5")
+ .ImageId("mno")
+ .Build());
+ ClusterBuilder c1_copy = cluster1_builder;
+ ClusterBuilder c2_copy = cluster2_builder;
+
+ clusters.emplace_back(cluster1_builder.Build());
+ clusters.emplace_back(cluster2_builder.Build());
+
+ clusters_copy.emplace_back(c1_copy.Build());
+ clusters_copy.emplace_back(c2_copy.Build());
+
+ SetFakeResponse(SerializedResponseProto("Peek Text", std::move(clusters)));
+ MockClustersCallback callback;
+ SendAndAwaitResponse(GURL("http://www.article.com/"), &callback);
+
+ EXPECT_TRUE(callback.has_run);
+ ExpectResponsesMatch(std::move(callback), "Peek Text",
+ std::move(clusters_copy));
+
+ histogram_tester.ExpectTotalCount("ContextualSuggestions.FetchResponseSizeKB",
+ 1);
}
-TEST_F(ContextualSuggestionsFetcherTest, ShouldFetchEmptySuggestionsList) {
- InitializeFakeCredentials();
- const std::string kValidEmptyCategoryResponseData =
- "{\"categories\" : [{"
- " \"id\": 0,"
- " \"suggestions\": []"
- "}]}";
- SetFakeResponse(/*response_data=*/kValidEmptyCategoryResponseData,
- net::HTTP_OK, net::URLRequestStatus::SUCCESS);
- EXPECT_CALL(mock_suggestions_available_callback(),
- Run(Property(&Status::IsSuccess, true),
- /*fetched_categories=*/IsEmptySuggestionsList()));
-
- fetcher().FetchContextualSuggestions(
- GURL(kValidURL),
- ToSuggestionsAvailableCallback(&mock_suggestions_available_callback()));
-
- identity_test_env()->WaitForAccessTokenRequestAndRespondWithToken(
- "access_token", base::Time::Max());
-
- FastForwardUntilNoTasksRemain();
- EXPECT_THAT(fetcher().GetLastStatusForTesting(), Eq("OK"));
- EXPECT_THAT(fetcher().GetLastJsonForTesting(),
- Eq(kValidEmptyCategoryResponseData));
+TEST_F(ContextualSuggestionsFetcherTest, FlatResponse) {
+ SetFakeResponse(SerializedResponseProto("Peek Text", DefaultCluster()));
+ MockClustersCallback callback;
+ SendAndAwaitResponse(GURL("http://www.article.com/"), &callback);
+
+ EXPECT_TRUE(callback.has_run);
+ // There's no title for the flat/unclustered response case, since there's no
+ // PivotCluster to copy it from. So we clear the expected title.
+ std::vector<Cluster> expected_clusters = DefaultClusters();
+ expected_clusters[0].title = "";
+ ExpectResponsesMatch(std::move(callback), "Peek Text",
+ std::move(expected_clusters));
}
-TEST_F(ContextualSuggestionsFetcherTest,
- ShouldReportErrorForEmptyResponseData) {
- InitializeFakeCredentials();
- SetFakeResponse(/*response_data=*/std::string(), net::HTTP_NOT_FOUND,
- net::URLRequestStatus::SUCCESS);
- EXPECT_CALL(mock_suggestions_available_callback(),
- Run(Property(&Status::IsSuccess, false), _));
+TEST_F(ContextualSuggestionsFetcherTest, HtmlEntitiesAreUnescaped) {
+ ClusterBuilder cluster_builder("Category 1");
+ cluster_builder.AddSuggestion(SuggestionBuilder(GURL("http://www.test.com"))
+ .Title("&quot;foobar&quot;")
+ .PublisherName("test.com")
+ .Snippet("&#39;barbaz&#39;")
+ .ImageId("abc")
+ .Build());
+ ClusterBuilder builder_copy = cluster_builder;
+ SetFakeResponse(
+ SerializedResponseProto("Peek Text", cluster_builder.Build()));
+ MockClustersCallback callback;
+ SendAndAwaitResponse(GURL("http://www.article.com/"), &callback);
+
+ EXPECT_TRUE(callback.has_run);
+ std::vector<Cluster> expected_clusters;
+ expected_clusters.emplace_back(builder_copy.Build());
+ // Clear the title since it's a flat response.
+ expected_clusters[0].title = "";
+ // Adjust the expected title and snippet to manually unescape the html
+ // entities we added.
+ expected_clusters[0].suggestions[0].title = "\"foobar\"";
+ expected_clusters[0].suggestions[0].snippet = "\'barbaz\'";
+ ExpectResponsesMatch(std::move(callback), "Peek Text",
+ std::move(expected_clusters));
+}
- fetcher().FetchContextualSuggestions(
- GURL(kValidURL),
- ToSuggestionsAvailableCallback(&mock_suggestions_available_callback()));
+TEST_F(ContextualSuggestionsFetcherTest, RequestHeaderSetCorrectly) {
+ net::HttpRequestHeaders headers;
+ base::RunLoop interceptor_run_loop;
+ base::HistogramTester histogram_tester;
+
+ test_factory()->SetInterceptor(
+ base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+ headers = request.headers;
+ interceptor_run_loop.Quit();
+ }));
+ SetFakeResponse(SerializedResponseProto("Peek Text", DefaultClusters()));
+
+ MockClustersCallback callback;
+ SendAndAwaitResponse(GURL("http://www.article.com/"), &callback);
+
+ interceptor_run_loop.Run();
+
+ std::string protobuf_header;
+ ASSERT_TRUE(
+ headers.GetHeader("X-Protobuffer-Request-Payload", &protobuf_header));
+
+ std::string decoded_header_value;
+ base::Base64Decode(protobuf_header, &decoded_header_value);
+ GetPivotsRequest request;
+ ASSERT_TRUE(request.ParseFromString(decoded_header_value));
+
+ EXPECT_EQ(request.context().localization_context().language_code(), "en");
+ EXPECT_EQ(request.query().context()[0].url(), "http://www.article.com/");
+ EXPECT_TRUE(request.query().document_params().enabled());
+ EXPECT_EQ(request.query().document_params().num(), 10);
+ EXPECT_TRUE(request.query().document_params().enable_images());
+ EXPECT_TRUE(request.query().clustering_params().enabled());
+ EXPECT_TRUE(request.query().peek_text_params().enabled());
+
+ histogram_tester.ExpectTotalCount(
+ "ContextualSuggestions.FetchRequestProtoSizeKB", 1);
+}
- identity_test_env()->WaitForAccessTokenRequestAndRespondWithToken(
- "access_token", base::Time::Max());
+TEST_F(ContextualSuggestionsFetcherTest, ProtocolError) {
+ MockClustersCallback callback;
+ MockMetricsCallback metrics_callback;
+ base::HistogramTester histogram_tester;
+
+ SetFakeResponse("", net::HTTP_NOT_FOUND);
+ SendAndAwaitResponse(GURL("http://www.article.com"), &callback,
+ &metrics_callback);
+
+ EXPECT_TRUE(callback.has_run);
+ EXPECT_EQ(callback.response_clusters.size(), 0u);
+ EXPECT_THAT(
+ histogram_tester.GetAllSamples("ContextualSuggestions.FetchResponseCode"),
+ ElementsAre(base::Bucket(/*min=*/net::HTTP_NOT_FOUND, /*count=*/1)));
+ EXPECT_EQ(metrics_callback.events,
+ std::vector<ContextualSuggestionsEvent>(
+ {contextual_suggestions::FETCH_ERROR}));
+}
- FastForwardUntilNoTasksRemain();
+TEST_F(ContextualSuggestionsFetcherTest, ServerUnavailable) {
+ MockClustersCallback callback;
+ MockMetricsCallback metrics_callback;
+ base::HistogramTester histogram_tester;
+
+ SetFakeResponse("", net::HTTP_SERVICE_UNAVAILABLE);
+ SendAndAwaitResponse(GURL("http://www.article.com"), &callback,
+ &metrics_callback);
+
+ EXPECT_TRUE(callback.has_run);
+ EXPECT_EQ(callback.response_clusters.size(), 0u);
+ EXPECT_THAT(
+ histogram_tester.GetAllSamples("ContextualSuggestions.FetchResponseCode"),
+ ElementsAre(base::Bucket(/*min=*/net::HTTP_SERVICE_UNAVAILABLE,
+ /*count=*/1)));
+ EXPECT_EQ(metrics_callback.events,
+ std::vector<ContextualSuggestionsEvent>(
+ {contextual_suggestions::FETCH_SERVER_BUSY}));
}
-TEST_F(ContextualSuggestionsFetcherTest,
- ShouldReportErrorForInvalidResponseData) {
- InitializeFakeCredentials();
- const std::string kInvalidResponseData = "{ \"recos\": []";
- SetFakeResponse(/*response_data=*/kInvalidResponseData, net::HTTP_OK,
- net::URLRequestStatus::SUCCESS);
- EXPECT_CALL(
- mock_suggestions_available_callback(),
- Run(Field(&Status::code, StatusCode::TEMPORARY_ERROR),
- /*fetched_categories=*/Property(
- &ContextualSuggestionsFetcher::OptionalSuggestions::has_value,
- false)));
-
- fetcher().FetchContextualSuggestions(
- GURL(kValidURL),
- ToSuggestionsAvailableCallback(&mock_suggestions_available_callback()));
-
- identity_test_env()->WaitForAccessTokenRequestAndRespondWithToken(
- "access_token", base::Time::Max());
-
- FastForwardUntilNoTasksRemain();
- EXPECT_THAT(fetcher().GetLastStatusForTesting(),
- StartsWith("Received invalid JSON (error "));
- EXPECT_THAT(fetcher().GetLastJsonForTesting(), Eq(kInvalidResponseData));
+TEST_F(ContextualSuggestionsFetcherTest, NetworkError) {
+ MockClustersCallback callback;
+ MockMetricsCallback metrics_callback;
+ base::HistogramTester histogram_tester;
+
+ SetFakeResponse(
+ "", net::HTTP_OK,
+ network::URLLoaderCompletionStatus(net::ERR_CERT_COMMON_NAME_INVALID));
+ SendAndAwaitResponse(GURL("http://www.article.com"), &callback,
+ &metrics_callback);
+
+ EXPECT_TRUE(callback.has_run);
+ EXPECT_EQ(callback.response_clusters.size(), 0u);
+ EXPECT_THAT(
+ histogram_tester.GetAllSamples("ContextualSuggestions.FetchErrorCode"),
+ ElementsAre(base::Bucket(
+ /*min=*/net::ERR_CERT_COMMON_NAME_INVALID, /*count=*/1)));
+
+ EXPECT_EQ(metrics_callback.events,
+ std::vector<ContextualSuggestionsEvent>(
+ {contextual_suggestions::FETCH_ERROR}));
+}
+
+TEST_F(ContextualSuggestionsFetcherTest, EmptyResponse) {
+ MockClustersCallback callback;
+ MockMetricsCallback metrics_callback;
+ SetFakeResponse(SerializedResponseProto("", Cluster()));
+ SendAndAwaitResponse(GURL("http://www.article.com/"), &callback,
+ &metrics_callback);
+
+ EXPECT_TRUE(callback.has_run);
+ EXPECT_EQ(callback.response_clusters.size(), 0u);
+
+ EXPECT_EQ(metrics_callback.events,
+ std::vector<ContextualSuggestionsEvent>(
+ {contextual_suggestions::FETCH_EMPTY}));
+}
+
+TEST_F(ContextualSuggestionsFetcherTest, ResponseWithUnsetFields) {
+ GetPivotsResponse response;
+ Pivots* pivots = response.mutable_pivots();
+ pivots->add_item()->mutable_document();
+ pivots->add_item();
+
+ SetFakeResponse(" " + response.SerializeAsString());
+ MockClustersCallback callback;
+ SendAndAwaitResponse(GURL("http://www.article.com/"), &callback);
+
+ std::vector<Cluster> expected_clusters;
+ expected_clusters.emplace_back(
+ ClusterBuilder("")
+ .AddSuggestion(SuggestionBuilder(GURL()).Build())
+ .AddSuggestion(SuggestionBuilder(GURL()).Build())
+ .Build());
+
+ EXPECT_TRUE(callback.has_run);
+ EXPECT_EQ(callback.response_clusters.size(), 1u);
+ ExpectResponsesMatch(std::move(callback), "", std::move(expected_clusters));
+}
+
+TEST_F(ContextualSuggestionsFetcherTest, CorruptResponse) {
+ SetFakeResponse("unparseable proto string");
+ MockClustersCallback callback;
+ SendAndAwaitResponse(GURL("http://www.article.com/"), &callback);
+
+ EXPECT_TRUE(callback.has_run);
+ EXPECT_EQ(callback.response_clusters.size(), 0u);
}
} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.cc b/chromium/components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.cc
new file mode 100644
index 00000000000..f3f4b600a1d
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.cc
@@ -0,0 +1,110 @@
+// Copyright 2018 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/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.h"
+
+#include "base/metrics/histogram_macros.h"
+#include "components/ntp_snippets/contextual/contextual_suggestions_ukm_entry.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
+
+namespace contextual_suggestions {
+
+ContextualSuggestionsMetricsReporterProvider::
+ ContextualSuggestionsMetricsReporterProvider() = default;
+
+ContextualSuggestionsMetricsReporterProvider::
+ ~ContextualSuggestionsMetricsReporterProvider() = default;
+
+std::unique_ptr<ContextualSuggestionsMetricsReporter>
+ContextualSuggestionsMetricsReporterProvider::CreateMetricsReporter() {
+ return std::make_unique<ContextualSuggestionsMetricsReporter>();
+}
+
+ContextualSuggestionsMetricsReporter::ContextualSuggestionsMetricsReporter()
+ : sheet_peeked_(false),
+ sheet_opened_(false),
+ sheet_closed_(false),
+ any_suggestion_downloaded_(false),
+ any_suggestion_taken_(false),
+ ukm_entry_(nullptr) {}
+
+ContextualSuggestionsMetricsReporter::~ContextualSuggestionsMetricsReporter() {
+ DCHECK(!ukm_entry_) << "Flush should be called before destruction!";
+}
+
+void ContextualSuggestionsMetricsReporter::SetupForPage(
+ ukm::SourceId source_id) {
+ DCHECK(!ukm_entry_) << "Flush should be called before SetupForPage!";
+ DCHECK(source_id != ukm::kInvalidSourceId);
+ ukm_entry_.reset(new ContextualSuggestionsUkmEntry(source_id));
+}
+
+void ContextualSuggestionsMetricsReporter::RecordEvent(
+ ContextualSuggestionsEvent event) {
+ DCHECK(ukm_entry_)
+ << "Page not set up. Did you forget to call SetupForPage or Flush?";
+ ukm_entry_->RecordEventMetrics(event);
+ RecordUmaMetrics(event);
+}
+
+void ContextualSuggestionsMetricsReporter::Flush() {
+ ResetUma();
+ if (ukm_entry_) {
+ ukm_entry_->Flush();
+ ukm_entry_.reset();
+ }
+}
+
+void ContextualSuggestionsMetricsReporter::RecordUmaMetrics(
+ ContextualSuggestionsEvent event) {
+ switch (event) {
+ case FETCH_DELAYED:
+ case FETCH_REQUESTED:
+ case FETCH_ERROR:
+ case FETCH_SERVER_BUSY:
+ case FETCH_BELOW_THRESHOLD:
+ case FETCH_EMPTY:
+ case FETCH_COMPLETED:
+ break;
+ case UI_PEEK_REVERSE_SCROLL:
+ if (sheet_peeked_)
+ return;
+ sheet_peeked_ = true;
+ break;
+ case UI_OPENED:
+ if (sheet_opened_)
+ return;
+ sheet_opened_ = true;
+ break;
+ case UI_CLOSED:
+ if (sheet_closed_)
+ return;
+ sheet_closed_ = true;
+ break;
+ case SUGGESTION_DOWNLOADED:
+ if (any_suggestion_downloaded_)
+ return;
+ any_suggestion_downloaded_ = true;
+ break;
+ case SUGGESTION_CLICKED:
+ if (any_suggestion_taken_)
+ return;
+ any_suggestion_taken_ = true;
+ break;
+ default:
+ NOTREACHED() << "Unexpected event, not correctly handled!";
+ break;
+ }
+ UMA_HISTOGRAM_ENUMERATION("ContextualSuggestions.Events", event);
+}
+
+void ContextualSuggestionsMetricsReporter::ResetUma() {
+ sheet_peeked_ = false;
+ sheet_opened_ = false;
+ sheet_closed_ = false;
+ any_suggestion_downloaded_ = false;
+ any_suggestion_taken_ = false;
+}
+
+} // namespace contextual_suggestions
diff --git a/chromium/components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.h b/chromium/components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.h
new file mode 100644
index 00000000000..8772d05a8ef
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.h
@@ -0,0 +1,163 @@
+// Copyright 2018 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_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_SUGGESTIONS_METRICS_REPORTER_H_
+#define COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_SUGGESTIONS_METRICS_REPORTER_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
+
+namespace contextual_suggestions {
+
+class ContextualSuggestionsUkmEntry;
+
+FORWARD_DECLARE_TEST(ContextualSuggestionsMetricsReporterTest, BaseTest);
+
+// NOTE: because this is used for UMA reporting, these values should not be
+// changed or reused; new values should be appended immediately before the
+// EVENT_MAX value. Make sure to update the histogram enum
+// (ContextualSuggestions.Event in enums.xml) accordingly!
+// A Java counterpart will be generated for this enum.
+// GENERATED_JAVA_ENUM_PACKAGE: (
+// org.chromium.chrome.browser.contextual_suggestions)
+enum ContextualSuggestionsEvent {
+ // Indicates that this state is not initialized.
+ // Should never be intentionally recorded, just used as a default value.
+ UNINITIALIZED = 0,
+ // Records that fetching suggestions has been delayed on the client side.
+ FETCH_DELAYED = 1,
+ // The fetch request has been made but a response has not yet been received.
+ FETCH_REQUESTED = 2,
+ // The fetch response has been received, but there was some error.
+ FETCH_ERROR = 3,
+ // The fetch response indicates that the server was too busy to return any
+ // suggestions.
+ FETCH_SERVER_BUSY = 4,
+ // The fetch response includes suggestions but they were all below the
+ // confidence threshold needed to show them to the user.
+ FETCH_BELOW_THRESHOLD = 5,
+ // The fetch response has been received and parsed, but there were no
+ // suggestions.
+ FETCH_EMPTY = 6,
+ // The fetch response has been received and there are suggestions to show.
+ FETCH_COMPLETED = 7,
+ // The UI was shown in the "peeking bar" state, triggered by a reverse-scroll.
+ // If new gestures are added to trigger the peeking sheet then a new event
+ // should be added to this list.
+ UI_PEEK_REVERSE_SCROLL = 8,
+ // The UI sheet was opened.
+ UI_OPENED = 9,
+ // The UI was closed (via the close box).
+ UI_CLOSED = 10,
+ // A suggestion was downloaded.
+ SUGGESTION_DOWNLOADED = 11,
+ // A suggestion was taken, either with a click, or opened in a separate tab.
+ SUGGESTION_CLICKED = 12,
+ // Special name that marks the maximum value in an Enum used for UMA.
+ // https://cs.chromium.org/chromium/src/tools/metrics/histograms/README.md.
+ kMaxValue = SUGGESTION_CLICKED,
+};
+
+class ContextualSuggestionsMetricsReporter;
+
+// Class producing |ContextualSuggestionsMetricsReporter| instances. It enables
+// classes like |ContextualContentSuggestionService| to produce metrics
+// reporters when needed, e.g. creation of service proxy, without knowing how to
+// initialize them.
+class ContextualSuggestionsMetricsReporterProvider {
+ public:
+ ContextualSuggestionsMetricsReporterProvider();
+ virtual ~ContextualSuggestionsMetricsReporterProvider();
+
+ virtual std::unique_ptr<ContextualSuggestionsMetricsReporter>
+ CreateMetricsReporter();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ContextualSuggestionsMetricsReporterProvider);
+};
+
+// Tracks various UKM and UMA metrics based on reports of events that take place
+// within the Contextual Suggestions component.
+//
+// For example:
+// Java UI -> ContextualSuggestionsBridge#reportEvent(
+// /* web_contents */, @ContextualSuggestionsEvent int eventId) -> native
+// -> ContextualContentSuggestionsService#reportEvent(
+// ukm::GetSourceIdForWebContentsDocument(web_contents), eventId)) ->
+// if(!reporter) {
+// ContextualSuggestionsMetricsReporter* reporter =
+// new ContextualSuggestionsMetricsReporter();
+// }
+// ukm::SourceId source_id_for_web_contents;
+// reporter->SetupForPage(source_id_for_web_contents);
+// reporter->RecordEvent(FETCH_REQUESTED);
+// ...
+//
+// if (!my_results)
+// reporter->RecordEvent(FETCH_ERROR);
+// else if (my_result.size() == 0)
+// reporter->RecordEvent(FETCH_EMPTY);
+// else
+// reporter->RecordEvent(FETCH_COMPLETED);
+// ...
+// When the UI shows:
+// reporter->RecordEvent(UI_PEEK_SHOWN);
+// Make sure data is flushed when leaving the page:
+// reporter->Flush();
+class ContextualSuggestionsMetricsReporter {
+ public:
+ ContextualSuggestionsMetricsReporter();
+ ~ContextualSuggestionsMetricsReporter();
+
+ // Sets up the page with the given |source_id| for event reporting.
+ // All subsequent RecordEvent calls will apply to this page.
+ void SetupForPage(ukm::SourceId source_id);
+
+ // Reports that an event occurred for the current page.
+ // Some data is not written immediately, but will be written when |Reset| is
+ // called.
+ void RecordEvent(ContextualSuggestionsEvent event);
+
+ // Flushes all data staged using |RecordEvent|.
+ // This is required before a subsequent call to |SetupForPage|, and can be
+ // called multiple times.
+ void Flush();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ContextualSuggestionsMetricsReporterTest, BaseTest);
+
+ // Records UMA metrics for this event.
+ void RecordUmaMetrics(ContextualSuggestionsEvent event);
+
+ // Reset UMA metrics.
+ void ResetUma();
+
+ // Internal UMA state data.
+ // Whether the sheet ever peeked.
+ bool sheet_peeked_;
+ // Whether the sheet was ever opened.
+ bool sheet_opened_;
+ // Whether the sheet was closed.
+ bool sheet_closed_;
+ // Whether any suggestion was downloaded.
+ bool any_suggestion_downloaded_;
+ // Whether any suggestion was taken.
+ bool any_suggestion_taken_;
+
+ // The current UKM entry.
+ std::unique_ptr<ContextualSuggestionsUkmEntry> ukm_entry_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContextualSuggestionsMetricsReporter);
+};
+
+using ReportFetchMetricsCallback = base::RepeatingCallback<void(
+ contextual_suggestions::ContextualSuggestionsEvent event)>;
+
+} // namespace contextual_suggestions
+
+#endif // COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_SUGGESTIONS_METRICS_REPORTER_H_
diff --git a/chromium/components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter_unittest.cc b/chromium/components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter_unittest.cc
new file mode 100644
index 00000000000..ce95774f2d2
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter_unittest.cc
@@ -0,0 +1,90 @@
+// Copyright 2018 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/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.h"
+#include "base/macros.h"
+#include "base/test/histogram_tester.h"
+#include "base/test/scoped_task_environment.h"
+#include "components/ntp_snippets/contextual/contextual_suggestions_ukm_entry.h"
+#include "components/ukm/test_ukm_recorder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using ukm::TestUkmRecorder;
+
+namespace contextual_suggestions {
+
+namespace {
+const char kTestNavigationUrl[] = "https://foo.com";
+} // namespace
+
+class ContextualSuggestionsMetricsReporterTest : public ::testing::Test {
+ protected:
+ ContextualSuggestionsMetricsReporterTest() = default;
+
+ TestUkmRecorder* GetTestUkmRecorder() { return &test_ukm_recorder_; }
+
+ ukm::SourceId GetSourceId();
+
+ ContextualSuggestionsMetricsReporter& GetReporter() { return reporter_; }
+
+ private:
+ // The reporter under test.
+ ContextualSuggestionsMetricsReporter reporter_;
+
+ // Sets up the task scheduling/task-runner environment for each test.
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+ // Sets itself as the global UkmRecorder on construction.
+ ukm::TestAutoSetUkmRecorder test_ukm_recorder_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContextualSuggestionsMetricsReporterTest);
+};
+
+ukm::SourceId ContextualSuggestionsMetricsReporterTest::GetSourceId() {
+ ukm::SourceId source_id = ukm::UkmRecorder::GetNewSourceID();
+ test_ukm_recorder_.UpdateSourceURL(source_id, GURL(kTestNavigationUrl));
+ return source_id;
+}
+
+TEST_F(ContextualSuggestionsMetricsReporterTest, BaseTest) {
+ base::HistogramTester histogram_tester;
+ GetReporter().SetupForPage(GetSourceId());
+ GetReporter().RecordEvent(FETCH_REQUESTED);
+ GetReporter().RecordEvent(FETCH_COMPLETED);
+ GetReporter().RecordEvent(UI_PEEK_REVERSE_SCROLL);
+ GetReporter().RecordEvent(UI_OPENED);
+ GetReporter().RecordEvent(SUGGESTION_DOWNLOADED);
+ GetReporter().RecordEvent(SUGGESTION_CLICKED);
+ // Flush data to write to UKM.
+ GetReporter().Flush();
+ // Check what we wrote.
+ TestUkmRecorder* test_ukm_recorder = GetTestUkmRecorder();
+ std::vector<const ukm::mojom::UkmEntry*> entry_vector =
+ test_ukm_recorder->GetEntriesByName(kContextualSuggestionsUkmEntryName);
+ EXPECT_EQ(1U, entry_vector.size());
+ const ukm::mojom::UkmEntry* first_entry = entry_vector[0];
+ EXPECT_TRUE(test_ukm_recorder->EntryHasMetric(
+ first_entry, kContextualSuggestionsFetchMetricName));
+ EXPECT_EQ(static_cast<int64_t>(FetchState::COMPLETED),
+ *(test_ukm_recorder->GetEntryMetric(
+ first_entry, kContextualSuggestionsFetchMetricName)));
+ histogram_tester.ExpectBucketCount("ContextualSuggestions.Events", 0, 0);
+ histogram_tester.ExpectBucketCount("ContextualSuggestions.Events", 1, 0);
+ histogram_tester.ExpectBucketCount("ContextualSuggestions.Events", 2, 1);
+ histogram_tester.ExpectBucketCount("ContextualSuggestions.Events", 3, 0);
+ histogram_tester.ExpectBucketCount("ContextualSuggestions.Events", 4, 0);
+ histogram_tester.ExpectBucketCount("ContextualSuggestions.Events", 5, 0);
+ histogram_tester.ExpectBucketCount("ContextualSuggestions.Events", 6, 0);
+ histogram_tester.ExpectBucketCount("ContextualSuggestions.Events", 7, 1);
+ histogram_tester.ExpectBucketCount("ContextualSuggestions.Events", 8, 1);
+ histogram_tester.ExpectBucketCount("ContextualSuggestions.Events", 9, 1);
+ histogram_tester.ExpectBucketCount("ContextualSuggestions.Events", 10, 0);
+ histogram_tester.ExpectBucketCount("ContextualSuggestions.Events", 11, 1);
+ histogram_tester.ExpectBucketCount("ContextualSuggestions.Events", 12, 1);
+}
+
+// TODO(donnd): add more tests, and test UMA data!
+
+} // namespace contextual_suggestions
diff --git a/chromium/components/ntp_snippets/contextual/contextual_suggestions_ukm_entry.cc b/chromium/components/ntp_snippets/contextual/contextual_suggestions_ukm_entry.cc
new file mode 100644
index 00000000000..968e333739d
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/contextual_suggestions_ukm_entry.cc
@@ -0,0 +1,146 @@
+// Copyright 2018 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/ntp_snippets/contextual/contextual_suggestions_ukm_entry.h"
+
+#include <algorithm>
+
+#include "base/timer/elapsed_timer.h"
+#include "components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.h"
+#include "services/metrics/public/cpp/metrics_utils.h"
+#include "services/metrics/public/cpp/ukm_entry_builder.h"
+#include "services/metrics/public/cpp/ukm_recorder.h"
+
+using ukm::UkmEntryBuilder;
+
+namespace contextual_suggestions {
+
+namespace {
+// Spacing between buckets for the duration in milliseconds.
+// The values recorded will be the min of the bucket in a power of 2 range.
+// E.g. 1100 => 1024 since 1024 <= 1100 < 2048.
+const double kShowDurationBucketSpacing = 2.0;
+const int64_t kMinShowDuration = 1;
+} // namespace
+
+ContextualSuggestionsUkmEntry::ContextualSuggestionsUkmEntry(
+ ukm::SourceId source_id)
+ : fetch_state_(FetchState::NOT_STARTED),
+ trigger_event_(TriggerEvent::NEVER_SHOWN),
+ closed_from_peek_(false),
+ was_sheet_opened_(false),
+ any_suggestion_taken_(false),
+ any_suggestion_downloaded_(false),
+ show_duration_exponential_bucket_(0),
+ show_duration_timer_(nullptr),
+ source_id_(source_id) {}
+
+ContextualSuggestionsUkmEntry::~ContextualSuggestionsUkmEntry() {}
+
+void ContextualSuggestionsUkmEntry::RecordEventMetrics(
+ ContextualSuggestionsEvent event) {
+ switch (event) {
+ case UNINITIALIZED:
+ NOTREACHED() << "An uninitialized event value was sent!";
+ break;
+ case FETCH_DELAYED:
+ fetch_state_ = FetchState::DELAYED;
+ break;
+ case FETCH_REQUESTED:
+ fetch_state_ = FetchState::REQUESTED;
+ break;
+ case FETCH_ERROR:
+ fetch_state_ = FetchState::ERROR_RESULT;
+ break;
+ case FETCH_SERVER_BUSY:
+ fetch_state_ = FetchState::SERVER_BUSY;
+ break;
+ case FETCH_BELOW_THRESHOLD:
+ fetch_state_ = FetchState::BELOW_THRESHOLD;
+ break;
+ case FETCH_EMPTY:
+ fetch_state_ = FetchState::EMPTY;
+ break;
+ case FETCH_COMPLETED:
+ fetch_state_ = FetchState::COMPLETED;
+ break;
+ case UI_PEEK_REVERSE_SCROLL:
+ trigger_event_ = TriggerEvent::REVERSE_SCROLL;
+ StartTimerIfNeeded();
+ break;
+ case UI_OPENED:
+ was_sheet_opened_ = true;
+ StartTimerIfNeeded();
+ break;
+ case UI_CLOSED:
+ if (!was_sheet_opened_)
+ closed_from_peek_ = true;
+ StopTimerIfNeeded();
+ break;
+ case SUGGESTION_DOWNLOADED:
+ any_suggestion_downloaded_ = true;
+ StopTimerIfNeeded();
+ break;
+ case SUGGESTION_CLICKED:
+ any_suggestion_taken_ = true;
+ StopTimerIfNeeded();
+ break;
+ }
+}
+
+void ContextualSuggestionsUkmEntry::Flush() {
+ if (source_id_ == ukm::kInvalidSourceId)
+ return;
+
+ if (show_duration_timer_)
+ StopTimerIfNeeded();
+
+ // When this variable goes out of scope the builder's destructor will write.
+ std::unique_ptr<UkmEntryBuilder> builder =
+ ukm::UkmRecorder::Get()->GetEntryBuilder(
+ source_id_, kContextualSuggestionsUkmEntryName);
+ // Keep these writes alphabetical by metric name (matching ukm.xml ordering).
+ builder->AddMetric(kContextualSuggestionsClosedMetricName, closed_from_peek_);
+ builder->AddMetric(kContextualSuggestionsDownloadedMetricName,
+ any_suggestion_downloaded_);
+ builder->AddMetric(kContextualSuggestionsOpenedMetricName, was_sheet_opened_);
+ builder->AddMetric(kContextualSuggestionsFetchMetricName,
+ static_cast<int64_t>(fetch_state_));
+ builder->AddMetric(kContextualSuggestionsDurationMetricName,
+ show_duration_exponential_bucket_);
+ builder->AddMetric(kContextualSuggestionsTriggerMetricName,
+ static_cast<int64_t>(trigger_event_));
+ builder->AddMetric(kContextualSuggestionsTakenMetricName,
+ any_suggestion_taken_);
+
+ source_id_ = ukm::kInvalidSourceId;
+}
+
+void ContextualSuggestionsUkmEntry::StartTimerIfNeeded() {
+ // If the timer is already running, don't restart it.
+ if (show_duration_timer_)
+ return;
+
+ show_duration_timer_.reset(new base::ElapsedTimer());
+}
+
+void ContextualSuggestionsUkmEntry::StopTimerIfNeeded() {
+ // We should either have a timer or a computed duration from the timer.
+ DCHECK(show_duration_timer_ || show_duration_exponential_bucket_ > 0);
+
+ // If we've already computed the duration, or there's no timer to stop, then
+ // there's nothing to do.
+ if (show_duration_exponential_bucket_ > 0 || !show_duration_timer_)
+ return;
+
+ show_duration_exponential_bucket_ = ukm::GetExponentialBucketMin(
+ show_duration_timer_->Elapsed().InMillisecondsRoundedUp(),
+ kShowDurationBucketSpacing);
+ // Ensure a positive value.
+ show_duration_exponential_bucket_ =
+ std::max(kMinShowDuration, show_duration_exponential_bucket_);
+ show_duration_timer_.reset();
+}
+
+} // namespace contextual_suggestions
diff --git a/chromium/components/ntp_snippets/contextual/contextual_suggestions_ukm_entry.h b/chromium/components/ntp_snippets/contextual/contextual_suggestions_ukm_entry.h
new file mode 100644
index 00000000000..6c596c89768
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/contextual_suggestions_ukm_entry.h
@@ -0,0 +1,99 @@
+// Copyright 2018 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_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_SUGGESTIONS_UKM_ENTRY_H_
+#define COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_SUGGESTIONS_UKM_ENTRY_H_
+
+#include "base/gtest_prod_util.h"
+#include "components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
+
+namespace base {
+class ElapsedTimer;
+}
+
+namespace contextual_suggestions {
+
+FORWARD_DECLARE_TEST(ContextualSuggestionsUkmEntryTest, BinaryOrderTest);
+
+// The name of the UKM entry written by this class.
+const char kContextualSuggestionsUkmEntryName[] = "ContextualSuggestions";
+
+// The names of individual UKM metrics written to our UKM entry.
+const char kContextualSuggestionsDownloadedMetricName[] = "AnyDownloaded";
+const char kContextualSuggestionsTakenMetricName[] = "AnySuggestionTaken";
+const char kContextualSuggestionsClosedMetricName[] = "ClosedFromPeek";
+const char kContextualSuggestionsOpenedMetricName[] = "EverOpened";
+const char kContextualSuggestionsFetchMetricName[] = "FetchState";
+const char kContextualSuggestionsDurationMetricName[] = "ShowDurationBucketMin";
+const char kContextualSuggestionsTriggerMetricName[] = "TriggerEvent";
+
+// The state of the Fetcher request.
+// This value is written to the "FetchState" UKM metric.
+enum class FetchState {
+ NOT_STARTED,
+ DELAYED,
+ REQUESTED,
+ ERROR_RESULT,
+ SERVER_BUSY,
+ BELOW_THRESHOLD,
+ EMPTY,
+ COMPLETED,
+};
+
+// The way that the sheet UI was triggered.
+// This value is written to the "TriggerEvent" UKM metric.
+enum class TriggerEvent {
+ NEVER_SHOWN,
+ REVERSE_SCROLL,
+};
+
+// Writes a single UKM entry that describes the latest state of the event stream
+// monitored through |RecordEventMetrics|.
+class ContextualSuggestionsUkmEntry {
+ public:
+ // Sets up recording of a UKM entry for the given |source_id|.
+ explicit ContextualSuggestionsUkmEntry(ukm::SourceId source_id);
+ ~ContextualSuggestionsUkmEntry();
+
+ // Updates tracked metrics for the given |event|.
+ void RecordEventMetrics(ContextualSuggestionsEvent event);
+
+ // Writes the latest data tracked into a single UKM entry.
+ void Flush();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(ContextualSuggestionsUkmEntryTest, BinaryOrderTest);
+
+ // Starts the elapsed timer if not already started.
+ void StartTimerIfNeeded();
+
+ // Stops the elapsed timer if not already stopped.
+ void StopTimerIfNeeded();
+
+ // Internal state trackers.
+ FetchState fetch_state_;
+ TriggerEvent trigger_event_;
+ bool closed_from_peek_;
+ bool was_sheet_opened_;
+ bool any_suggestion_taken_;
+ bool any_suggestion_downloaded_;
+
+ // The minimum exponential bucket of the duration in milliseconds that the
+ // sheet was viewed before taking action. A value of 0 means not yet
+ // computed.
+ int64_t show_duration_exponential_bucket_;
+
+ // Simple timer for how long the sheet is showing.
+ std::unique_ptr<base::ElapsedTimer> show_duration_timer_;
+
+ // The UKM identifier for the current URL / page in use.
+ ukm::SourceId source_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContextualSuggestionsUkmEntry);
+};
+
+} // namespace contextual_suggestions
+
+#endif // COMPONENTS_NTP_SNIPPETS_CONTEXTUAL_CONTEXTUAL_SUGGESTIONS_UKM_ENTRY_H_
diff --git a/chromium/components/ntp_snippets/contextual/contextual_suggestions_ukm_entry_unittest.cc b/chromium/components/ntp_snippets/contextual/contextual_suggestions_ukm_entry_unittest.cc
new file mode 100644
index 00000000000..93d78903cf6
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/contextual_suggestions_ukm_entry_unittest.cc
@@ -0,0 +1,104 @@
+// Copyright 2018 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/ntp_snippets/contextual/contextual_suggestions_ukm_entry.h"
+#include "base/macros.h"
+#include "base/test/scoped_task_environment.h"
+#include "components/ntp_snippets/contextual/contextual_suggestions_metrics_reporter.h"
+#include "components/ukm/test_ukm_recorder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ukm::TestUkmRecorder;
+
+namespace contextual_suggestions {
+
+const int NO_ENTRY = -1;
+
+class ContextualSuggestionsUkmEntryTest : public ::testing::Test {
+ protected:
+ ContextualSuggestionsUkmEntryTest() = default;
+
+ void SetUp() override;
+
+ // The entry under test.
+ std::unique_ptr<ContextualSuggestionsUkmEntry> ukm_entry_;
+
+ TestUkmRecorder* GetTestUkmRecorder() { return &test_ukm_recorder_; }
+
+ const ukm::mojom::UkmEntry* FirstEntry();
+ bool HasEntryMetric(const char* metric_name);
+ int GetEntryMetric(const char* metric_name);
+
+ private:
+ // Sets up the task scheduling/task-runner environment for each test.
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+ // Sets itself as the global UkmRecorder on construction.
+ ukm::TestAutoSetUkmRecorder test_ukm_recorder_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContextualSuggestionsUkmEntryTest);
+};
+
+void ContextualSuggestionsUkmEntryTest::SetUp() {
+ ukm_entry_.reset(
+ new ContextualSuggestionsUkmEntry(ukm::UkmRecorder::GetNewSourceID()));
+}
+
+const ukm::mojom::UkmEntry* ContextualSuggestionsUkmEntryTest::FirstEntry() {
+ TestUkmRecorder* recorder = GetTestUkmRecorder();
+ std::vector<const ukm::mojom::UkmEntry*> entry_vector =
+ recorder->GetEntriesByName(kContextualSuggestionsUkmEntryName);
+ EXPECT_EQ(1U, entry_vector.size());
+ return entry_vector[0];
+}
+
+bool ContextualSuggestionsUkmEntryTest::HasEntryMetric(
+ const char* metric_name) {
+ TestUkmRecorder* recorder = GetTestUkmRecorder();
+ return recorder->EntryHasMetric(FirstEntry(), metric_name);
+}
+
+int ContextualSuggestionsUkmEntryTest::GetEntryMetric(const char* metric_name) {
+ TestUkmRecorder* recorder = GetTestUkmRecorder();
+ const ukm::mojom::UkmEntry* first_entry = FirstEntry();
+ if (!recorder->EntryHasMetric(first_entry, metric_name))
+ return NO_ENTRY;
+
+ return (int)*(recorder->GetEntryMetric(first_entry, metric_name));
+}
+
+TEST_F(ContextualSuggestionsUkmEntryTest, BaseTest) {
+ ukm_entry_->Flush();
+ // Deleting the entry should write default values for everything.
+ EXPECT_EQ(0, GetEntryMetric(kContextualSuggestionsDownloadedMetricName));
+ EXPECT_EQ(0, GetEntryMetric(kContextualSuggestionsTakenMetricName));
+ EXPECT_EQ(0, GetEntryMetric(kContextualSuggestionsClosedMetricName));
+ EXPECT_EQ(0, GetEntryMetric(kContextualSuggestionsOpenedMetricName));
+ EXPECT_EQ(0, GetEntryMetric(kContextualSuggestionsFetchMetricName));
+ EXPECT_EQ(0, GetEntryMetric(kContextualSuggestionsDurationMetricName));
+ EXPECT_EQ(0, GetEntryMetric(kContextualSuggestionsTriggerMetricName));
+}
+
+TEST_F(ContextualSuggestionsUkmEntryTest, ExpectedOperationTest) {
+ ukm_entry_->RecordEventMetrics(FETCH_DELAYED);
+ ukm_entry_->RecordEventMetrics(FETCH_REQUESTED);
+ ukm_entry_->RecordEventMetrics(FETCH_COMPLETED);
+ ukm_entry_->RecordEventMetrics(UI_PEEK_REVERSE_SCROLL);
+ ukm_entry_->RecordEventMetrics(UI_OPENED);
+ ukm_entry_->RecordEventMetrics(SUGGESTION_DOWNLOADED);
+ ukm_entry_->RecordEventMetrics(SUGGESTION_DOWNLOADED);
+ ukm_entry_->RecordEventMetrics(SUGGESTION_CLICKED);
+ ukm_entry_->Flush();
+ EXPECT_EQ(1, GetEntryMetric(kContextualSuggestionsDownloadedMetricName));
+ EXPECT_EQ(1, GetEntryMetric(kContextualSuggestionsTakenMetricName));
+ EXPECT_EQ(0, GetEntryMetric(kContextualSuggestionsClosedMetricName));
+ EXPECT_EQ(1, GetEntryMetric(kContextualSuggestionsOpenedMetricName));
+ EXPECT_EQ(7, GetEntryMetric(kContextualSuggestionsFetchMetricName));
+ EXPECT_LT(0, GetEntryMetric(kContextualSuggestionsDurationMetricName));
+ EXPECT_EQ(1, GetEntryMetric(kContextualSuggestionsTriggerMetricName));
+}
+
+// TODO(donnd): add more tests!
+
+} // namespace contextual_suggestions
diff --git a/chromium/components/ntp_snippets/contextual/proto/chrome_search_api_request_context.proto b/chromium/components/ntp_snippets/contextual/proto/chrome_search_api_request_context.proto
new file mode 100644
index 00000000000..7cf84cdae7d
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/proto/chrome_search_api_request_context.proto
@@ -0,0 +1,38 @@
+// Copyright 2018 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";
+
+option optimize_for = LITE_RUNTIME;
+
+package contextual_suggestions;
+
+message SearchApiRequestContext {
+ optional SearchApiClientContext client_context = 2;
+ optional LocalizationContext localization_context = 4;
+ optional CookieRequestContext cookie_context = 3;
+}
+
+message SearchApiClientContext {
+ optional ClientId client_id = 1;
+}
+
+message ClientId {
+ enum ClientType {
+ UNKNOWN_CLIENT_TYPE = 0;
+ ANDROID_CHROME = 79;
+ }
+ optional ClientType client_type = 1;
+}
+
+message LocalizationContext {
+ optional string language_code = 1;
+ optional string country_code = 2;
+ optional string search_domain = 4;
+ optional string spoken_language = 7;
+}
+
+message CookieRequestContext {
+ optional string nid = 1;
+}
diff --git a/chromium/components/ntp_snippets/contextual/proto/get_pivots_request.proto b/chromium/components/ntp_snippets/contextual/proto/get_pivots_request.proto
new file mode 100644
index 00000000000..48cb0c572cc
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/proto/get_pivots_request.proto
@@ -0,0 +1,86 @@
+// Copyright 2018 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";
+
+option optimize_for = LITE_RUNTIME;
+
+package contextual_suggestions;
+
+import "chrome_search_api_request_context.proto";
+
+message GetPivotsRequest {
+ // Context common to the search API.
+ optional SearchApiRequestContext context = 1;
+
+ // Request fields specific to the Pivots request.
+ optional GetPivotsQuery query = 2;
+}
+
+message GetPivotsQuery {
+ // A historical chain of the user's past exploration to date, sorted from
+ // most recent to least recent (reverse chronological order). A context is
+ // usually a document and some related data (eg. salient terms and/or query
+ // that generated that doc).
+ repeated ExploreContext context = 1;
+
+ // Parameters related to pivot documents.
+ optional PivotDocumentParams document_params = 2;
+
+ // Parameters related to clustering pivot items (documents, queries,
+ // questions, etc.).
+ optional PivotClusteringParams clustering_params = 6;
+
+ // Parameters related to peek text.
+ optional PivotPeekTextParams peek_text_params = 7;
+}
+
+// One piece of historical context in the user's exploration session.
+message ExploreContext {
+ // If specified, the (possibly uncanonicalized) document the user visited.
+ optional string url = 1;
+}
+
+message PivotDocumentParams {
+ // If true, compute pivot documents.
+ optional bool enabled = 1 [default = false];
+
+ // How many documents should we return?
+ optional int32 num = 2 [default = 5];
+
+ // If we have fewer documents than this to return, then don't return any.
+ optional int32 min_documents = 6 [default = 3];
+
+ // If true, return images for documents.
+ optional bool enable_images = 3 [default = false];
+
+ // When images are returned, what aspect ratio should be used.
+ enum ImageAspectRatio {
+ // Allow the server to select. This will typically return a thumbnail in
+ // the same aspect ratio as the original image.
+ ASPECT_RATIO_UNSPECIFIED = 0;
+ // Crop to a square image.
+ SQUARE = 1;
+ }
+ optional ImageAspectRatio image_aspect_ratio = 4
+ [default = ASPECT_RATIO_UNSPECIFIED];
+}
+
+// Parameters for clustering pivot items (documents, queries, questions, etc).
+message PivotClusteringParams {
+ // Whether or not to group PivotItems on similar topics into clusters.
+ optional bool enabled = 1;
+
+ // Minimum number of clusters we should return.
+ optional int32 min = 2;
+
+ // Maximum number of clusters we should return.
+ optional int32 max = 3;
+}
+
+// Parameters related to the peek text.
+message PivotPeekTextParams {
+ // Whether or not to return peek text.
+ optional bool enabled = 1 [default = false];
+} \ No newline at end of file
diff --git a/chromium/components/ntp_snippets/contextual/proto/get_pivots_response.proto b/chromium/components/ntp_snippets/contextual/proto/get_pivots_response.proto
new file mode 100644
index 00000000000..826c8f4f9e7
--- /dev/null
+++ b/chromium/components/ntp_snippets/contextual/proto/get_pivots_response.proto
@@ -0,0 +1,125 @@
+// Copyright 2018 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";
+
+option optimize_for = LITE_RUNTIME;
+
+package contextual_suggestions;
+
+message GetPivotsResponse {
+ // The suggested followups.
+ optional Pivots pivots = 2;
+}
+
+// Container for all explore on content data.
+message Pivots {
+ // Items related to the user's context.
+ repeated PivotItem item = 1;
+
+ // The conditions under which auto-peek should be enabled.
+ optional AutoPeekConditions auto_peek_conditions = 3;
+
+ // The text to present to the user in the peek.
+ optional PeekText peek_text = 4;
+}
+
+// One related response.
+message PivotItem {
+ oneof kind {
+ PivotDocument document = 1;
+ PivotCluster cluster = 6;
+ }
+}
+
+// A document related to the source data.
+message PivotDocument {
+ // A link to the document.
+ optional Url url = 1;
+
+ // An image that may be shown next to the document.
+ optional Image image = 2;
+
+ // Title of this document.
+ optional string title = 3;
+
+ // Snippet summary of this document.
+ optional string summary = 4;
+
+ // Human-readable version of the source site's name (e.g. "CNN" instead of
+ // "cnn.com").
+ optional string site_name = 5;
+
+ // A favicon image, usually related to the site.
+ optional Image favicon_image = 6;
+}
+
+// A cluster of responses related to the source data. The cluster optionally has
+// a label.
+message PivotCluster {
+ // Label for the cluster.
+ optional ClusterLabel label = 1;
+
+ // Ranked items that belong to this cluster.
+ repeated PivotItem item = 2;
+
+ // Number of items to show to the user before they expand.
+ optional int32 num_items_to_display_pre_expansion = 3 [default = 1];
+}
+
+// The label for a cluster of followup items.
+message ClusterLabel {
+ // Label to display to the user.
+ optional string label = 1;
+}
+
+message AutoPeekConditions {
+ // A measure of confidence that auto-peek should be enabled for this response
+ // in the range [0, 1]. In general, if the value is 1.0, auto-peek should be
+ // enabled; if 0.0, it shouldn’t; and intermediate values are left to the
+ // client to decide.
+ optional float confidence = 1 [default = 1.0];
+
+ // The percentage of the page that the user scrolls required for an auto
+ // peek to occur. Value should be a decimal between 0 and 1.0.
+ // 1.0 represents the entire page's length.
+ optional float page_scroll_percentage = 2 [default = 0.0];
+
+ // The minimum time (seconds) the user spends on the page required for
+ // auto peek.
+ optional float minimum_seconds_on_page = 3 [default = 0.0];
+
+ // The maximum number of auto peeks that we can show for this page.
+ // If set to 0, we will never auto peek. Service can set this to a very
+ // large number to effectively have unlimited auto peeks.
+ optional uint64 maximum_number_of_peeks = 4 [default = 0];
+}
+
+message PeekText {
+ // Text to display to the user.
+ optional string text = 1;
+}
+
+message Url {
+ optional string raw_url = 1;
+}
+
+message Image {
+ // The identity of this image.
+ optional ImageId id = 1;
+}
+
+message ImageId {
+ // The encrypted document ID of the image, which is a base64-encoded string
+ // that can be used to generate image URLs of the form:
+ //
+ // "http://www.google.com/images?q=tbn:<encrypted doc id>"
+ //
+ // For example, the image:
+ //
+ // "http://www.google.com/images?q=tbn:e-AB_0zkH08qtM
+ //
+ // ... has an encrypted doc ID of "e-AB_0zkH08qtM".
+ optional string encrypted_docid = 1;
+}
diff --git a/chromium/components/ntp_snippets/mock_content_suggestions_provider.cc b/chromium/components/ntp_snippets/mock_content_suggestions_provider.cc
index cf447b336ba..46dc34f6e74 100644
--- a/chromium/components/ntp_snippets/mock_content_suggestions_provider.cc
+++ b/chromium/components/ntp_snippets/mock_content_suggestions_provider.cc
@@ -64,6 +64,12 @@ void MockContentSuggestionsProvider::FetchSuggestionImage(
FetchSuggestionImageMock(id, callback);
}
+void MockContentSuggestionsProvider::FetchSuggestionImageData(
+ const ContentSuggestion::ID& id,
+ ImageDataFetchedCallback callback) {
+ FetchSuggestionImageDataMock(id, &callback);
+}
+
void MockContentSuggestionsProvider::FireSuggestionsChanged(
Category category,
std::vector<ContentSuggestion> suggestions) {
diff --git a/chromium/components/ntp_snippets/mock_content_suggestions_provider.h b/chromium/components/ntp_snippets/mock_content_suggestions_provider.h
index 22c43d1d660..7cabecf8b97 100644
--- a/chromium/components/ntp_snippets/mock_content_suggestions_provider.h
+++ b/chromium/components/ntp_snippets/mock_content_suggestions_provider.h
@@ -76,8 +76,12 @@ class MockContentSuggestionsProvider : public ContentSuggestionsProvider {
void(const ContentSuggestion::ID& suggestion_id));
void FetchSuggestionImage(const ContentSuggestion::ID& id,
ImageFetchedCallback callback) override;
+ void FetchSuggestionImageData(const ContentSuggestion::ID& id,
+ ImageDataFetchedCallback callback) override;
MOCK_METHOD2(FetchSuggestionImageMock,
void(const ContentSuggestion::ID&, const ImageFetchedCallback&));
+ MOCK_METHOD2(FetchSuggestionImageDataMock,
+ void(const ContentSuggestion::ID&, ImageDataFetchedCallback*));
private:
std::vector<Category> provided_categories_;
diff --git a/chromium/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.cc b/chromium/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.cc
index 728ca5c95fc..8c64425f1a3 100644
--- a/chromium/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.cc
+++ b/chromium/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.cc
@@ -113,6 +113,15 @@ void RecentTabSuggestionsProvider::FetchSuggestionImage(
FROM_HERE, base::BindOnce(std::move(callback), gfx::Image()));
}
+void RecentTabSuggestionsProvider::FetchSuggestionImageData(
+ const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) {
+ // TODO(vitaliii): Fetch proper thumbnail from OfflinePageModel once it's
+ // available there.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), std::string()));
+}
+
void RecentTabSuggestionsProvider::Fetch(
const Category& category,
const std::set<std::string>& known_suggestion_ids,
diff --git a/chromium/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.h b/chromium/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.h
index 9aa31ce2a36..2c8c11e3228 100644
--- a/chromium/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.h
+++ b/chromium/components/ntp_snippets/offline_pages/recent_tab_suggestions_provider.h
@@ -44,6 +44,8 @@ class RecentTabSuggestionsProvider : public ContentSuggestionsProvider,
void DismissSuggestion(const ContentSuggestion::ID& suggestion_id) override;
void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
ImageFetchedCallback callback) override;
+ void FetchSuggestionImageData(const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) override;
void Fetch(const Category& category,
const std::set<std::string>& known_suggestion_ids,
FetchDoneCallback callback) override;
diff --git a/chromium/components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.cc b/chromium/components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.cc
index 8d05d52f526..357a8c74c2e 100644
--- a/chromium/components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.cc
+++ b/chromium/components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.cc
@@ -145,6 +145,13 @@ void PhysicalWebPageSuggestionsProvider::FetchSuggestionImage(
raw_data.size())));
}
+void PhysicalWebPageSuggestionsProvider::FetchSuggestionImageData(
+ const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) {
+ // Not implemented for this provider.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), std::string()));
+}
void PhysicalWebPageSuggestionsProvider::Fetch(
const Category& category,
const std::set<std::string>& known_suggestion_ids,
diff --git a/chromium/components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.h b/chromium/components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.h
index 20c00f82fcf..c4c1130ad65 100644
--- a/chromium/components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.h
+++ b/chromium/components/ntp_snippets/physical_web_pages/physical_web_page_suggestions_provider.h
@@ -44,6 +44,8 @@ class PhysicalWebPageSuggestionsProvider
void DismissSuggestion(const ContentSuggestion::ID& suggestion_id) override;
void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
ImageFetchedCallback callback) override;
+ void FetchSuggestionImageData(const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) override;
void Fetch(const Category& category,
const std::set<std::string>& known_suggestion_ids,
FetchDoneCallback callback) override;
diff --git a/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.cc b/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.cc
index b8a12e1d3ac..e4937822070 100644
--- a/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.cc
+++ b/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.cc
@@ -29,7 +29,7 @@ const int kMaxEntries = 3;
bool CompareEntries(const ReadingListEntry* lhs, const ReadingListEntry* rhs) {
return lhs->UpdateTime() > rhs->UpdateTime();
}
-}
+} // namespace
ReadingListSuggestionsProvider::ReadingListSuggestionsProvider(
ContentSuggestionsProvider::Observer* observer,
@@ -86,6 +86,13 @@ void ReadingListSuggestionsProvider::FetchSuggestionImage(
FROM_HERE, base::BindOnce(std::move(callback), gfx::Image()));
}
+void ReadingListSuggestionsProvider::FetchSuggestionImageData(
+ const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), std::string()));
+}
+
void ReadingListSuggestionsProvider::Fetch(
const Category& category,
const std::set<std::string>& known_suggestion_ids,
diff --git a/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.h b/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.h
index 69c603fa8a0..1a2aeee8f50 100644
--- a/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.h
+++ b/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider.h
@@ -35,6 +35,8 @@ class ReadingListSuggestionsProvider : public ContentSuggestionsProvider,
void DismissSuggestion(const ContentSuggestion::ID& suggestion_id) override;
void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
ImageFetchedCallback callback) override;
+ void FetchSuggestionImageData(const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) override;
void Fetch(const Category& category,
const std::set<std::string>& known_suggestion_ids,
FetchDoneCallback callback) override;
diff --git a/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider_unittest.cc b/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider_unittest.cc
index cc6065ecc9c..8c1d72592b2 100644
--- a/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider_unittest.cc
+++ b/chromium/components/ntp_snippets/reading_list/reading_list_suggestions_provider_unittest.cc
@@ -30,11 +30,8 @@ using ::testing::Property;
class ReadingListSuggestionsProviderTest : public ::testing::Test {
public:
ReadingListSuggestionsProviderTest() {
- std::unique_ptr<base::SimpleTestClock> clock =
- std::make_unique<base::SimpleTestClock>();
- clock_ = clock.get();
model_ = std::make_unique<ReadingListModelImpl>(
- /*storage_layer=*/nullptr, /*pref_service=*/nullptr, std::move(clock));
+ /*storage_layer=*/nullptr, /*pref_service=*/nullptr, &clock_);
}
void CreateProvider() {
@@ -56,23 +53,23 @@ class ReadingListSuggestionsProviderTest : public ::testing::Test {
void AddEntries() {
model_->AddEntry(url_unread1_, kTitleUnread1,
reading_list::ADDED_VIA_CURRENT_APP);
- clock_->Advance(base::TimeDelta::FromMilliseconds(10));
+ clock_.Advance(base::TimeDelta::FromMilliseconds(10));
model_->AddEntry(url_unread2_, kTitleUnread2,
reading_list::ADDED_VIA_CURRENT_APP);
- clock_->Advance(base::TimeDelta::FromMilliseconds(10));
+ clock_.Advance(base::TimeDelta::FromMilliseconds(10));
model_->AddEntry(url_read1_, kTitleRead1,
reading_list::ADDED_VIA_CURRENT_APP);
model_->SetReadStatus(url_read1_, true);
- clock_->Advance(base::TimeDelta::FromMilliseconds(10));
+ clock_.Advance(base::TimeDelta::FromMilliseconds(10));
model_->AddEntry(url_unread3_, kTitleUnread3,
reading_list::ADDED_VIA_CURRENT_APP);
- clock_->Advance(base::TimeDelta::FromMilliseconds(10));
+ clock_.Advance(base::TimeDelta::FromMilliseconds(10));
model_->AddEntry(url_unread4_, kTitleUnread4,
reading_list::ADDED_VIA_CURRENT_APP);
}
protected:
- base::SimpleTestClock* clock_;
+ base::SimpleTestClock clock_;
std::unique_ptr<ReadingListModelImpl> model_;
testing::StrictMock<MockContentSuggestionsProviderObserver> observer_;
std::unique_ptr<ReadingListSuggestionsProvider> provider_;
@@ -117,7 +114,7 @@ TEST_F(ReadingListSuggestionsProviderTest, ReturnsOnlyUnreadSuggestion) {
std::string title_read1 = "title_read1";
model_->AddEntry(url_unread1, title_unread1,
reading_list::ADDED_VIA_CURRENT_APP);
- clock_->Advance(base::TimeDelta::FromMilliseconds(10));
+ clock_.Advance(base::TimeDelta::FromMilliseconds(10));
model_->AddEntry(url_read1, title_read1, reading_list::ADDED_VIA_CURRENT_APP);
model_->SetReadStatus(url_read1, true);
diff --git a/chromium/components/ntp_snippets/remote/cached_image_fetcher.cc b/chromium/components/ntp_snippets/remote/cached_image_fetcher.cc
index b9995faa750..eaeda716d4c 100644
--- a/chromium/components/ntp_snippets/remote/cached_image_fetcher.cc
+++ b/chromium/components/ntp_snippets/remote/cached_image_fetcher.cc
@@ -3,9 +3,10 @@
// found in the LICENSE file.
#include "components/ntp_snippets/remote/cached_image_fetcher.h"
-
#include "base/bind.h"
+#include "base/bind_helpers.h"
#include "base/location.h"
+#include "base/logging.h"
#include "components/image_fetcher/core/image_decoder.h"
#include "components/image_fetcher/core/image_fetcher.h"
#include "components/ntp_snippets/remote/remote_suggestions_database.h"
@@ -14,6 +15,31 @@
#include "ui/gfx/image/image.h"
namespace ntp_snippets {
+namespace {
+constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
+ net::DefineNetworkTrafficAnnotation("remote_suggestions_provider", R"(
+ semantics {
+ sender: "Content Suggestion Thumbnail Fetch"
+ description:
+ "Retrieves thumbnails for content suggestions, for display on the "
+ "New Tab page or Chrome Home."
+ trigger:
+ "Triggered when the user looks at a content suggestion (and its "
+ "thumbnail isn't cached yet)."
+ data: "None."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting: "Currently not available, but in progress: crbug.com/703684"
+ chrome_policy {
+ NTPContentSuggestionsEnabled {
+ policy_options {mode: MANDATORY}
+ NTPContentSuggestionsEnabled: false
+ }
+ }
+ })");
+} // namespace
CachedImageFetcher::CachedImageFetcher(
std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
@@ -26,7 +52,6 @@ CachedImageFetcher::CachedImageFetcher(
RequestThrottler::RequestType::CONTENT_SUGGESTION_THUMBNAIL) {
// |image_fetcher_| can be null in tests.
if (image_fetcher_) {
- image_fetcher_->SetImageFetcherDelegate(this);
image_fetcher_->SetDataUseServiceName(
data_use_measurement::DataUseUserData::NTP_SNIPPETS_THUMBNAILS);
}
@@ -37,23 +62,13 @@ CachedImageFetcher::~CachedImageFetcher() {}
void CachedImageFetcher::FetchSuggestionImage(
const ContentSuggestion::ID& suggestion_id,
const GURL& url,
- ImageFetchedCallback callback) {
+ ImageDataFetchedCallback image_data_callback,
+ ImageFetchedCallback image_callback) {
database_->LoadImage(
suggestion_id.id_within_category(),
- base::Bind(&CachedImageFetcher::OnImageFetchedFromDatabase,
- base::Unretained(this), base::Passed(std::move(callback)),
- suggestion_id, url));
-}
-
-// This function gets only called for caching the image data received from the
-// network. The actual decoding is done in OnImageDecodedFromDatabase().
-void CachedImageFetcher::OnImageDataFetched(
- const std::string& id_within_category,
- const std::string& image_data) {
- if (image_data.empty()) {
- return;
- }
- database_->SaveImage(id_within_category, image_data);
+ base::BindOnce(&CachedImageFetcher::OnImageFetchedFromDatabase,
+ base::Unretained(this), std::move(image_data_callback),
+ std::move(image_callback), suggestion_id, url));
}
void CachedImageFetcher::OnImageDecodingDone(
@@ -65,23 +80,32 @@ void CachedImageFetcher::OnImageDecodingDone(
}
void CachedImageFetcher::OnImageFetchedFromDatabase(
- ImageFetchedCallback callback,
+ ImageDataFetchedCallback image_data_callback,
+ ImageFetchedCallback image_callback,
const ContentSuggestion::ID& suggestion_id,
const GURL& url,
std::string data) { // SnippetImageCallback requires by-value.
- // The image decoder is null in tests.
- if (image_fetcher_->GetImageDecoder() && !data.empty()) {
+ if (data.empty()) {
+ // Fetching from the DB failed; start a network fetch.
+ FetchImageFromNetwork(suggestion_id, url, std::move(image_data_callback),
+ std::move(image_callback));
+ return;
+ }
+
+ if (image_data_callback) {
+ std::move(image_data_callback).Run(data);
+ }
+
+ if (image_callback) {
image_fetcher_->GetImageDecoder()->DecodeImage(
data,
// We're not dealing with multi-frame images.
/*desired_image_frame_size=*/gfx::Size(),
base::Bind(&CachedImageFetcher::OnImageDecodedFromDatabase,
- base::Unretained(this), base::Passed(std::move(callback)),
- suggestion_id, url));
- return;
+ base::Unretained(this),
+ base::Passed(std::move(image_callback)), suggestion_id,
+ url));
}
- // Fetching from the DB failed; start a network fetch.
- FetchImageFromNetwork(suggestion_id, url, std::move(callback));
}
void CachedImageFetcher::OnImageDecodedFromDatabase(
@@ -95,50 +119,55 @@ void CachedImageFetcher::OnImageDecodedFromDatabase(
}
// If decoding the image failed, delete the DB entry.
database_->DeleteImage(suggestion_id.id_within_category());
- FetchImageFromNetwork(suggestion_id, url, std::move(callback));
+ FetchImageFromNetwork(suggestion_id, url, ImageDataFetchedCallback(),
+ std::move(callback));
}
void CachedImageFetcher::FetchImageFromNetwork(
const ContentSuggestion::ID& suggestion_id,
const GURL& url,
- ImageFetchedCallback callback) {
+ ImageDataFetchedCallback image_data_callback,
+ ImageFetchedCallback image_callback) {
if (url.is_empty() || !thumbnail_requests_throttler_.DemandQuotaForRequest(
/*interactive_request=*/true)) {
// Return an empty image. Directly, this is never synchronous with the
// original FetchSuggestionImage() call - an asynchronous database query has
// happened in the meantime.
- std::move(callback).Run(gfx::Image());
+ if (image_data_callback) {
+ std::move(image_data_callback).Run(std::string());
+ }
+ if (image_callback) {
+ std::move(image_callback).Run(gfx::Image());
+ }
return;
}
+ // Image decoding callback only set when requested.
+ image_fetcher::ImageFetcher::ImageFetcherCallback decode_callback;
+ if (image_callback) {
+ decode_callback = base::BindOnce(&CachedImageFetcher::OnImageDecodingDone,
+ base::Unretained(this),
+ base::Passed(std::move(image_callback)));
+ }
- net::NetworkTrafficAnnotationTag traffic_annotation =
- net::DefineNetworkTrafficAnnotation("remote_suggestions_provider", R"(
- semantics {
- sender: "Content Suggestion Thumbnail Fetch"
- description:
- "Retrieves thumbnails for content suggestions, for display on the "
- "New Tab page or Chrome Home."
- trigger:
- "Triggered when the user looks at a content suggestion (and its "
- "thumbnail isn't cached yet)."
- data: "None."
- destination: GOOGLE_OWNED_SERVICE
- }
- policy {
- cookies_allowed: NO
- setting: "Currently not available, but in progress: crbug.com/703684"
- chrome_policy {
- NTPContentSuggestionsEnabled {
- policy_options {mode: MANDATORY}
- NTPContentSuggestionsEnabled: false
- }
- }
- })");
- image_fetcher_->StartOrQueueNetworkRequest(
+ image_fetcher_->FetchImageAndData(
suggestion_id.id_within_category(), url,
- base::Bind(&CachedImageFetcher::OnImageDecodingDone,
- base::Unretained(this), base::Passed(std::move(callback))),
- traffic_annotation);
+ base::BindOnce(&CachedImageFetcher::SaveImageAndInvokeDataCallback,
+ base::Unretained(this), suggestion_id.id_within_category(),
+ std::move(image_data_callback)),
+ std::move(decode_callback), kTrafficAnnotation);
+}
+
+void CachedImageFetcher::SaveImageAndInvokeDataCallback(
+ const std::string& id_within_category,
+ ImageDataFetchedCallback callback,
+ const std::string& image_data,
+ const image_fetcher::RequestMetadata& request_metadata) {
+ if (!image_data.empty()) {
+ database_->SaveImage(id_within_category, image_data);
+ }
+ if (callback) {
+ std::move(callback).Run(image_data);
+ }
}
} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/remote/cached_image_fetcher.h b/chromium/components/ntp_snippets/remote/cached_image_fetcher.h
index 51eef498ac9..d1e1c6d55d6 100644
--- a/chromium/components/ntp_snippets/remote/cached_image_fetcher.h
+++ b/chromium/components/ntp_snippets/remote/cached_image_fetcher.h
@@ -13,7 +13,6 @@
#include "base/callback_forward.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
-#include "components/image_fetcher/core/image_fetcher_delegate.h"
#include "components/ntp_snippets/callbacks.h"
#include "components/ntp_snippets/content_suggestion.h"
#include "components/ntp_snippets/remote/request_throttler.h"
@@ -35,43 +34,55 @@ class RemoteSuggestionsDatabase;
// CachedImageFetcher takes care of fetching images from the network and caching
// them in the database.
-class CachedImageFetcher : public image_fetcher::ImageFetcherDelegate {
+class CachedImageFetcher {
public:
// |pref_service| and |database| need to outlive the created image fetcher
// instance.
CachedImageFetcher(std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher,
PrefService* pref_service,
RemoteSuggestionsDatabase* database);
- ~CachedImageFetcher() override;
+ virtual ~CachedImageFetcher();
// Fetches the image for a suggestion. The fetcher will first issue a lookup
// to the underlying cache with a fallback to the network.
- virtual void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
- const GURL& image_url,
- ImageFetchedCallback callback);
+ virtual void FetchSuggestionImage(
+ const ContentSuggestion::ID& suggestion_id,
+ const GURL& image_url,
+ ImageDataFetchedCallback image_data_callback,
+ ImageFetchedCallback image_callback);
private:
- // image_fetcher::ImageFetcherDelegate implementation.
void OnImageDataFetched(const std::string& id_within_category,
- const std::string& image_data) override;
+ const std::string& image_data,
+ const image_fetcher::RequestMetadata& metadata);
void OnImageDecodingDone(ImageFetchedCallback callback,
const std::string& id_within_category,
const gfx::Image& image,
const image_fetcher::RequestMetadata& metadata);
+
void OnImageFetchedFromDatabase(
- ImageFetchedCallback callback,
+ ImageDataFetchedCallback image_data_callback,
+ ImageFetchedCallback image_callback,
const ContentSuggestion::ID& suggestion_id,
const GURL& image_url,
// SnippetImageCallback requires by-value (not const ref).
std::string data);
+
void OnImageDecodedFromDatabase(ImageFetchedCallback callback,
const ContentSuggestion::ID& suggestion_id,
const GURL& url,
const gfx::Image& image);
+
void FetchImageFromNetwork(const ContentSuggestion::ID& suggestion_id,
const GURL& url,
- ImageFetchedCallback callback);
+ ImageDataFetchedCallback image_data_callback,
+ ImageFetchedCallback image_callback);
+ void SaveImageAndInvokeDataCallback(
+ const std::string& id_within_category,
+ ImageDataFetchedCallback callback,
+ const std::string& image_data,
+ const image_fetcher::RequestMetadata& request_metadata);
std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher_;
RemoteSuggestionsDatabase* database_;
diff --git a/chromium/components/ntp_snippets/remote/cached_image_fetcher_unittest.cc b/chromium/components/ntp_snippets/remote/cached_image_fetcher_unittest.cc
index 48e47227776..64dc9e19951 100644
--- a/chromium/components/ntp_snippets/remote/cached_image_fetcher_unittest.cc
+++ b/chromium/components/ntp_snippets/remote/cached_image_fetcher_unittest.cc
@@ -37,6 +37,9 @@ namespace {
const char kImageData[] = "data";
const char kImageURL[] = "http://image.test/test.png";
const char kSnippetID[] = "http://localhost";
+const ContentSuggestion::ID kSuggestionID(
+ Category::FromKnownCategory(KnownCategories::ARTICLES),
+ kSnippetID);
// Always decodes a valid image for all non-empty input.
class FakeImageDecoder : public image_fetcher::ImageDecoder {
@@ -45,17 +48,31 @@ class FakeImageDecoder : public image_fetcher::ImageDecoder {
const std::string& image_data,
const gfx::Size& desired_image_frame_size,
const image_fetcher::ImageDecodedCallback& callback) override {
+ ASSERT_TRUE(enabled_);
gfx::Image image;
if (!image_data.empty()) {
+ ASSERT_EQ(kImageData, image_data);
image = gfx::test::CreateImage();
}
- callback.Run(image);
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindRepeating(callback, image));
}
+ void SetEnabled(bool enabled) { enabled_ = enabled; }
+
+ private:
+ bool enabled_ = true;
};
+enum class TestType {
+ kImageCallback,
+ kImageDataCallback,
+ kBothCallbacks,
+};
} // namespace
-class CachedImageFetcherTest : public testing::Test {
+// This test is parameterized to run all tests in the three configurations:
+// both callbacks used, only image_callback used, only image_data_callback used.
+class CachedImageFetcherTest : public testing::TestWithParam<TestType> {
public:
CachedImageFetcherTest() : fake_url_fetcher_factory_(nullptr) {
EXPECT_TRUE(database_dir_.CreateUniqueTempDir());
@@ -85,11 +102,36 @@ class CachedImageFetcherTest : public testing::Test {
RunUntilIdle();
}
- void FetchImage(ImageFetchedCallback callback) {
- ContentSuggestion::ID content_suggestion_id(
- Category::FromKnownCategory(KnownCategories::ARTICLES), kSnippetID);
- cached_image_fetcher_->FetchSuggestionImage(
- content_suggestion_id, GURL(kImageURL), std::move(callback));
+ void Fetch(std::string expected_image_data, bool expect_image) {
+ fake_image_decoder()->SetEnabled(GetParam() !=
+ TestType::kImageDataCallback);
+ base::MockCallback<ImageFetchedCallback> image_callback;
+ base::MockCallback<ImageDataFetchedCallback> image_data_callback;
+ switch (GetParam()) {
+ case TestType::kImageCallback: {
+ EXPECT_CALL(image_callback,
+ Run(Property(&gfx::Image::IsEmpty, Eq(!expect_image))));
+ cached_image_fetcher()->FetchSuggestionImage(
+ kSuggestionID, GURL(kImageURL), ImageDataFetchedCallback(),
+ image_callback.Get());
+
+ } break;
+ case TestType::kImageDataCallback: {
+ EXPECT_CALL(image_data_callback, Run(expected_image_data));
+ cached_image_fetcher()->FetchSuggestionImage(
+ kSuggestionID, GURL(kImageURL), image_data_callback.Get(),
+ ImageFetchedCallback());
+ } break;
+ case TestType::kBothCallbacks: {
+ EXPECT_CALL(image_data_callback, Run(expected_image_data));
+ EXPECT_CALL(image_callback,
+ Run(Property(&gfx::Image::IsEmpty, Eq(!expect_image))));
+ cached_image_fetcher()->FetchSuggestionImage(
+ kSuggestionID, GURL(kImageURL), image_data_callback.Get(),
+ image_callback.Get());
+ } break;
+ }
+ RunUntilIdle();
}
void RunUntilIdle() { scoped_task_environment_.RunUntilIdle(); }
@@ -99,6 +141,9 @@ class CachedImageFetcherTest : public testing::Test {
net::FakeURLFetcherFactory* fake_url_fetcher_factory() {
return &fake_url_fetcher_factory_;
}
+ CachedImageFetcher* cached_image_fetcher() {
+ return cached_image_fetcher_.get();
+ }
private:
std::unique_ptr<CachedImageFetcher> cached_image_fetcher_;
@@ -113,44 +158,44 @@ class CachedImageFetcherTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(CachedImageFetcherTest);
};
-TEST_F(CachedImageFetcherTest, FetchImageFromCache) {
+TEST_P(CachedImageFetcherTest, FetchImageFromCache) {
// Save the image in the database.
database()->SaveImage(kSnippetID, kImageData);
RunUntilIdle();
// Do not provide any URL responses and expect that the image is fetched (from
// cache).
- base::MockCallback<ImageFetchedCallback> mock_image_fetched_callback;
- EXPECT_CALL(mock_image_fetched_callback,
- Run(Property(&gfx::Image::IsEmpty, Eq(false))));
- FetchImage(mock_image_fetched_callback.Get());
- RunUntilIdle();
+ Fetch(kImageData, true);
}
-TEST_F(CachedImageFetcherTest, FetchImageNotInCache) {
+TEST_P(CachedImageFetcherTest, FetchImagePopulatesCache) {
// Expect the image to be fetched by URL.
- fake_url_fetcher_factory()->SetFakeResponse(GURL(kImageURL), kImageData,
- net::HTTP_OK,
- net::URLRequestStatus::SUCCESS);
- base::MockCallback<ImageFetchedCallback> mock_image_fetched_callback;
- EXPECT_CALL(mock_image_fetched_callback,
- Run(Property(&gfx::Image::IsEmpty, Eq(false))));
- FetchImage(mock_image_fetched_callback.Get());
- RunUntilIdle();
+ {
+ fake_url_fetcher_factory()->SetFakeResponse(GURL(kImageURL), kImageData,
+ net::HTTP_OK,
+ net::URLRequestStatus::SUCCESS);
+ Fetch(kImageData, true);
+ }
+ // Fetch again. The cache should be populated, no network request is needed.
+ {
+ fake_url_fetcher_factory()->ClearFakeResponses();
+ Fetch(kImageData, true);
+ }
}
-TEST_F(CachedImageFetcherTest, FetchNonExistingImage) {
+TEST_P(CachedImageFetcherTest, FetchNonExistingImage) {
const std::string kErrorResponse = "error-response";
fake_url_fetcher_factory()->SetFakeResponse(GURL(kImageURL), kErrorResponse,
net::HTTP_NOT_FOUND,
net::URLRequestStatus::FAILED);
// Expect an empty image is fetched if the URL cannot be requested.
- const std::string kEmptyImageData;
- base::MockCallback<ImageFetchedCallback> mock_image_fetched_callback;
- EXPECT_CALL(mock_image_fetched_callback,
- Run(Property(&gfx::Image::IsEmpty, Eq(true))));
- FetchImage(mock_image_fetched_callback.Get());
- RunUntilIdle();
+ Fetch("", false);
}
+INSTANTIATE_TEST_CASE_P(,
+ CachedImageFetcherTest,
+ testing::Values(TestType::kImageCallback,
+ TestType::kImageDataCallback,
+ TestType::kBothCallbacks));
+
} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/remote/json_request_unittest.cc b/chromium/components/ntp_snippets/remote/json_request_unittest.cc
index 08af1967adb..24da870e3e1 100644
--- a/chromium/components/ntp_snippets/remote/json_request_unittest.cc
+++ b/chromium/components/ntp_snippets/remote/json_request_unittest.cc
@@ -65,7 +65,6 @@ class JsonRequestTest : public testing::Test {
{ntp_snippets::kArticleSuggestionsFeature.name}),
pref_service_(std::make_unique<TestingPrefServiceSimple>()),
mock_task_runner_(new base::TestMockTimeTaskRunner()),
- clock_(mock_task_runner_->GetMockClock()),
request_context_getter_(
new net::TestURLRequestContextGetter(mock_task_runner_.get())) {
language::UrlLanguageHistogram::RegisterProfilePrefs(
@@ -88,7 +87,7 @@ class JsonRequestTest : public testing::Test {
JsonRequest::Builder CreateMinimalBuilder() {
JsonRequest::Builder builder;
builder.SetUrl(GURL("http://valid-url.test"))
- .SetClock(clock_.get())
+ .SetClock(mock_task_runner_->GetMockClock())
.SetUrlRequestContextGetter(request_context_getter_.get());
return builder;
}
@@ -97,7 +96,6 @@ class JsonRequestTest : public testing::Test {
variations::testing::VariationParamsManager params_manager_;
std::unique_ptr<TestingPrefServiceSimple> pref_service_;
scoped_refptr<base::TestMockTimeTaskRunner> mock_task_runner_;
- std::unique_ptr<base::Clock> clock_;
scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_;
net::TestURLFetcherFactory fetcher_factory_;
diff --git a/chromium/components/ntp_snippets/remote/remote_suggestion.h b/chromium/components/ntp_snippets/remote/remote_suggestion.h
index 7aef0b66549..9da686d9d62 100644
--- a/chromium/components/ntp_snippets/remote/remote_suggestion.h
+++ b/chromium/components/ntp_snippets/remote/remote_suggestion.h
@@ -32,7 +32,7 @@ class RemoteSuggestion {
public:
using PtrVector = std::vector<std::unique_ptr<RemoteSuggestion>>;
- enum ContentType { UNKNOWN, VIDEO };
+ enum class ContentType { UNKNOWN, VIDEO };
~RemoteSuggestion();
diff --git a/chromium/components/ntp_snippets/remote/remote_suggestion_builder.cc b/chromium/components/ntp_snippets/remote/remote_suggestion_builder.cc
index f552601147b..b9bcf31f997 100644
--- a/chromium/components/ntp_snippets/remote/remote_suggestion_builder.cc
+++ b/chromium/components/ntp_snippets/remote/remote_suggestion_builder.cc
@@ -7,7 +7,6 @@
#include <limits>
#include <memory>
-#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "components/ntp_snippets/remote/proto/ntp_snippets.pb.h"
diff --git a/chromium/components/ntp_snippets/remote/remote_suggestions_database.cc b/chromium/components/ntp_snippets/remote/remote_suggestions_database.cc
index 7e317ad9002..c8fb4090d7d 100644
--- a/chromium/components/ntp_snippets/remote/remote_suggestions_database.cc
+++ b/chromium/components/ntp_snippets/remote/remote_suggestions_database.cc
@@ -26,8 +26,7 @@ const char kImageDatabaseUMAClientName[] = "NTPSnippetImages";
const char kSnippetDatabaseFolder[] = "snippets";
const char kImageDatabaseFolder[] = "images";
-const size_t kDatabaseWriteBufferSizeBytes = 512 << 10;
-const size_t kDatabaseWriteBufferSizeBytesForLowEndDevice = 128 << 10;
+const size_t kDatabaseWriteBufferSizeBytes = 128 << 10;
} // namespace
namespace ntp_snippets {
@@ -59,11 +58,9 @@ RemoteSuggestionsDatabase::RemoteSuggestionsDatabase(
weak_ptr_factory_(this) {
base::FilePath snippet_dir = database_dir.AppendASCII(kSnippetDatabaseFolder);
leveldb_env::Options options = leveldb_proto::CreateSimpleOptions();
- if (base::SysInfo::IsLowEndDevice()) {
- options.write_buffer_size = kDatabaseWriteBufferSizeBytesForLowEndDevice;
- } else {
- options.write_buffer_size = kDatabaseWriteBufferSizeBytes;
- }
+ options.reuse_logs = false; // Consumes less RAM over time.
+ options.write_buffer_size = kDatabaseWriteBufferSizeBytes;
+
database_->Init(kDatabaseUMAClientName, snippet_dir, options,
base::Bind(&RemoteSuggestionsDatabase::OnDatabaseInited,
weak_ptr_factory_.GetWeakPtr()));
@@ -91,11 +88,11 @@ void RemoteSuggestionsDatabase::SetErrorCallback(
error_callback_ = error_callback;
}
-void RemoteSuggestionsDatabase::LoadSnippets(const SnippetsCallback& callback) {
+void RemoteSuggestionsDatabase::LoadSnippets(SnippetsCallback callback) {
if (IsInitialized()) {
- LoadSnippetsImpl(callback);
+ LoadSnippetsImpl(std::move(callback));
} else {
- pending_snippets_callbacks_.emplace_back(callback);
+ pending_snippets_callbacks_.emplace_back(std::move(callback));
}
}
@@ -135,13 +132,12 @@ void RemoteSuggestionsDatabase::DeleteSnippets(
weak_ptr_factory_.GetWeakPtr()));
}
-void RemoteSuggestionsDatabase::LoadImage(
- const std::string& snippet_id,
- const SnippetImageCallback& callback) {
+void RemoteSuggestionsDatabase::LoadImage(const std::string& snippet_id,
+ SnippetImageCallback callback) {
if (IsInitialized()) {
- LoadImageImpl(snippet_id, callback);
+ LoadImageImpl(snippet_id, std::move(callback));
} else {
- pending_image_callbacks_.emplace_back(snippet_id, callback);
+ pending_image_callbacks_.emplace_back(snippet_id, std::move(callback));
}
}
@@ -197,7 +193,7 @@ void RemoteSuggestionsDatabase::OnDatabaseInited(bool success) {
}
void RemoteSuggestionsDatabase::OnDatabaseLoaded(
- const SnippetsCallback& callback,
+ SnippetsCallback callback,
bool success,
std::unique_ptr<std::vector<SnippetProto>> entries) {
if (!success) {
@@ -226,7 +222,7 @@ void RemoteSuggestionsDatabase::OnDatabaseLoaded(
}
}
- callback.Run(std::move(snippets));
+ std::move(callback).Run(std::move(snippets));
// If any of the snippet protos couldn't be converted to actual snippets,
// clean them up now.
@@ -256,7 +252,7 @@ void RemoteSuggestionsDatabase::OnImageDatabaseInited(bool success) {
}
void RemoteSuggestionsDatabase::OnImageDatabaseLoaded(
- const SnippetImageCallback& callback,
+ SnippetImageCallback callback,
bool success,
std::unique_ptr<SnippetImageProto> entry) {
if (!success) {
@@ -266,12 +262,12 @@ void RemoteSuggestionsDatabase::OnImageDatabaseLoaded(
}
if (!entry) {
- callback.Run(std::string());
+ std::move(callback).Run(std::string());
return;
}
std::unique_ptr<std::string> data(entry->release_data());
- callback.Run(std::move(*data));
+ std::move(callback).Run(std::move(*data));
}
void RemoteSuggestionsDatabase::OnImageDatabaseSaved(bool success) {
@@ -292,23 +288,22 @@ void RemoteSuggestionsDatabase::OnDatabaseError() {
void RemoteSuggestionsDatabase::ProcessPendingLoads() {
DCHECK(IsInitialized());
- for (const auto& callback : pending_snippets_callbacks_) {
- LoadSnippetsImpl(callback);
+ for (auto& callback : pending_snippets_callbacks_) {
+ LoadSnippetsImpl(std::move(callback));
}
pending_snippets_callbacks_.clear();
- for (const auto& id_callback : pending_image_callbacks_) {
- LoadImageImpl(id_callback.first, id_callback.second);
+ for (auto& id_callback : pending_image_callbacks_) {
+ LoadImageImpl(id_callback.first, std::move(id_callback.second));
}
pending_image_callbacks_.clear();
}
-void RemoteSuggestionsDatabase::LoadSnippetsImpl(
- const SnippetsCallback& callback) {
+void RemoteSuggestionsDatabase::LoadSnippetsImpl(SnippetsCallback callback) {
DCHECK(IsInitialized());
database_->LoadEntries(
- base::Bind(&RemoteSuggestionsDatabase::OnDatabaseLoaded,
- weak_ptr_factory_.GetWeakPtr(), callback));
+ base::BindOnce(&RemoteSuggestionsDatabase::OnDatabaseLoaded,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void RemoteSuggestionsDatabase::SaveSnippetsImpl(
@@ -323,13 +318,13 @@ void RemoteSuggestionsDatabase::SaveSnippetsImpl(
weak_ptr_factory_.GetWeakPtr()));
}
-void RemoteSuggestionsDatabase::LoadImageImpl(
- const std::string& snippet_id,
- const SnippetImageCallback& callback) {
+void RemoteSuggestionsDatabase::LoadImageImpl(const std::string& snippet_id,
+ SnippetImageCallback callback) {
DCHECK(IsInitialized());
image_database_->GetEntry(
- snippet_id, base::Bind(&RemoteSuggestionsDatabase::OnImageDatabaseLoaded,
- weak_ptr_factory_.GetWeakPtr(), callback));
+ snippet_id,
+ base::BindOnce(&RemoteSuggestionsDatabase::OnImageDatabaseLoaded,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void RemoteSuggestionsDatabase::DeleteUnreferencedImages(
diff --git a/chromium/components/ntp_snippets/remote/remote_suggestions_database.h b/chromium/components/ntp_snippets/remote/remote_suggestions_database.h
index 6966cf13040..c957ddebd4c 100644
--- a/chromium/components/ntp_snippets/remote/remote_suggestions_database.h
+++ b/chromium/components/ntp_snippets/remote/remote_suggestions_database.h
@@ -31,8 +31,9 @@ class SnippetProto;
// TODO(gaschler): implement a Fake version for testing
class RemoteSuggestionsDatabase {
public:
- using SnippetsCallback = base::Callback<void(RemoteSuggestion::PtrVector)>;
- using SnippetImageCallback = base::Callback<void(std::string)>;
+ using SnippetsCallback =
+ base::OnceCallback<void(RemoteSuggestion::PtrVector)>;
+ using SnippetImageCallback = base::OnceCallback<void(std::string)>;
// Creates a RemoteSuggestionsDatabase backed by real ProtoDatabaseImpls.
RemoteSuggestionsDatabase(const base::FilePath& database_dir);
@@ -58,7 +59,7 @@ class RemoteSuggestionsDatabase {
void SetErrorCallback(const base::Closure& error_callback);
// Loads all snippets from storage and passes them to |callback|.
- void LoadSnippets(const SnippetsCallback& callback);
+ void LoadSnippets(SnippetsCallback callback);
// Adds or updates the given snippet.
void SaveSnippet(const RemoteSuggestion& snippet);
@@ -72,8 +73,7 @@ class RemoteSuggestionsDatabase {
// Loads the image data for the snippet with the given ID and passes it to
// |callback|. Passes an empty string if not found.
- void LoadImage(const std::string& snippet_id,
- const SnippetImageCallback& callback);
+ void LoadImage(const std::string& snippet_id, SnippetImageCallback callback);
// Adds or updates the image data for the given snippet ID.
void SaveImage(const std::string& snippet_id, const std::string& image_data);
@@ -102,14 +102,14 @@ class RemoteSuggestionsDatabase {
// Callbacks for ProtoDatabase<SnippetProto> operations.
void OnDatabaseInited(bool success);
- void OnDatabaseLoaded(const SnippetsCallback& callback,
+ void OnDatabaseLoaded(SnippetsCallback callback,
bool success,
std::unique_ptr<std::vector<SnippetProto>> entries);
void OnDatabaseSaved(bool success);
// Callbacks for ProtoDatabase<SnippetImageProto> operations.
void OnImageDatabaseInited(bool success);
- void OnImageDatabaseLoaded(const SnippetImageCallback& callback,
+ void OnImageDatabaseLoaded(SnippetImageCallback callback,
bool success,
std::unique_ptr<SnippetImageProto> entry);
void OnImageDatabaseSaved(bool success);
@@ -118,11 +118,11 @@ class RemoteSuggestionsDatabase {
void ProcessPendingLoads();
- void LoadSnippetsImpl(const SnippetsCallback& callback);
+ void LoadSnippetsImpl(SnippetsCallback callback);
void SaveSnippetsImpl(std::unique_ptr<KeyEntryVector> entries_to_save);
void LoadImageImpl(const std::string& snippet_id,
- const SnippetImageCallback& callback);
+ SnippetImageCallback callback);
void DeleteUnreferencedImages(
std::unique_ptr<std::set<std::string>> references,
bool load_keys_success,
diff --git a/chromium/components/ntp_snippets/remote/remote_suggestions_fetcher_impl_unittest.cc b/chromium/components/ntp_snippets/remote/remote_suggestions_fetcher_impl_unittest.cc
index f748abd2d6c..fe213ab6560 100644
--- a/chromium/components/ntp_snippets/remote/remote_suggestions_fetcher_impl_unittest.cc
+++ b/chromium/components/ntp_snippets/remote/remote_suggestions_fetcher_impl_unittest.cc
@@ -288,8 +288,7 @@ class RemoteSuggestionsFetcherImplTest : public testing::Test {
GetFetchEndpoint(version_info::Channel::STABLE), api_key,
user_classifier_.get());
- clock_ = mock_task_runner_->GetMockClock();
- fetcher_->SetClockForTesting(clock_.get());
+ fetcher_->SetClockForTesting(mock_task_runner_->GetMockClock());
}
void SignIn() { identity_test_env_.MakePrimaryAccountAvailable(kTestEmail); }
@@ -348,10 +347,6 @@ class RemoteSuggestionsFetcherImplTest : public testing::Test {
identity::IdentityTestEnvironment identity_test_env_;
private:
- // TODO(tzik): Remove |clock_| after updating GetMockTickClock to own the
- // instance. http://crbug.com/789079
- std::unique_ptr<base::Clock> clock_;
-
test::RemoteSuggestionsTestUtils utils_;
variations::testing::VariationParamsManager params_manager_;
scoped_refptr<base::TestMockTimeTaskRunner> mock_task_runner_;
diff --git a/chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc b/chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc
index 0035b61911b..25e7edb4fc2 100644
--- a/chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc
+++ b/chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl.cc
@@ -418,8 +418,8 @@ RemoteSuggestionsProviderImpl::RemoteSuggestionsProviderImpl(
// database is done loading.
database_load_start_ = base::TimeTicks::Now();
database_->LoadSnippets(
- base::Bind(&RemoteSuggestionsProviderImpl::OnDatabaseLoaded,
- base::Unretained(this)));
+ base::BindOnce(&RemoteSuggestionsProviderImpl::OnDatabaseLoaded,
+ base::Unretained(this)));
}
RemoteSuggestionsProviderImpl::~RemoteSuggestionsProviderImpl() {
@@ -442,7 +442,7 @@ void RemoteSuggestionsProviderImpl::ReloadSuggestions() {
if (!remote_suggestions_scheduler_->AcquireQuotaForInteractiveFetch()) {
return;
}
- auto callback = base::Bind(
+ auto callback = base::BindOnce(
[](RemoteSuggestionsScheduler* scheduler, Status status_code) {
scheduler->OnInteractiveFetchFinished(status_code);
},
@@ -1423,25 +1423,46 @@ void RemoteSuggestionsProviderImpl::NukeAllSuggestions() {
StoreCategoriesToPrefs();
}
+GURL RemoteSuggestionsProviderImpl::GetImageURLToFetch(
+ const ContentSuggestion::ID& suggestion_id) const {
+ if (!base::ContainsKey(category_contents_, suggestion_id.category())) {
+ return GURL();
+ }
+ return FindSuggestionImageUrl(suggestion_id);
+}
+
void RemoteSuggestionsProviderImpl::FetchSuggestionImage(
const ContentSuggestion::ID& suggestion_id,
ImageFetchedCallback callback) {
- if (!base::ContainsKey(category_contents_, suggestion_id.category())) {
+ GURL image_url = GetImageURLToFetch(suggestion_id);
+ if (image_url.is_empty()) {
+ // As we don't know the corresponding suggestion anymore, we don't expect to
+ // find it in the database (and also can't fetch it remotely). Cut the
+ // lookup short and return directly.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), gfx::Image()));
return;
}
- GURL image_url = FindSuggestionImageUrl(suggestion_id);
+ image_fetcher_.FetchSuggestionImage(suggestion_id, image_url,
+ ImageDataFetchedCallback(),
+ std::move(callback));
+}
+
+void RemoteSuggestionsProviderImpl::FetchSuggestionImageData(
+ const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) {
+ GURL image_url = GetImageURLToFetch(suggestion_id);
if (image_url.is_empty()) {
// As we don't know the corresponding suggestion anymore, we don't expect to
// find it in the database (and also can't fetch it remotely). Cut the
// lookup short and return directly.
base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(std::move(callback), gfx::Image()));
+ FROM_HERE, base::BindOnce(std::move(callback), std::string()));
return;
}
image_fetcher_.FetchSuggestionImage(suggestion_id, image_url,
- std::move(callback));
+ std::move(callback),
+ ntp_snippets::ImageFetchedCallback());
}
void RemoteSuggestionsProviderImpl::
diff --git a/chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl.h b/chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl.h
index 46a367e7ead..8a9c830e1eb 100644
--- a/chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl.h
+++ b/chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl.h
@@ -102,6 +102,8 @@ class RemoteSuggestionsProviderImpl final : public RemoteSuggestionsProvider {
void DismissSuggestion(const ContentSuggestion::ID& suggestion_id) override;
void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
ImageFetchedCallback callback) override;
+ void FetchSuggestionImageData(const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) override;
void Fetch(const Category& category,
const std::set<std::string>& known_suggestion_ids,
FetchDoneCallback callback) override;
@@ -417,6 +419,8 @@ class RemoteSuggestionsProviderImpl final : public RemoteSuggestionsProvider {
void NotifyFetchWithLoadingIndicatorStarted();
void NotifyFetchWithLoadingIndicatorFailedOrTimeouted();
+ GURL GetImageURLToFetch(const ContentSuggestion::ID& suggestion_id) const;
+
State state_;
PrefService* pref_service_;
diff --git a/chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc b/chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc
index 6e55a78f265..8f579741544 100644
--- a/chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc
+++ b/chromium/components/ntp_snippets/remote/remote_suggestions_provider_impl_unittest.cc
@@ -31,7 +31,7 @@
#include "base/timer/timer.h"
#include "components/image_fetcher/core/image_decoder.h"
#include "components/image_fetcher/core/image_fetcher.h"
-#include "components/image_fetcher/core/image_fetcher_delegate.h"
+#include "components/image_fetcher/core/mock_image_fetcher.h"
#include "components/image_fetcher/core/request_metadata.h"
#include "components/ntp_snippets/breaking_news/breaking_news_listener.h"
#include "components/ntp_snippets/category.h"
@@ -69,7 +69,7 @@
using base::TestMockTimeTaskRunner;
using image_fetcher::ImageFetcher;
-using image_fetcher::ImageFetcherDelegate;
+using image_fetcher::MockImageFetcher;
using ntp_snippets::test::FetchedCategoryBuilder;
using ntp_snippets::test::RemoteSuggestionBuilder;
using testing::_;
@@ -162,22 +162,16 @@ std::unique_ptr<RemoteSuggestion> CreateTestRemoteSuggestion(
return RemoteSuggestion::CreateFromProto(snippet_proto);
}
-using ServeImageCallback = base::Callback<void(
- const std::string&,
- base::Callback<void(const std::string&,
- const gfx::Image&,
- const image_fetcher::RequestMetadata&)>)>;
-
void ServeOneByOneImage(
- image_fetcher::ImageFetcherDelegate* notify,
const std::string& id,
- base::Callback<void(const std::string&,
- const gfx::Image&,
- const image_fetcher::RequestMetadata&)> callback) {
+ image_fetcher::ImageFetcher::ImageDataFetcherCallback* image_data_callback,
+ image_fetcher::ImageFetcher::ImageFetcherCallback* callback) {
+ std::move(*image_data_callback)
+ .Run("1-by-1-image-data", image_fetcher::RequestMetadata());
base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::Bind(callback, id, gfx::test::CreateImage(1, 1),
- image_fetcher::RequestMetadata()));
- notify->OnImageDataFetched(id, "1-by-1-image-data");
+ FROM_HERE,
+ base::BindOnce(std::move(*callback), id, gfx::test::CreateImage(1, 1),
+ image_fetcher::RequestMetadata()));
}
gfx::Image FetchImage(RemoteSuggestionsProviderImpl* provider,
@@ -196,21 +190,6 @@ gfx::Image FetchImage(RemoteSuggestionsProviderImpl* provider,
return result;
}
-class MockImageFetcher : public ImageFetcher {
- public:
- MOCK_METHOD1(SetImageFetcherDelegate, void(ImageFetcherDelegate*));
- MOCK_METHOD1(SetDataUseServiceName, void(DataUseServiceName));
- MOCK_METHOD1(SetImageDownloadLimit,
- void(base::Optional<int64_t> max_download_bytes));
- MOCK_METHOD1(SetDesiredImageFrameSize, void(const gfx::Size&));
- MOCK_METHOD4(StartOrQueueNetworkRequest,
- void(const std::string&,
- const GURL&,
- const ImageFetcherCallback&,
- const net::NetworkTrafficAnnotationTag&));
- MOCK_METHOD0(GetImageDecoder, image_fetcher::ImageDecoder*());
-};
-
class FakeImageDecoder : public image_fetcher::ImageDecoder {
public:
FakeImageDecoder() {}
@@ -344,7 +323,6 @@ class RemoteSuggestionsProviderImplTest : public ::testing::Test {
RemoteSuggestionsProviderImpl::RegisterProfilePrefs(
utils_.pref_service()->registry());
RequestThrottler::RegisterProfilePrefs(utils_.pref_service()->registry());
- tick_clock_ = timer_mock_task_runner_->GetMockTickClock();
EXPECT_TRUE(database_dir_.CreateUniqueTempDir());
}
@@ -413,7 +391,6 @@ class RemoteSuggestionsProviderImplTest : public ::testing::Test {
auto image_fetcher = std::make_unique<NiceMock<MockImageFetcher>>();
image_fetcher_ = image_fetcher.get();
- EXPECT_CALL(*image_fetcher, SetImageFetcherDelegate(_));
ON_CALL(*image_fetcher, GetImageDecoder())
.WillByDefault(Return(&image_decoder_));
EXPECT_FALSE(observer_);
@@ -422,8 +399,8 @@ class RemoteSuggestionsProviderImplTest : public ::testing::Test {
std::make_unique<RemoteSuggestionsDatabase>(database_dir_.GetPath());
database_ = database.get();
- auto fetch_timeout_timer =
- std::make_unique<base::OneShotTimer>(tick_clock_.get());
+ auto fetch_timeout_timer = std::make_unique<base::OneShotTimer>(
+ timer_mock_task_runner_->GetMockTickClock());
fetch_timeout_timer->SetTaskRunner(timer_mock_task_runner_);
return std::make_unique<RemoteSuggestionsProviderImpl>(
@@ -719,11 +696,6 @@ class RemoteSuggestionsProviderImplTest : public ::testing::Test {
RemoteSuggestionsDatabase* database_;
Logger debug_logger_;
-
- // TODO(tzik): Remove |mock_tick_clock_| after updating GetMockTickClock to
- // own the instance. http://crbug.com/789079
- std::unique_ptr<base::TickClock> tick_clock_;
-
scoped_refptr<TestMockTimeTaskRunner> timer_mock_task_runner_;
DISALLOW_COPY_AND_ASSIGN(RemoteSuggestionsProviderImplTest);
@@ -1348,10 +1320,10 @@ TEST_F(RemoteSuggestionsProviderImplTest,
ElementsAre(Pointee(Property(&RemoteSuggestion::id, "id"))));
image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
- ServeImageCallback serve_one_by_one_image_callback =
- base::Bind(&ServeOneByOneImage, &provider->GetImageFetcherForTesting());
- EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _, _))
- .WillOnce(WithArgs<0, 2>(
+ auto serve_one_by_one_image_callback =
+ base::BindRepeating(&ServeOneByOneImage);
+ EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _, _))
+ .WillOnce(WithArgs<0, 2, 3>(
Invoke(CreateFunctor(serve_one_by_one_image_callback))));
gfx::Image image = FetchImage(provider.get(), MakeArticleID("id"));
@@ -1420,10 +1392,10 @@ TEST_F(RemoteSuggestionsProviderImplTest,
Status::Success(), std::move(fetched_categories));
image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
- ServeImageCallback serve_one_by_one_image_callback =
- base::Bind(&ServeOneByOneImage, &provider->GetImageFetcherForTesting());
- EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _, _))
- .WillOnce(WithArgs<0, 2>(
+ auto serve_one_by_one_image_callback =
+ base::BindRepeating(&ServeOneByOneImage);
+ EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _, _))
+ .WillOnce(WithArgs<0, 2, 3>(
Invoke(CreateFunctor(serve_one_by_one_image_callback))));
gfx::Image image = FetchImage(provider.get(), MakeArticleID("id"));
@@ -1590,11 +1562,10 @@ TEST_F(RemoteSuggestionsProviderImplTest,
Status::Success(), std::move(fetched_categories));
// Make sure images of both batches are available. This is to sanity check our
// assumptions for the test are right.
- ServeImageCallback cb =
- base::Bind(&ServeOneByOneImage, &provider->GetImageFetcherForTesting());
- EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _, _))
+ EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _, _))
.Times(2)
- .WillRepeatedly(WithArgs<0, 2>(Invoke(CreateFunctor(cb))));
+ .WillRepeatedly(WithArgs<0, 2, 3>(
+ Invoke(CreateFunctor(base::BindRepeating(&ServeOneByOneImage)))));
image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
gfx::Image image = FetchImage(provider.get(), MakeArticleID("http://id-1"));
ASSERT_FALSE(image.IsEmpty());
@@ -1756,10 +1727,9 @@ TEST_F(RemoteSuggestionsProviderImplTest, Dismiss) {
ASSERT_THAT(provider->GetSuggestionsForTesting(articles_category()),
SizeIs(1));
// Load the image to store it in the database.
- ServeImageCallback cb =
- base::Bind(&ServeOneByOneImage, &provider->GetImageFetcherForTesting());
- EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _, _))
- .WillOnce(WithArgs<0, 2>(Invoke(CreateFunctor(cb))));
+ EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _, _))
+ .WillOnce(WithArgs<0, 2, 3>(
+ Invoke(CreateFunctor(base::BindRepeating(&ServeOneByOneImage)))));
image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
gfx::Image image =
FetchImage(provider.get(), MakeArticleID("http://site.com"));
@@ -1883,10 +1853,9 @@ TEST_F(RemoteSuggestionsProviderImplTest, RemoveExpiredDismissedContent) {
// Load the image to store it in the database.
// TODO(tschumann): Introduce some abstraction to nicely work with image
// fetching expectations.
- ServeImageCallback cb =
- base::Bind(&ServeOneByOneImage, &provider->GetImageFetcherForTesting());
- EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _, _))
- .WillOnce(WithArgs<0, 2>(Invoke(CreateFunctor(cb))));
+ EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _, _))
+ .WillOnce(WithArgs<0, 2, 3>(
+ Invoke(CreateFunctor(base::BindRepeating(&ServeOneByOneImage)))));
image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
gfx::Image image = FetchImage(provider.get(), MakeArticleID("http://first/"));
EXPECT_FALSE(image.IsEmpty());
@@ -2148,12 +2117,11 @@ TEST_F(RemoteSuggestionsProviderImplTest, ImageReturnedWithTheSameId) {
gfx::Image image;
MockFunction<void(const gfx::Image&)> image_fetched;
- ServeImageCallback cb =
- base::Bind(&ServeOneByOneImage, &provider->GetImageFetcherForTesting());
{
InSequence s;
- EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _, _))
- .WillOnce(WithArgs<0, 2>(Invoke(CreateFunctor(cb))));
+ EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _, _))
+ .WillOnce(WithArgs<0, 2, 3>(
+ Invoke(CreateFunctor(base::BindRepeating(&ServeOneByOneImage)))));
EXPECT_CALL(image_fetched, Call(_)).WillOnce(SaveArg<0>(&image));
}
@@ -2286,11 +2254,10 @@ TEST_F(RemoteSuggestionsProviderImplTest, ShouldClearOrphanedImagesOnRestart) {
.Build());
FetchTheseSuggestions(provider.get(), /*interactive_request=*/true,
Status::Success(), std::move(fetched_categories));
- ServeImageCallback cb =
- base::Bind(&ServeOneByOneImage, &provider->GetImageFetcherForTesting());
- EXPECT_CALL(*image_fetcher(), StartOrQueueNetworkRequest(_, _, _, _))
- .WillOnce(WithArgs<0, 2>(Invoke(CreateFunctor(cb))));
+ EXPECT_CALL(*image_fetcher(), FetchImageAndData_(_, _, _, _, _))
+ .WillOnce(WithArgs<0, 2, 3>(
+ Invoke(CreateFunctor(base::BindRepeating(&ServeOneByOneImage)))));
image_decoder()->SetDecodedImage(gfx::test::CreateImage(1, 1));
gfx::Image image = FetchImage(provider.get(), MakeArticleID(kSuggestionUrl));
diff --git a/chromium/components/ntp_snippets/remote/remote_suggestions_scheduler_impl_unittest.cc b/chromium/components/ntp_snippets/remote/remote_suggestions_scheduler_impl_unittest.cc
index 0dd76c852bb..1c079450e1b 100644
--- a/chromium/components/ntp_snippets/remote/remote_suggestions_scheduler_impl_unittest.cc
+++ b/chromium/components/ntp_snippets/remote/remote_suggestions_scheduler_impl_unittest.cc
@@ -145,6 +145,14 @@ class MockRemoteSuggestionsProvider : public RemoteSuggestionsProvider {
}
MOCK_METHOD2(FetchSuggestionImage,
void(const ContentSuggestion::ID&, ImageFetchedCallback*));
+
+ void FetchSuggestionImageData(const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) override {
+ FetchSuggestionImageData(suggestion_id, &callback);
+ }
+ MOCK_METHOD2(FetchSuggestionImageData,
+ void(const ContentSuggestion::ID&, ImageDataFetchedCallback*));
+
void GetDismissedSuggestionsForDebugging(
Category category,
DismissedSuggestionsCallback callback) override {
diff --git a/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.cc b/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.cc
index dbdb6013c70..82a3f34bd88 100644
--- a/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.cc
+++ b/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.cc
@@ -225,6 +225,13 @@ void ForeignSessionsSuggestionsProvider::FetchSuggestionImage(
FROM_HERE, base::BindOnce(std::move(callback), gfx::Image()));
}
+void ForeignSessionsSuggestionsProvider::FetchSuggestionImageData(
+ const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), std::string()));
+}
+
void ForeignSessionsSuggestionsProvider::Fetch(
const Category& category,
const std::set<std::string>& known_suggestion_ids,
diff --git a/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.h b/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.h
index 0399a8b11fb..0e86c2e86e7 100644
--- a/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.h
+++ b/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider.h
@@ -56,6 +56,8 @@ class ForeignSessionsSuggestionsProvider : public ContentSuggestionsProvider {
void DismissSuggestion(const ContentSuggestion::ID& suggestion_id) override;
void FetchSuggestionImage(const ContentSuggestion::ID& suggestion_id,
ImageFetchedCallback callback) override;
+ void FetchSuggestionImageData(const ContentSuggestion::ID& suggestion_id,
+ ImageDataFetchedCallback callback) override;
void Fetch(const Category& category,
const std::set<std::string>& known_suggestion_ids,
FetchDoneCallback callback) override;
diff --git a/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider_unittest.cc b/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider_unittest.cc
index aad3fdb7b50..586aba8087f 100644
--- a/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider_unittest.cc
+++ b/chromium/components/ntp_snippets/sessions/foreign_sessions_suggestions_provider_unittest.cc
@@ -50,11 +50,12 @@ const char kUrl11[] = "http://www.fake11.com/";
const char kTitle[] = "title is ignored";
SessionWindow* GetOrCreateWindow(SyncedSession* session, int window_id) {
- if (session->windows.find(window_id) == session->windows.end()) {
- session->windows[window_id] = std::make_unique<SyncedSessionWindow>();
+ SessionID id = SessionID::FromSerializedValue(window_id);
+ if (session->windows.find(id) == session->windows.end()) {
+ session->windows[id] = std::make_unique<SyncedSessionWindow>();
}
- return &session->windows[window_id]->wrapped_window;
+ return &session->windows[id]->wrapped_window;
}
void AddTabToSession(SyncedSession* session,
diff --git a/chromium/components/ntp_snippets/sessions/tab_delegate_sync_adapter_unittest.cc b/chromium/components/ntp_snippets/sessions/tab_delegate_sync_adapter_unittest.cc
index bcdf618f9db..cf6fe741f09 100644
--- a/chromium/components/ntp_snippets/sessions/tab_delegate_sync_adapter_unittest.cc
+++ b/chromium/components/ntp_snippets/sessions/tab_delegate_sync_adapter_unittest.cc
@@ -41,9 +41,7 @@ class MockOpenTabsUIDelegate : public OpenTabsUIDelegate {
scoped_refptr<RefCountedMemory>*));
MOCK_METHOD1(GetAllForeignSessions, bool(std::vector<const SyncedSession*>*));
MOCK_METHOD3(GetForeignTab,
- bool(const std::string&,
- SessionID::id_type,
- const SessionTab**));
+ bool(const std::string&, SessionID, const SessionTab**));
MOCK_METHOD1(DeleteForeignSession, void(const std::string&));
MOCK_METHOD2(GetForeignSession,
bool(const std::string&, std::vector<const SessionWindow*>*));