| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/search/suggestions/suggestions_source.h" | |
| 6 | |
| 7 #include <stddef.h> | |
| 8 #include <stdint.h> | |
| 9 | |
| 10 #include <vector> | |
| 11 | |
| 12 #include "base/barrier_closure.h" | |
| 13 #include "base/base64.h" | |
| 14 #include "base/bind.h" | |
| 15 #include "base/memory/ref_counted_memory.h" | |
| 16 #include "base/strings/string16.h" | |
| 17 #include "base/strings/string_number_conversions.h" | |
| 18 #include "base/strings/string_piece.h" | |
| 19 #include "base/strings/string_util.h" | |
| 20 #include "base/strings/utf_string_conversions.h" | |
| 21 #include "base/time/time.h" | |
| 22 #include "chrome/browser/profiles/profile.h" | |
| 23 #include "chrome/browser/search/suggestions/suggestions_service_factory.h" | |
| 24 #include "chrome/common/url_constants.h" | |
| 25 #include "components/suggestions/suggestions_service.h" | |
| 26 #include "net/base/escape.h" | |
| 27 #include "ui/base/l10n/time_format.h" | |
| 28 #include "ui/gfx/codec/png_codec.h" | |
| 29 #include "ui/gfx/image/image_skia.h" | |
| 30 #include "url/gurl.h" | |
| 31 | |
| 32 namespace suggestions { | |
| 33 | |
| 34 namespace { | |
| 35 | |
| 36 const char kHtmlHeader[] = | |
| 37 "<!DOCTYPE html>\n<html>\n<head>\n<title>Suggestions</title>\n" | |
| 38 "<meta charset=\"utf-8\">\n" | |
| 39 "<style type=\"text/css\">\nli {white-space: nowrap;}\n</style>\n"; | |
| 40 const char kHtmlBody[] = "</head>\n<body>\n"; | |
| 41 const char kHtmlFooter[] = "</body>\n</html>\n"; | |
| 42 | |
| 43 const char kRefreshPath[] = "refresh"; | |
| 44 | |
| 45 std::string GetRefreshHtml(bool is_refresh) { | |
| 46 if (is_refresh) | |
| 47 return "<p>Refreshing in the background, reload to see new data.</p>\n"; | |
| 48 return std::string("<p><a href=\"") + chrome::kChromeUISuggestionsURL + | |
| 49 kRefreshPath + "\">Refresh</a></p>\n"; | |
| 50 } | |
| 51 // Returns the HTML needed to display the suggestions. | |
| 52 std::string RenderOutputHtml( | |
| 53 bool is_refresh, | |
| 54 const SuggestionsProfile& profile, | |
| 55 const std::map<GURL, std::string>& base64_encoded_pngs) { | |
| 56 std::vector<std::string> out; | |
| 57 out.push_back(kHtmlHeader); | |
| 58 out.push_back(kHtmlBody); | |
| 59 out.push_back("<h1>Suggestions</h1>\n"); | |
| 60 out.push_back(GetRefreshHtml(is_refresh)); | |
| 61 out.push_back("<ul>"); | |
| 62 int64_t now = (base::Time::NowFromSystemTime() - base::Time::UnixEpoch()) | |
| 63 .ToInternalValue(); | |
| 64 size_t size = profile.suggestions_size(); | |
| 65 for (size_t i = 0; i < size; ++i) { | |
| 66 const ChromeSuggestion& suggestion = profile.suggestions(i); | |
| 67 base::TimeDelta remaining_time = base::TimeDelta::FromMicroseconds( | |
| 68 suggestion.expiry_ts() - now); | |
| 69 base::string16 remaining_time_formatted = ui::TimeFormat::Detailed( | |
| 70 ui::TimeFormat::Format::FORMAT_DURATION, | |
| 71 ui::TimeFormat::Length::LENGTH_LONG, | |
| 72 -1, remaining_time); | |
| 73 std::string line; | |
| 74 line += "<li><a href=\""; | |
| 75 line += net::EscapeForHTML(suggestion.url()); | |
| 76 line += "\" target=\"_blank\">"; | |
| 77 line += net::EscapeForHTML(suggestion.title()); | |
| 78 std::map<GURL, std::string>::const_iterator it = | |
| 79 base64_encoded_pngs.find(GURL(suggestion.url())); | |
| 80 if (it != base64_encoded_pngs.end()) { | |
| 81 line += "<br><img src='"; | |
| 82 line += it->second; | |
| 83 line += "'>"; | |
| 84 } | |
| 85 line += "</a> Expires in "; | |
| 86 line += base::UTF16ToUTF8(remaining_time_formatted); | |
| 87 std::vector<std::string> providers; | |
| 88 for (int p = 0; p < suggestion.providers_size(); ++p) | |
| 89 providers.push_back(base::IntToString(suggestion.providers(p))); | |
| 90 line += ". Provider IDs: " + base::JoinString(providers, ", "); | |
| 91 line += "</li>\n"; | |
| 92 out.push_back(line); | |
| 93 } | |
| 94 out.push_back("</ul>"); | |
| 95 out.push_back(kHtmlFooter); | |
| 96 return base::JoinString(out, base::StringPiece()); | |
| 97 } | |
| 98 | |
| 99 // Returns the HTML needed to display that no suggestions are available. | |
| 100 std::string RenderOutputHtmlNoSuggestions(bool is_refresh) { | |
| 101 std::vector<std::string> out; | |
| 102 out.push_back(kHtmlHeader); | |
| 103 out.push_back(kHtmlBody); | |
| 104 out.push_back("<h1>Suggestions</h1>\n"); | |
| 105 out.push_back("<p>You have no suggestions.</p>\n"); | |
| 106 out.push_back(GetRefreshHtml(is_refresh)); | |
| 107 out.push_back(kHtmlFooter); | |
| 108 return base::JoinString(out, base::StringPiece()); | |
| 109 } | |
| 110 | |
| 111 } // namespace | |
| 112 | |
| 113 SuggestionsSource::SuggestionsSource(Profile* profile) | |
| 114 : profile_(profile), weak_ptr_factory_(this) {} | |
| 115 | |
| 116 SuggestionsSource::~SuggestionsSource() {} | |
| 117 | |
| 118 SuggestionsSource::RequestContext::RequestContext( | |
| 119 bool is_refresh_in, | |
| 120 const SuggestionsProfile& suggestions_profile_in, | |
| 121 const content::URLDataSource::GotDataCallback& callback_in) | |
| 122 : is_refresh(is_refresh_in), | |
| 123 suggestions_profile(suggestions_profile_in), // Copy. | |
| 124 callback(callback_in) // Copy. | |
| 125 {} | |
| 126 | |
| 127 SuggestionsSource::RequestContext::~RequestContext() {} | |
| 128 | |
| 129 std::string SuggestionsSource::GetSource() const { | |
| 130 return chrome::kChromeUISuggestionsHost; | |
| 131 } | |
| 132 | |
| 133 void SuggestionsSource::StartDataRequest( | |
| 134 const std::string& path, int render_process_id, int render_frame_id, | |
| 135 const content::URLDataSource::GotDataCallback& callback) { | |
| 136 // If this was called as "chrome://suggestions/refresh", we also trigger an | |
| 137 // async update of the suggestions. | |
| 138 bool is_refresh = (path == kRefreshPath); | |
| 139 | |
| 140 SuggestionsService* suggestions_service = | |
| 141 SuggestionsServiceFactory::GetForProfile(profile_); | |
| 142 | |
| 143 // |suggestions_service| is null for guest profiles. | |
| 144 if (!suggestions_service) { | |
| 145 std::string output = RenderOutputHtmlNoSuggestions(is_refresh); | |
| 146 callback.Run(base::RefCountedString::TakeString(&output)); | |
| 147 return; | |
| 148 } | |
| 149 | |
| 150 if (is_refresh) | |
| 151 suggestions_service->FetchSuggestionsData(); | |
| 152 | |
| 153 SuggestionsProfile suggestions_profile = | |
| 154 suggestions_service->GetSuggestionsDataFromCache(); | |
| 155 size_t size = suggestions_profile.suggestions_size(); | |
| 156 if (!size) { | |
| 157 std::string output = RenderOutputHtmlNoSuggestions(is_refresh); | |
| 158 callback.Run(base::RefCountedString::TakeString(&output)); | |
| 159 } else { | |
| 160 RequestContext* context = | |
| 161 new RequestContext(is_refresh, suggestions_profile, callback); | |
| 162 base::Closure barrier = BarrierClosure( | |
| 163 size, base::Bind(&SuggestionsSource::OnThumbnailsFetched, | |
| 164 weak_ptr_factory_.GetWeakPtr(), context)); | |
| 165 for (size_t i = 0; i < size; ++i) { | |
| 166 const ChromeSuggestion& suggestion = suggestions_profile.suggestions(i); | |
| 167 // Fetch the thumbnail for this URL (exercising the fetcher). After all | |
| 168 // fetches are done, including NULL callbacks for unavailable thumbnails, | |
| 169 // SuggestionsSource::OnThumbnailsFetched will be called. | |
| 170 SuggestionsService* suggestions_service( | |
| 171 SuggestionsServiceFactory::GetForProfile(profile_)); | |
| 172 suggestions_service->GetPageThumbnail( | |
| 173 GURL(suggestion.url()), | |
| 174 base::Bind(&SuggestionsSource::OnThumbnailAvailable, | |
| 175 weak_ptr_factory_.GetWeakPtr(), context, barrier)); | |
| 176 } | |
| 177 } | |
| 178 } | |
| 179 | |
| 180 std::string SuggestionsSource::GetMimeType(const std::string& path) const { | |
| 181 return "text/html"; | |
| 182 } | |
| 183 | |
| 184 base::MessageLoop* SuggestionsSource::MessageLoopForRequestPath( | |
| 185 const std::string& path) const { | |
| 186 // This can be accessed from the IO thread. | |
| 187 return content::URLDataSource::MessageLoopForRequestPath(path); | |
| 188 } | |
| 189 | |
| 190 void SuggestionsSource::OnThumbnailsFetched(RequestContext* context) { | |
| 191 std::unique_ptr<RequestContext> context_deleter(context); | |
| 192 | |
| 193 std::string output = | |
| 194 RenderOutputHtml(context->is_refresh, context->suggestions_profile, | |
| 195 context->base64_encoded_pngs); | |
| 196 context->callback.Run(base::RefCountedString::TakeString(&output)); | |
| 197 } | |
| 198 | |
| 199 void SuggestionsSource::OnThumbnailAvailable(RequestContext* context, | |
| 200 base::Closure barrier, | |
| 201 const GURL& url, | |
| 202 const SkBitmap* bitmap) { | |
| 203 if (bitmap) { | |
| 204 std::vector<unsigned char> output; | |
| 205 gfx::PNGCodec::EncodeBGRASkBitmap(*bitmap, false, &output); | |
| 206 | |
| 207 std::string encoded_output; | |
| 208 base::Base64Encode( | |
| 209 base::StringPiece(reinterpret_cast<const char*>(output.data()), | |
| 210 output.size()), | |
| 211 &encoded_output); | |
| 212 context->base64_encoded_pngs[url] = "data:image/png;base64,"; | |
| 213 context->base64_encoded_pngs[url] += encoded_output; | |
| 214 } | |
| 215 barrier.Run(); | |
| 216 } | |
| 217 | |
| 218 } // namespace suggestions | |
| OLD | NEW |