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