summaryrefslogtreecommitdiff
path: root/chromium/components/ntp_snippets
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@theqtcompany.com>2016-05-09 14:22:11 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2016-05-09 15:11:45 +0000
commit2ddb2d3e14eef3de7dbd0cef553d669b9ac2361c (patch)
treee75f511546c5fd1a173e87c1f9fb11d7ac8d1af3 /chromium/components/ntp_snippets
parenta4f3d46271c57e8155ba912df46a05559d14726e (diff)
downloadqtwebengine-chromium-2ddb2d3e14eef3de7dbd0cef553d669b9ac2361c.tar.gz
BASELINE: Update Chromium to 51.0.2704.41
Also adds in all smaller components by reversing logic for exclusion. Change-Id: Ibf90b506e7da088ea2f65dcf23f2b0992c504422 Reviewed-by: Joerg Bornemann <joerg.bornemann@theqtcompany.com>
Diffstat (limited to 'chromium/components/ntp_snippets')
-rw-r--r--chromium/components/ntp_snippets/BUILD.gn48
-rw-r--r--chromium/components/ntp_snippets/DEPS10
-rw-r--r--chromium/components/ntp_snippets/OWNERS3
-rw-r--r--chromium/components/ntp_snippets/inner_iterator.h96
-rw-r--r--chromium/components/ntp_snippets/inner_iterator_unittest.cc73
-rw-r--r--chromium/components/ntp_snippets/ntp_snippet.cc114
-rw-r--r--chromium/components/ntp_snippets/ntp_snippet.h97
-rw-r--r--chromium/components/ntp_snippets/ntp_snippets_fetcher.cc134
-rw-r--r--chromium/components/ntp_snippets/ntp_snippets_fetcher.h65
-rw-r--r--chromium/components/ntp_snippets/ntp_snippets_scheduler.h35
-rw-r--r--chromium/components/ntp_snippets/ntp_snippets_service.cc411
-rw-r--r--chromium/components/ntp_snippets/ntp_snippets_service.h195
-rw-r--r--chromium/components/ntp_snippets/ntp_snippets_service_unittest.cc221
-rw-r--r--chromium/components/ntp_snippets/pref_names.cc16
-rw-r--r--chromium/components/ntp_snippets/pref_names.h19
-rw-r--r--chromium/components/ntp_snippets/switches.cc22
-rw-r--r--chromium/components/ntp_snippets/switches.h20
17 files changed, 1579 insertions, 0 deletions
diff --git a/chromium/components/ntp_snippets/BUILD.gn b/chromium/components/ntp_snippets/BUILD.gn
new file mode 100644
index 00000000000..1bfdf1f1780
--- /dev/null
+++ b/chromium/components/ntp_snippets/BUILD.gn
@@ -0,0 +1,48 @@
+# Copyright 2014 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.
+
+# GYP version: components/ntp_snippets.gypi:ntp_snippets
+source_set("ntp_snippets") {
+ sources = [
+ "inner_iterator.h",
+ "ntp_snippet.cc",
+ "ntp_snippet.h",
+ "ntp_snippets_fetcher.cc",
+ "ntp_snippets_fetcher.h",
+ "ntp_snippets_scheduler.h",
+ "ntp_snippets_service.cc",
+ "ntp_snippets_service.h",
+ "pref_names.cc",
+ "pref_names.h",
+ "switches.cc",
+ "switches.h",
+ ]
+
+ public_deps = [
+ "//base",
+ "//components/keyed_service/core",
+ "//components/prefs",
+ "//components/signin/core/browser",
+ "//components/suggestions",
+ "//google_apis",
+ "//net",
+ "//url",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "inner_iterator_unittest.cc",
+ "ntp_snippets_service_unittest.cc",
+ ]
+
+ deps = [
+ ":ntp_snippets",
+ "//base",
+ "//components/signin/core/browser:test_support",
+ "//net:test_support",
+ "//testing/gtest",
+ ]
+}
diff --git a/chromium/components/ntp_snippets/DEPS b/chromium/components/ntp_snippets/DEPS
new file mode 100644
index 00000000000..38ff3b82f7e
--- /dev/null
+++ b/chromium/components/ntp_snippets/DEPS
@@ -0,0 +1,10 @@
+include_rules = [
+ "+components/keyed_service/core",
+ "+components/prefs",
+ "+components/signin",
+ "+components/suggestions",
+ "+net/base",
+ "+net/http",
+ "+net/url_request",
+ "+google_apis",
+]
diff --git a/chromium/components/ntp_snippets/OWNERS b/chromium/components/ntp_snippets/OWNERS
new file mode 100644
index 00000000000..7fe619f60c2
--- /dev/null
+++ b/chromium/components/ntp_snippets/OWNERS
@@ -0,0 +1,3 @@
+noyau@chromium.org
+bauerb@chromium.org
+treib@chromium.org
diff --git a/chromium/components/ntp_snippets/inner_iterator.h b/chromium/components/ntp_snippets/inner_iterator.h
new file mode 100644
index 00000000000..ad46d44b13c
--- /dev/null
+++ b/chromium/components/ntp_snippets/inner_iterator.h
@@ -0,0 +1,96 @@
+// Copyright 2015 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_INNER_ITERATOR_H_
+#define COMPONENTS_NTP_SNIPPETS_INNER_ITERATOR_H_
+
+#include <iterator>
+
+namespace ntp_snippets {
+
+// Build an iterator returning a dereference of an original iterator. For
+// example an object with an instance variable of type
+// std::vector<std::unique_ptr<SomeClass>> instance_;
+// could serve a const_iterator (of const SomeClass&) by simply doing the
+// following:
+//
+// using const_iterator = InnerIterator<
+// std::vector<std::unique_ptr<SomeClass>>::const_iterator,
+// const Someclass>;
+// [...]
+// const_iterator begin() { return const_iterator(instance_.begin()); }
+//
+template <typename I, // type of original iterator
+ typename R // type returned by this iterator
+ >
+class InnerIterator {
+ public:
+ using difference_type = typename std::iterator_traits<I>::difference_type;
+ using value_type = R;
+ using pointer = R*;
+ using reference = R&;
+ using iterator_category = std::random_access_iterator_tag;
+
+ // Construction from an iterator.
+ explicit InnerIterator(I from) : it_(from) {}
+
+ // Operators *, ->, and [] are first forwarded to the contained
+ // iterator, then extract the inner member.
+ reference operator*() const { return **it_; }
+ pointer operator->() const { return &(**it_); }
+ reference operator[](difference_type n) const { return *(it_[n]); }
+
+ // All operators that have to do with position are forwarded
+ // to the contained iterator.
+ InnerIterator& operator++() {
+ ++it_;
+ return *this;
+ }
+ InnerIterator operator++(int) {
+ I tmp(it_);
+ it_++;
+ return InnerIterator(tmp);
+ }
+ InnerIterator& operator--() {
+ --it_;
+ return *this;
+ }
+ InnerIterator operator--(int) {
+ I tmp(it_);
+ it_--;
+ return InnerIterator(tmp);
+ }
+ InnerIterator& operator+=(difference_type n) {
+ it_ += n;
+ return *this;
+ }
+ InnerIterator operator+(difference_type n) const {
+ I tmp(it_);
+ tmp += n;
+ return InnerIterator(tmp);
+ }
+ InnerIterator& operator-=(difference_type n) {
+ it_ -= n;
+ return *this;
+ }
+ InnerIterator operator-(difference_type n) const {
+ I tmp(it_);
+ tmp -= n;
+ return InnerIterator(tmp);
+ }
+
+ bool operator==(const InnerIterator<I, R>& rhs) const {
+ return it_ == rhs.it_;
+ }
+ bool operator!=(const InnerIterator<I, R>& rhs) const {
+ return it_ != rhs.it_;
+ }
+
+ protected:
+ I it_;
+};
+
+} // namespace ntp_snippets
+
+#endif // COMPONENTS_NTP_SNIPPETS_INNER_ITERATOR_H_
diff --git a/chromium/components/ntp_snippets/inner_iterator_unittest.cc b/chromium/components/ntp_snippets/inner_iterator_unittest.cc
new file mode 100644
index 00000000000..6c5eb0771a2
--- /dev/null
+++ b/chromium/components/ntp_snippets/inner_iterator_unittest.cc
@@ -0,0 +1,73 @@
+// Copyright 2015 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 <memory>
+#include <vector>
+
+#include "components/ntp_snippets/inner_iterator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+int ii[] = {0, 1, 2, 3, 4};
+
+class InnerIteratorTest : public testing::Test {
+ protected:
+ using ListType = std::vector<int*>;
+
+ void SetUp() override {
+ ints_.push_back(&ii[0]);
+ ints_.push_back(&ii[1]);
+ ints_.push_back(&ii[2]);
+ ints_.push_back(&ii[3]);
+ ints_.push_back(&ii[4]);
+ }
+
+ ListType ints_;
+};
+
+TEST_F(InnerIteratorTest, Create) {
+ ntp_snippets::InnerIterator<ListType::iterator, int> x(ints_.begin());
+ EXPECT_EQ(0, *x);
+}
+TEST_F(InnerIteratorTest, PreIncrement) {
+ ntp_snippets::InnerIterator<ListType::iterator, int> x(ints_.begin());
+ EXPECT_EQ(1, *(++x));
+ EXPECT_EQ(1, *x);
+}
+TEST_F(InnerIteratorTest, PostIncrement) {
+ ntp_snippets::InnerIterator<ListType::iterator, int> x(ints_.begin());
+ EXPECT_EQ(0, *(x++));
+ EXPECT_EQ(1, *x);
+}
+TEST_F(InnerIteratorTest, Add) {
+ ntp_snippets::InnerIterator<ListType::iterator, int> x(ints_.begin());
+ EXPECT_EQ(2, *(x + 2));
+ EXPECT_EQ(0, *x);
+}
+TEST_F(InnerIteratorTest, Sub) {
+ ntp_snippets::InnerIterator<ListType::iterator, int> x(ints_.end());
+ EXPECT_EQ(2, *(x - 3));
+ EXPECT_EQ(4, *(--x));
+}
+TEST_F(InnerIteratorTest, PreDecrement) {
+ ntp_snippets::InnerIterator<ListType::iterator, int> x(ints_.end());
+ EXPECT_EQ(4, *(--x));
+ EXPECT_EQ(4, *x);
+}
+TEST_F(InnerIteratorTest, PostDecrement) {
+ ntp_snippets::InnerIterator<ListType::iterator, int> x(ints_.end());
+ EXPECT_EQ(4, *(--x));
+ EXPECT_EQ(4, *(x--));
+ EXPECT_EQ(3, *x);
+}
+TEST_F(InnerIteratorTest, Equality) {
+ ntp_snippets::InnerIterator<ListType::iterator, int> x(ints_.begin());
+ ntp_snippets::InnerIterator<ListType::iterator, int> y(ints_.end());
+ x += 2;
+ y -= 3;
+ EXPECT_EQ(x, y);
+}
+
+} // namespace
diff --git a/chromium/components/ntp_snippets/ntp_snippet.cc b/chromium/components/ntp_snippets/ntp_snippet.cc
new file mode 100644
index 00000000000..d21e4913fd1
--- /dev/null
+++ b/chromium/components/ntp_snippets/ntp_snippet.cc
@@ -0,0 +1,114 @@
+// Copyright 2015 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/ntp_snippet.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+
+namespace {
+
+const char kUrl[] = "url";
+const char kSiteTitle[] = "site_title";
+const char kTitle[] = "title";
+const char kFaviconUrl[] = "favicon_url";
+const char kSalientImageUrl[] = "thumbnailUrl";
+const char kSnippet[] = "snippet";
+const char kPublishDate[] = "creationTimestampSec";
+const char kExpiryDate[] = "expiryTimestampSec";
+
+} // namespace
+
+namespace ntp_snippets {
+
+NTPSnippet::NTPSnippet(const GURL& url) : url_(url) {
+ DCHECK(url_.is_valid());
+}
+
+NTPSnippet::~NTPSnippet() {}
+
+// static
+std::unique_ptr<NTPSnippet> NTPSnippet::CreateFromDictionary(
+ const base::DictionaryValue& dict) {
+ // Need at least the url.
+ std::string url_str;
+ if (!dict.GetString("url", &url_str))
+ return nullptr;
+ GURL url(url_str);
+ if (!url.is_valid())
+ return nullptr;
+
+ std::unique_ptr<NTPSnippet> snippet(new NTPSnippet(url));
+
+ std::string site_title;
+ if (dict.GetString(kSiteTitle, &site_title))
+ snippet->set_site_title(site_title);
+ std::string title;
+ if (dict.GetString(kTitle, &title))
+ snippet->set_title(title);
+ std::string favicon_url;
+ if (dict.GetString(kFaviconUrl, &favicon_url))
+ snippet->set_favicon_url(GURL(favicon_url));
+ std::string salient_image_url;
+ if (dict.GetString(kSalientImageUrl, &salient_image_url))
+ snippet->set_salient_image_url(GURL(salient_image_url));
+ std::string snippet_str;
+ if (dict.GetString(kSnippet, &snippet_str))
+ snippet->set_snippet(snippet_str);
+ // creationTimestampSec is a uint64, which is stored using strings
+ std::string creation_timestamp_str;
+ if (dict.GetString(kPublishDate, &creation_timestamp_str)) {
+ int64_t creation_timestamp = 0;
+ if (!base::StringToInt64(creation_timestamp_str, &creation_timestamp)) {
+ // Even if there's an error in the conversion, some garbage data may still
+ // be written to the output var, so reset it
+ creation_timestamp = 0;
+ }
+ snippet->set_publish_date(base::Time::UnixEpoch() +
+ base::TimeDelta::FromSeconds(creation_timestamp));
+ }
+ std::string expiry_timestamp_str;
+ if (dict.GetString(kExpiryDate, &expiry_timestamp_str)) {
+ int64_t expiry_timestamp = 0;
+ if (!base::StringToInt64(expiry_timestamp_str, &expiry_timestamp)) {
+ // Even if there's an error in the conversion, some garbage data may still
+ // be written to the output var, so reset it
+ expiry_timestamp = 0;
+ }
+ snippet->set_expiry_date(base::Time::UnixEpoch() +
+ base::TimeDelta::FromSeconds(expiry_timestamp));
+ }
+
+ return snippet;
+}
+
+std::unique_ptr<base::DictionaryValue> NTPSnippet::ToDictionary() const {
+ std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+
+ dict->SetString(kUrl, url_.spec());
+ if (!site_title_.empty())
+ dict->SetString(kSiteTitle, site_title_);
+ if (!title_.empty())
+ dict->SetString(kTitle, title_);
+ if (favicon_url_.is_valid())
+ dict->SetString(kFaviconUrl, favicon_url_.spec());
+ if (salient_image_url_.is_valid())
+ dict->SetString(kSalientImageUrl, salient_image_url_.spec());
+ if (!snippet_.empty())
+ dict->SetString(kSnippet, snippet_);
+ if (!publish_date_.is_null()) {
+ dict->SetString(kPublishDate,
+ base::Int64ToString(
+ (publish_date_ - base::Time::UnixEpoch()).InSeconds()));
+ }
+ if (!expiry_date_.is_null()) {
+ dict->SetString(kExpiryDate,
+ base::Int64ToString(
+ (expiry_date_ - base::Time::UnixEpoch()).InSeconds()));
+ }
+
+ return dict;
+}
+
+} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/ntp_snippet.h b/chromium/components/ntp_snippets/ntp_snippet.h
new file mode 100644
index 00000000000..ead67b85c8e
--- /dev/null
+++ b/chromium/components/ntp_snippets/ntp_snippet.h
@@ -0,0 +1,97 @@
+// Copyright 2015 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_NTP_SNIPPET_H_
+#define COMPONENTS_NTP_SNIPPETS_NTP_SNIPPET_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "url/gurl.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace ntp_snippets {
+
+// Stores and vend fresh content data for the NTP. This is a dumb class with no
+// smarts at all, all the logic is in the service.
+class NTPSnippet {
+ public:
+ // Creates a new snippet with the given URL. URL must be valid.
+ NTPSnippet(const GURL& url);
+
+ ~NTPSnippet();
+
+ // Creates an NTPSnippet from a dictionary. Returns a null pointer if the
+ // dictionary doesn't contain at least a url. The keys in the dictionary are
+ // expected to be the same as the property name, with exceptions documented in
+ // the property comment.
+ static std::unique_ptr<NTPSnippet> CreateFromDictionary(
+ const base::DictionaryValue& dict);
+
+ std::unique_ptr<base::DictionaryValue> ToDictionary() const;
+
+ // URL of the page described by this snippet.
+ const GURL& url() const { return url_; }
+
+ // Subtitle to identify the site the snippet is from.
+ const std::string& site_title() const { return site_title_; }
+ void set_site_title(const std::string& site_title) {
+ site_title_ = site_title;
+ }
+
+ // Favicon for the site. Do not use to directly retrieve the favicon.
+ const GURL& favicon_url() const { return favicon_url_; }
+ void set_favicon_url(const GURL& favicon_url) { favicon_url_ = favicon_url; }
+
+ // Title of the snippet.
+ const std::string& title() const { return title_; }
+ void set_title(const std::string& title) { title_ = title; }
+
+ // Summary or relevant extract from the content.
+ const std::string& snippet() const { return snippet_; }
+ void set_snippet(const std::string& snippet) { snippet_ = snippet; }
+
+ // Link to an image representative of the content. Do not fetch this image
+ // directly. If initialized by CreateFromDictionary() the relevant key is
+ // 'thumbnailUrl'
+ const GURL& salient_image_url() const { return salient_image_url_; }
+ void set_salient_image_url(const GURL& salient_image_url) {
+ salient_image_url_ = salient_image_url;
+ }
+
+ // When the page pointed by this snippet was published. If initialized by
+ // CreateFromDictionary() the relevant key is 'creationTimestampSec'
+ const base::Time& publish_date() const { return publish_date_; }
+ void set_publish_date(const base::Time& publish_date) {
+ publish_date_ = publish_date;
+ }
+
+ // After this expiration date this snippet should no longer be presented to
+ // the user.
+ const base::Time& expiry_date() const { return expiry_date_; }
+ void set_expiry_date(const base::Time& expiry_date) {
+ expiry_date_ = expiry_date;
+ }
+
+ private:
+ const GURL url_;
+ std::string site_title_;
+ std::string title_;
+ GURL favicon_url_;
+ GURL salient_image_url_;
+ std::string snippet_;
+ base::Time publish_date_;
+ base::Time expiry_date_;
+
+ DISALLOW_COPY_AND_ASSIGN(NTPSnippet);
+};
+
+} // namespace ntp_snippets
+
+#endif // COMPONENTS_NTP_SNIPPETS_NTP_SNIPPET_H_
diff --git a/chromium/components/ntp_snippets/ntp_snippets_fetcher.cc b/chromium/components/ntp_snippets/ntp_snippets_fetcher.cc
new file mode 100644
index 00000000000..c0eb082d9e8
--- /dev/null
+++ b/chromium/components/ntp_snippets/ntp_snippets_fetcher.cc
@@ -0,0 +1,134 @@
+// 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/ntp_snippets_fetcher.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/task_runner_util.h"
+#include "google_apis/google_api_keys.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+
+using net::URLFetcher;
+using net::URLRequestContextGetter;
+using net::HttpRequestHeaders;
+using net::URLRequestStatus;
+
+namespace ntp_snippets {
+
+const char kContentSnippetsServerFormat[] =
+ "https://chromereader-pa.googleapis.com/v1/fetch?key=%s";
+
+const char kRequestParameterFormat[] =
+ "{"
+ " \"response_detail_level\": \"STANDARD\","
+ " \"advanced_options\": {"
+ " \"local_scoring_params\": {"
+ " \"content_params\": {"
+ " \"only_return_personalized_results\": false"
+ " },"
+ " \"content_restricts\": {"
+ " \"type\": \"METADATA\","
+ " \"value\": \"TITLE\""
+ " },"
+ " \"content_restricts\": {"
+ " \"type\": \"METADATA\","
+ " \"value\": \"SNIPPET\""
+ " },"
+ " \"content_restricts\": {"
+ " \"type\": \"METADATA\","
+ " \"value\": \"THUMBNAIL\""
+ " }"
+ "%s"
+ " },"
+ " \"global_scoring_params\": {"
+ " \"num_to_return\": 10"
+ " }"
+ " }"
+ "}";
+
+const char kHostRestrictFormat[] =
+ " ,\"content_selectors\": {"
+ " \"type\": \"HOST_RESTRICT\","
+ " \"value\": \"%s\""
+ " }";
+
+NTPSnippetsFetcher::NTPSnippetsFetcher(
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner,
+ scoped_refptr<URLRequestContextGetter> url_request_context_getter,
+ bool is_stable_channel)
+ : file_task_runner_(file_task_runner),
+ url_request_context_getter_(url_request_context_getter),
+ is_stable_channel_(is_stable_channel) {}
+
+NTPSnippetsFetcher::~NTPSnippetsFetcher() {
+}
+
+scoped_ptr<NTPSnippetsFetcher::SnippetsAvailableCallbackList::Subscription>
+NTPSnippetsFetcher::AddCallback(const SnippetsAvailableCallback& callback) {
+ return callback_list_.Add(callback);
+}
+
+void NTPSnippetsFetcher::FetchSnippets(const std::set<std::string>& hosts) {
+ // TODO(treib): What to do if there's already a pending request?
+ const std::string& key = is_stable_channel_
+ ? google_apis::GetAPIKey()
+ : google_apis::GetNonStableAPIKey();
+ std::string url =
+ base::StringPrintf(kContentSnippetsServerFormat, key.c_str());
+ url_fetcher_ = URLFetcher::Create(GURL(url), URLFetcher::POST, this);
+ url_fetcher_->SetRequestContext(url_request_context_getter_.get());
+ url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+ HttpRequestHeaders headers;
+ headers.SetHeader("Content-Type", "application/json; charset=UTF-8");
+ url_fetcher_->SetExtraRequestHeaders(headers.ToString());
+ std::string host_restricts;
+ for (const std::string& host : hosts)
+ host_restricts += base::StringPrintf(kHostRestrictFormat, host.c_str());
+ url_fetcher_->SetUploadData("application/json",
+ base::StringPrintf(kRequestParameterFormat,
+ host_restricts.c_str()));
+
+ // Fetchers are sometimes cancelled because a network change was detected.
+ url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
+ // Try to make fetching the files bit more robust even with poor connection.
+ url_fetcher_->SetMaxRetriesOn5xx(3);
+ url_fetcher_->Start();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// URLFetcherDelegate overrides
+void NTPSnippetsFetcher::OnURLFetchComplete(const URLFetcher* source) {
+ DCHECK_EQ(url_fetcher_.get(), source);
+
+ const URLRequestStatus& status = source->GetStatus();
+ if (!status.is_success()) {
+ DLOG(WARNING) << "URLRequestStatus error " << status.error()
+ << " while trying to download " << source->GetURL().spec();
+ return;
+ }
+
+ int response_code = source->GetResponseCode();
+ if (response_code != net::HTTP_OK) {
+ DLOG(WARNING) << "HTTP error " << response_code
+ << " while trying to download " << source->GetURL().spec();
+ return;
+ }
+
+ std::string response;
+ bool stores_result_to_string = source->GetResponseAsString(&response);
+ DCHECK(stores_result_to_string);
+
+ callback_list_.Notify(response);
+}
+
+} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/ntp_snippets_fetcher.h b/chromium/components/ntp_snippets/ntp_snippets_fetcher.h
new file mode 100644
index 00000000000..517198d5a09
--- /dev/null
+++ b/chromium/components/ntp_snippets/ntp_snippets_fetcher.h
@@ -0,0 +1,65 @@
+// 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_NTP_SNIPPETS_FETCHER_H_
+#define COMPONENTS_NTP_SNIPPETS_NTP_SNIPPETS_FETCHER_H_
+
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/callback_list.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequenced_task_runner.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace ntp_snippets {
+
+// Fetches snippet data for the NTP from the server
+class NTPSnippetsFetcher : public net::URLFetcherDelegate {
+ public:
+ using SnippetsAvailableCallback = base::Callback<void(const std::string&)>;
+ using SnippetsAvailableCallbackList =
+ base::CallbackList<void(const std::string&)>;
+
+ NTPSnippetsFetcher(
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner,
+ scoped_refptr<net::URLRequestContextGetter> url_request_context_getter,
+ bool is_stable_channel);
+ ~NTPSnippetsFetcher() override;
+
+ // Adds a callback that is called when a new set of snippets are downloaded.
+ scoped_ptr<SnippetsAvailableCallbackList::Subscription> AddCallback(
+ const SnippetsAvailableCallback& callback) WARN_UNUSED_RESULT;
+
+ // Fetches snippets from the server. |hosts| can be used to restrict the
+ // results to a set of hosts, e.g. "www.google.com". If it is empty, no
+ // restrictions are applied.
+ void FetchSnippets(const std::set<std::string>& hosts);
+
+ private:
+ // URLFetcherDelegate implementation.
+ void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+ // The SequencedTaskRunner on which file system operations will be run.
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
+
+ // Holds the URL request context.
+ scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
+
+ // The fetcher for downloading the snippets.
+ scoped_ptr<net::URLFetcher> url_fetcher_;
+
+ // The callbacks to notify when new snippets get fetched.
+ SnippetsAvailableCallbackList callback_list_;
+
+ // Flag for picking the right (stable/non-stable) API key for Chrome Reader
+ bool is_stable_channel_;
+
+ DISALLOW_COPY_AND_ASSIGN(NTPSnippetsFetcher);
+};
+} // namespace ntp_snippets
+
+#endif // COMPONENTS_NTP_SNIPPETS_NTP_SNIPPETS_FETCHER_H_
diff --git a/chromium/components/ntp_snippets/ntp_snippets_scheduler.h b/chromium/components/ntp_snippets/ntp_snippets_scheduler.h
new file mode 100644
index 00000000000..c374409c0b0
--- /dev/null
+++ b/chromium/components/ntp_snippets/ntp_snippets_scheduler.h
@@ -0,0 +1,35 @@
+// 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_NTP_SNIPPETS_SCHEDULER_H_
+#define COMPONENTS_NTP_SNIPPETS_NTP_SNIPPETS_SCHEDULER_H_
+
+#include "base/macros.h"
+#include "base/time/time.h"
+
+namespace ntp_snippets {
+
+// Interface to schedule the periodic fetching of snippets.
+class NTPSnippetsScheduler {
+ public:
+ // Schedule periodic fetching of snippets, with different period depending on
+ // network and charging state. The concrete implementation should call
+ // NTPSnippetsService::FetchSnippets once per period.
+ virtual bool Schedule(base::TimeDelta period_wifi_charging,
+ base::TimeDelta period_wifi,
+ base::TimeDelta period_fallback) = 0;
+
+ // Cancel the scheduled fetching task, if any.
+ virtual bool Unschedule() = 0;
+
+ protected:
+ NTPSnippetsScheduler() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NTPSnippetsScheduler);
+};
+
+} // namespace ntp_snippets
+
+#endif // COMPONENTS_NTP_SNIPPETS_NTP_SNIPPETS_SCHEDULER_H_
diff --git a/chromium/components/ntp_snippets/ntp_snippets_service.cc b/chromium/components/ntp_snippets/ntp_snippets_service.cc
new file mode 100644
index 00000000000..38c98f19931
--- /dev/null
+++ b/chromium/components/ntp_snippets/ntp_snippets_service.cc
@@ -0,0 +1,411 @@
+// Copyright 2015 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/ntp_snippets_service.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/location.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task_runner_util.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "components/ntp_snippets/pref_names.h"
+#include "components/ntp_snippets/switches.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/suggestions/proto/suggestions.pb.h"
+
+using suggestions::ChromeSuggestion;
+using suggestions::SuggestionsProfile;
+using suggestions::SuggestionsService;
+
+namespace ntp_snippets {
+
+namespace {
+
+const int kFetchingIntervalWifiChargingSeconds = 30 * 60;
+const int kFetchingIntervalWifiSeconds = 2 * 60 * 60;
+const int kFetchingIntervalFallbackSeconds = 24 * 60 * 60;
+
+const int kDefaultExpiryTimeMins = 24 * 60;
+
+base::TimeDelta GetFetchingInterval(const char* switch_name,
+ int default_value_seconds) {
+ int value_seconds = default_value_seconds;
+ const base::CommandLine& cmdline = *base::CommandLine::ForCurrentProcess();
+ if (cmdline.HasSwitch(switch_name)) {
+ std::string str = cmdline.GetSwitchValueASCII(switch_name);
+ int switch_value_seconds = 0;
+ if (base::StringToInt(str, &switch_value_seconds))
+ value_seconds = switch_value_seconds;
+ else
+ LOG(WARNING) << "Invalid value for switch " << switch_name;
+ }
+ return base::TimeDelta::FromSeconds(value_seconds);
+}
+
+base::TimeDelta GetFetchingIntervalWifiCharging() {
+ return GetFetchingInterval(switches::kFetchingIntervalWifiChargingSeconds,
+ kFetchingIntervalWifiChargingSeconds);
+}
+
+base::TimeDelta GetFetchingIntervalWifi() {
+ return GetFetchingInterval(switches::kFetchingIntervalWifiSeconds,
+ kFetchingIntervalWifiSeconds);
+}
+
+base::TimeDelta GetFetchingIntervalFallback() {
+ return GetFetchingInterval(switches::kFetchingIntervalFallbackSeconds,
+ kFetchingIntervalFallbackSeconds);
+}
+
+// Extracts the hosts from |suggestions| and returns them in a set.
+std::set<std::string> GetSuggestionsHosts(
+ const SuggestionsProfile& suggestions) {
+ std::set<std::string> hosts;
+ for (int i = 0; i < suggestions.suggestions_size(); ++i) {
+ const ChromeSuggestion& suggestion = suggestions.suggestions(i);
+ GURL url(suggestion.url());
+ if (url.is_valid())
+ hosts.insert(url.host());
+ }
+ return hosts;
+}
+
+const char kContentInfo[] = "contentInfo";
+
+// Parses snippets from |list| and adds them to |snippets|. Returns true on
+// success, false if anything went wrong.
+bool AddSnippetsFromListValue(const base::ListValue& list,
+ NTPSnippetsService::NTPSnippetStorage* snippets) {
+ for (const base::Value* const value : list) {
+ const base::DictionaryValue* dict = nullptr;
+ if (!value->GetAsDictionary(&dict))
+ return false;
+
+ const base::DictionaryValue* content = nullptr;
+ if (!dict->GetDictionary(kContentInfo, &content))
+ return false;
+ scoped_ptr<NTPSnippet> snippet = NTPSnippet::CreateFromDictionary(*content);
+ if (!snippet)
+ return false;
+
+ snippets->push_back(std::move(snippet));
+ }
+ return true;
+}
+
+scoped_ptr<base::ListValue> SnippetsToListValue(
+ const NTPSnippetsService::NTPSnippetStorage& snippets) {
+ scoped_ptr<base::ListValue> list(new base::ListValue);
+ for (const auto& snippet : snippets) {
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
+ dict->Set(kContentInfo, snippet->ToDictionary());
+ list->Append(std::move(dict));
+ }
+ return list;
+}
+
+} // namespace
+
+NTPSnippetsService::NTPSnippetsService(
+ PrefService* pref_service,
+ SuggestionsService* suggestions_service,
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner,
+ const std::string& application_language_code,
+ NTPSnippetsScheduler* scheduler,
+ scoped_ptr<NTPSnippetsFetcher> snippets_fetcher,
+ const ParseJSONCallback& parse_json_callback)
+ : pref_service_(pref_service),
+ suggestions_service_(suggestions_service),
+ file_task_runner_(file_task_runner),
+ application_language_code_(application_language_code),
+ scheduler_(scheduler),
+ snippets_fetcher_(std::move(snippets_fetcher)),
+ parse_json_callback_(parse_json_callback),
+ weak_ptr_factory_(this) {
+ snippets_fetcher_subscription_ = snippets_fetcher_->AddCallback(base::Bind(
+ &NTPSnippetsService::OnSnippetsDownloaded, base::Unretained(this)));
+}
+
+NTPSnippetsService::~NTPSnippetsService() {}
+
+// static
+void NTPSnippetsService::RegisterProfilePrefs(PrefRegistrySimple* registry) {
+ registry->RegisterListPref(prefs::kSnippets);
+ registry->RegisterListPref(prefs::kDiscardedSnippets);
+ registry->RegisterListPref(prefs::kSnippetHosts);
+}
+
+void NTPSnippetsService::Init(bool enabled) {
+ if (enabled) {
+ // |suggestions_service_| can be null in tests.
+ if (suggestions_service_) {
+ suggestions_service_subscription_ = suggestions_service_->AddCallback(
+ base::Bind(&NTPSnippetsService::OnSuggestionsChanged,
+ base::Unretained(this)));
+ }
+
+ // Get any existing snippets immediately from prefs.
+ LoadDiscardedSnippetsFromPrefs();
+ LoadSnippetsFromPrefs();
+
+ // If we don't have any snippets yet, start a fetch.
+ if (snippets_.empty())
+ FetchSnippets();
+ }
+
+ // The scheduler only exists on Android so far, it's null on other platforms.
+ if (!scheduler_)
+ return;
+
+ if (enabled) {
+ scheduler_->Schedule(GetFetchingIntervalWifiCharging(),
+ GetFetchingIntervalWifi(),
+ GetFetchingIntervalFallback());
+ } else {
+ scheduler_->Unschedule();
+ }
+}
+
+void NTPSnippetsService::Shutdown() {
+ FOR_EACH_OBSERVER(NTPSnippetsServiceObserver, observers_,
+ NTPSnippetsServiceShutdown());
+}
+
+void NTPSnippetsService::FetchSnippets() {
+ // |suggestions_service_| can be null in tests.
+ if (!suggestions_service_)
+ return;
+
+ FetchSnippetsImpl(GetSuggestionsHosts(
+ suggestions_service_->GetSuggestionsDataFromCache()));
+}
+
+bool NTPSnippetsService::DiscardSnippet(const GURL& url) {
+ auto it = std::find_if(snippets_.begin(), snippets_.end(),
+ [&url](const scoped_ptr<NTPSnippet>& snippet) {
+ return snippet->url() == url;
+ });
+ if (it == snippets_.end())
+ return false;
+ discarded_snippets_.push_back(std::move(*it));
+ snippets_.erase(it);
+ StoreDiscardedSnippetsToPrefs();
+ StoreSnippetsToPrefs();
+ return true;
+}
+
+void NTPSnippetsService::AddObserver(NTPSnippetsServiceObserver* observer) {
+ observers_.AddObserver(observer);
+ observer->NTPSnippetsServiceLoaded();
+}
+
+void NTPSnippetsService::RemoveObserver(NTPSnippetsServiceObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void NTPSnippetsService::OnSuggestionsChanged(
+ const SuggestionsProfile& suggestions) {
+ std::set<std::string> hosts = GetSuggestionsHosts(suggestions);
+ if (hosts == GetSnippetHostsFromPrefs())
+ return;
+
+ // Remove existing snippets that aren't in the suggestions anymore.
+ snippets_.erase(
+ std::remove_if(snippets_.begin(), snippets_.end(),
+ [&hosts](const scoped_ptr<NTPSnippet>& snippet) {
+ return !hosts.count(snippet->url().host());
+ }),
+ snippets_.end());
+
+ StoreSnippetsToPrefs();
+ StoreSnippetHostsToPrefs(hosts);
+
+ FOR_EACH_OBSERVER(NTPSnippetsServiceObserver, observers_,
+ NTPSnippetsServiceLoaded());
+
+ FetchSnippetsImpl(hosts);
+}
+
+void NTPSnippetsService::OnSnippetsDownloaded(
+ const std::string& snippets_json) {
+ parse_json_callback_.Run(
+ snippets_json, base::Bind(&NTPSnippetsService::OnJsonParsed,
+ weak_ptr_factory_.GetWeakPtr(), snippets_json),
+ base::Bind(&NTPSnippetsService::OnJsonError,
+ weak_ptr_factory_.GetWeakPtr(), snippets_json));
+}
+
+void NTPSnippetsService::OnJsonParsed(const std::string& snippets_json,
+ scoped_ptr<base::Value> parsed) {
+ LOG_IF(WARNING, !LoadFromValue(*parsed)) << "Received invalid snippets: "
+ << snippets_json;
+}
+
+void NTPSnippetsService::OnJsonError(const std::string& snippets_json,
+ const std::string& error) {
+ LOG(WARNING) << "Received invalid JSON (" << error << "): " << snippets_json;
+}
+
+void NTPSnippetsService::FetchSnippetsImpl(
+ const std::set<std::string>& hosts) {
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kDontRestrict)) {
+ snippets_fetcher_->FetchSnippets(std::set<std::string>());
+ return;
+ }
+ if (!hosts.empty())
+ snippets_fetcher_->FetchSnippets(hosts);
+}
+
+bool NTPSnippetsService::LoadFromValue(const base::Value& value) {
+ const base::DictionaryValue* top_dict = nullptr;
+ if (!value.GetAsDictionary(&top_dict))
+ return false;
+
+ const base::ListValue* list = nullptr;
+ if (!top_dict->GetList("recos", &list))
+ return false;
+
+ return LoadFromListValue(*list);
+}
+
+bool NTPSnippetsService::LoadFromListValue(const base::ListValue& list) {
+ NTPSnippetStorage new_snippets;
+ if (!AddSnippetsFromListValue(list, &new_snippets))
+ return false;
+ for (scoped_ptr<NTPSnippet>& snippet : new_snippets) {
+ // If this snippet has previously been discarded, don't add it again.
+ if (HasDiscardedSnippet(snippet->url()))
+ continue;
+
+ // If the snippet has no publish/expiry dates, fill in defaults.
+ if (snippet->publish_date().is_null())
+ snippet->set_publish_date(base::Time::Now());
+ if (snippet->expiry_date().is_null()) {
+ snippet->set_expiry_date(
+ snippet->publish_date() +
+ base::TimeDelta::FromMinutes(kDefaultExpiryTimeMins));
+ }
+
+ // Check if we already have a snippet with the same URL. If so, replace it
+ // rather than adding a duplicate.
+ const GURL& url = snippet->url();
+ auto it = std::find_if(snippets_.begin(), snippets_.end(),
+ [&url](const scoped_ptr<NTPSnippet>& old_snippet) {
+ return old_snippet->url() == url;
+ });
+ if (it != snippets_.end())
+ *it = std::move(snippet);
+ else
+ snippets_.push_back(std::move(snippet));
+ }
+
+ // Immediately remove any already-expired snippets. This will also notify our
+ // observers and schedule the expiry timer.
+ RemoveExpiredSnippets();
+
+ return true;
+}
+
+void NTPSnippetsService::LoadSnippetsFromPrefs() {
+ bool success = LoadFromListValue(*pref_service_->GetList(prefs::kSnippets));
+ DCHECK(success) << "Failed to parse snippets from prefs";
+}
+
+void NTPSnippetsService::StoreSnippetsToPrefs() {
+ pref_service_->Set(prefs::kSnippets, *SnippetsToListValue(snippets_));
+}
+
+void NTPSnippetsService::LoadDiscardedSnippetsFromPrefs() {
+ discarded_snippets_.clear();
+ bool success = AddSnippetsFromListValue(
+ *pref_service_->GetList(prefs::kDiscardedSnippets),
+ &discarded_snippets_);
+ DCHECK(success) << "Failed to parse discarded snippets from prefs";
+}
+
+void NTPSnippetsService::StoreDiscardedSnippetsToPrefs() {
+ pref_service_->Set(prefs::kDiscardedSnippets,
+ *SnippetsToListValue(discarded_snippets_));
+}
+
+std::set<std::string> NTPSnippetsService::GetSnippetHostsFromPrefs() const {
+ std::set<std::string> hosts;
+ const base::ListValue* list = pref_service_->GetList(prefs::kSnippetHosts);
+ for (const base::Value* value : *list) {
+ std::string str;
+ bool success = value->GetAsString(&str);
+ DCHECK(success) << "Failed to parse snippet host from prefs";
+ hosts.insert(std::move(str));
+ }
+ return hosts;
+}
+
+void NTPSnippetsService::StoreSnippetHostsToPrefs(
+ const std::set<std::string>& hosts) {
+ base::ListValue list;
+ for (const std::string& host : hosts)
+ list.AppendString(host);
+ pref_service_->Set(prefs::kSnippetHosts, list);
+}
+
+bool NTPSnippetsService::HasDiscardedSnippet(const GURL& url) const {
+ auto it = std::find_if(discarded_snippets_.begin(), discarded_snippets_.end(),
+ [&url](const scoped_ptr<NTPSnippet>& snippet) {
+ return snippet->url() == url;
+ });
+ return it != discarded_snippets_.end();
+}
+
+void NTPSnippetsService::RemoveExpiredSnippets() {
+ base::Time expiry = base::Time::Now();
+
+ snippets_.erase(
+ std::remove_if(snippets_.begin(), snippets_.end(),
+ [&expiry](const scoped_ptr<NTPSnippet>& snippet) {
+ return snippet->expiry_date() <= expiry;
+ }),
+ snippets_.end());
+ StoreSnippetsToPrefs();
+
+ discarded_snippets_.erase(
+ std::remove_if(discarded_snippets_.begin(), discarded_snippets_.end(),
+ [&expiry](const scoped_ptr<NTPSnippet>& snippet) {
+ return snippet->expiry_date() <= expiry;
+ }),
+ discarded_snippets_.end());
+ StoreDiscardedSnippetsToPrefs();
+
+ FOR_EACH_OBSERVER(NTPSnippetsServiceObserver, observers_,
+ NTPSnippetsServiceLoaded());
+
+ // If there are any snippets left, schedule a timer for the next expiry.
+ if (snippets_.empty() && discarded_snippets_.empty())
+ return;
+
+ base::Time next_expiry = base::Time::Max();
+ for (const auto& snippet : snippets_) {
+ if (snippet->expiry_date() < next_expiry)
+ next_expiry = snippet->expiry_date();
+ }
+ for (const auto& snippet : discarded_snippets_) {
+ if (snippet->expiry_date() < next_expiry)
+ next_expiry = snippet->expiry_date();
+ }
+ DCHECK_GT(next_expiry, expiry);
+ expiry_timer_.Start(FROM_HERE, next_expiry - expiry,
+ base::Bind(&NTPSnippetsService::RemoveExpiredSnippets,
+ base::Unretained(this)));
+}
+
+} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/ntp_snippets_service.h b/chromium/components/ntp_snippets/ntp_snippets_service.h
new file mode 100644
index 00000000000..d6ab94c7f47
--- /dev/null
+++ b/chromium/components/ntp_snippets/ntp_snippets_service.h
@@ -0,0 +1,195 @@
+// Copyright 2015 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_NTP_SNIPPETS_SERVICE_H_
+#define COMPONENTS_NTP_SNIPPETS_NTP_SNIPPETS_SERVICE_H_
+
+#include <stddef.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/sequenced_task_runner.h"
+#include "base/timer/timer.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/ntp_snippets/inner_iterator.h"
+#include "components/ntp_snippets/ntp_snippet.h"
+#include "components/ntp_snippets/ntp_snippets_fetcher.h"
+#include "components/ntp_snippets/ntp_snippets_scheduler.h"
+#include "components/suggestions/suggestions_service.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace base {
+class ListValue;
+class Value;
+}
+
+namespace suggestions {
+class SuggestionsProfile;
+}
+
+namespace ntp_snippets {
+
+class NTPSnippetsServiceObserver;
+
+// Stores and vends fresh content data for the NTP.
+class NTPSnippetsService : public KeyedService {
+ public:
+ using NTPSnippetStorage = std::vector<scoped_ptr<NTPSnippet>>;
+ using const_iterator =
+ InnerIterator<NTPSnippetStorage::const_iterator, const NTPSnippet>;
+
+ // Callbacks for JSON parsing.
+ using SuccessCallback = base::Callback<void(scoped_ptr<base::Value>)>;
+ using ErrorCallback = base::Callback<void(const std::string&)>;
+ using ParseJSONCallback = base::Callback<
+ void(const std::string&, const SuccessCallback&, const ErrorCallback&)>;
+
+ // |application_language_code| should be a ISO 639-1 compliant string, e.g.
+ // 'en' or 'en-US'. Note that this code should only specify the language, not
+ // the locale, so 'en_US' (English language with US locale) and 'en-GB_US'
+ // (British English person in the US) are not language codes.
+ NTPSnippetsService(PrefService* pref_service,
+ suggestions::SuggestionsService* suggestions_service,
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner,
+ const std::string& application_language_code,
+ NTPSnippetsScheduler* scheduler,
+ scoped_ptr<NTPSnippetsFetcher> snippets_fetcher,
+ const ParseJSONCallback& parse_json_callback);
+ ~NTPSnippetsService() override;
+
+ static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+
+ void Init(bool enabled);
+
+ // Inherited from KeyedService.
+ void Shutdown() override;
+
+ // Fetches snippets from the server and adds them to the current ones.
+ void FetchSnippets();
+
+ // Discards the snippet with the given |url|, if it exists. Returns true iff
+ // a snippet was discarded.
+ bool DiscardSnippet(const GURL& url);
+
+ // Observer accessors.
+ void AddObserver(NTPSnippetsServiceObserver* observer);
+ void RemoveObserver(NTPSnippetsServiceObserver* observer);
+
+ // Number of snippets available.
+ size_t size() const { return snippets_.size(); }
+
+ // The snippets can be iterated upon only via a const_iterator. Recommended
+ // way to iterate is as follows:
+ //
+ // NTPSnippetsService* service; // Assume is set.
+ // for (auto& snippet : *service) {
+ // // |snippet| here is a const object.
+ // }
+ const_iterator begin() const { return const_iterator(snippets_.begin()); }
+ const_iterator end() const { return const_iterator(snippets_.end()); }
+
+ private:
+ friend class NTPSnippetsServiceTest;
+
+ void OnSuggestionsChanged(const suggestions::SuggestionsProfile& suggestions);
+ void OnSnippetsDownloaded(const std::string& snippets_json);
+
+ void OnJsonParsed(const std::string& snippets_json,
+ scoped_ptr<base::Value> parsed);
+ void OnJsonError(const std::string& snippets_json, const std::string& error);
+
+ void FetchSnippetsImpl(const std::set<std::string>& hosts);
+
+ // Expects a top-level dictionary containing a "recos" list, which will be
+ // passed to LoadFromListValue().
+ bool LoadFromValue(const base::Value& value);
+
+ // Expects a list of dictionaries each containing a "contentInfo" dictionary
+ // with keys matching the properties of a snippet (url, title, site_title,
+ // etc...). The URL is the only mandatory value.
+ bool LoadFromListValue(const base::ListValue& list);
+
+ // TODO(treib): Investigate a better storage, maybe LevelDB or SQLite?
+ void LoadSnippetsFromPrefs();
+ void StoreSnippetsToPrefs();
+
+ void LoadDiscardedSnippetsFromPrefs();
+ void StoreDiscardedSnippetsToPrefs();
+
+ std::set<std::string> GetSnippetHostsFromPrefs() const;
+ void StoreSnippetHostsToPrefs(const std::set<std::string>& hosts);
+
+ bool HasDiscardedSnippet(const GURL& url) const;
+
+ void RemoveExpiredSnippets();
+
+ PrefService* pref_service_;
+
+ suggestions::SuggestionsService* suggestions_service_;
+
+ // The SequencedTaskRunner on which file system operations will be run.
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
+
+ // All current suggestions (i.e. not discarded ones).
+ NTPSnippetStorage snippets_;
+
+ // Suggestions that the user discarded. We keep these around until they expire
+ // so we won't re-add them on the next fetch.
+ NTPSnippetStorage discarded_snippets_;
+
+ // The ISO 639-1 code of the language used by the application.
+ const std::string application_language_code_;
+
+ // The observers.
+ base::ObserverList<NTPSnippetsServiceObserver> observers_;
+
+ // Scheduler for fetching snippets. Not owned.
+ NTPSnippetsScheduler* scheduler_;
+
+ // The subscription to the SuggestionsService. When the suggestions change,
+ // SuggestionsService will call |OnSuggestionsChanged|, which triggers an
+ // update to the set of snippets.
+ using SuggestionsSubscription =
+ suggestions::SuggestionsService::ResponseCallbackList::Subscription;
+ scoped_ptr<SuggestionsSubscription> suggestions_service_subscription_;
+
+ // The snippets fetcher.
+ scoped_ptr<NTPSnippetsFetcher> snippets_fetcher_;
+
+ // The subscription to the snippets fetcher.
+ scoped_ptr<NTPSnippetsFetcher::SnippetsAvailableCallbackList::Subscription>
+ snippets_fetcher_subscription_;
+
+ // Timer that calls us back when the next snippet expires.
+ base::OneShotTimer expiry_timer_;
+
+ const ParseJSONCallback parse_json_callback_;
+
+ base::WeakPtrFactory<NTPSnippetsService> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NTPSnippetsService);
+};
+
+class NTPSnippetsServiceObserver {
+ public:
+ // Sent every time the service loads a new set of data.
+ virtual void NTPSnippetsServiceLoaded() = 0;
+ // Sent when the service is shutting down.
+ virtual void NTPSnippetsServiceShutdown() = 0;
+
+ protected:
+ virtual ~NTPSnippetsServiceObserver() {}
+};
+
+} // namespace ntp_snippets
+
+#endif // COMPONENTS_NTP_SNIPPETS_NTP_SNIPPETS_SERVICE_H_
diff --git a/chromium/components/ntp_snippets/ntp_snippets_service_unittest.cc b/chromium/components/ntp_snippets/ntp_snippets_service_unittest.cc
new file mode 100644
index 00000000000..699a20ff5f5
--- /dev/null
+++ b/chromium/components/ntp_snippets/ntp_snippets_service_unittest.cc
@@ -0,0 +1,221 @@
+// Copyright 2015 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 "base/json/json_reader.h"
+#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "components/ntp_snippets/ntp_snippet.h"
+#include "components/ntp_snippets/ntp_snippets_fetcher.h"
+#include "components/ntp_snippets/ntp_snippets_service.h"
+#include "components/prefs/testing_pref_service.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ntp_snippets {
+
+namespace {
+
+std::string GetTestJson(const std::string& content_creation_time_str,
+ const std::string& expiry_time_str) {
+ char json_str_format[] =
+ "{ \"recos\": [ "
+ "{ \"contentInfo\": {"
+ "\"url\" : \"http://localhost/foobar\","
+ "\"site_title\" : \"Site Title\","
+ "\"favicon_url\" : \"http://localhost/favicon\","
+ "\"title\" : \"Title\","
+ "\"snippet\" : \"Snippet\","
+ "\"thumbnailUrl\" : \"http://localhost/salient_image\","
+ "\"creationTimestampSec\" : \"%s\","
+ "\"expiryTimestampSec\" : \"%s\""
+ "}}"
+ "]}";
+
+ return base::StringPrintf(json_str_format, content_creation_time_str.c_str(),
+ expiry_time_str.c_str());
+}
+
+std::string GetTestJson(const std::string& content_creation_time_str) {
+ int64_t expiry_time =
+ (base::Time::Now() + base::TimeDelta::FromHours(1)).ToJavaTime() /
+ base::Time::kMillisecondsPerSecond;
+
+ return GetTestJson(content_creation_time_str,
+ base::Int64ToString(expiry_time));
+}
+
+std::string GetTestJson(int64_t content_creation_time_sec) {
+ int64_t expiry_time =
+ (base::Time::Now() + base::TimeDelta::FromHours(1)).ToJavaTime() /
+ base::Time::kMillisecondsPerSecond;
+
+ return GetTestJson(base::Int64ToString(content_creation_time_sec),
+ base::Int64ToString(expiry_time));
+}
+
+std::string GetTestExpiredJson(int64_t content_creation_time_sec) {
+ int64_t expiry_time =
+ (base::Time::Now()).ToJavaTime() / base::Time::kMillisecondsPerSecond;
+
+ return GetTestJson(base::Int64ToString(content_creation_time_sec),
+ base::Int64ToString(expiry_time));
+}
+
+void ParseJson(
+ const std::string& json,
+ const ntp_snippets::NTPSnippetsService::SuccessCallback& success_callback,
+ const ntp_snippets::NTPSnippetsService::ErrorCallback& error_callback) {
+ base::JSONReader json_reader;
+ scoped_ptr<base::Value> value = json_reader.ReadToValue(json);
+ if (value) {
+ success_callback.Run(std::move(value));
+ } else {
+ error_callback.Run(json_reader.GetErrorMessage());
+ }
+}
+
+} // namespace
+
+class NTPSnippetsServiceTest : public testing::Test {
+ public:
+ NTPSnippetsServiceTest()
+ : pref_service_(new TestingPrefServiceSimple()) {}
+ ~NTPSnippetsServiceTest() override {}
+
+ void SetUp() override {
+ NTPSnippetsService::RegisterProfilePrefs(pref_service_->registry());
+
+ CreateSnippetsService();
+ }
+
+ void CreateSnippetsService() {
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner(
+ base::ThreadTaskRunnerHandle::Get());
+ scoped_refptr<net::TestURLRequestContextGetter> request_context_getter =
+ new net::TestURLRequestContextGetter(task_runner.get());
+
+ service_.reset(new NTPSnippetsService(
+ pref_service_.get(), nullptr, task_runner, std::string("fr"), nullptr,
+ make_scoped_ptr(new NTPSnippetsFetcher(
+ task_runner, std::move(request_context_getter), true)),
+ base::Bind(&ParseJson)));
+ }
+
+ protected:
+ NTPSnippetsService* service() {
+ return service_.get();
+ }
+
+ bool LoadFromJSONString(const std::string& json) {
+ scoped_ptr<base::Value> value = base::JSONReader::Read(json);
+ if (!value)
+ return false;
+
+ return service_->LoadFromValue(*value);
+ }
+
+ private:
+ base::MessageLoop message_loop_;
+ scoped_ptr<TestingPrefServiceSimple> pref_service_;
+ scoped_ptr<NTPSnippetsService> service_;
+
+ DISALLOW_COPY_AND_ASSIGN(NTPSnippetsServiceTest);
+};
+
+TEST_F(NTPSnippetsServiceTest, Loop) {
+ std::string json_str(
+ "{ \"recos\": [ "
+ "{ \"contentInfo\": { \"url\" : \"http://localhost/foobar\" }}"
+ "]}");
+ ASSERT_TRUE(LoadFromJSONString(json_str));
+
+ // The same for loop without the '&' should not compile.
+ for (auto& snippet : *service()) {
+ // Snippet here is a const.
+ EXPECT_EQ(snippet.url(), GURL("http://localhost/foobar"));
+ }
+ // Without the const, this should not compile.
+ for (const NTPSnippet& snippet : *service()) {
+ EXPECT_EQ(snippet.url(), GURL("http://localhost/foobar"));
+ }
+}
+
+TEST_F(NTPSnippetsServiceTest, Full) {
+ std::string json_str(GetTestJson(1448459205));
+
+ ASSERT_TRUE(LoadFromJSONString(json_str));
+ EXPECT_EQ(service()->size(), 1u);
+
+ // The same for loop without the '&' should not compile.
+ for (auto& snippet : *service()) {
+ // Snippet here is a const.
+ EXPECT_EQ(snippet.url(), GURL("http://localhost/foobar"));
+ EXPECT_EQ(snippet.site_title(), "Site Title");
+ EXPECT_EQ(snippet.favicon_url(), GURL("http://localhost/favicon"));
+ EXPECT_EQ(snippet.title(), "Title");
+ EXPECT_EQ(snippet.snippet(), "Snippet");
+ EXPECT_EQ(snippet.salient_image_url(),
+ GURL("http://localhost/salient_image"));
+ base::Time then =
+ base::Time::FromUTCExploded({2015, 11, 4, 25, 13, 46, 45});
+ EXPECT_EQ(then, snippet.publish_date());
+ }
+}
+
+TEST_F(NTPSnippetsServiceTest, Discard) {
+ std::string json_str(
+ "{ \"recos\": [ { \"contentInfo\": { \"url\" : \"http://site.com\" }}]}");
+ ASSERT_TRUE(LoadFromJSONString(json_str));
+
+ ASSERT_EQ(1u, service()->size());
+
+ // Discarding a non-existent snippet shouldn't do anything.
+ EXPECT_FALSE(service()->DiscardSnippet(GURL("http://othersite.com")));
+ EXPECT_EQ(1u, service()->size());
+
+ // Discard the snippet.
+ EXPECT_TRUE(service()->DiscardSnippet(GURL("http://site.com")));
+ EXPECT_EQ(0u, service()->size());
+
+ // Make sure that fetching the same snippet again does not re-add it.
+ ASSERT_TRUE(LoadFromJSONString(json_str));
+ EXPECT_EQ(0u, service()->size());
+
+ // The snippet should stay discarded even after re-creating the service.
+ CreateSnippetsService();
+ // Init the service, so the prefs get loaded.
+ // TODO(treib): This should happen in CreateSnippetsService.
+ service()->Init(true);
+ ASSERT_TRUE(LoadFromJSONString(json_str));
+ EXPECT_EQ(0u, service()->size());
+}
+
+TEST_F(NTPSnippetsServiceTest, CreationTimestampParseFail) {
+ std::string json_str(GetTestJson("aaa1448459205"));
+
+ ASSERT_TRUE(LoadFromJSONString(json_str));
+ EXPECT_EQ(service()->size(), 1u);
+
+ // The same for loop without the '&' should not compile.
+ for (auto& snippet : *service()) {
+ // Snippet here is a const.
+ EXPECT_EQ(snippet.url(), GURL("http://localhost/foobar"));
+ EXPECT_EQ(snippet.title(), "Title");
+ EXPECT_EQ(snippet.snippet(), "Snippet");
+ EXPECT_EQ(base::Time::UnixEpoch(), snippet.publish_date());
+ }
+}
+
+TEST_F(NTPSnippetsServiceTest, RemoveExpiredContent) {
+ std::string json_str(GetTestExpiredJson(1448459205));
+
+ ASSERT_TRUE(LoadFromJSONString(json_str));
+ EXPECT_EQ(service()->size(), 0u);
+}
+
+} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/pref_names.cc b/chromium/components/ntp_snippets/pref_names.cc
new file mode 100644
index 00000000000..a7b9f888b0e
--- /dev/null
+++ b/chromium/components/ntp_snippets/pref_names.cc
@@ -0,0 +1,16 @@
+// 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/pref_names.h"
+
+namespace ntp_snippets {
+namespace prefs {
+
+const char kSnippets[] = "ntp_snippets.snippets";
+const char kDiscardedSnippets[] = "ntp_snippets.discarded_snippets";
+
+const char kSnippetHosts[] = "ntp_snippets.hosts";
+
+} // namespace prefs
+} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/pref_names.h b/chromium/components/ntp_snippets/pref_names.h
new file mode 100644
index 00000000000..bcd400d3f85
--- /dev/null
+++ b/chromium/components/ntp_snippets/pref_names.h
@@ -0,0 +1,19 @@
+// 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_PREF_NAMES_H_
+#define COMPONENTS_NTP_SNIPPETS_PREF_NAMES_H_
+
+namespace ntp_snippets {
+namespace prefs {
+
+extern const char kSnippets[];
+extern const char kDiscardedSnippets[];
+
+extern const char kSnippetHosts[];
+
+} // namespace prefs
+} // namespace ntp_snippets
+
+#endif
diff --git a/chromium/components/ntp_snippets/switches.cc b/chromium/components/ntp_snippets/switches.cc
new file mode 100644
index 00000000000..4fdbf2bb9fa
--- /dev/null
+++ b/chromium/components/ntp_snippets/switches.cc
@@ -0,0 +1,22 @@
+// 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/switches.h"
+
+namespace ntp_snippets {
+namespace switches {
+
+const char kFetchingIntervalWifiChargingSeconds[] =
+ "ntp-snippets-fetching-interval-wifi-charging";
+const char kFetchingIntervalWifiSeconds[] =
+ "ntp-snippets-fetching-interval-wifi";
+const char kFetchingIntervalFallbackSeconds[] =
+ "ntp-snippets-fetching-interval-fallback";
+
+// If this flag is set, the snippets won't be restricted to the user's NTP
+// suggestions.
+const char kDontRestrict[] = "ntp-snippets-dont-restrict";
+
+} // namespace switches
+} // namespace ntp_snippets
diff --git a/chromium/components/ntp_snippets/switches.h b/chromium/components/ntp_snippets/switches.h
new file mode 100644
index 00000000000..44618b3fb3b
--- /dev/null
+++ b/chromium/components/ntp_snippets/switches.h
@@ -0,0 +1,20 @@
+// 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_SWITCHES_H_
+#define COMPONENTS_NTP_SNIPPETS_SWITCHES_H_
+
+namespace ntp_snippets {
+namespace switches {
+
+extern const char kFetchingIntervalWifiChargingSeconds[];
+extern const char kFetchingIntervalWifiSeconds[];
+extern const char kFetchingIntervalFallbackSeconds[];
+
+extern const char kDontRestrict[];
+
+} // namespace switches
+} // namespace ntp_snippets
+
+#endif // COMPONENTS_NTP_SNIPPETS_SWITCHES_H_