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. 45static const int kMaxSearchResults = 100; 46 47//////////////////////////////////////////////////////////////////////////////// 48// 49// HistoryHTMLSource 50// 51//////////////////////////////////////////////////////////////////////////////// 52 53HistoryUIHTMLSource::HistoryUIHTMLSource() 54 : DataSource(chrome::kChromeUIHistoryHost, MessageLoop::current()) { 55} 56 57void 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 111std::string HistoryUIHTMLSource::GetMimeType(const std::string&) const { 112 return "text/html"; 113} 114 115//////////////////////////////////////////////////////////////////////////////// 116// 117// HistoryHandler 118// 119//////////////////////////////////////////////////////////////////////////////// 120BrowsingHistoryHandler::BrowsingHistoryHandler() 121 : search_text_() { 122} 123 124BrowsingHistoryHandler::~BrowsingHistoryHandler() { 125 cancelable_search_consumer_.CancelAllRequests(); 126 cancelable_delete_consumer_.CancelAllRequests(); 127} 128 129WebUIMessageHandler* 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 138void 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 149void 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 175void 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 200void 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 236void 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 244void 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 298void BrowsingHistoryHandler::RemoveComplete() { 299 // Some Visits were deleted from history. Reload the list. 300 web_ui_->CallJavascriptFunction("deleteComplete"); 301} 302 303void 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 317history::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 366HistoryUI::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 376const GURL HistoryUI::GetHistoryURLWithSearchText(const string16& text) { 377 return GURL(std::string(chrome::kChromeUIHistoryURL) + "#q=" + 378 EscapeQueryParamValue(UTF16ToUTF8(text), true)); 379} 380 381// static 382RefCountedMemory* HistoryUI::GetFaviconResourceBytes() { 383 return ResourceBundle::GetSharedInstance(). 384 LoadDataResourceBytes(IDR_HISTORY_FAVICON); 385} 386