diff options
author | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-24 20:29:07 +0000 |
---|---|---|
committer | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-24 20:29:07 +0000 |
commit | d2e6bd6562fc25f6e01e333602c132ea14a1a17e (patch) | |
tree | 2eedba3e87d82c019fac8f934b87b423abbc2378 /chrome/browser/history | |
parent | 1a157553f8382a9c09cb8a5ccdfa798ac49cb10c (diff) | |
download | chromium_src-d2e6bd6562fc25f6e01e333602c132ea14a1a17e.zip chromium_src-d2e6bd6562fc25f6e01e333602c132ea14a1a17e.tar.gz chromium_src-d2e6bd6562fc25f6e01e333602c132ea14a1a17e.tar.bz2 |
Move history extension API implementation to history dir.
We're moving all the extension api implementations out of
browser/extensions into the directory with the code they are
automating.
BUG=101244
Review URL: http://codereview.chromium.org/8372021
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@106968 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/history')
-rw-r--r-- | chrome/browser/history/history_extension_api.cc | 405 | ||||
-rw-r--r-- | chrome/browser/history/history_extension_api.h | 140 | ||||
-rw-r--r-- | chrome/browser/history/history_extension_apitest.cc | 58 |
3 files changed, 603 insertions, 0 deletions
diff --git a/chrome/browser/history/history_extension_api.cc b/chrome/browser/history/history_extension_api.cc new file mode 100644 index 0000000..1c7d1e6 --- /dev/null +++ b/chrome/browser/history/history_extension_api.cc @@ -0,0 +1,405 @@ +// 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<Profile> source = content::Source<Profile>(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<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 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<GURL>::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<GURL> 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<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_, + base::Bind(&DeleteAllHistoryFunction::DeleteComplete, + base::Unretained(this))); + + return true; +} + +void DeleteAllHistoryFunction::DeleteComplete() { + SendAsyncResponse(); +} diff --git a/chrome/browser/history/history_extension_api.h b/chrome/browser/history/history_extension_api.h new file mode 100644 index 0000000..e66ea76 --- /dev/null +++ b/chrome/browser/history/history_extension_api.h @@ -0,0 +1,140 @@ +// 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. + +#ifndef CHROME_BROWSER_HISTORY_HISTORY_EXTENSION_API_H_ +#define CHROME_BROWSER_HISTORY_HISTORY_EXTENSION_API_H_ +#pragma once + +#include <string> + +#include "base/compiler_specific.h" +#include "chrome/browser/extensions/extension_function.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/history/history_notifications.h" +#include "content/public/browser/notification_registrar.h" + +// Observes History service and routes the notifications as events to the +// extension system. +class HistoryExtensionEventRouter : public content::NotificationObserver { + public: + explicit HistoryExtensionEventRouter(); + virtual ~HistoryExtensionEventRouter(); + + void ObserveProfile(Profile* profile); + + private: + // content::NotificationObserver::Observe. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + void HistoryUrlVisited(Profile* profile, + const history::URLVisitedDetails* details); + + void HistoryUrlsRemoved(Profile* profile, + const history::URLsDeletedDetails* details); + + void DispatchEvent(Profile* profile, + const char* event_name, + const std::string& json_args); + + // Used for tracking registrations to history service notifications. + content::NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(HistoryExtensionEventRouter); +}; + + +// Base class for history function APIs. +class HistoryFunction : public AsyncExtensionFunction { + public: + virtual void Run() OVERRIDE; + virtual bool RunImpl() = 0; + + bool GetUrlFromValue(base::Value* value, GURL* url); + bool GetTimeFromValue(base::Value* value, base::Time* time); +}; + +// Base class for history funciton APIs which require async interaction with +// chrome services and the extension thread. +class HistoryFunctionWithCallback : public HistoryFunction { + public: + HistoryFunctionWithCallback(); + virtual ~HistoryFunctionWithCallback(); + + // Return true if the async call was completed, false otherwise. + virtual bool RunAsyncImpl() = 0; + + // Call this method to report the results of the async method to the caller. + // This method calls Release(). + virtual void SendAsyncResponse(); + + // Override HistoryFunction::RunImpl. + virtual bool RunImpl() OVERRIDE; + + protected: + // The consumer for the HistoryService callbacks. + CancelableRequestConsumer cancelable_consumer_; + + private: + // The actual call to SendResponse. This is required since the semantics for + // CancelableRequestConsumerT require it to be accessed after the call. + void SendResponseToCallback(); +}; + +class GetVisitsHistoryFunction : public HistoryFunctionWithCallback { + public: + // Override HistoryFunction. + virtual bool RunAsyncImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("history.getVisits"); + + // Callback for the history function to provide results. + void QueryComplete(HistoryService::Handle request_service, + bool success, + const history::URLRow* url_row, + history::VisitVector* visits); +}; + +class SearchHistoryFunction : public HistoryFunctionWithCallback { + public: + virtual bool RunAsyncImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("history.search"); + + // Callback for the history function to provide results. + void SearchComplete(HistoryService::Handle request_handle, + history::QueryResults* results); +}; + +class AddUrlHistoryFunction : public HistoryFunction { + public: + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("history.addUrl"); +}; + +class DeleteAllHistoryFunction : public HistoryFunctionWithCallback { + public: + virtual bool RunAsyncImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("history.deleteAll"); + + // Callback for the history service to acknowledge deletion. + void DeleteComplete(); +}; + + +class DeleteUrlHistoryFunction : public HistoryFunction { + public: + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("history.deleteUrl"); +}; + +class DeleteRangeHistoryFunction : public HistoryFunctionWithCallback { + public: + virtual bool RunAsyncImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("history.deleteRange"); + + // Callback for the history service to acknowledge deletion. + void DeleteComplete(); +}; + +#endif // CHROME_BROWSER_HISTORY_HISTORY_EXTENSION_API_H_ diff --git a/chrome/browser/history/history_extension_apitest.cc b/chrome/browser/history/history_extension_apitest.cc new file mode 100644 index 0000000..129c9dc --- /dev/null +++ b/chrome/browser/history/history_extension_apitest.cc @@ -0,0 +1,58 @@ +// 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 "base/base_switches.h" +#include "base/command_line.h" +#include "chrome/browser/extensions/extension_apitest.h" +#include "net/base/mock_host_resolver.h" + +class HistoryExtensionApiTest : public ExtensionApiTest { + public: + virtual void SetUpInProcessBrowserTestFixture() { + ExtensionApiTest::SetUpInProcessBrowserTestFixture(); + + host_resolver()->AddRule("www.a.com", "127.0.0.1"); + host_resolver()->AddRule("www.b.com", "127.0.0.1"); + + ASSERT_TRUE(StartTestServer()); + } +}; + +// Full text search indexing sometimes exceeds a timeout. +// Fix this as part of crbug/76170. +IN_PROC_BROWSER_TEST_F(HistoryExtensionApiTest, DISABLED_MiscSearch) { + ASSERT_TRUE(RunExtensionSubtest("history", "misc_search.html")) << message_; +} + +IN_PROC_BROWSER_TEST_F(HistoryExtensionApiTest, TimedSearch) { + ASSERT_TRUE(RunExtensionSubtest("history", "timed_search.html")) << message_; +} + +#if defined(OS_WIN) +// Flakily times out on Win - See http://crbug.com/88318 +#define MAYBE_Delete FLAKY_Delete +#else +#define MAYBE_Delete Delete +#endif +IN_PROC_BROWSER_TEST_F(HistoryExtensionApiTest, MAYBE_Delete) { + ASSERT_TRUE(RunExtensionSubtest("history", "delete.html")) << message_; +} + +// See crbug.com/79074 +IN_PROC_BROWSER_TEST_F(HistoryExtensionApiTest, FLAKY_GetVisits) { + ASSERT_TRUE(RunExtensionSubtest("history", "get_visits.html")) << message_; +} + +#if defined(OS_WIN) +// Searching for a URL right after adding it fails on win XP. +// Fix this as part of crbug/76170. +#define MAYBE_SearchAfterAdd FLAKY_SearchAfterAdd +#else +#define MAYBE_SearchAfterAdd SearchAfterAdd +#endif + +IN_PROC_BROWSER_TEST_F(HistoryExtensionApiTest, MAYBE_SearchAfterAdd) { + ASSERT_TRUE(RunExtensionSubtest("history", "search_after_add.html")) + << message_; +} |