// 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/history/history_extension_api.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" #include "base/json/json_writer.h" #include "base/message_loop.h" #include "base/string_number_conversions.h" #include "base/task.h" #include "base/values.h" #include "chrome/browser/extensions/extension_event_router.h" #include "chrome/browser/history/history.h" #include "chrome/browser/history/history_types.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/chrome_notification_types.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" namespace { const char kAllHistoryKey[] = "allHistory"; const char kEndTimeKey[] = "endTime"; const char kFaviconUrlKey[] = "favIconUrl"; const char kIdKey[] = "id"; const char kLastVisitdKey[] = "lastVisitTime"; const char kMaxResultsKey[] = "maxResults"; const char kNewKey[] = "new"; const char kReferringVisitId[] = "referringVisitId"; const char kRemovedKey[] = "removed"; const char kStartTimeKey[] = "startTime"; const char kTextKey[] = "text"; const char kTitleKey[] = "title"; const char kTypedCountKey[] = "typedCount"; const char kVisitCountKey[] = "visitCount"; const char kTransition[] = "transition"; const char kUrlKey[] = "url"; const char kUrlsKey[] = "urls"; const char kVisitId[] = "visitId"; const char kVisitTime[] = "visitTime"; const char kOnVisited[] = "history.onVisited"; const char kOnVisitRemoved[] = "history.onVisitRemoved"; const char kInvalidIdError[] = "History item id is invalid."; const char kInvalidUrlError[] = "Url is invalid."; double MilliSecondsFromTime(const base::Time& time) { return 1000 * time.ToDoubleT(); } void GetHistoryItemDictionary(const history::URLRow& row, DictionaryValue* value) { value->SetString(kIdKey, base::Int64ToString(row.id())); value->SetString(kUrlKey, row.url().spec()); value->SetString(kTitleKey, row.title()); value->SetDouble(kLastVisitdKey, MilliSecondsFromTime(row.last_visit())); value->SetInteger(kTypedCountKey, row.typed_count()); value->SetInteger(kVisitCountKey, row.visit_count()); } void AddHistoryNode(const history::URLRow& row, ListValue* list) { DictionaryValue* dict = new DictionaryValue(); GetHistoryItemDictionary(row, dict); list->Append(dict); } void GetVisitInfoDictionary(const history::VisitRow& row, DictionaryValue* value) { value->SetString(kIdKey, base::Int64ToString(row.url_id)); value->SetString(kVisitId, base::Int64ToString(row.visit_id)); value->SetDouble(kVisitTime, MilliSecondsFromTime(row.visit_time)); value->SetString(kReferringVisitId, base::Int64ToString(row.referring_visit)); const char* trans = content::PageTransitionGetCoreTransitionString(row.transition); DCHECK(trans) << "Invalid transition."; value->SetString(kTransition, trans); } void AddVisitNode(const history::VisitRow& row, ListValue* list) { DictionaryValue* dict = new DictionaryValue(); GetVisitInfoDictionary(row, dict); list->Append(dict); } } // namespace HistoryExtensionEventRouter::HistoryExtensionEventRouter() {} HistoryExtensionEventRouter::~HistoryExtensionEventRouter() {} void HistoryExtensionEventRouter::ObserveProfile(Profile* profile) { CHECK(registrar_.IsEmpty()); const content::Source source = content::Source(profile); registrar_.Add(this, chrome::NOTIFICATION_HISTORY_URL_VISITED, source); registrar_.Add(this, chrome::NOTIFICATION_HISTORY_URLS_DELETED, source); } void HistoryExtensionEventRouter::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { switch (type) { case chrome::NOTIFICATION_HISTORY_URL_VISITED: HistoryUrlVisited( content::Source(source).ptr(), content::Details(details).ptr()); break; case chrome::NOTIFICATION_HISTORY_URLS_DELETED: HistoryUrlsRemoved( content::Source(source).ptr(), content::Details(details).ptr()); break; default: NOTREACHED(); } } void HistoryExtensionEventRouter::HistoryUrlVisited( Profile* profile, const history::URLVisitedDetails* details) { ListValue args; DictionaryValue* dict = new DictionaryValue(); GetHistoryItemDictionary(details->row, dict); args.Append(dict); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(profile, kOnVisited, json_args); } void HistoryExtensionEventRouter::HistoryUrlsRemoved( Profile* profile, const history::URLsDeletedDetails* details) { ListValue args; DictionaryValue* dict = new DictionaryValue(); dict->SetBoolean(kAllHistoryKey, details->all_history); ListValue* urls = new ListValue(); for (std::set::const_iterator iterator = details->urls.begin(); iterator != details->urls.end(); ++iterator) { urls->Append(new StringValue(iterator->spec())); } dict->Set(kUrlsKey, urls); args.Append(dict); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(profile, kOnVisitRemoved, json_args); } void HistoryExtensionEventRouter::DispatchEvent(Profile* profile, const char* event_name, const std::string& json_args) { if (profile && profile->GetExtensionEventRouter()) { profile->GetExtensionEventRouter()->DispatchEventToRenderers( event_name, json_args, profile, GURL()); } } void HistoryFunction::Run() { if (!RunImpl()) { SendResponse(false); } } bool HistoryFunction::GetUrlFromValue(Value* value, GURL* url) { std::string url_string; if (!value->GetAsString(&url_string)) { bad_message_ = true; return false; } GURL temp_url(url_string); if (!temp_url.is_valid()) { error_ = kInvalidUrlError; return false; } url->Swap(&temp_url); return true; } bool HistoryFunction::GetTimeFromValue(Value* value, base::Time* time) { double ms_from_epoch = 0.0; if (!value->GetAsDouble(&ms_from_epoch)) return false; // The history service has seconds resolution, while javascript Date() has // milliseconds resolution. double seconds_from_epoch = ms_from_epoch / 1000.0; // Time::FromDoubleT converts double time 0 to empty Time object. So we need // to do special handling here. *time = (seconds_from_epoch == 0) ? base::Time::UnixEpoch() : base::Time::FromDoubleT(seconds_from_epoch); return true; } HistoryFunctionWithCallback::HistoryFunctionWithCallback() { } HistoryFunctionWithCallback::~HistoryFunctionWithCallback() { } bool HistoryFunctionWithCallback::RunImpl() { AddRef(); // Balanced in SendAysncRepose() and below. bool retval = RunAsyncImpl(); if (false == retval) Release(); return retval; } void HistoryFunctionWithCallback::SendAsyncResponse() { MessageLoop::current()->PostTask( FROM_HERE, NewRunnableMethod( this, &HistoryFunctionWithCallback::SendResponseToCallback)); } void HistoryFunctionWithCallback::SendResponseToCallback() { SendResponse(true); Release(); // Balanced in RunImpl(). } bool GetVisitsHistoryFunction::RunAsyncImpl() { DictionaryValue* json; EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &json)); Value* value; EXTENSION_FUNCTION_VALIDATE(json->Get(kUrlKey, &value)); GURL url; if (!GetUrlFromValue(value, &url)) return false; HistoryService* hs = profile()->GetHistoryService(Profile::EXPLICIT_ACCESS); hs->QueryURL(url, true, // Retrieve full history of a URL. &cancelable_consumer_, base::Bind(&GetVisitsHistoryFunction::QueryComplete, base::Unretained(this))); return true; } void GetVisitsHistoryFunction::QueryComplete( HistoryService::Handle request_service, bool success, const history::URLRow* url_row, history::VisitVector* visits) { ListValue* list = new ListValue(); if (visits && !visits->empty()) { for (history::VisitVector::iterator iterator = visits->begin(); iterator != visits->end(); ++iterator) { AddVisitNode(*iterator, list); } } result_.reset(list); SendAsyncResponse(); } bool SearchHistoryFunction::RunAsyncImpl() { DictionaryValue* json; EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &json)); // Initialize the HistoryQuery string16 search_text; EXTENSION_FUNCTION_VALIDATE(json->GetString(kTextKey, &search_text)); history::QueryOptions options; options.SetRecentDayRange(1); options.max_count = 100; if (json->HasKey(kStartTimeKey)) { // Optional. Value* value; EXTENSION_FUNCTION_VALIDATE(json->Get(kStartTimeKey, &value)); EXTENSION_FUNCTION_VALIDATE(GetTimeFromValue(value, &options.begin_time)); } if (json->HasKey(kEndTimeKey)) { // Optional. Value* value; EXTENSION_FUNCTION_VALIDATE(json->Get(kEndTimeKey, &value)); EXTENSION_FUNCTION_VALIDATE(GetTimeFromValue(value, &options.end_time)); } if (json->HasKey(kMaxResultsKey)) { // Optional. EXTENSION_FUNCTION_VALIDATE(json->GetInteger(kMaxResultsKey, &options.max_count)); } HistoryService* hs = profile()->GetHistoryService(Profile::EXPLICIT_ACCESS); hs->QueryHistory(search_text, options, &cancelable_consumer_, base::Bind(&SearchHistoryFunction::SearchComplete, base::Unretained(this))); return true; } void SearchHistoryFunction::SearchComplete( HistoryService::Handle request_handle, history::QueryResults* results) { ListValue* list = new ListValue(); if (results && !results->empty()) { for (history::QueryResults::URLResultVector::const_iterator iterator = results->begin(); iterator != results->end(); ++iterator) { AddHistoryNode(**iterator, list); } } result_.reset(list); SendAsyncResponse(); } bool AddUrlHistoryFunction::RunImpl() { DictionaryValue* json; EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &json)); Value* value; EXTENSION_FUNCTION_VALIDATE(json->Get(kUrlKey, &value)); GURL url; if (!GetUrlFromValue(value, &url)) return false; HistoryService* hs = profile()->GetHistoryService(Profile::EXPLICIT_ACCESS); hs->AddPage(url, history::SOURCE_EXTENSION); SendResponse(true); return true; } bool DeleteUrlHistoryFunction::RunImpl() { DictionaryValue* json; EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &json)); Value* value; EXTENSION_FUNCTION_VALIDATE(json->Get(kUrlKey, &value)); GURL url; if (!GetUrlFromValue(value, &url)) return false; HistoryService* hs = profile()->GetHistoryService(Profile::EXPLICIT_ACCESS); hs->DeleteURL(url); SendResponse(true); return true; } bool DeleteRangeHistoryFunction::RunAsyncImpl() { DictionaryValue* json; EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &json)); Value* value = NULL; EXTENSION_FUNCTION_VALIDATE(json->Get(kStartTimeKey, &value)); base::Time begin_time; EXTENSION_FUNCTION_VALIDATE(GetTimeFromValue(value, &begin_time)); EXTENSION_FUNCTION_VALIDATE(json->Get(kEndTimeKey, &value)); base::Time end_time; EXTENSION_FUNCTION_VALIDATE(GetTimeFromValue(value, &end_time)); std::set restrict_urls; HistoryService* hs = profile()->GetHistoryService(Profile::EXPLICIT_ACCESS); hs->ExpireHistoryBetween( restrict_urls, begin_time, end_time, &cancelable_consumer_, base::Bind(&DeleteRangeHistoryFunction::DeleteComplete, base::Unretained(this))); return true; } void DeleteRangeHistoryFunction::DeleteComplete() { SendAsyncResponse(); } bool DeleteAllHistoryFunction::RunAsyncImpl() { std::set restrict_urls; HistoryService* hs = profile()->GetHistoryService(Profile::EXPLICIT_ACCESS); hs->ExpireHistoryBetween( restrict_urls, base::Time::UnixEpoch(), // From the beginning of the epoch. base::Time::Now(), // To the current time. &cancelable_consumer_, base::Bind(&DeleteAllHistoryFunction::DeleteComplete, base::Unretained(this))); return true; } void DeleteAllHistoryFunction::DeleteComplete() { SendAsyncResponse(); }