blob: cb75c9664ae4456b6c914c31c8e8d2ef3d74b196 [file] [log] [blame]
// Copyright (c) 2012 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/api/history/history_api.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/prefs/pref_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/activity_log/activity_log.h"
#include "chrome/browser/extensions/event_router.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/history/history_service.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/history/history_types.h"
#include "chrome/browser/history/visit_filter.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/cancelable_task_tracker.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/api/experimental_history.h"
#include "chrome/common/extensions/api/history.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
namespace extensions {
using api::experimental_history::MostVisitedItem;
using api::history::HistoryItem;
using api::history::VisitItem;
using extensions::ActivityLog;
typedef std::vector<linked_ptr<api::history::HistoryItem> >
HistoryItemList;
typedef std::vector<linked_ptr<api::history::VisitItem> >
VisitItemList;
namespace AddUrl = api::history::AddUrl;
namespace DeleteUrl = api::history::DeleteUrl;
namespace DeleteRange = api::history::DeleteRange;
namespace GetMostVisited = api::experimental_history::GetMostVisited;
namespace GetVisits = api::history::GetVisits;
namespace OnVisited = api::history::OnVisited;
namespace OnVisitRemoved = api::history::OnVisitRemoved;
namespace Search = api::history::Search;
namespace {
const char kInvalidUrlError[] = "Url is invalid.";
const char kDeleteProhibitedError[] = "Browsing history is not allowed to be "
"deleted.";
double MilliSecondsFromTime(const base::Time& time) {
return 1000 * time.ToDoubleT();
}
scoped_ptr<HistoryItem> GetHistoryItem(const history::URLRow& row) {
scoped_ptr<HistoryItem> history_item(new HistoryItem());
history_item->id = base::Int64ToString(row.id());
history_item->url.reset(new std::string(row.url().spec()));
history_item->title.reset(new std::string(UTF16ToUTF8(row.title())));
history_item->last_visit_time.reset(
new double(MilliSecondsFromTime(row.last_visit())));
history_item->typed_count.reset(new int(row.typed_count()));
history_item->visit_count.reset(new int(row.visit_count()));
return history_item.Pass();
}
scoped_ptr<VisitItem> GetVisitItem(const history::VisitRow& row) {
scoped_ptr<VisitItem> visit_item(new VisitItem());
visit_item->id = base::Int64ToString(row.url_id);
visit_item->visit_id = base::Int64ToString(row.visit_id);
visit_item->visit_time.reset(
new double(MilliSecondsFromTime(row.visit_time)));
visit_item->referring_visit_id = base::Int64ToString(row.referring_visit);
VisitItem::Transition transition = VisitItem::TRANSITION_LINK;
switch (row.transition & content::PAGE_TRANSITION_CORE_MASK) {
case content::PAGE_TRANSITION_LINK:
transition = VisitItem::TRANSITION_LINK;
break;
case content::PAGE_TRANSITION_TYPED:
transition = VisitItem::TRANSITION_TYPED;
break;
case content::PAGE_TRANSITION_AUTO_BOOKMARK:
transition = VisitItem::TRANSITION_AUTO_BOOKMARK;
break;
case content::PAGE_TRANSITION_AUTO_SUBFRAME:
transition = VisitItem::TRANSITION_AUTO_SUBFRAME;
break;
case content::PAGE_TRANSITION_MANUAL_SUBFRAME:
transition = VisitItem::TRANSITION_MANUAL_SUBFRAME;
break;
case content::PAGE_TRANSITION_GENERATED:
transition = VisitItem::TRANSITION_GENERATED;
break;
case content::PAGE_TRANSITION_AUTO_TOPLEVEL:
transition = VisitItem::TRANSITION_AUTO_TOPLEVEL;
break;
case content::PAGE_TRANSITION_FORM_SUBMIT:
transition = VisitItem::TRANSITION_FORM_SUBMIT;
break;
case content::PAGE_TRANSITION_RELOAD:
transition = VisitItem::TRANSITION_RELOAD;
break;
case content::PAGE_TRANSITION_KEYWORD:
transition = VisitItem::TRANSITION_KEYWORD;
break;
case content::PAGE_TRANSITION_KEYWORD_GENERATED:
transition = VisitItem::TRANSITION_KEYWORD_GENERATED;
break;
default:
DCHECK(false);
}
visit_item->transition = transition;
return visit_item.Pass();
}
} // namespace
HistoryEventRouter::HistoryEventRouter(Profile* profile) {
const content::Source<Profile> source = content::Source<Profile>(profile);
registrar_.Add(this,
chrome::NOTIFICATION_HISTORY_URL_VISITED,
source);
registrar_.Add(this,
chrome::NOTIFICATION_HISTORY_URLS_DELETED,
source);
}
HistoryEventRouter::~HistoryEventRouter() {}
void HistoryEventRouter::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case chrome::NOTIFICATION_HISTORY_URL_VISITED:
HistoryUrlVisited(
content::Source<Profile>(source).ptr(),
content::Details<const history::URLVisitedDetails>(details).ptr());
break;
case chrome::NOTIFICATION_HISTORY_URLS_DELETED:
HistoryUrlsRemoved(
content::Source<Profile>(source).ptr(),
content::Details<const history::URLsDeletedDetails>(details).ptr());
break;
default:
NOTREACHED();
}
}
void HistoryEventRouter::HistoryUrlVisited(
Profile* profile,
const history::URLVisitedDetails* details) {
scoped_ptr<HistoryItem> history_item = GetHistoryItem(details->row);
scoped_ptr<base::ListValue> args = OnVisited::Create(*history_item);
DispatchEvent(profile, api::history::OnVisited::kEventName, args.Pass());
}
void HistoryEventRouter::HistoryUrlsRemoved(
Profile* profile,
const history::URLsDeletedDetails* details) {
OnVisitRemoved::Removed removed;
removed.all_history = details->all_history;
std::vector<std::string>* urls = new std::vector<std::string>();
for (history::URLRows::const_iterator iterator = details->rows.begin();
iterator != details->rows.end(); ++iterator) {
urls->push_back(iterator->url().spec());
}
removed.urls.reset(urls);
scoped_ptr<base::ListValue> args = OnVisitRemoved::Create(removed);
DispatchEvent(profile, api::history::OnVisitRemoved::kEventName, args.Pass());
}
void HistoryEventRouter::DispatchEvent(
Profile* profile,
const std::string& event_name,
scoped_ptr<base::ListValue> event_args) {
if (profile && extensions::ExtensionSystem::Get(profile)->event_router()) {
scoped_ptr<extensions::Event> event(new extensions::Event(
event_name, event_args.Pass()));
event->restrict_to_profile = profile;
extensions::ExtensionSystem::Get(profile)->event_router()->
BroadcastEvent(event.Pass());
}
}
HistoryAPI::HistoryAPI(Profile* profile) : profile_(profile) {
ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
this, api::history::OnVisited::kEventName);
ExtensionSystem::Get(profile_)->event_router()->RegisterObserver(
this, api::history::OnVisitRemoved::kEventName);
}
HistoryAPI::~HistoryAPI() {
}
void HistoryAPI::Shutdown() {
ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
}
static base::LazyInstance<ProfileKeyedAPIFactory<HistoryAPI> >
g_factory = LAZY_INSTANCE_INITIALIZER;
// static
ProfileKeyedAPIFactory<HistoryAPI>* HistoryAPI::GetFactoryInstance() {
return &g_factory.Get();
}
template<>
void ProfileKeyedAPIFactory<HistoryAPI>::DeclareFactoryDependencies() {
DependsOn(ActivityLogFactory::GetInstance());
}
void HistoryAPI::OnListenerAdded(const EventListenerInfo& details) {
history_event_router_.reset(new HistoryEventRouter(profile_));
ExtensionSystem::Get(profile_)->event_router()->UnregisterObserver(this);
}
void HistoryFunction::Run() {
if (!RunImpl()) {
SendResponse(false);
}
}
bool HistoryFunction::ValidateUrl(const std::string& url_string, GURL* url) {
GURL temp_url(url_string);
if (!temp_url.is_valid()) {
error_ = kInvalidUrlError;
return false;
}
url->Swap(&temp_url);
return true;
}
bool HistoryFunction::VerifyDeleteAllowed() {
PrefService* prefs = GetProfile()->GetPrefs();
if (!prefs->GetBoolean(prefs::kAllowDeletingBrowserHistory)) {
error_ = kDeleteProhibitedError;
return false;
}
return true;
}
base::Time HistoryFunction::GetTime(double ms_from_epoch) {
// 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.
return (seconds_from_epoch == 0) ?
base::Time::UnixEpoch() : base::Time::FromDoubleT(seconds_from_epoch);
}
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() {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&HistoryFunctionWithCallback::SendResponseToCallback, this));
}
void HistoryFunctionWithCallback::SendResponseToCallback() {
SendResponse(true);
Release(); // Balanced in RunImpl().
}
bool HistoryGetMostVisitedFunction::RunAsyncImpl() {
scoped_ptr<GetMostVisited::Params> params =
GetMostVisited::Params::Create(*args_);
EXTENSION_FUNCTION_VALIDATE(params.get());
history::VisitFilter filter;
if (params->details.filter_time.get())
filter.SetFilterTime(GetTime(*params->details.filter_time));
if (params->details.filter_width.get()) {
filter.SetFilterWidth(base::TimeDelta::FromMilliseconds(
static_cast<int64>(*params->details.filter_width)));
}
if (params->details.day_of_the_week.get())
filter.SetDayOfTheWeekFilter(*params->details.day_of_the_week);
int max_results = 100;
if (params->details.max_results.get())
max_results = *params->details.max_results;
HistoryService* hs = HistoryServiceFactory::GetForProfile(
GetProfile(), Profile::EXPLICIT_ACCESS);
hs->QueryFilteredURLs(max_results, filter, false, &cancelable_consumer_,
base::Bind(&HistoryGetMostVisitedFunction::QueryComplete,
base::Unretained(this)));
return true;
}
void HistoryGetMostVisitedFunction::QueryComplete(
CancelableRequestProvider::Handle handle,
const history::FilteredURLList& data) {
std::vector<linked_ptr<MostVisitedItem> > results;
results.reserve(data.size());
for (size_t i = 0; i < data.size(); i++) {
linked_ptr<MostVisitedItem> item(new MostVisitedItem);
item->url = data[i].url.spec();
item->title = UTF16ToUTF8(data[i].title);
results.push_back(item);
}
results_ = GetMostVisited::Results::Create(results);
SendAsyncResponse();
}
bool HistoryGetVisitsFunction::RunAsyncImpl() {
scoped_ptr<GetVisits::Params> params(GetVisits::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
GURL url;
if (!ValidateUrl(params->details.url, &url))
return false;
HistoryService* hs = HistoryServiceFactory::GetForProfile(
GetProfile(), Profile::EXPLICIT_ACCESS);
hs->QueryURL(url,
true, // Retrieve full history of a URL.
&cancelable_consumer_,
base::Bind(&HistoryGetVisitsFunction::QueryComplete,
base::Unretained(this)));
return true;
}
void HistoryGetVisitsFunction::QueryComplete(
HistoryService::Handle request_service,
bool success,
const history::URLRow* url_row,
history::VisitVector* visits) {
VisitItemList visit_item_vec;
if (visits && !visits->empty()) {
for (history::VisitVector::iterator iterator = visits->begin();
iterator != visits->end();
++iterator) {
visit_item_vec.push_back(make_linked_ptr(
GetVisitItem(*iterator).release()));
}
}
results_ = GetVisits::Results::Create(visit_item_vec);
SendAsyncResponse();
}
bool HistorySearchFunction::RunAsyncImpl() {
scoped_ptr<Search::Params> params(Search::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
string16 search_text = UTF8ToUTF16(params->query.text);
history::QueryOptions options;
options.SetRecentDayRange(1);
options.max_count = 100;
if (params->query.start_time.get())
options.begin_time = GetTime(*params->query.start_time);
if (params->query.end_time.get())
options.end_time = GetTime(*params->query.end_time);
if (params->query.max_results.get())
options.max_count = *params->query.max_results;
HistoryService* hs = HistoryServiceFactory::GetForProfile(
GetProfile(), Profile::EXPLICIT_ACCESS);
hs->QueryHistory(search_text, options, &cancelable_consumer_,
base::Bind(&HistorySearchFunction::SearchComplete,
base::Unretained(this)));
return true;
}
void HistorySearchFunction::SearchComplete(
HistoryService::Handle request_handle,
history::QueryResults* results) {
HistoryItemList history_item_vec;
if (results && !results->empty()) {
for (history::QueryResults::URLResultVector::const_iterator iterator =
results->begin();
iterator != results->end();
++iterator) {
history_item_vec.push_back(make_linked_ptr(
GetHistoryItem(**iterator).release()));
}
}
results_ = Search::Results::Create(history_item_vec);
SendAsyncResponse();
}
bool HistoryAddUrlFunction::RunImpl() {
scoped_ptr<AddUrl::Params> params(AddUrl::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
GURL url;
if (!ValidateUrl(params->details.url, &url))
return false;
HistoryService* hs = HistoryServiceFactory::GetForProfile(
GetProfile(), Profile::EXPLICIT_ACCESS);
hs->AddPage(url, base::Time::Now(), history::SOURCE_EXTENSION);
SendResponse(true);
return true;
}
bool HistoryDeleteUrlFunction::RunImpl() {
scoped_ptr<DeleteUrl::Params> params(DeleteUrl::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
if (!VerifyDeleteAllowed())
return false;
GURL url;
if (!ValidateUrl(params->details.url, &url))
return false;
HistoryService* hs = HistoryServiceFactory::GetForProfile(
GetProfile(), Profile::EXPLICIT_ACCESS);
hs->DeleteURL(url);
// Also clean out from the activity log. If the activity log testing flag is
// set then don't clean so testers can see what potentially malicious
// extensions have been trying to clean from their logs.
if (!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExtensionActivityLogTesting)) {
ActivityLog* activity_log = ActivityLog::GetInstance(GetProfile());
DCHECK(activity_log);
activity_log->RemoveURL(url);
}
SendResponse(true);
return true;
}
bool HistoryDeleteRangeFunction::RunAsyncImpl() {
scoped_ptr<DeleteRange::Params> params(DeleteRange::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
if (!VerifyDeleteAllowed())
return false;
base::Time start_time = GetTime(params->range.start_time);
base::Time end_time = GetTime(params->range.end_time);
std::set<GURL> restrict_urls;
HistoryService* hs = HistoryServiceFactory::GetForProfile(
GetProfile(), Profile::EXPLICIT_ACCESS);
hs->ExpireHistoryBetween(
restrict_urls,
start_time,
end_time,
base::Bind(&HistoryDeleteRangeFunction::DeleteComplete,
base::Unretained(this)),
&task_tracker_);
// Also clean from the activity log unless in testing mode.
if (!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExtensionActivityLogTesting)) {
ActivityLog* activity_log = ActivityLog::GetInstance(GetProfile());
DCHECK(activity_log);
activity_log->RemoveURLs(restrict_urls);
}
return true;
}
void HistoryDeleteRangeFunction::DeleteComplete() {
SendAsyncResponse();
}
bool HistoryDeleteAllFunction::RunAsyncImpl() {
if (!VerifyDeleteAllowed())
return false;
std::set<GURL> restrict_urls;
HistoryService* hs = HistoryServiceFactory::GetForProfile(
GetProfile(), Profile::EXPLICIT_ACCESS);
hs->ExpireHistoryBetween(
restrict_urls,
base::Time(), // Unbounded beginning...
base::Time(), // ...and the end.
base::Bind(&HistoryDeleteAllFunction::DeleteComplete,
base::Unretained(this)),
&task_tracker_);
// Also clean from the activity log unless in testing mode.
if (!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExtensionActivityLogTesting)) {
ActivityLog* activity_log = ActivityLog::GetInstance(GetProfile());
DCHECK(activity_log);
activity_log->RemoveURLs(restrict_urls);
}
return true;
}
void HistoryDeleteAllFunction::DeleteComplete() {
SendAsyncResponse();
}
} // namespace extensions