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/history2_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
53HistoryUIHTMLSource2::HistoryUIHTMLSource2()
54    : DataSource(chrome::kChromeUIHistory2Host, MessageLoop::current()) {
55}
56
57void HistoryUIHTMLSource2::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_HISTORY2_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 HistoryUIHTMLSource2::GetMimeType(const std::string&) const {
112  return "text/html";
113}
114
115////////////////////////////////////////////////////////////////////////////////
116//
117// HistoryHandler
118//
119////////////////////////////////////////////////////////////////////////////////
120BrowsingHistoryHandler2::BrowsingHistoryHandler2()
121    : search_text_() {
122}
123
124BrowsingHistoryHandler2::~BrowsingHistoryHandler2() {
125  cancelable_search_consumer_.CancelAllRequests();
126  cancelable_delete_consumer_.CancelAllRequests();
127}
128
129WebUIMessageHandler* BrowsingHistoryHandler2::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 BrowsingHistoryHandler2::RegisterMessages() {
139  web_ui_->RegisterMessageCallback("getHistory",
140      NewCallback(this, &BrowsingHistoryHandler2::HandleGetHistory));
141  web_ui_->RegisterMessageCallback("searchHistory",
142      NewCallback(this, &BrowsingHistoryHandler2::HandleSearchHistory));
143  web_ui_->RegisterMessageCallback("removeURLsOnOneDay",
144      NewCallback(this, &BrowsingHistoryHandler2::HandleRemoveURLsOnOneDay));
145  web_ui_->RegisterMessageCallback("clearBrowsingData",
146      NewCallback(this, &BrowsingHistoryHandler2::HandleClearBrowsingData));
147}
148
149void BrowsingHistoryHandler2::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, &BrowsingHistoryHandler2::QueryComplete));
173}
174
175void BrowsingHistoryHandler2::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, &BrowsingHistoryHandler2::QueryComplete));
198}
199
200void BrowsingHistoryHandler2::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, &BrowsingHistoryHandler2::RemoveComplete));
234}
235
236void BrowsingHistoryHandler2::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 BrowsingHistoryHandler2::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 BrowsingHistoryHandler2::RemoveComplete() {
299  // Some Visits were deleted from history. Reload the list.
300  web_ui_->CallJavascriptFunction("deleteComplete");
301}
302
303void BrowsingHistoryHandler2::ExtractSearchHistoryArguments(
304    const ListValue* args,
305    int* month,
306    string16* query) {
307  *month = 0;
308  Value* list_member;
309
310  // Get search string.
311  if (args->Get(0, &list_member) &&
312      list_member->GetType() == Value::TYPE_STRING) {
313    const StringValue* string_value =
314      static_cast<const StringValue*>(list_member);
315    string_value->GetAsString(query);
316  }
317
318  // Get search month.
319  if (args->Get(1, &list_member) &&
320      list_member->GetType() == Value::TYPE_STRING) {
321    const StringValue* string_value =
322      static_cast<const StringValue*>(list_member);
323    string16 string16_value;
324    string_value->GetAsString(&string16_value);
325    base::StringToInt(string16_value, month);
326  }
327}
328
329history::QueryOptions BrowsingHistoryHandler2::CreateMonthQueryOptions(
330    int month) {
331  history::QueryOptions options;
332
333  // Configure the begin point of the search to the start of the
334  // current month.
335  base::Time::Exploded exploded;
336  base::Time::Now().LocalMidnight().LocalExplode(&exploded);
337  exploded.day_of_month = 1;
338
339  if (month == 0) {
340    options.begin_time = base::Time::FromLocalExploded(exploded);
341
342    // Set the end time of this first search to null (which will
343    // show results from the future, should the user's clock have
344    // been set incorrectly).
345    options.end_time = base::Time();
346  } else {
347    // Set the end-time of this search to the end of the month that is
348    // |depth| months before the search end point. The end time is not
349    // inclusive, so we should feel free to set it to midnight on the
350    // first day of the following month.
351    exploded.month -= month - 1;
352    while (exploded.month < 1) {
353      exploded.month += 12;
354      exploded.year--;
355    }
356    options.end_time = base::Time::FromLocalExploded(exploded);
357
358    // Set the begin-time of the search to the start of the month
359    // that is |depth| months prior to search_start_.
360    if (exploded.month > 1) {
361      exploded.month--;
362    } else {
363      exploded.month = 12;
364      exploded.year--;
365    }
366    options.begin_time = base::Time::FromLocalExploded(exploded);
367  }
368
369  return options;
370}
371
372////////////////////////////////////////////////////////////////////////////////
373//
374// HistoryUIContents
375//
376////////////////////////////////////////////////////////////////////////////////
377
378HistoryUI2::HistoryUI2(TabContents* contents) : WebUI(contents) {
379  AddMessageHandler((new BrowsingHistoryHandler2())->Attach(this));
380
381  HistoryUIHTMLSource2* html_source = new HistoryUIHTMLSource2();
382
383  // Set up the chrome://history2/ source.
384  contents->profile()->GetChromeURLDataManager()->AddDataSource(html_source);
385}
386
387// static
388const GURL HistoryUI2::GetHistoryURLWithSearchText(const string16& text) {
389  return GURL(std::string(chrome::kChromeUIHistory2URL) + "#q=" +
390              EscapeQueryParamValue(UTF16ToUTF8(text), true));
391}
392
393// static
394RefCountedMemory* HistoryUI2::GetFaviconResourceBytes() {
395  return ResourceBundle::GetSharedInstance().
396      LoadDataResourceBytes(IDR_HISTORY_FAVICON);
397}
398