root/chrome/browser/extensions/api/history/history_api.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. MilliSecondsFromTime
  2. GetHistoryItem
  3. GetVisitItem
  4. Observe
  5. HistoryUrlVisited
  6. HistoryUrlsRemoved
  7. DispatchEvent
  8. Shutdown
  9. GetFactoryInstance
  10. DeclareFactoryDependencies
  11. OnListenerAdded
  12. Run
  13. ValidateUrl
  14. VerifyDeleteAllowed
  15. GetTime
  16. RunImpl
  17. SendAsyncResponse
  18. SendResponseToCallback
  19. RunAsyncImpl
  20. QueryComplete
  21. RunAsyncImpl
  22. SearchComplete
  23. RunImpl
  24. RunImpl
  25. RunAsyncImpl
  26. DeleteComplete
  27. RunAsyncImpl
  28. DeleteComplete

// 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/task/cancelable_task_tracker.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/history/history_service.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/history/history_types.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_switches.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"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_system_provider.h"
#include "extensions/browser/extensions_browser_client.h"

namespace extensions {

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 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(base::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_browser_context = profile;
    extensions::ExtensionSystem::Get(profile)->event_router()->
        BroadcastEvent(event.Pass());
  }
}

HistoryAPI::HistoryAPI(content::BrowserContext* context)
    : browser_context_(context) {
  ExtensionSystem::Get(browser_context_)->event_router()->RegisterObserver(
      this, api::history::OnVisited::kEventName);
  ExtensionSystem::Get(browser_context_)->event_router()->RegisterObserver(
      this, api::history::OnVisitRemoved::kEventName);
}

HistoryAPI::~HistoryAPI() {
}

void HistoryAPI::Shutdown() {
  ExtensionSystem::Get(browser_context_)->event_router()->UnregisterObserver(
      this);
}

static base::LazyInstance<BrowserContextKeyedAPIFactory<HistoryAPI> >
    g_factory = LAZY_INSTANCE_INITIALIZER;

// static
BrowserContextKeyedAPIFactory<HistoryAPI>* HistoryAPI::GetFactoryInstance() {
  return g_factory.Pointer();
}

template <>
void BrowserContextKeyedAPIFactory<HistoryAPI>::DeclareFactoryDependencies() {
  DependsOn(ActivityLog::GetFactoryInstance());
  DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
}

void HistoryAPI::OnListenerAdded(const EventListenerInfo& details) {
  history_event_router_.reset(
      new HistoryEventRouter(Profile::FromBrowserContext(browser_context_)));
  ExtensionSystem::Get(browser_context_)->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 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());

  base::string16 search_text = base::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

/* [<][>][^][v][top][bottom][index][help] */