summaryrefslogtreecommitdiffstats
path: root/chrome/browser/history
diff options
context:
space:
mode:
authoraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-24 20:29:07 +0000
committeraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-24 20:29:07 +0000
commitd2e6bd6562fc25f6e01e333602c132ea14a1a17e (patch)
tree2eedba3e87d82c019fac8f934b87b423abbc2378 /chrome/browser/history
parent1a157553f8382a9c09cb8a5ccdfa798ac49cb10c (diff)
downloadchromium_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.cc405
-rw-r--r--chrome/browser/history/history_extension_api.h140
-rw-r--r--chrome/browser/history/history_extension_apitest.cc58
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_;
+}