1 // Copyright (c) 2011 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/ui/webui/history_ui.h" 6 7 #include <algorithm> 8 #include <set> 9 10 #include "base/callback.h" 11 #include "base/i18n/time_formatting.h" 12 #include "base/memory/singleton.h" 13 #include "base/message_loop.h" 14 #include "base/string16.h" 15 #include "base/string_number_conversions.h" 16 #include "base/string_piece.h" 17 #include "base/threading/thread.h" 18 #include "base/time.h" 19 #include "base/utf_string_conversions.h" 20 #include "base/values.h" 21 #include "chrome/browser/bookmarks/bookmark_model.h" 22 #include "chrome/browser/history/history_types.h" 23 #include "chrome/browser/metrics/user_metrics.h" 24 #include "chrome/browser/profiles/profile.h" 25 #include "chrome/browser/ui/browser.h" 26 #include "chrome/browser/ui/browser_list.h" 27 #include "chrome/browser/ui/webui/favicon_source.h" 28 #include "chrome/common/jstemplate_builder.h" 29 #include "chrome/common/time_format.h" 30 #include "chrome/common/url_constants.h" 31 #include "content/browser/browser_thread.h" 32 #include "content/browser/tab_contents/tab_contents.h" 33 #include "content/browser/tab_contents/tab_contents_delegate.h" 34 #include "grit/browser_resources.h" 35 #include "grit/chromium_strings.h" 36 #include "grit/generated_resources.h" 37 #include "grit/locale_settings.h" 38 #include "grit/theme_resources.h" 39 #include "net/base/escape.h" 40 #include "ui/base/l10n/l10n_util.h" 41 #include "ui/base/resource/resource_bundle.h" 42 43 // Maximum number of search results to return in a given search. We should 44 // eventually remove this. 45 static const int kMaxSearchResults = 100; 46 47 //////////////////////////////////////////////////////////////////////////////// 48 // 49 // HistoryHTMLSource 50 // 51 //////////////////////////////////////////////////////////////////////////////// 52 53 HistoryUIHTMLSource::HistoryUIHTMLSource() 54 : DataSource(chrome::kChromeUIHistoryHost, MessageLoop::current()) { 55 } 56 57 void HistoryUIHTMLSource::StartDataRequest(const std::string& path, 58 bool is_incognito, 59 int request_id) { 60 DictionaryValue localized_strings; 61 localized_strings.SetString("loading", 62 l10n_util::GetStringUTF16(IDS_HISTORY_LOADING)); 63 localized_strings.SetString("title", 64 l10n_util::GetStringUTF16(IDS_HISTORY_TITLE)); 65 localized_strings.SetString("loading", 66 l10n_util::GetStringUTF16(IDS_HISTORY_LOADING)); 67 localized_strings.SetString("newest", 68 l10n_util::GetStringUTF16(IDS_HISTORY_NEWEST)); 69 localized_strings.SetString("newer", 70 l10n_util::GetStringUTF16(IDS_HISTORY_NEWER)); 71 localized_strings.SetString("older", 72 l10n_util::GetStringUTF16(IDS_HISTORY_OLDER)); 73 localized_strings.SetString("searchresultsfor", 74 l10n_util::GetStringUTF16(IDS_HISTORY_SEARCHRESULTSFOR)); 75 localized_strings.SetString("history", 76 l10n_util::GetStringUTF16(IDS_HISTORY_BROWSERESULTS)); 77 localized_strings.SetString("cont", 78 l10n_util::GetStringUTF16(IDS_HISTORY_CONTINUED)); 79 localized_strings.SetString("searchbutton", 80 l10n_util::GetStringUTF16(IDS_HISTORY_SEARCH_BUTTON)); 81 localized_strings.SetString("noresults", 82 l10n_util::GetStringUTF16(IDS_HISTORY_NO_RESULTS)); 83 localized_strings.SetString("noitems", 84 l10n_util::GetStringUTF16(IDS_HISTORY_NO_ITEMS)); 85 localized_strings.SetString("edithistory", 86 l10n_util::GetStringUTF16(IDS_HISTORY_START_EDITING_HISTORY)); 87 localized_strings.SetString("doneediting", 88 l10n_util::GetStringUTF16(IDS_HISTORY_STOP_EDITING_HISTORY)); 89 localized_strings.SetString("removeselected", 90 l10n_util::GetStringUTF16(IDS_HISTORY_REMOVE_SELECTED_ITEMS)); 91 localized_strings.SetString("clearallhistory", 92 l10n_util::GetStringUTF16(IDS_HISTORY_OPEN_CLEAR_BROWSING_DATA_DIALOG)); 93 localized_strings.SetString("deletewarning", 94 l10n_util::GetStringUTF16(IDS_HISTORY_DELETE_PRIOR_VISITS_WARNING)); 95 96 SetFontAndTextDirection(&localized_strings); 97 98 static const base::StringPiece history_html( 99 ResourceBundle::GetSharedInstance().GetRawDataResource( 100 IDR_HISTORY_HTML)); 101 const std::string full_html = jstemplate_builder::GetI18nTemplateHtml( 102 history_html, &localized_strings); 103 104 scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes); 105 html_bytes->data.resize(full_html.size()); 106 std::copy(full_html.begin(), full_html.end(), html_bytes->data.begin()); 107 108 SendResponse(request_id, html_bytes); 109 } 110 111 std::string HistoryUIHTMLSource::GetMimeType(const std::string&) const { 112 return "text/html"; 113 } 114 115 //////////////////////////////////////////////////////////////////////////////// 116 // 117 // HistoryHandler 118 // 119 //////////////////////////////////////////////////////////////////////////////// 120 BrowsingHistoryHandler::BrowsingHistoryHandler() 121 : search_text_() { 122 } 123 124 BrowsingHistoryHandler::~BrowsingHistoryHandler() { 125 cancelable_search_consumer_.CancelAllRequests(); 126 cancelable_delete_consumer_.CancelAllRequests(); 127 } 128 129 WebUIMessageHandler* BrowsingHistoryHandler::Attach(WebUI* web_ui) { 130 // Create our favicon data source. 131 Profile* profile = web_ui->GetProfile(); 132 profile->GetChromeURLDataManager()->AddDataSource( 133 new FaviconSource(profile)); 134 135 return WebUIMessageHandler::Attach(web_ui); 136 } 137 138 void BrowsingHistoryHandler::RegisterMessages() { 139 web_ui_->RegisterMessageCallback("getHistory", 140 NewCallback(this, &BrowsingHistoryHandler::HandleGetHistory)); 141 web_ui_->RegisterMessageCallback("searchHistory", 142 NewCallback(this, &BrowsingHistoryHandler::HandleSearchHistory)); 143 web_ui_->RegisterMessageCallback("removeURLsOnOneDay", 144 NewCallback(this, &BrowsingHistoryHandler::HandleRemoveURLsOnOneDay)); 145 web_ui_->RegisterMessageCallback("clearBrowsingData", 146 NewCallback(this, &BrowsingHistoryHandler::HandleClearBrowsingData)); 147 } 148 149 void BrowsingHistoryHandler::HandleGetHistory(const ListValue* args) { 150 // Anything in-flight is invalid. 151 cancelable_search_consumer_.CancelAllRequests(); 152 153 // Get arguments (if any). 154 int day = 0; 155 ExtractIntegerValue(args, &day); 156 157 // Set our query options. 158 history::QueryOptions options; 159 options.begin_time = base::Time::Now().LocalMidnight(); 160 options.begin_time -= base::TimeDelta::FromDays(day); 161 options.end_time = base::Time::Now().LocalMidnight(); 162 options.end_time -= base::TimeDelta::FromDays(day - 1); 163 164 // Need to remember the query string for our results. 165 search_text_ = string16(); 166 167 HistoryService* hs = 168 web_ui_->GetProfile()->GetHistoryService(Profile::EXPLICIT_ACCESS); 169 hs->QueryHistory(search_text_, 170 options, 171 &cancelable_search_consumer_, 172 NewCallback(this, &BrowsingHistoryHandler::QueryComplete)); 173 } 174 175 void BrowsingHistoryHandler::HandleSearchHistory(const ListValue* args) { 176 // Anything in-flight is invalid. 177 cancelable_search_consumer_.CancelAllRequests(); 178 179 // Get arguments (if any). 180 int month = 0; 181 string16 query; 182 ExtractSearchHistoryArguments(args, &month, &query); 183 184 // Set the query ranges for the given month. 185 history::QueryOptions options = CreateMonthQueryOptions(month); 186 187 // When searching, limit the number of results returned. 188 options.max_count = kMaxSearchResults; 189 190 // Need to remember the query string for our results. 191 search_text_ = query; 192 HistoryService* hs = 193 web_ui_->GetProfile()->GetHistoryService(Profile::EXPLICIT_ACCESS); 194 hs->QueryHistory(search_text_, 195 options, 196 &cancelable_search_consumer_, 197 NewCallback(this, &BrowsingHistoryHandler::QueryComplete)); 198 } 199 200 void BrowsingHistoryHandler::HandleRemoveURLsOnOneDay(const ListValue* args) { 201 if (cancelable_delete_consumer_.HasPendingRequests()) { 202 web_ui_->CallJavascriptFunction("deleteFailed"); 203 return; 204 } 205 206 // Get day to delete data from. 207 int visit_time = 0; 208 ExtractIntegerValue(args, &visit_time); 209 base::Time::Exploded exploded; 210 base::Time::FromTimeT( 211 static_cast<time_t>(visit_time)).LocalExplode(&exploded); 212 exploded.hour = exploded.minute = exploded.second = exploded.millisecond = 0; 213 base::Time begin_time = base::Time::FromLocalExploded(exploded); 214 base::Time end_time = begin_time + base::TimeDelta::FromDays(1); 215 216 // Get URLs. 217 std::set<GURL> urls; 218 for (ListValue::const_iterator v = args->begin() + 1; 219 v != args->end(); ++v) { 220 if ((*v)->GetType() != Value::TYPE_STRING) 221 continue; 222 const StringValue* string_value = static_cast<const StringValue*>(*v); 223 string16 string16_value; 224 if (!string_value->GetAsString(&string16_value)) 225 continue; 226 urls.insert(GURL(string16_value)); 227 } 228 229 HistoryService* hs = 230 web_ui_->GetProfile()->GetHistoryService(Profile::EXPLICIT_ACCESS); 231 hs->ExpireHistoryBetween( 232 urls, begin_time, end_time, &cancelable_delete_consumer_, 233 NewCallback(this, &BrowsingHistoryHandler::RemoveComplete)); 234 } 235 236 void BrowsingHistoryHandler::HandleClearBrowsingData(const ListValue* args) { 237 // TODO(beng): This is an improper direct dependency on Browser. Route this 238 // through some sort of delegate. 239 Browser* browser = BrowserList::FindBrowserWithProfile(web_ui_->GetProfile()); 240 if (browser) 241 browser->OpenClearBrowsingDataDialog(); 242 } 243 244 void BrowsingHistoryHandler::QueryComplete( 245 HistoryService::Handle request_handle, 246 history::QueryResults* results) { 247 248 ListValue results_value; 249 base::Time midnight_today = base::Time::Now().LocalMidnight(); 250 251 for (size_t i = 0; i < results->size(); ++i) { 252 history::URLResult const &page = (*results)[i]; 253 DictionaryValue* page_value = new DictionaryValue(); 254 SetURLAndTitle(page_value, page.title(), page.url()); 255 256 // Need to pass the time in epoch time (fastest JS conversion). 257 page_value->SetInteger("time", 258 static_cast<int>(page.visit_time().ToTimeT())); 259 260 // Until we get some JS i18n infrastructure, we also need to 261 // pass the dates in as strings. This could use some 262 // optimization. 263 264 // Only pass in the strings we need (search results need a shortdate 265 // and snippet, browse results need day and time information). 266 if (search_text_.empty()) { 267 // Figure out the relative date string. 268 string16 date_str = TimeFormat::RelativeDate(page.visit_time(), 269 &midnight_today); 270 if (date_str.empty()) { 271 date_str = base::TimeFormatFriendlyDate(page.visit_time()); 272 } else { 273 date_str = l10n_util::GetStringFUTF16( 274 IDS_HISTORY_DATE_WITH_RELATIVE_TIME, 275 date_str, 276 base::TimeFormatFriendlyDate(page.visit_time())); 277 } 278 page_value->SetString("dateRelativeDay", date_str); 279 page_value->SetString("dateTimeOfDay", 280 base::TimeFormatTimeOfDay(page.visit_time())); 281 } else { 282 page_value->SetString("dateShort", 283 base::TimeFormatShortDate(page.visit_time())); 284 page_value->SetString("snippet", page.snippet().text()); 285 } 286 page_value->SetBoolean("starred", 287 web_ui_->GetProfile()->GetBookmarkModel()->IsBookmarked(page.url())); 288 results_value.Append(page_value); 289 } 290 291 DictionaryValue info_value; 292 info_value.SetString("term", search_text_); 293 info_value.SetBoolean("finished", results->reached_beginning()); 294 295 web_ui_->CallJavascriptFunction("historyResult", info_value, results_value); 296 } 297 298 void BrowsingHistoryHandler::RemoveComplete() { 299 // Some Visits were deleted from history. Reload the list. 300 web_ui_->CallJavascriptFunction("deleteComplete"); 301 } 302 303 void BrowsingHistoryHandler::ExtractSearchHistoryArguments( 304 const ListValue* args, 305 int* month, 306 string16* query) { 307 CHECK(args->GetSize() == 2); 308 query->clear(); 309 CHECK(args->GetString(0, query)); 310 311 string16 string16_value; 312 CHECK(args->GetString(1, &string16_value)); 313 *month = 0; 314 base::StringToInt(string16_value, month); 315 } 316 317 history::QueryOptions BrowsingHistoryHandler::CreateMonthQueryOptions( 318 int month) { 319 history::QueryOptions options; 320 321 // Configure the begin point of the search to the start of the 322 // current month. 323 base::Time::Exploded exploded; 324 base::Time::Now().LocalMidnight().LocalExplode(&exploded); 325 exploded.day_of_month = 1; 326 327 if (month == 0) { 328 options.begin_time = base::Time::FromLocalExploded(exploded); 329 330 // Set the end time of this first search to null (which will 331 // show results from the future, should the user's clock have 332 // been set incorrectly). 333 options.end_time = base::Time(); 334 } else { 335 // Set the end-time of this search to the end of the month that is 336 // |depth| months before the search end point. The end time is not 337 // inclusive, so we should feel free to set it to midnight on the 338 // first day of the following month. 339 exploded.month -= month - 1; 340 while (exploded.month < 1) { 341 exploded.month += 12; 342 exploded.year--; 343 } 344 options.end_time = base::Time::FromLocalExploded(exploded); 345 346 // Set the begin-time of the search to the start of the month 347 // that is |depth| months prior to search_start_. 348 if (exploded.month > 1) { 349 exploded.month--; 350 } else { 351 exploded.month = 12; 352 exploded.year--; 353 } 354 options.begin_time = base::Time::FromLocalExploded(exploded); 355 } 356 357 return options; 358 } 359 360 //////////////////////////////////////////////////////////////////////////////// 361 // 362 // HistoryUIContents 363 // 364 //////////////////////////////////////////////////////////////////////////////// 365 366 HistoryUI::HistoryUI(TabContents* contents) : WebUI(contents) { 367 AddMessageHandler((new BrowsingHistoryHandler())->Attach(this)); 368 369 HistoryUIHTMLSource* html_source = new HistoryUIHTMLSource(); 370 371 // Set up the chrome://history/ source. 372 contents->profile()->GetChromeURLDataManager()->AddDataSource(html_source); 373 } 374 375 // static 376 const GURL HistoryUI::GetHistoryURLWithSearchText(const string16& text) { 377 return GURL(std::string(chrome::kChromeUIHistoryURL) + "#q=" + 378 EscapeQueryParamValue(UTF16ToUTF8(text), true)); 379 } 380 381 // static 382 RefCountedMemory* HistoryUI::GetFaviconResourceBytes() { 383 return ResourceBundle::GetSharedInstance(). 384 LoadDataResourceBytes(IDR_HISTORY_FAVICON); 385 } 386