// 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/extensions/extension_history_api.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/extensions/extension_history_api_constants.h" #include "chrome/browser/history/history.h" #include "chrome/browser/history/history_types.h" #include "chrome/browser/profiles/profile.h" #include "content/common/notification_service.h" #include "content/common/notification_type.h" namespace keys = extension_history_api_constants; namespace { double MilliSecondsFromTime(const base::Time& time) { return 1000 * time.ToDoubleT(); } void GetHistoryItemDictionary(const history::URLRow& row, DictionaryValue* value) { value->SetString(keys::kIdKey, base::Int64ToString(row.id())); value->SetString(keys::kUrlKey, row.url().spec()); value->SetString(keys::kTitleKey, row.title()); value->SetDouble(keys::kLastVisitdKey, MilliSecondsFromTime(row.last_visit())); value->SetInteger(keys::kTypedCountKey, row.typed_count()); value->SetInteger(keys::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(keys::kIdKey, base::Int64ToString(row.url_id)); value->SetString(keys::kVisitId, base::Int64ToString(row.visit_id)); value->SetDouble(keys::kVisitTime, MilliSecondsFromTime(row.visit_time)); value->SetString(keys::kReferringVisitId, base::Int64ToString(row.referring_visit)); const char* trans = PageTransition::CoreTransitionString(row.transition); DCHECK(trans) << "Invalid transition."; value->SetString(keys::kTransition, trans); } void AddVisitNode(const history::VisitRow& row, ListValue* list) { DictionaryValue* dict = new DictionaryValue(); GetVisitInfoDictionary(row, dict); list->Append(dict); } } // namespace ExtensionHistoryEventRouter* ExtensionHistoryEventRouter::GetInstance() { return Singleton<ExtensionHistoryEventRouter>::get(); } void ExtensionHistoryEventRouter::ObserveProfile(Profile* profile) { NotificationSource source = Source<Profile>(profile); if (profiles_.find(source.map_key()) == profiles_.end()) profiles_[source.map_key()] = profile; if (registrar_.IsEmpty()) { registrar_.Add(this, NotificationType::HISTORY_URL_VISITED, NotificationService::AllSources()); registrar_.Add(this, NotificationType::HISTORY_URLS_DELETED, NotificationService::AllSources()); } } ExtensionHistoryEventRouter::ExtensionHistoryEventRouter() {} ExtensionHistoryEventRouter::~ExtensionHistoryEventRouter() {} void ExtensionHistoryEventRouter::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { ProfileMap::iterator it = profiles_.find(source.map_key()); if (it != profiles_.end()) { Profile* profile = it->second; switch (type.value) { case NotificationType::HISTORY_URL_VISITED: HistoryUrlVisited( profile, Details<const history::URLVisitedDetails>(details).ptr()); break; case NotificationType::HISTORY_URLS_DELETED: HistoryUrlsRemoved( profile, Details<const history::URLsDeletedDetails>(details).ptr()); break; default: NOTREACHED(); } } } void ExtensionHistoryEventRouter::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, keys::kOnVisited, json_args); } void ExtensionHistoryEventRouter::HistoryUrlsRemoved( Profile* profile, const history::URLsDeletedDetails* details) { ListValue args; DictionaryValue* dict = new DictionaryValue(); dict->SetBoolean(keys::kAllHistoryKey, details->all_history); ListValue* urls = new ListValue(); for (std::set<GURL>::const_iterator iterator = details->urls.begin(); iterator != details->urls.end(); ++iterator) { urls->Append(new StringValue(iterator->spec())); } dict->Set(keys::kUrlsKey, urls); args.Append(dict); std::string json_args; base::JSONWriter::Write(&args, false, &json_args); DispatchEvent(profile, keys::kOnVisitRemoved, json_args); } void ExtensionHistoryEventRouter::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_ = keys::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)) { int ms_from_epoch_as_int = 0; if (!value->GetAsInteger(&ms_from_epoch_as_int)) return false; ms_from_epoch = static_cast<double>(ms_from_epoch_as_int); } // 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(keys::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_, NewCallback(this, &GetVisitsHistoryFunction::QueryComplete)); 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(keys::kTextKey, &search_text)); history::QueryOptions options; options.SetRecentDayRange(1); options.max_count = 100; if (json->HasKey(keys::kStartTimeKey)) { // Optional. Value* value; EXTENSION_FUNCTION_VALIDATE(json->Get(keys::kStartTimeKey, &value)); EXTENSION_FUNCTION_VALIDATE(GetTimeFromValue(value, &options.begin_time)); } if (json->HasKey(keys::kEndTimeKey)) { // Optional. Value* value; EXTENSION_FUNCTION_VALIDATE(json->Get(keys::kEndTimeKey, &value)); EXTENSION_FUNCTION_VALIDATE(GetTimeFromValue(value, &options.end_time)); } if (json->HasKey(keys::kMaxResultsKey)) { // Optional. EXTENSION_FUNCTION_VALIDATE(json->GetInteger(keys::kMaxResultsKey, &options.max_count)); } HistoryService* hs = profile()->GetHistoryService(Profile::EXPLICIT_ACCESS); hs->QueryHistory(search_text, options, &cancelable_consumer_, NewCallback(this, &SearchHistoryFunction::SearchComplete)); 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(keys::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(keys::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(keys::kStartTimeKey, &value)); base::Time begin_time; EXTENSION_FUNCTION_VALIDATE(GetTimeFromValue(value, &begin_time)); EXTENSION_FUNCTION_VALIDATE(json->Get(keys::kEndTimeKey, &value)); base::Time end_time; EXTENSION_FUNCTION_VALIDATE(GetTimeFromValue(value, &end_time)); std::set<GURL> restrict_urls; HistoryService* hs = profile()->GetHistoryService(Profile::EXPLICIT_ACCESS); hs->ExpireHistoryBetween( restrict_urls, begin_time, end_time, &cancelable_consumer_, NewCallback(this, &DeleteRangeHistoryFunction::DeleteComplete)); return true; } void DeleteRangeHistoryFunction::DeleteComplete() { SendAsyncResponse(); } bool DeleteAllHistoryFunction::RunAsyncImpl() { std::set<GURL> 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_, NewCallback(this, &DeleteAllHistoryFunction::DeleteComplete)); return true; } void DeleteAllHistoryFunction::DeleteComplete() { SendAsyncResponse(); }