diff options
Diffstat (limited to 'chromium/components/suggestions')
10 files changed, 297 insertions, 17 deletions
diff --git a/chromium/components/suggestions/BUILD.gn b/chromium/components/suggestions/BUILD.gn index 6d93d045bc4..fa4acf4a65f 100644 --- a/chromium/components/suggestions/BUILD.gn +++ b/chromium/components/suggestions/BUILD.gn @@ -18,6 +18,8 @@ static_library("suggestions") { "suggestions_service_impl.h", "suggestions_store.cc", "suggestions_store.h", + "webui/suggestions_source.cc", + "webui/suggestions_source.h", ] public_deps = [ @@ -25,6 +27,7 @@ static_library("suggestions") { "//components/prefs", "//components/suggestions/proto", "//net", + "//ui/base", "//ui/gfx", "//url", ] diff --git a/chromium/components/suggestions/image_encoder.cc b/chromium/components/suggestions/image_encoder.cc index 8b0fa09fc26..414766446c4 100644 --- a/chromium/components/suggestions/image_encoder.cc +++ b/chromium/components/suggestions/image_encoder.cc @@ -20,10 +20,8 @@ bool EncodeSkBitmapToJPEG(const SkBitmap& bitmap, if (!bitmap.readyToDraw() || bitmap.isNull()) { return false; } - return gfx::JPEGCodec::Encode( - reinterpret_cast<unsigned char*>(bitmap.getAddr32(0, 0)), - gfx::JPEGCodec::FORMAT_SkBitmap, bitmap.width(), bitmap.height(), - bitmap.rowBytes(), 100, dest); + + return gfx::JPEGCodec::Encode(bitmap, 100, dest); } } // namespace suggestions diff --git a/chromium/components/suggestions/image_manager.cc b/chromium/components/suggestions/image_manager.cc index d2fb227f9ac..321aef28ac1 100644 --- a/chromium/components/suggestions/image_manager.cc +++ b/chromium/components/suggestions/image_manager.cc @@ -10,6 +10,7 @@ #include "base/bind.h" #include "base/location.h" #include "base/task_runner_util.h" +#include "base/task_scheduler/post_task.h" #include "components/image_fetcher/core/image_fetcher.h" #include "components/suggestions/image_encoder.h" #include "net/traffic_annotation/network_traffic_annotation.h" @@ -82,11 +83,11 @@ ImageManager::ImageManager() : weak_ptr_factory_(this) {} ImageManager::ImageManager( std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher, std::unique_ptr<ProtoDatabase<ImageData>> database, - const base::FilePath& database_dir, - scoped_refptr<base::TaskRunner> background_task_runner) + const base::FilePath& database_dir) : image_fetcher_(std::move(image_fetcher)), database_(std::move(database)), - background_task_runner_(background_task_runner), + background_task_runner_(base::CreateSequencedTaskRunnerWithTraits( + {base::TaskPriority::USER_VISIBLE})), database_ready_(false), weak_ptr_factory_(this) { image_fetcher_->SetImageFetcherDelegate(this); @@ -216,6 +217,8 @@ void ImageManager::ServeFromCacheOrNetwork( void ImageManager::SaveImage(const std::string& url, const SkBitmap& bitmap) { scoped_refptr<base::RefCountedBytes> encoded_data( new base::RefCountedBytes()); + // TODO(treib): Should encoding happen on the |background_task_runner_|? + // *De*coding happens there. if (!EncodeSkBitmapToJPEG(bitmap, &encoded_data->data())) { return; } diff --git a/chromium/components/suggestions/image_manager.h b/chromium/components/suggestions/image_manager.h index 20de2eea4d3..04ffc002b77 100644 --- a/chromium/components/suggestions/image_manager.h +++ b/chromium/components/suggestions/image_manager.h @@ -47,8 +47,7 @@ class ImageManager : public image_fetcher::ImageFetcherDelegate { ImageManager( std::unique_ptr<image_fetcher::ImageFetcher> image_fetcher, std::unique_ptr<leveldb_proto::ProtoDatabase<ImageData>> database, - const base::FilePath& database_dir, - scoped_refptr<base::TaskRunner> background_task_runner); + const base::FilePath& database_dir); ~ImageManager() override; virtual void Initialize(const SuggestionsProfile& suggestions); diff --git a/chromium/components/suggestions/image_manager_unittest.cc b/chromium/components/suggestions/image_manager_unittest.cc index 86717dd169b..5c42a290d47 100644 --- a/chromium/components/suggestions/image_manager_unittest.cc +++ b/chromium/components/suggestions/image_manager_unittest.cc @@ -11,7 +11,6 @@ #include "base/memory/ptr_util.h" #include "base/run_loop.h" #include "base/test/scoped_task_environment.h" -#include "base/threading/thread_task_runner_handle.h" #include "components/image_fetcher/core/image_fetcher.h" #include "components/image_fetcher/core/image_fetcher_delegate.h" #include "components/leveldb_proto/proto_database.h" @@ -23,7 +22,6 @@ #include "testing/gtest/include/gtest/gtest.h" #include "ui/gfx/geometry/size.h" #include "ui/gfx/image/image.h" -#include "ui/gfx/image/image_skia.h" #include "url/gurl.h" using ::testing::Return; @@ -129,8 +127,7 @@ class ImageManagerTest : public testing::Test { EXPECT_CALL(*mock_image_fetcher_, SetImageFetcherDelegate(_)); return new ImageManager(base::WrapUnique(mock_image_fetcher_), base::WrapUnique(fake_db), - FakeDB<ImageData>::DirectoryForTestDB(), - base::ThreadTaskRunnerHandle::Get()); + FakeDB<ImageData>::DirectoryForTestDB()); } EntryMap db_model_; diff --git a/chromium/components/suggestions/proto/suggestions.proto b/chromium/components/suggestions/proto/suggestions.proto index 27f5d141c33..5fcf03ae8ac 100644 --- a/chromium/components/suggestions/proto/suggestions.proto +++ b/chromium/components/suggestions/proto/suggestions.proto @@ -23,15 +23,19 @@ enum ProviderId { // // Notice that the tags on this proto must match the ones on the server side. // -// Next tag: 2 +// Next tag: 17 message SuggestionsProfile { repeated ChromeSuggestion suggestions = 1; + reserved 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15; + // Timestamp when the profile was generated (usec). optional int64 timestamp = 16; } -// The suggestions for this user, ordered from best to worst. +// An individual suggestion. +// +// Notice that the tags on this proto must match the ones on the server side. // // Next tag: 15 message ChromeSuggestion { @@ -50,8 +54,12 @@ message ChromeSuggestion { // The provider(s) responsible for this suggestion. repeated ProviderId providers = 5; + reserved 6; + // The timestamp (usec) at which this suggestion ceases to be valid. optional int64 expiry_ts = 7; + + reserved 8, 9, 10, 11, 12, 13, 14; } // A list of URLs that should be filtered from the SuggestionsProfile. diff --git a/chromium/components/suggestions/suggestions_service_impl.cc b/chromium/components/suggestions/suggestions_service_impl.cc index aacd400d7b5..e1af0660e44 100644 --- a/chromium/components/suggestions/suggestions_service_impl.cc +++ b/chromium/components/suggestions/suggestions_service_impl.cc @@ -92,7 +92,7 @@ const char kSuggestionsBlacklistClearURLFormat[] = const char kSuggestionsBlacklistURLParam[] = "url"; const char kSuggestionsDeviceParam[] = "t=%s"; -const char kSuggestionsMinParam[] = "min=%i"; +const char kSuggestionsMinParam[] = "num=%i"; const char kSuggestionsMinVariationName[] = "min_suggestions"; const int kSuggestionsMinVariationDefault = 0; diff --git a/chromium/components/suggestions/suggestions_service_impl_unittest.cc b/chromium/components/suggestions/suggestions_service_impl_unittest.cc index 7aa45beb51e..90fc4f5959c 100644 --- a/chromium/components/suggestions/suggestions_service_impl_unittest.cc +++ b/chromium/components/suggestions/suggestions_service_impl_unittest.cc @@ -343,7 +343,7 @@ TEST_F(SuggestionsServiceTest, BuildUrlWithDefaultMinZeroParamForFewFeature) { ASSERT_TRUE(GetCurrentlyQueriedUrl().is_valid()); EXPECT_EQ(GetCurrentlyQueriedUrl().path(), kSuggestionsUrlPath); std::string min_suggestions; - EXPECT_TRUE(net::GetValueForKeyInQuery(GetCurrentlyQueriedUrl(), "min", + EXPECT_TRUE(net::GetValueForKeyInQuery(GetCurrentlyQueriedUrl(), "num", &min_suggestions)); EXPECT_EQ(min_suggestions, "0"); RespondToFetchWithProfile(CreateSuggestionsProfile()); diff --git a/chromium/components/suggestions/webui/suggestions_source.cc b/chromium/components/suggestions/webui/suggestions_source.cc new file mode 100644 index 00000000000..ab9b927cd5e --- /dev/null +++ b/chromium/components/suggestions/webui/suggestions_source.cc @@ -0,0 +1,197 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/suggestions/webui/suggestions_source.h" + +#include "base/barrier_closure.h" +#include "base/base64.h" +#include "base/bind.h" +#include "base/strings/string16.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "components/suggestions/proto/suggestions.pb.h" +#include "net/base/escape.h" +#include "ui/base/l10n/time_format.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/image/image_skia.h" + +namespace suggestions { + +namespace { + +const char kHtmlHeader[] = + "<!DOCTYPE html>\n<html>\n<head>\n<title>Suggestions</title>\n" + "<meta charset=\"utf-8\">\n" + "<style type=\"text/css\">\nli {white-space: nowrap;}\n</style>\n"; +const char kHtmlBody[] = "</head>\n<body>\n"; +const char kHtmlFooter[] = "</body>\n</html>\n"; + +const char kRefreshPath[] = "refresh"; + +std::string GetRefreshHtml(const std::string& base_url, bool is_refresh) { + if (is_refresh) + return "<p>Refreshing in the background, reload to see new data.</p>\n"; + return std::string("<p><a href=\"") + base_url + kRefreshPath + + "\">Refresh</a></p>\n"; +} +// Returns the HTML needed to display the suggestions. +std::string RenderOutputHtml( + const std::string& base_url, + bool is_refresh, + const SuggestionsProfile& profile, + const std::map<GURL, std::string>& base64_encoded_pngs) { + std::vector<std::string> out; + out.push_back(kHtmlHeader); + out.push_back(kHtmlBody); + out.push_back("<h1>Suggestions</h1>\n"); + out.push_back(GetRefreshHtml(base_url, is_refresh)); + out.push_back("<ul>"); + int64_t now = (base::Time::NowFromSystemTime() - base::Time::UnixEpoch()) + .ToInternalValue(); + size_t size = profile.suggestions_size(); + for (size_t i = 0; i < size; ++i) { + const ChromeSuggestion& suggestion = profile.suggestions(i); + base::TimeDelta remaining_time = + base::TimeDelta::FromMicroseconds(suggestion.expiry_ts() - now); + base::string16 remaining_time_formatted = ui::TimeFormat::Detailed( + ui::TimeFormat::Format::FORMAT_DURATION, + ui::TimeFormat::Length::LENGTH_LONG, -1, remaining_time); + std::string line; + line += "<li><a href=\""; + line += net::EscapeForHTML(suggestion.url()); + line += "\" target=\"_blank\">"; + line += net::EscapeForHTML(suggestion.title()); + std::map<GURL, std::string>::const_iterator it = + base64_encoded_pngs.find(GURL(suggestion.url())); + if (it != base64_encoded_pngs.end()) { + line += "<br><img src='"; + line += it->second; + line += "'>"; + } + line += "</a> Expires in "; + line += base::UTF16ToUTF8(remaining_time_formatted); + std::vector<std::string> providers; + for (int p = 0; p < suggestion.providers_size(); ++p) + providers.push_back(base::IntToString(suggestion.providers(p))); + line += ". Provider IDs: " + base::JoinString(providers, ", "); + line += "</li>\n"; + out.push_back(line); + } + out.push_back("</ul>"); + out.push_back(kHtmlFooter); + return base::JoinString(out, base::StringPiece()); +} + +// Returns the HTML needed to display that no suggestions are available. +std::string RenderOutputHtmlNoSuggestions(const std::string& base_url, + bool is_refresh) { + std::vector<std::string> out; + out.push_back(kHtmlHeader); + out.push_back(kHtmlBody); + out.push_back("<h1>Suggestions</h1>\n"); + out.push_back("<p>You have no suggestions.</p>\n"); + out.push_back(GetRefreshHtml(base_url, is_refresh)); + out.push_back(kHtmlFooter); + return base::JoinString(out, base::StringPiece()); +} + +} // namespace + +SuggestionsSource::SuggestionsSource(SuggestionsService* suggestions_service, + const std::string& base_url) + : suggestions_service_(suggestions_service), + base_url_(base_url), + weak_ptr_factory_(this) {} + +SuggestionsSource::~SuggestionsSource() {} + +SuggestionsSource::RequestContext::RequestContext( + bool is_refresh_in, + const SuggestionsProfile& suggestions_profile_in, + const GotDataCallback& callback_in) + : is_refresh(is_refresh_in), + suggestions_profile(suggestions_profile_in), // Copy. + callback(callback_in) // Copy. +{} + +SuggestionsSource::RequestContext::~RequestContext() {} + +void SuggestionsSource::StartDataRequest(const std::string& path, + const GotDataCallback& callback) { + // If this was called as "chrome://suggestions/refresh", we also trigger an + // async update of the suggestions. + bool is_refresh = (path == kRefreshPath); + + // |suggestions_service| is null for guest profiles. + if (!suggestions_service_) { + std::string output = RenderOutputHtmlNoSuggestions(base_url_, is_refresh); + callback.Run(base::RefCountedString::TakeString(&output)); + return; + } + + if (is_refresh) + suggestions_service_->FetchSuggestionsData(); + + SuggestionsProfile suggestions_profile = + suggestions_service_->GetSuggestionsDataFromCache().value_or( + SuggestionsProfile()); + size_t size = suggestions_profile.suggestions_size(); + if (!size) { + std::string output = RenderOutputHtmlNoSuggestions(base_url_, is_refresh); + callback.Run(base::RefCountedString::TakeString(&output)); + } else { + RequestContext* context = + new RequestContext(is_refresh, suggestions_profile, callback); + base::Closure barrier = BarrierClosure( + size, base::BindOnce(&SuggestionsSource::OnThumbnailsFetched, + weak_ptr_factory_.GetWeakPtr(), context)); + for (size_t i = 0; i < size; ++i) { + const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i); + // Fetch the thumbnail for this URL (exercising the fetcher). After all + // fetches are done, including NULL callbacks for unavailable thumbnails, + // SuggestionsSource::OnThumbnailsFetched will be called. + suggestions_service_->GetPageThumbnail( + GURL(suggestion.url()), + base::Bind(&SuggestionsSource::OnThumbnailAvailable, + weak_ptr_factory_.GetWeakPtr(), context, barrier)); + } + } +} + +std::string SuggestionsSource::GetMimeType(const std::string& path) const { + return "text/html"; +} + +void SuggestionsSource::OnThumbnailsFetched(RequestContext* context) { + std::unique_ptr<RequestContext> context_deleter(context); + + std::string output = RenderOutputHtml(base_url_, context->is_refresh, + context->suggestions_profile, + context->base64_encoded_pngs); + context->callback.Run(base::RefCountedString::TakeString(&output)); +} + +void SuggestionsSource::OnThumbnailAvailable(RequestContext* context, + const base::Closure& barrier, + const GURL& url, + const gfx::Image& image) { + if (!image.IsEmpty()) { + std::vector<unsigned char> output; + gfx::PNGCodec::EncodeBGRASkBitmap(*image.ToSkBitmap(), false, &output); + + std::string encoded_output; + base::Base64Encode( + base::StringPiece(reinterpret_cast<const char*>(output.data()), + output.size()), + &encoded_output); + context->base64_encoded_pngs[url] = "data:image/png;base64,"; + context->base64_encoded_pngs[url] += encoded_output; + } + barrier.Run(); +} + +} // namespace suggestions diff --git a/chromium/components/suggestions/webui/suggestions_source.h b/chromium/components/suggestions/webui/suggestions_source.h new file mode 100644 index 00000000000..c5c2b4f9c38 --- /dev/null +++ b/chromium/components/suggestions/webui/suggestions_source.h @@ -0,0 +1,75 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SUGGESTIONS_WEBUI_SUGGESTIONS_SOURCE_H_ +#define COMPONENTS_SUGGESTIONS_WEBUI_SUGGESTIONS_SOURCE_H_ + +#include <map> +#include <string> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted_memory.h" +#include "base/memory/weak_ptr.h" +#include "components/suggestions/suggestions_service.h" +#include "ui/gfx/image/image.h" +#include "url/gurl.h" + +namespace suggestions { + +// SuggestionsSource renders a webpage to list SuggestionsService data. +class SuggestionsSource { + public: + SuggestionsSource(SuggestionsService* suggestions_service, + const std::string& base_url); + ~SuggestionsSource(); + + using GotDataCallback = + base::Callback<void(scoped_refptr<base::RefCountedMemory>)>; + + void StartDataRequest(const std::string& path, + const GotDataCallback& callback); + std::string GetMimeType(const std::string& path) const; + + private: + // Container for the state of a request. + struct RequestContext { + RequestContext( + bool is_refresh_in, + const suggestions::SuggestionsProfile& suggestions_profile_in, + const GotDataCallback& callback_in); + ~RequestContext(); + + const bool is_refresh; + const suggestions::SuggestionsProfile suggestions_profile; + const GotDataCallback callback; + std::map<GURL, std::string> base64_encoded_pngs; + }; + + // Callback for responses from each Thumbnail request. + void OnThumbnailAvailable(RequestContext* context, + const base::Closure& barrier, + const GURL& url, + const gfx::Image& image); + + // Callback for when all requests are complete. Renders the output webpage and + // passes the result to the original caller. + void OnThumbnailsFetched(RequestContext* context); + + // Only used when servicing requests on the UI thread. + SuggestionsService* suggestions_service_; + + // The base URL at which which the Suggestions WebUI lives in the context of + // the embedder. + const std::string base_url_; + + // For callbacks may be run after destruction. + base::WeakPtrFactory<SuggestionsSource> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(SuggestionsSource); +}; + +} // namespace suggestions + +#endif // COMPONENTS_SUGGESTIONS_WEBUI_SUGGESTIONS_SOURCE_H_ |