// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/webui/history_ui.h"
#include <algorithm>
#include <set>
#include "base/callback.h"
#include "base/i18n/time_formatting.h"
#include "base/memory/singleton.h"
#include "base/message_loop.h"
#include "base/string16.h"
#include "base/string_number_conversions.h"
#include "base/string_piece.h"
#include "base/threading/thread.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/history/history_types.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/webui/favicon_source.h"
#include "chrome/common/jstemplate_builder.h"
#include "chrome/common/time_format.h"
#include "chrome/common/url_constants.h"
#include "content/browser/browser_thread.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/browser/tab_contents/tab_contents_delegate.h"
#include "grit/browser_resources.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "grit/theme_resources.h"
#include "net/base/escape.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
// Maximum number of search results to return in a given search. We should
// eventually remove this.
static const int kMaxSearchResults = 100;
////////////////////////////////////////////////////////////////////////////////
//
// HistoryHTMLSource
//
////////////////////////////////////////////////////////////////////////////////
HistoryUIHTMLSource::HistoryUIHTMLSource()
: DataSource(chrome::kChromeUIHistoryHost, MessageLoop::current()) {
}
void HistoryUIHTMLSource::StartDataRequest(const std::string& path,
bool is_incognito,
int request_id) {
DictionaryValue localized_strings;
localized_strings.SetString("loading",
l10n_util::GetStringUTF16(IDS_HISTORY_LOADING));
localized_strings.SetString("title",
l10n_util::GetStringUTF16(IDS_HISTORY_TITLE));
localized_strings.SetString("loading",
l10n_util::GetStringUTF16(IDS_HISTORY_LOADING));
localized_strings.SetString("newest",
l10n_util::GetStringUTF16(IDS_HISTORY_NEWEST));
localized_strings.SetString("newer",
l10n_util::GetStringUTF16(IDS_HISTORY_NEWER));
localized_strings.SetString("older",
l10n_util::GetStringUTF16(IDS_HISTORY_OLDER));
localized_strings.SetString("searchresultsfor",
l10n_util::GetStringUTF16(IDS_HISTORY_SEARCHRESULTSFOR));
localized_strings.SetString("history",
l10n_util::GetStringUTF16(IDS_HISTORY_BROWSERESULTS));
localized_strings.SetString("cont",
l10n_util::GetStringUTF16(IDS_HISTORY_CONTINUED));
localized_strings.SetString("searchbutton",
l10n_util::GetStringUTF16(IDS_HISTORY_SEARCH_BUTTON));
localized_strings.SetString("noresults",
l10n_util::GetStringUTF16(IDS_HISTORY_NO_RESULTS));
localized_strings.SetString("noitems",
l10n_util::GetStringUTF16(IDS_HISTORY_NO_ITEMS));
localized_strings.SetString("edithistory",
l10n_util::GetStringUTF16(IDS_HISTORY_START_EDITING_HISTORY));
localized_strings.SetString("doneediting",
l10n_util::GetStringUTF16(IDS_HISTORY_STOP_EDITING_HISTORY));
localized_strings.SetString("removeselected",
l10n_util::GetStringUTF16(IDS_HISTORY_REMOVE_SELECTED_ITEMS));
localized_strings.SetString("clearallhistory",
l10n_util::GetStringUTF16(IDS_HISTORY_OPEN_CLEAR_BROWSING_DATA_DIALOG));
localized_strings.SetString("deletewarning",
l10n_util::GetStringUTF16(IDS_HISTORY_DELETE_PRIOR_VISITS_WARNING));
SetFontAndTextDirection(&localized_strings);
static const base::StringPiece history_html(
ResourceBundle::GetSharedInstance().GetRawDataResource(
IDR_HISTORY_HTML));
const std::string full_html = jstemplate_builder::GetI18nTemplateHtml(
history_html, &localized_strings);
scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes);
html_bytes->data.resize(full_html.size());
std::copy(full_html.begin(), full_html.end(), html_bytes->data.begin());
SendResponse(request_id, html_bytes);
}
std::string HistoryUIHTMLSource::GetMimeType(const std::string&) const {
return "text/html";
}
////////////////////////////////////////////////////////////////////////////////
//
// HistoryHandler
//
////////////////////////////////////////////////////////////////////////////////
BrowsingHistoryHandler::BrowsingHistoryHandler()
: search_text_() {
}
BrowsingHistoryHandler::~BrowsingHistoryHandler() {
cancelable_search_consumer_.CancelAllRequests();
cancelable_delete_consumer_.CancelAllRequests();
}
WebUIMessageHandler* BrowsingHistoryHandler::Attach(WebUI* web_ui) {
// Create our favicon data source.
Profile* profile = web_ui->GetProfile();
profile->GetChromeURLDataManager()->AddDataSource(
new FaviconSource(profile));
return WebUIMessageHandler::Attach(web_ui);
}
void BrowsingHistoryHandler::RegisterMessages() {
web_ui_->RegisterMessageCallback("getHistory",
NewCallback(this, &BrowsingHistoryHandler::HandleGetHistory));
web_ui_->RegisterMessageCallback("searchHistory",
NewCallback(this, &BrowsingHistoryHandler::HandleSearchHistory));
web_ui_->RegisterMessageCallback("removeURLsOnOneDay",
NewCallback(this, &BrowsingHistoryHandler::HandleRemoveURLsOnOneDay));
web_ui_->RegisterMessageCallback("clearBrowsingData",
NewCallback(this, &BrowsingHistoryHandler::HandleClearBrowsingData));
}
void BrowsingHistoryHandler::HandleGetHistory(const ListValue* args) {
// Anything in-flight is invalid.
cancelable_search_consumer_.CancelAllRequests();
// Get arguments (if any).
int day = 0;
ExtractIntegerValue(args, &day);
// Set our query options.
history::QueryOptions options;
options.begin_time = base::Time::Now().LocalMidnight();
options.begin_time -= base::TimeDelta::FromDays(day);
options.end_time = base::Time::Now().LocalMidnight();
options.end_time -= base::TimeDelta::FromDays(day - 1);
// Need to remember the query string for our results.
search_text_ = string16();
HistoryService* hs =
web_ui_->GetProfile()->GetHistoryService(Profile::EXPLICIT_ACCESS);
hs->QueryHistory(search_text_,
options,
&cancelable_search_consumer_,
NewCallback(this, &BrowsingHistoryHandler::QueryComplete));
}
void BrowsingHistoryHandler::HandleSearchHistory(const ListValue* args) {
// Anything in-flight is invalid.
cancelable_search_consumer_.CancelAllRequests();
// Get arguments (if any).
int month = 0;
string16 query;
ExtractSearchHistoryArguments(args, &month, &query);
// Set the query ranges for the given month.
history::QueryOptions options = CreateMonthQueryOptions(month);
// When searching, limit the number of results returned.
options.max_count = kMaxSearchResults;
// Need to remember the query string for our results.
search_text_ = query;
HistoryService* hs =
web_ui_->GetProfile()->GetHistoryService(Profile::EXPLICIT_ACCESS);
hs->QueryHistory(search_text_,
options,
&cancelable_search_consumer_,
NewCallback(this, &BrowsingHistoryHandler::QueryComplete));
}
void BrowsingHistoryHandler::HandleRemoveURLsOnOneDay(const ListValue* args) {
if (cancelable_delete_consumer_.HasPendingRequests()) {
web_ui_->CallJavascriptFunction("deleteFailed");
return;
}
// Get day to delete data from.
int visit_time = 0;
ExtractIntegerValue(args, &visit_time);
base::Time::Exploded exploded;
base::Time::FromTimeT(
static_cast<time_t>(visit_time)).LocalExplode(&exploded);
exploded.hour = exploded.minute = exploded.second = exploded.millisecond = 0;
base::Time begin_time = base::Time::FromLocalExploded(exploded);
base::Time end_time = begin_time + base::TimeDelta::FromDays(1);
// Get URLs.
std::set<GURL> urls;
for (ListValue::const_iterator v = args->begin() + 1;
v != args->end(); ++v) {
if ((*v)->GetType() != Value::TYPE_STRING)
continue;
const StringValue* string_value = static_cast<const StringValue*>(*v);
string16 string16_value;
if (!string_value->GetAsString(&string16_value))
continue;
urls.insert(GURL(string16_value));
}
HistoryService* hs =
web_ui_->GetProfile()->GetHistoryService(Profile::EXPLICIT_ACCESS);
hs->ExpireHistoryBetween(
urls, begin_time, end_time, &cancelable_delete_consumer_,
NewCallback(this, &BrowsingHistoryHandler::RemoveComplete));
}
void BrowsingHistoryHandler::HandleClearBrowsingData(const ListValue* args) {
// TODO(beng): This is an improper direct dependency on Browser. Route this
// through some sort of delegate.
Browser* browser = BrowserList::FindBrowserWithProfile(web_ui_->GetProfile());
if (browser)
browser->OpenClearBrowsingDataDialog();
}
void BrowsingHistoryHandler::QueryComplete(
HistoryService::Handle request_handle,
history::QueryResults* results) {
ListValue results_value;
base::Time midnight_today = base::Time::Now().LocalMidnight();
for (size_t i = 0; i < results->size(); ++i) {
history::URLResult const &page = (*results)[i];
DictionaryValue* page_value = new DictionaryValue();
SetURLAndTitle(page_value, page.title(), page.url());
// Need to pass the time in epoch time (fastest JS conversion).
page_value->SetInteger("time",
static_cast<int>(page.visit_time().ToTimeT()));
// Until we get some JS i18n infrastructure, we also need to
// pass the dates in as strings. This could use some
// optimization.
// Only pass in the strings we need (search results need a shortdate
// and snippet, browse results need day and time information).
if (search_text_.empty()) {
// Figure out the relative date string.
string16 date_str = TimeFormat::RelativeDate(page.visit_time(),
&midnight_today);
if (date_str.empty()) {
date_str = base::TimeFormatFriendlyDate(page.visit_time());
} else {
date_str = l10n_util::GetStringFUTF16(
IDS_HISTORY_DATE_WITH_RELATIVE_TIME,
date_str,
base::TimeFormatFriendlyDate(page.visit_time()));
}
page_value->SetString("dateRelativeDay", date_str);
page_value->SetString("dateTimeOfDay",
base::TimeFormatTimeOfDay(page.visit_time()));
} else {
page_value->SetString("dateShort",
base::TimeFormatShortDate(page.visit_time()));
page_value->SetString("snippet", page.snippet().text());
}
page_value->SetBoolean("starred",
web_ui_->GetProfile()->GetBookmarkModel()->IsBookmarked(page.url()));
results_value.Append(page_value);
}
DictionaryValue info_value;
info_value.SetString("term", search_text_);
info_value.SetBoolean("finished", results->reached_beginning());
web_ui_->CallJavascriptFunction("historyResult", info_value, results_value);
}
void BrowsingHistoryHandler::RemoveComplete() {
// Some Visits were deleted from history. Reload the list.
web_ui_->CallJavascriptFunction("deleteComplete");
}
void BrowsingHistoryHandler::ExtractSearchHistoryArguments(
const ListValue* args,
int* month,
string16* query) {
CHECK(args->GetSize() == 2);
query->clear();
CHECK(args->GetString(0, query));
string16 string16_value;
CHECK(args->GetString(1, &string16_value));
*month = 0;
base::StringToInt(string16_value, month);
}
history::QueryOptions BrowsingHistoryHandler::CreateMonthQueryOptions(
int month) {
history::QueryOptions options;
// Configure the begin point of the search to the start of the
// current month.
base::Time::Exploded exploded;
base::Time::Now().LocalMidnight().LocalExplode(&exploded);
exploded.day_of_month = 1;
if (month == 0) {
options.begin_time = base::Time::FromLocalExploded(exploded);
// Set the end time of this first search to null (which will
// show results from the future, should the user's clock have
// been set incorrectly).
options.end_time = base::Time();
} else {
// Set the end-time of this search to the end of the month that is
// |depth| months before the search end point. The end time is not
// inclusive, so we should feel free to set it to midnight on the
// first day of the following month.
exploded.month -= month - 1;
while (exploded.month < 1) {
exploded.month += 12;
exploded.year--;
}
options.end_time = base::Time::FromLocalExploded(exploded);
// Set the begin-time of the search to the start of the month
// that is |depth| months prior to search_start_.
if (exploded.month > 1) {
exploded.month--;
} else {
exploded.month = 12;
exploded.year--;
}
options.begin_time = base::Time::FromLocalExploded(exploded);
}
return options;
}
////////////////////////////////////////////////////////////////////////////////
//
// HistoryUIContents
//
////////////////////////////////////////////////////////////////////////////////
HistoryUI::HistoryUI(TabContents* contents) : WebUI(contents) {
AddMessageHandler((new BrowsingHistoryHandler())->Attach(this));
HistoryUIHTMLSource* html_source = new HistoryUIHTMLSource();
// Set up the chrome://history/ source.
contents->profile()->GetChromeURLDataManager()->AddDataSource(html_source);
}
// static
const GURL HistoryUI::GetHistoryURLWithSearchText(const string16& text) {
return GURL(std::string(chrome::kChromeUIHistoryURL) + "#q=" +
EscapeQueryParamValue(UTF16ToUTF8(text), true));
}
// static
RefCountedMemory* HistoryUI::GetFaviconResourceBytes() {
return ResourceBundle::GetSharedInstance().
LoadDataResourceBytes(IDR_HISTORY_FAVICON);
}