diff options
author | Allan Sandfeld Jensen <allan.jensen@theqtcompany.com> | 2016-05-09 14:22:11 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2016-05-09 15:11:45 +0000 |
commit | 2ddb2d3e14eef3de7dbd0cef553d669b9ac2361c (patch) | |
tree | e75f511546c5fd1a173e87c1f9fb11d7ac8d1af3 /chromium/components/ntp_snippets | |
parent | a4f3d46271c57e8155ba912df46a05559d14726e (diff) | |
download | qtwebengine-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')
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_ |