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
HistoryUIHTMLSource()53 HistoryUIHTMLSource::HistoryUIHTMLSource()
54 : DataSource(chrome::kChromeUIHistoryHost, MessageLoop::current()) {
55 }
56
StartDataRequest(const std::string & path,bool is_incognito,int request_id)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
GetMimeType(const std::string &) const111 std::string HistoryUIHTMLSource::GetMimeType(const std::string&) const {
112 return "text/html";
113 }
114
115 ////////////////////////////////////////////////////////////////////////////////
116 //
117 // HistoryHandler
118 //
119 ////////////////////////////////////////////////////////////////////////////////
BrowsingHistoryHandler()120 BrowsingHistoryHandler::BrowsingHistoryHandler()
121 : search_text_() {
122 }
123
~BrowsingHistoryHandler()124 BrowsingHistoryHandler::~BrowsingHistoryHandler() {
125 cancelable_search_consumer_.CancelAllRequests();
126 cancelable_delete_consumer_.CancelAllRequests();
127 }
128
Attach(WebUI * web_ui)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
RegisterMessages()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
HandleGetHistory(const ListValue * args)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
HandleSearchHistory(const ListValue * args)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
HandleRemoveURLsOnOneDay(const ListValue * args)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
HandleClearBrowsingData(const ListValue * args)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
QueryComplete(HistoryService::Handle request_handle,history::QueryResults * results)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
RemoveComplete()298 void BrowsingHistoryHandler::RemoveComplete() {
299 // Some Visits were deleted from history. Reload the list.
300 web_ui_->CallJavascriptFunction("deleteComplete");
301 }
302
ExtractSearchHistoryArguments(const ListValue * args,int * month,string16 * query)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
CreateMonthQueryOptions(int month)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
HistoryUI(TabContents * contents)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
GetHistoryURLWithSearchText(const string16 & text)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
GetFaviconResourceBytes()382 RefCountedMemory* HistoryUI::GetFaviconResourceBytes() {
383 return ResourceBundle::GetSharedInstance().
384 LoadDataResourceBytes(IDR_HISTORY_FAVICON);
385 }
386