// 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/strcat.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[] = "\n\n\nSuggestions\n" "\n" "\n"; const char kHtmlBody[] = "\n\n"; const char kHtmlFooter[] = "\n\n"; const char kRefreshPath[] = "refresh"; std::string GetRefreshHtml(const std::string& base_url, bool is_refresh) { if (is_refresh) return "

Refreshing in the background, reload to see new data.

\n"; return std::string("

Refresh

\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& base64_encoded_pngs) { std::vector out; out.push_back(kHtmlHeader); out.push_back(kHtmlBody); out.push_back("

Suggestions

\n"); out.push_back(GetRefreshHtml(base_url, is_refresh)); out.push_back(""); out.push_back(kHtmlFooter); return base::StrCat(out); } // Returns the HTML needed to display that no suggestions are available. std::string RenderOutputHtmlNoSuggestions(const std::string& base_url, bool is_refresh) { std::vector out; out.push_back(kHtmlHeader); out.push_back(kHtmlBody); out.push_back("

Suggestions

\n"); out.push_back("

You have no suggestions.

\n"); out.push_back(GetRefreshHtml(base_url, is_refresh)); out.push_back(kHtmlFooter); return base::StrCat(out); } } // 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 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 output; gfx::PNGCodec::EncodeBGRASkBitmap(*image.ToSkBitmap(), false, &output); std::string encoded_output; base::Base64Encode( base::StringPiece(reinterpret_cast(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