summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-15 22:58:33 +0000
committeraa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-15 22:58:33 +0000
commitb83e4600fc1c2f1c42598f8d89dbd36d9415309d (patch)
tree4b2ee497813a59a91105f91f6a5329224da18eac
parenta9a2668386addfa40ff262005d396468f54b99e2 (diff)
downloadchromium_src-b83e4600fc1c2f1c42598f8d89dbd36d9415309d.zip
chromium_src-b83e4600fc1c2f1c42598f8d89dbd36d9415309d.tar.gz
chromium_src-b83e4600fc1c2f1c42598f8d89dbd36d9415309d.tar.bz2
First step to enable end-to-end testing of extensions through the
automation interface. This adds a method to turn on automation of extension API functions, plumbing that redirects API requests through the automation interface when appropriate, and a couple of UITests that exercise the functionality. See http://codereview.chromium.org/113277 for the original review. Review URL: http://codereview.chromium.org/115427 Patch from Joi Sigurdsson <joi.sigurdsson@gmail.com>. git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16207 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/automation/automation_extension_function.cc125
-rw-r--r--chrome/browser/automation/automation_extension_function.h53
-rw-r--r--chrome/browser/automation/automation_provider.cc12
-rw-r--r--chrome/browser/automation/automation_provider.h3
-rw-r--r--chrome/browser/browser.vcproj8
-rw-r--r--chrome/browser/extensions/extension_function.cc29
-rw-r--r--chrome/browser/extensions/extension_function.h81
-rw-r--r--chrome/browser/extensions/extension_function_dispatcher.cc108
-rw-r--r--chrome/browser/extensions/extension_function_dispatcher.h19
-rw-r--r--chrome/browser/extensions/extension_uitest.cc282
-rw-r--r--chrome/chrome.gyp5
-rw-r--r--chrome/test/automation/automation_messages_internal.h6
-rw-r--r--chrome/test/automation/automation_proxy.cc5
-rw-r--r--chrome/test/automation/automation_proxy.h6
-rw-r--r--chrome/test/automation/automation_proxy_uitest.cc156
-rw-r--r--chrome/test/automation/automation_proxy_uitest.h107
-rw-r--r--chrome/test/automation/extension_automation_constants.cc19
-rw-r--r--chrome/test/automation/extension_automation_constants.h24
-rw-r--r--chrome/test/data/extensions/uitest/roundtrip_api_call/manifest.json6
-rw-r--r--chrome/test/data/extensions/uitest/roundtrip_api_call/test.html9
-rw-r--r--chrome/test/data/extensions/uitest/simple_api_call/manifest.json6
-rw-r--r--chrome/test/data/extensions/uitest/simple_api_call/test.html5
22 files changed, 889 insertions, 185 deletions
diff --git a/chrome/browser/automation/automation_extension_function.cc b/chrome/browser/automation/automation_extension_function.cc
new file mode 100644
index 0000000..314718a
--- /dev/null
+++ b/chrome/browser/automation/automation_extension_function.cc
@@ -0,0 +1,125 @@
+// Copyright (c) 2009 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.
+
+// Implements AutomationExtensionFunction.
+
+#include "chrome/browser/automation/automation_extension_function.h"
+
+#include "base/json_reader.h"
+#include "base/json_writer.h"
+#include "chrome/browser/extensions/extension_function_dispatcher.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/test/automation/extension_automation_constants.h"
+
+// We are the only compilation unit outside of test code that needs the
+// definition of these constants. Including the .cc file here instead of
+// putting it into chrome.gyp (SCons builds on Linux don't like it when you
+// have the same file in two different projects) or linking to the test support
+// lib.
+#include "chrome/test/automation/extension_automation_constants.cc"
+
+bool AutomationExtensionFunction::enabled_ = false;
+
+void AutomationExtensionFunction::SetName(const std::string& name) {
+ name_ = name;
+}
+
+void AutomationExtensionFunction::SetArgs(const std::string& args) {
+ args_ = args;
+}
+
+const std::string AutomationExtensionFunction::GetResult() {
+ // Our API result passing is done through InterceptMessageFromExternalHost
+ return "";
+}
+
+const std::string AutomationExtensionFunction::GetError() {
+ // Our API result passing is done through InterceptMessageFromExternalHost
+ return "";
+}
+
+void AutomationExtensionFunction::Run() {
+ namespace keys = extension_automation_constants;
+
+ // We are being driven through automation, so we send the extension API
+ // request over to the automation host. We do this before decoding the
+ // 'args' JSON, otherwise we'd be decoding it only to encode it again.
+ DictionaryValue message_to_host;
+ message_to_host.SetString(keys::kAutomationNameKey, name_);
+ message_to_host.SetString(keys::kAutomationArgsKey, args_);
+ message_to_host.SetInteger(keys::kAutomationRequestIdKey, request_id_);
+ message_to_host.SetBoolean(keys::kAutomationHasCallbackKey, has_callback_);
+
+ std::string message;
+ JSONWriter::Write(&message_to_host, false, &message);
+ dispatcher_->render_view_host_->delegate()->ProcessExternalHostMessage(
+ message, keys::kAutomationOrigin, keys::kAutomationRequestTarget);
+}
+
+ExtensionFunction* AutomationExtensionFunction::Factory() {
+ return new AutomationExtensionFunction();
+}
+
+void AutomationExtensionFunction::SetEnabled(bool enabled) {
+ if (enabled) {
+ std::vector<std::string> function_names;
+ ExtensionFunctionDispatcher::GetAllFunctionNames(&function_names);
+
+ for (std::vector<std::string>::iterator it = function_names.begin();
+ it != function_names.end(); it++) {
+ // TODO(joi) Could make this a per-profile change rather than a global
+ // change. Could e.g. have the AutomationExtensionFunction store the
+ // profile pointer and dispatch to the original ExtensionFunction when the
+ // current profile is not that.
+ bool result = ExtensionFunctionDispatcher::OverrideFunction(
+ *it, AutomationExtensionFunction::Factory);
+ DCHECK(result);
+ }
+ } else {
+ ExtensionFunctionDispatcher::ResetFunctions();
+ }
+}
+
+bool AutomationExtensionFunction::InterceptMessageFromExternalHost(
+ RenderViewHost* view_host,
+ const std::string& message,
+ const std::string& origin,
+ const std::string& target) {
+ namespace keys = extension_automation_constants;
+
+ if (origin == keys::kAutomationOrigin &&
+ target == keys::kAutomationResponseTarget) {
+ // This is an extension API response being sent back via postMessage,
+ // so redirect it.
+ scoped_ptr<Value> message_value(JSONReader::Read(message, false));
+ DCHECK(message_value->IsType(Value::TYPE_DICTIONARY));
+ if (message_value->IsType(Value::TYPE_DICTIONARY)) {
+ DictionaryValue* message_dict =
+ reinterpret_cast<DictionaryValue*>(message_value.get());
+
+ int request_id = -1;
+ bool got_value = message_dict->GetInteger(keys::kAutomationRequestIdKey,
+ &request_id);
+ DCHECK(got_value);
+ if (got_value) {
+ std::string error;
+ bool success = !message_dict->GetString(keys::kAutomationErrorKey,
+ &error);
+
+ std::string response;
+ got_value = message_dict->GetString(keys::kAutomationResponseKey,
+ &response);
+ DCHECK(!success || got_value);
+
+ // TODO(joi) Once ExtensionFunctionDispatcher supports asynchronous
+ // functions, we should use that instead.
+ view_host->SendExtensionResponse(request_id, success,
+ response, error);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
diff --git a/chrome/browser/automation/automation_extension_function.h b/chrome/browser/automation/automation_extension_function.h
new file mode 100644
index 0000000..a399cd7
--- /dev/null
+++ b/chrome/browser/automation/automation_extension_function.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2009 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.
+
+// Defines AutomationExtensionFunction.
+
+#ifndef CHROME_BROWSER_AUTOMATION_AUTOMATION_EXTENSION_FUNCTION_H_
+#define CHROME_BROWSER_AUTOMATION_AUTOMATION_EXTENSION_FUNCTION_H_
+
+#include <string>
+
+#include "base/singleton.h"
+#include "chrome/browser/extensions/extension_function.h"
+
+class RenderViewHost;
+
+// An extension function that pipes the extension API call through the
+// automation interface, so that extensions can be tested using UITests.
+class AutomationExtensionFunction : public ExtensionFunction {
+ public:
+ AutomationExtensionFunction() { }
+
+ // ExtensionFunction implementation.
+ virtual void SetName(const std::string& name);
+ virtual void SetArgs(const std::string& args);
+ virtual const std::string GetResult();
+ virtual const std::string GetError();
+ virtual void Run();
+
+ static ExtensionFunction* Factory();
+
+ // If enabled, we set an instance of this function as the functor
+ // for all function names in ExtensionFunctionFactoryRegistry.
+ // If disabled, we restore the initial functions.
+ static void SetEnabled(bool enabled);
+
+ // Intercepts messages sent from the external host to check if they
+ // are actually responses to extension API calls. If they are, redirects
+ // the message to view_host->SendExtensionResponse and returns true,
+ // otherwise returns false to indicate the message was not intercepted.
+ static bool InterceptMessageFromExternalHost(RenderViewHost* view_host,
+ const std::string& message,
+ const std::string& origin,
+ const std::string& target);
+
+ private:
+ static bool enabled_;
+ std::string name_;
+ std::string args_;
+ DISALLOW_COPY_AND_ASSIGN(AutomationExtensionFunction);
+};
+
+#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_EXTENSION_FUNCTION_H_
diff --git a/chrome/browser/automation/automation_provider.cc b/chrome/browser/automation/automation_provider.cc
index 69ea1e9..c48f1eb 100644
--- a/chrome/browser/automation/automation_provider.cc
+++ b/chrome/browser/automation/automation_provider.cc
@@ -13,6 +13,7 @@
#include "chrome/app/chrome_dll_resource.h"
#include "chrome/browser/app_modal_dialog.h"
#include "chrome/browser/app_modal_dialog_queue.h"
+#include "chrome/browser/automation/automation_extension_function.h"
#include "chrome/browser/automation/automation_provider_list.h"
#include "chrome/browser/automation/url_request_failed_dns_job.h"
#include "chrome/browser/automation/url_request_mock_http_job.h"
@@ -1145,6 +1146,8 @@ void AutomationProvider::OnMessageReceived(const IPC::Message& message) {
SavePackageShouldPromptUser)
IPC_MESSAGE_HANDLER(AutomationMsg_WindowTitle,
GetWindowTitle)
+ IPC_MESSAGE_HANDLER(AutomationMsg_SetEnableExtensionAutomation,
+ SetEnableExtensionAutomation)
IPC_END_MESSAGE_MAP()
}
@@ -2695,7 +2698,10 @@ void AutomationProvider::OnMessageFromExternalHost(int handle,
return;
}
- view_host->ForwardMessageFromExternalHost(message, origin, target);
+ if (!AutomationExtensionFunction::InterceptMessageFromExternalHost(
+ view_host, message, origin, target)) {
+ view_host->ForwardMessageFromExternalHost(message, origin, target);
+ }
}
}
#endif // defined(OS_WIN) || defined(OS_LINUX)
@@ -2925,6 +2931,10 @@ void AutomationProvider::SavePackageShouldPromptUser(bool should_prompt) {
SavePackage::SetShouldPromptUser(should_prompt);
}
+void AutomationProvider::SetEnableExtensionAutomation(bool automation_enabled) {
+ AutomationExtensionFunction::SetEnabled(automation_enabled);
+}
+
#if defined(OS_WIN)
// TODO(port): Reposition_Params is win-specific. We'll need to port it.
void AutomationProvider::OnTabReposition(
diff --git a/chrome/browser/automation/automation_provider.h b/chrome/browser/automation/automation_provider.h
index f7aa80f..513caa9 100644
--- a/chrome/browser/automation/automation_provider.h
+++ b/chrome/browser/automation/automation_provider.h
@@ -430,6 +430,9 @@ class AutomationProvider : public base::RefCounted<AutomationProvider>,
void SavePackageShouldPromptUser(bool should_prompt);
+ // Enables extension automation (for e.g. UITests).
+ void SetEnableExtensionAutomation(bool automation_enabled);
+
void GetWindowTitle(int handle, string16* text);
// Convert a tab handle into a TabContents. If |tab| is non-NULL a pointer
diff --git a/chrome/browser/browser.vcproj b/chrome/browser/browser.vcproj
index bb869e6..94c5c43 100644
--- a/chrome/browser/browser.vcproj
+++ b/chrome/browser/browser.vcproj
@@ -850,6 +850,14 @@
>
</File>
<File
+ RelativePath=".\automation\automation_extension_function.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\automation\automation_extension_function.h"
+ >
+ </File>
+ <File
RelativePath=".\automation\automation_provider.cc"
>
</File>
diff --git a/chrome/browser/extensions/extension_function.cc b/chrome/browser/extensions/extension_function.cc
index bfc2f2c..f74fdb9 100644
--- a/chrome/browser/extensions/extension_function.cc
+++ b/chrome/browser/extensions/extension_function.cc
@@ -4,10 +4,33 @@
#include "chrome/browser/extensions/extension_function.h"
+#include "base/json_reader.h"
+#include "base/json_writer.h"
#include "base/logging.h"
#include "chrome/browser/extensions/extension_function_dispatcher.h"
-void ExtensionFunction::SendResponse(bool success) {
+void AsyncExtensionFunction::SetArgs(const std::string& args) {
+ DCHECK(!args_); // should only be called once
+ if (!args.empty()) {
+ JSONReader reader;
+ args_ = reader.JsonToValue(args, false, false);
+
+ // Since we do the serialization in the v8 extension, we should always get
+ // valid JSON.
+ if (!args_) {
+ DCHECK(false);
+ return;
+ }
+ }
+}
+
+const std::string AsyncExtensionFunction::GetResult() {
+ std::string json;
+ JSONWriter::Write(result_.get(), false, &json);
+ return json;
+}
+
+void AsyncExtensionFunction::SendResponse(bool success) {
if (bad_message_) {
dispatcher_->HandleBadMessage(this);
} else {
@@ -15,10 +38,10 @@ void ExtensionFunction::SendResponse(bool success) {
}
}
-std::string ExtensionFunction::extension_id() {
+std::string AsyncExtensionFunction::extension_id() {
return dispatcher_->extension_id();
}
-Profile* ExtensionFunction::profile() {
+Profile* AsyncExtensionFunction::profile() {
return dispatcher_->profile();
}
diff --git a/chrome/browser/extensions/extension_function.h b/chrome/browser/extensions/extension_function.h
index b9074b7..cbe85b9 100644
--- a/chrome/browser/extensions/extension_function.h
+++ b/chrome/browser/extensions/extension_function.h
@@ -20,31 +20,71 @@ class Profile;
} \
} while (0)
-// Base class for an extension function.
-// TODO(aa): This will have to become reference counted when we introduce APIs
-// that live beyond a single stack frame.
+// Abstract base class for extension functions the ExtensionFunctionDispatcher
+// knows how to dispatch to.
+//
+// TODO(aa): This will have to become reference counted when we introduce
+// APIs that live beyond a single stack frame.
class ExtensionFunction {
public:
- ExtensionFunction() : bad_message_(false) {}
- virtual ~ExtensionFunction() {}
+ ExtensionFunction() : request_id_(-1), has_callback_(false) {}
+
+ // Specifies the name of the function.
+ virtual void SetName(const std::string& name) { }
+
+ // Specifies the raw arguments to the function, as a JSON-encoded string.
+ virtual void SetArgs(const std::string& args) = 0;
+
+ // Retrieves the results of the function as a JSON-encoded string (may
+ // be empty).
+ virtual const std::string GetResult() = 0;
+
+ // Retrieves any error string from the function.
+ virtual const std::string GetError() = 0;
void set_dispatcher(ExtensionFunctionDispatcher* dispatcher) {
dispatcher_ = dispatcher;
}
- void set_args(Value* args) { args_ = args; }
void set_request_id(int request_id) { request_id_ = request_id; }
int request_id() { return request_id_; }
- Value* result() { return result_.get(); }
- const std::string& error() { return error_; }
-
- void set_has_callback(bool has_callback) { has_callback_ = has_callback; }
+ void set_has_callback(bool has_callback) { has_callback_ = has_callback; }
bool has_callback() { return has_callback_; }
- // Execute the API. Clients should call set_args() and set_callback_id()
- // before calling this method. Derived classes should populate result_ and
- // error_ before returning.
+ // Execute the API. Clients should call set_raw_args() and
+ // set_request_id() before calling this method. Derived classes should be
+ // ready to return raw_result() and error() before returning from this
+ // function.
+ virtual void Run() = 0;
+
+ protected:
+ // The dispatcher that will service this extension function call.
+ ExtensionFunctionDispatcher* dispatcher_;
+
+ // Id of this request, used to map the response back to the caller.
+ int request_id_;
+
+ // True if the js caller provides a callback function to receive the response
+ // of this call.
+ bool has_callback_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ExtensionFunction);
+};
+
+// Base class for an extension function that runs asynchronously *relative to
+// the browser's UI thread*.
+// TODO(aa) Remove this extra level of inheritance once the browser stops
+// parsing JSON (and instead uses custom serialization of Value objects).
+class AsyncExtensionFunction : public ExtensionFunction {
+ public:
+ AsyncExtensionFunction() : bad_message_(false) {}
+ virtual ~AsyncExtensionFunction() {}
+
+ virtual void SetArgs(const std::string& args);
+ virtual const std::string GetResult();
+ virtual const std::string GetError() { return error_; }
virtual void Run() = 0;
protected:
@@ -69,21 +109,10 @@ class ExtensionFunction {
// returning. The calling renderer process will be killed.
bool bad_message_;
- // The dispatcher that will service this extension function call.
- ExtensionFunctionDispatcher* dispatcher_;
-
private:
- // Id of this request, used to map the response back to the caller.
- int request_id_;
-
- // True if the js caller provides a callback function to receive the response
- // of this call.
- bool has_callback_;
-
- DISALLOW_COPY_AND_ASSIGN(ExtensionFunction);
+ DISALLOW_COPY_AND_ASSIGN(AsyncExtensionFunction);
};
-
// A SyncExtensionFunction is an ExtensionFunction that runs synchronously
// *relative to the browser's UI thread*. Note that this has nothing to do with
// running synchronously relative to the extension process. From the extension
@@ -91,7 +120,7 @@ class ExtensionFunction {
//
// This kind of function is convenient for implementing simple APIs that just
// need to interact with things on the browser UI thread.
-class SyncExtensionFunction : public ExtensionFunction {
+class SyncExtensionFunction : public AsyncExtensionFunction {
public:
SyncExtensionFunction() {}
diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc
index b6943e9..135e657 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.cc
+++ b/chrome/browser/extensions/extension_function_dispatcher.cc
@@ -4,8 +4,6 @@
#include "chrome/browser/extensions/extension_function_dispatcher.h"
-#include "base/json_reader.h"
-#include "base/json_writer.h"
#include "base/process_util.h"
#include "base/singleton.h"
#include "base/values.h"
@@ -23,16 +21,31 @@
namespace {
-// A pointer to a function that create an instance of an ExtensionFunction.
-typedef ExtensionFunction* (*ExtensionFunctionFactory)();
+// Template for defining ExtensionFunctionFactory.
+template<class T>
+ExtensionFunction* NewExtensionFunction() {
+ return new T();
+}
-// Contains a list of all known extension functions and allows clients to create
-// instances of them.
+// Contains a list of all known extension functions and allows clients to
+// create instances of them.
class FactoryRegistry {
public:
static FactoryRegistry* instance();
- FactoryRegistry();
+ FactoryRegistry() { ResetFunctions(); }
+
+ // Resets all functions to their default values.
+ void ResetFunctions();
+
+ // Adds all function names to 'names'.
void GetAllNames(std::vector<std::string>* names);
+
+ // Allows overriding of specific functions (e.g. for testing). Functions
+ // must be previously registered. Returns true if successful.
+ bool OverrideFunction(const std::string& name,
+ ExtensionFunctionFactory factory);
+
+ // Factory method for the ExtensionFunction registered as 'name'.
ExtensionFunction* NewFunction(const std::string& name);
private:
@@ -40,17 +53,11 @@ class FactoryRegistry {
FactoryMap factories_;
};
-// Template for defining ExtensionFunctionFactory.
-template<class T>
-ExtensionFunction* NewExtensionFunction() {
- return new T();
-}
-
FactoryRegistry* FactoryRegistry::instance() {
return Singleton<FactoryRegistry>::get();
}
-FactoryRegistry::FactoryRegistry() {
+void FactoryRegistry::ResetFunctions() {
// Register all functions here.
// Windows
@@ -63,10 +70,11 @@ FactoryRegistry::FactoryRegistry() {
factories_["CreateWindow"] = &NewExtensionFunction<CreateWindowFunction>;
factories_["UpdateWindow"] = &NewExtensionFunction<UpdateWindowFunction>;
factories_["RemoveWindow"] = &NewExtensionFunction<RemoveWindowFunction>;
-
+
// Tabs
factories_["GetTab"] = &NewExtensionFunction<GetTabFunction>;
- factories_["GetSelectedTab"] = &NewExtensionFunction<GetSelectedTabFunction>;
+ factories_["GetSelectedTab"] =
+ &NewExtensionFunction<GetSelectedTabFunction>;
factories_["GetAllTabsInWindow"] =
&NewExtensionFunction<GetAllTabsInWindowFunction>;
factories_["CreateTab"] = &NewExtensionFunction<CreateTabFunction>;
@@ -86,29 +94,42 @@ FactoryRegistry::FactoryRegistry() {
&NewExtensionFunction<GetBookmarkTreeFunction>;
factories_["SearchBookmarks"] =
&NewExtensionFunction<SearchBookmarksFunction>;
- factories_["RemoveBookmark"] = &NewExtensionFunction<RemoveBookmarkFunction>;
- factories_["CreateBookmark"] = &NewExtensionFunction<CreateBookmarkFunction>;
+ factories_["RemoveBookmark"] =
+ &NewExtensionFunction<RemoveBookmarkFunction>;
+ factories_["CreateBookmark"] =
+ &NewExtensionFunction<CreateBookmarkFunction>;
factories_["MoveBookmark"] = &NewExtensionFunction<MoveBookmarkFunction>;
factories_["SetBookmarkTitle"] =
&NewExtensionFunction<SetBookmarkTitleFunction>;
}
-void FactoryRegistry::GetAllNames(
- std::vector<std::string>* names) {
- for (FactoryMap::iterator iter = factories_.begin(); iter != factories_.end();
- ++iter) {
+void FactoryRegistry::GetAllNames(std::vector<std::string>* names) {
+ for (FactoryMap::iterator iter = factories_.begin();
+ iter != factories_.end(); ++iter) {
names->push_back(iter->first);
}
}
+bool FactoryRegistry::OverrideFunction(const std::string& name,
+ ExtensionFunctionFactory factory) {
+ FactoryMap::iterator iter = factories_.find(name);
+ if (iter == factories_.end()) {
+ return false;
+ } else {
+ iter->second = factory;
+ return true;
+ }
+}
+
ExtensionFunction* FactoryRegistry::NewFunction(const std::string& name) {
FactoryMap::iterator iter = factories_.find(name);
DCHECK(iter != factories_.end());
- return iter->second();
+ ExtensionFunction* function = iter->second();
+ function->SetName(name);
+ return function;
}
-};
-
+}; // namespace
// ExtensionFunctionDispatcher -------------------------------------------------
@@ -117,6 +138,15 @@ void ExtensionFunctionDispatcher::GetAllFunctionNames(
FactoryRegistry::instance()->GetAllNames(names);
}
+bool ExtensionFunctionDispatcher::OverrideFunction(
+ const std::string& name, ExtensionFunctionFactory factory) {
+ return FactoryRegistry::instance()->OverrideFunction(name, factory);
+}
+
+void ExtensionFunctionDispatcher::ResetFunctions() {
+ FactoryRegistry::instance()->ResetFunctions();
+}
+
ExtensionFunctionDispatcher::ExtensionFunctionDispatcher(
RenderViewHost* render_view_host,
Delegate* delegate,
@@ -124,9 +154,8 @@ ExtensionFunctionDispatcher::ExtensionFunctionDispatcher(
: render_view_host_(render_view_host),
delegate_(delegate),
extension_id_(extension_id) {
- DCHECK(delegate);
RenderProcessHost* process = render_view_host_->process();
- ExtensionMessageService* message_service =
+ ExtensionMessageService* message_service =
ExtensionMessageService::GetInstance(profile()->GetRequestContext());
DCHECK(process);
DCHECK(message_service);
@@ -134,6 +163,8 @@ ExtensionFunctionDispatcher::ExtensionFunctionDispatcher(
}
Browser* ExtensionFunctionDispatcher::GetBrowser() {
+ DCHECK(delegate_);
+
Browser* retval = delegate_->GetBrowser();
DCHECK(retval);
return retval;
@@ -143,25 +174,12 @@ void ExtensionFunctionDispatcher::HandleRequest(const std::string& name,
const std::string& args,
int request_id,
bool has_callback) {
- scoped_ptr<Value> value;
- if (!args.empty()) {
- JSONReader reader;
- value.reset(reader.JsonToValue(args, false, false));
-
- // Since we do the serialization in the v8 extension, we should always get
- // valid JSON.
- if (!value.get()) {
- DCHECK(false);
- return;
- }
- }
-
// TODO(aa): This will get a bit more complicated when we support functions
// that live longer than the stack frame.
scoped_ptr<ExtensionFunction> function(
FactoryRegistry::instance()->NewFunction(name));
function->set_dispatcher(this);
- function->set_args(value.get());
+ function->SetArgs(args);
function->set_request_id(request_id);
function->set_has_callback(has_callback);
function->Run();
@@ -169,14 +187,8 @@ void ExtensionFunctionDispatcher::HandleRequest(const std::string& name,
void ExtensionFunctionDispatcher::SendResponse(ExtensionFunction* function,
bool success) {
- std::string json;
-
- // Some functions might not need to return any results.
- if (success && function->result())
- JSONWriter::Write(function->result(), false, &json);
-
render_view_host_->SendExtensionResponse(function->request_id(), success,
- json, function->error());
+ function->GetResult(), function->GetError());
}
void ExtensionFunctionDispatcher::HandleBadMessage(ExtensionFunction* api) {
diff --git a/chrome/browser/extensions/extension_function_dispatcher.h b/chrome/browser/extensions/extension_function_dispatcher.h
index fed54791..a9af552 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.h
+++ b/chrome/browser/extensions/extension_function_dispatcher.h
@@ -5,6 +5,7 @@
#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_FUNCTION_DISPATCHER_H_
#define CHROME_BROWSER_EXTENSIONS_EXTENSION_FUNCTION_DISPATCHER_H_
+#include <map>
#include <string>
#include <vector>
@@ -16,6 +17,9 @@ class Profile;
class RenderViewHost;
class RenderViewHostDelegate;
+// A factory function for creating new ExtensionFunction instances.
+typedef ExtensionFunction* (*ExtensionFunctionFactory)();
+
// ExtensionFunctionDispatcher receives requests to execute functions from
// Chromium extensions running in a RenderViewHost and dispatches them to the
// appropriate handler. It lives entirely on the UI thread.
@@ -29,6 +33,14 @@ class ExtensionFunctionDispatcher {
// Gets a list of all known extension function names.
static void GetAllFunctionNames(std::vector<std::string>* names);
+ // Override a previously registered function. Returns true if successful,
+ // false if no such function was registered.
+ static bool OverrideFunction(const std::string& name,
+ ExtensionFunctionFactory factory);
+
+ // Resets all functions to their initial implementation.
+ static void ResetFunctions();
+
ExtensionFunctionDispatcher(RenderViewHost* render_view_host,
Delegate* delegate,
const std::string& extension_id);
@@ -60,6 +72,13 @@ class ExtensionFunctionDispatcher {
Delegate* delegate_;
std::string extension_id_;
+
+ // AutomationExtensionFunction requires access to the RenderViewHost
+ // associated with us. We make it a friend rather than exposing the
+ // RenderViewHost as a public method as we wouldn't want everyone to
+ // start assuming a 1:1 relationship between us and RenderViewHost,
+ // whereas AutomationExtensionFunction is by necessity "tight" with us.
+ friend class AutomationExtensionFunction;
};
#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_FUNCTION_DISPATCHER_H_
diff --git a/chrome/browser/extensions/extension_uitest.cc b/chrome/browser/extensions/extension_uitest.cc
new file mode 100644
index 0000000..da075ca
--- /dev/null
+++ b/chrome/browser/extensions/extension_uitest.cc
@@ -0,0 +1,282 @@
+// Copyright (c) 2009 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/command_line.h"
+#include "base/gfx/rect.h"
+#include "base/json_reader.h"
+#include "base/json_writer.h"
+#include "base/values.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/automation/automation_proxy_uitest.h"
+#include "chrome/test/automation/extension_automation_constants.h"
+#include "chrome/test/automation/tab_proxy.h"
+#include "chrome/test/ui/ui_test.h"
+#include "googleurl/src/gurl.h"
+
+namespace {
+
+static const char kTestDirectorySimpleApiCall[] =
+ "extensions/uitest/simple_api_call";
+static const char kTestDirectoryRoundtripApiCall[] =
+ "extensions/uitest/roundtrip_api_call";
+
+// Base class to test extensions almost end-to-end by including browser
+// startup, manifest parsing, and the actual process model in the
+// equation. This would also let you write UITests that test individual
+// Chrome Extensions as running in Chrome. Takes over implementation of
+// extension API calls so that behavior can be tested deterministically
+// through code, instead of having to contort the browser into a state
+// suitable for testing.
+template <class ParentTestType>
+class ExtensionUITest : public ParentTestType {
+ public:
+ explicit ExtensionUITest(const std::string& extension_path) {
+ launch_arguments_.AppendSwitch(switches::kEnableExtensions);
+
+ FilePath filename(test_data_directory_);
+ filename = filename.AppendASCII(extension_path);
+ launch_arguments_.AppendSwitchWithValue(switches::kLoadExtension,
+ filename.value());
+ }
+
+ void SetUp() {
+ ParentTestType::SetUp();
+ automation()->SetEnableExtensionAutomation(true);
+ }
+
+ void TearDown() {
+ automation()->SetEnableExtensionAutomation(false);
+ ParentTestType::TearDown();
+ }
+
+ void TestWithURL(const GURL& url) {
+ HWND external_tab_container = NULL;
+ scoped_ptr<TabProxy> tab(automation()->CreateExternalTab(NULL, gfx::Rect(),
+ WS_POPUP, false, &external_tab_container));
+ ASSERT_TRUE(tab != NULL);
+ ASSERT_NE(FALSE, ::IsWindow(external_tab_container));
+ DoAdditionalPreNavigateSetup(tab.get());
+
+ // We explicitly do not make this a toolstrip in the extension manifest,
+ // so that the test can control when it gets loaded, and so that we test
+ // the intended behavior that tabs should be able to show extension pages
+ // (useful for development etc.)
+ tab->NavigateInExternalTab(url);
+ EXPECT_EQ(true, ExternalTabMessageLoop(external_tab_container, 5000));
+ // Since the tab goes away lazily, wait a bit.
+ PlatformThread::Sleep(1000);
+ EXPECT_FALSE(tab->is_valid());
+ }
+
+ // Override if you need additional stuff before we navigate the page.
+ virtual void DoAdditionalPreNavigateSetup(TabProxy* tab) {
+ }
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(ExtensionUITest);
+};
+
+// For tests that only need to check for a single postMessage
+// being received from the tab in Chrome. These tests can send a message
+// to the tab before receiving the new message, but there will not be
+// a chance to respond by sending a message from the test to the tab after
+// the postMessage is received.
+typedef ExtensionUITest<ExternalTabTestType> SingleMessageExtensionUITest;
+
+// A test that loads a basic extension that makes an API call that does
+// not require a response.
+class SimpleApiCallExtensionTest : public SingleMessageExtensionUITest {
+ public:
+ SimpleApiCallExtensionTest()
+ : SingleMessageExtensionUITest(kTestDirectorySimpleApiCall) {
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SimpleApiCallExtensionTest);
+};
+
+// TODO(port) Should become portable once ExternalTabMessageLoop is ported.
+#if defined(OS_WIN)
+TEST_F(SimpleApiCallExtensionTest, RunTest) {
+ namespace keys = extension_automation_constants;
+
+ TestWithURL(GURL(
+ "chrome-extension://77774444789ABCDEF0123456789ABCDEF0123456/test.html"));
+ AutomationProxyForExternalTab* proxy =
+ static_cast<AutomationProxyForExternalTab*>(automation());
+ ASSERT_GT(proxy->messages_received(), 0);
+
+ // Using EXPECT_TRUE rather than EXPECT_EQ as the compiler (VC++) isn't
+ // finding the right match for EqHelper.
+ EXPECT_TRUE(proxy->origin() == keys::kAutomationOrigin);
+ EXPECT_TRUE(proxy->target() == keys::kAutomationRequestTarget);
+
+ scoped_ptr<Value> message_value(JSONReader::Read(proxy->message(), false));
+ ASSERT_TRUE(message_value->IsType(Value::TYPE_DICTIONARY));
+ DictionaryValue* message_dict =
+ reinterpret_cast<DictionaryValue*>(message_value.get());
+ std::string result;
+ message_dict->GetString(keys::kAutomationNameKey, &result);
+ EXPECT_EQ(result, "RemoveTab");
+
+ result = "";
+ message_dict->GetString(keys::kAutomationArgsKey, &result);
+ EXPECT_NE(result, "");
+
+ int callback_id = 0xBAADF00D;
+ message_dict->GetInteger(keys::kAutomationRequestIdKey, &callback_id);
+ EXPECT_NE(callback_id, 0xBAADF00D);
+
+ bool has_callback = true;
+ EXPECT_TRUE(message_dict->GetBoolean(keys::kAutomationHasCallbackKey,
+ &has_callback));
+ EXPECT_FALSE(has_callback);
+}
+#endif // defined(OS_WIN)
+
+// A base class for an automation proxy that checks several messages in
+// a row.
+class MultiMessageAutomationProxy : public AutomationProxyForExternalTab {
+ public:
+ explicit MultiMessageAutomationProxy(int execution_timeout)
+ : AutomationProxyForExternalTab(execution_timeout) {
+ }
+
+ // Call when testing with the current tab is finished.
+ void Quit() {
+ PostQuitMessage(0);
+ }
+
+ protected:
+ virtual void OnMessageReceived(const IPC::Message& msg) {
+ IPC_BEGIN_MESSAGE_MAP(MultiMessageAutomationProxy, msg)
+ IPC_MESSAGE_HANDLER(AutomationMsg_DidNavigate,
+ AutomationProxyForExternalTab::OnDidNavigate)
+ IPC_MESSAGE_HANDLER(AutomationMsg_ForwardMessageToExternalHost,
+ OnForwardMessageToExternalHost)
+ IPC_END_MESSAGE_MAP()
+ }
+
+ void OnForwardMessageToExternalHost(int handle,
+ const std::string& message,
+ const std::string& origin,
+ const std::string& target) {
+ messages_received_++;
+ message_ = message;
+ origin_ = origin;
+ target_ = target;
+ HandleMessageFromChrome();
+ }
+
+ // Override to do your custom checking and initiate any custom actions
+ // needed in your particular unit test.
+ virtual void HandleMessageFromChrome() = 0;
+};
+
+// This proxy is specific to RoundtripApiCallExtensionTest.
+class RoundtripAutomationProxy : public MultiMessageAutomationProxy {
+ public:
+ explicit RoundtripAutomationProxy(int execution_timeout)
+ : MultiMessageAutomationProxy(execution_timeout),
+ tab_(NULL) {
+ }
+
+ // Must set before initiating test.
+ TabProxy* tab_;
+
+ protected:
+ virtual void HandleMessageFromChrome() {
+ namespace keys = extension_automation_constants;
+
+ ASSERT_TRUE(tab_ != NULL);
+ ASSERT_TRUE(messages_received_ == 1 || messages_received_ == 2);
+
+ // Using EXPECT_TRUE rather than EXPECT_EQ as the compiler (VC++) isn't
+ // finding the right match for EqHelper.
+ EXPECT_TRUE(origin_ == keys::kAutomationOrigin);
+ EXPECT_TRUE(target_ == keys::kAutomationRequestTarget);
+
+ scoped_ptr<Value> message_value(JSONReader::Read(message_, false));
+ ASSERT_TRUE(message_value->IsType(Value::TYPE_DICTIONARY));
+ DictionaryValue* request_dict =
+ static_cast<DictionaryValue*>(message_value.get());
+ std::string function_name;
+ ASSERT_TRUE(request_dict->GetString(keys::kAutomationNameKey,
+ &function_name));
+ int request_id = -2;
+ EXPECT_TRUE(request_dict->GetInteger(keys::kAutomationRequestIdKey,
+ &request_id));
+ bool has_callback = false;
+ EXPECT_TRUE(request_dict->GetBoolean(keys::kAutomationHasCallbackKey,
+ &has_callback));
+
+ if (messages_received_ == 1) {
+ EXPECT_EQ(function_name, "GetLastFocusedWindow");
+ EXPECT_GE(request_id, 0);
+ EXPECT_TRUE(has_callback);
+
+ DictionaryValue response_dict;
+ EXPECT_TRUE(response_dict.SetInteger(keys::kAutomationRequestIdKey,
+ request_id));
+ EXPECT_TRUE(response_dict.SetString(keys::kAutomationResponseKey, "42"));
+
+ std::string response_json;
+ JSONWriter::Write(&response_dict, false, &response_json);
+
+ tab_->HandleMessageFromExternalHost(
+ response_json,
+ keys::kAutomationOrigin,
+ keys::kAutomationResponseTarget);
+ } else if (messages_received_ == 2) {
+ EXPECT_EQ(function_name, "RemoveTab");
+ EXPECT_FALSE(has_callback);
+
+ std::string args;
+ EXPECT_TRUE(request_dict->GetString(keys::kAutomationArgsKey, &args));
+ EXPECT_NE(args.find("42"), -1);
+
+ Quit();
+ } else {
+ Quit();
+ FAIL();
+ }
+ }
+};
+
+class RoundtripApiCallExtensionTest
+ : public ExtensionUITest<
+ CustomAutomationProxyTest<RoundtripAutomationProxy>> {
+ public:
+ RoundtripApiCallExtensionTest()
+ : ExtensionUITest<
+ CustomAutomationProxyTest<
+ RoundtripAutomationProxy> >(kTestDirectoryRoundtripApiCall) {
+ }
+
+ void DoAdditionalPreNavigateSetup(TabProxy* tab) {
+ RoundtripAutomationProxy* proxy =
+ static_cast<RoundtripAutomationProxy*>(automation());
+ proxy->tab_ = tab;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RoundtripApiCallExtensionTest);
+};
+
+// TODO(port) Should become portable once
+// ExternalTabMessageLoop is ported.
+#if defined(OS_WIN)
+TEST_F(RoundtripApiCallExtensionTest, RunTest) {
+ TestWithURL(GURL(
+ "chrome-extension://66664444789ABCDEF0123456789ABCDEF0123456/test.html"));
+ RoundtripAutomationProxy* proxy =
+ static_cast<RoundtripAutomationProxy*>(automation());
+
+ // Validation is done in the RoundtripAutomationProxy, so we just check
+ // something basic here.
+ EXPECT_EQ(proxy->messages_received(), 2);
+}
+#endif // defined(OS_WIN)
+
+} // namespace
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index 32890b6..1c6dafa 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -620,6 +620,8 @@
'browser/automation/automation_autocomplete_edit_tracker.h',
'browser/automation/automation_browser_tracker.h',
'browser/automation/automation_constrained_window_tracker.h',
+ 'browser/automation/automation_extension_function.h',
+ 'browser/automation/automation_extension_function.cc',
'browser/automation/automation_provider.cc',
'browser/automation/automation_provider.h',
'browser/automation/automation_provider_list.cc',
@@ -2439,6 +2441,8 @@
'test/automation/browser_proxy.h',
'test/automation/constrained_window_proxy.cc',
'test/automation/constrained_window_proxy.h',
+ 'test/automation/extension_automation_constants.h',
+ 'test/automation/extension_automation_constants.cc',
'test/automation/tab_proxy.cc',
'test/automation/tab_proxy.h',
'test/automation/window_proxy.cc',
@@ -2689,6 +2693,7 @@
}, { # else: OS != "win"
'sources!': [
# TODO(port)? (Most of these include windows.h or similar.)
+ 'browser/extensions/extension_uitest.cc',
'browser/printing/printing_layout_uitest.cc',
'browser/ssl/ssl_uitest.cc',
'browser/views/find_bar_win_uitest.cc',
diff --git a/chrome/test/automation/automation_messages_internal.h b/chrome/test/automation/automation_messages_internal.h
index decfc24..eaf5c4a 100644
--- a/chrome/test/automation/automation_messages_internal.h
+++ b/chrome/test/automation/automation_messages_internal.h
@@ -916,4 +916,10 @@ IPC_BEGIN_MESSAGES(Automation)
// value is the number of windows.
IPC_SYNC_MESSAGE_ROUTED0_1(AutomationMsg_NormalBrowserWindowCount, int)
+ // Used to put the browser into "extension automation mode" for the
+ // current profile, or turn off the mode.
+ IPC_SYNC_MESSAGE_ROUTED1_0(AutomationMsg_SetEnableExtensionAutomation,
+ bool /* true to enable extension automation */)
+
+
IPC_END_MESSAGES(Automation)
diff --git a/chrome/test/automation/automation_proxy.cc b/chrome/test/automation/automation_proxy.cc
index 0c50b4c..3f6508b 100644
--- a/chrome/test/automation/automation_proxy.cc
+++ b/chrome/test/automation/automation_proxy.cc
@@ -215,6 +215,11 @@ bool AutomationProxy::SavePackageShouldPromptUser(bool should_prompt) {
return Send(new AutomationMsg_SavePackageShouldPromptUser(0, should_prompt));
}
+bool AutomationProxy::SetEnableExtensionAutomation(bool enable_automation) {
+ return Send(
+ new AutomationMsg_SetEnableExtensionAutomation(0, enable_automation));
+}
+
bool AutomationProxy::GetBrowserWindowCount(int* num_windows) {
if (!num_windows) {
NOTREACHED();
diff --git a/chrome/test/automation/automation_proxy.h b/chrome/test/automation/automation_proxy.h
index abe4334..0b8ad57 100644
--- a/chrome/test/automation/automation_proxy.h
+++ b/chrome/test/automation/automation_proxy.h
@@ -158,6 +158,12 @@ class AutomationProxy : public IPC::Channel::Listener,
// sent.
bool SavePackageShouldPromptUser(bool should_prompt);
+ // Turn extension automation mode on and off. When extension automation
+ // mode is turned on, the automation host can overtake extension API calls
+ // e.g. to make UI tests for extensions easier to write. Returns true if
+ // the message is successfully sent.
+ bool SetEnableExtensionAutomation(bool enable_automation);
+
// Returns the ID of the automation IPC channel, so that it can be
// passed to the app as a launch parameter.
const std::wstring& channel_id() const { return channel_id_; }
diff --git a/chrome/test/automation/automation_proxy_uitest.cc b/chrome/test/automation/automation_proxy_uitest.cc
index a9fd7ee..ef7001c 100644
--- a/chrome/test/automation/automation_proxy_uitest.cc
+++ b/chrome/test/automation/automation_proxy_uitest.cc
@@ -15,6 +15,7 @@
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/json_value_serializer.h"
+#include "chrome/test/automation/automation_proxy_uitest.h"
#include "chrome/test/automation/constrained_window_proxy.h"
#include "chrome/test/automation/browser_proxy.h"
#include "chrome/test/automation/tab_proxy.h"
@@ -32,28 +33,6 @@ class AutomationProxyTest : public UITest {
}
};
-class AutomationProxyVisibleTest : public UITest {
- protected:
- AutomationProxyVisibleTest() {
- show_window_ = true;
- }
-};
-
-template <class AutomationProxyClass>
-class CustomAutomationProxyTest : public AutomationProxyVisibleTest {
- protected:
- CustomAutomationProxyTest() {
- }
-
- // Override UITest's CreateAutomationProxy to provide our the unit test
- // with our special implementation of AutomationProxy.
- // This function is called from within UITest::LaunchBrowserAndServer.
- virtual AutomationProxy* CreateAutomationProxy(int execution_timeout) {
- AutomationProxyClass* proxy = new AutomationProxyClass(execution_timeout);
- return proxy;
- }
-};
-
TEST_F(AutomationProxyTest, GetBrowserWindowCount) {
int window_count = 0;
EXPECT_TRUE(automation()->GetBrowserWindowCount(&window_count));
@@ -691,16 +670,16 @@ TEST_F(AutomationProxyTest, CantEscapeByOnloadMoveto) {
}
#endif // defined(OS_WIN)
-
-// TODO(port): Remove HWND if possible.
+// TODO(port): Remove HWND if possible
#if defined(OS_WIN)
-// Creates a top-level window, makes the |external_tab_window| a child
-// of that window and displays them. After displaying the windows the function
-// enters a message loop that processes window messages as well as calling
-// MessageLoop::current()->RunAllPending() to process any incoming IPC messages.
-// The time_to_wait parameter is the maximum time the loop will run.
-// To end the loop earlier, post a quit message to the thread.
-bool ExternalTabHandler(HWND external_tab_window, int time_to_wait) {
+// Creates a top-level window, makes the |external_tab_window| a child of
+// that window and displays them. After displaying the windows the
+// function enters a message loop that processes window messages as well
+// as calling MessageLoop::current()->RunAllPending() to process any
+// incoming IPC messages. The time_to_wait_ms parameter is the maximum
+// time the loop will run. To end the loop earlier, post a quit message to
+// the thread.
+bool ExternalTabMessageLoop(HWND external_tab_window, int time_to_wait_ms) {
static const wchar_t class_name[] = L"External_Tab_UI_Test_Class";
static const wchar_t window_title[] = L"External Tab Tester";
@@ -738,7 +717,7 @@ bool ExternalTabHandler(HWND external_tab_window, int time_to_wait) {
const int kTimerIdQuit = 100;
const int kTimerIdProcessPendingMessages = 101;
- ::SetTimer(external_tab_ui_parent, kTimerIdQuit, time_to_wait, NULL);
+ ::SetTimer(external_tab_ui_parent, kTimerIdQuit, time_to_wait_ms, NULL);
// Process pending messages every 50 milliseconds
::SetTimer(external_tab_ui_parent, kTimerIdProcessPendingMessages, 50, NULL);
@@ -781,83 +760,46 @@ bool ExternalTabHandler(HWND external_tab_window, int time_to_wait) {
return true;
}
-// A single-use AutomationProxy implementation that's good
-// for a single navigation and a single ForwardMessageToExternalHost
-// message. Once the ForwardMessageToExternalHost message is received
-// the class posts a quit message to the thread on which the message
-// was received.
-class AutomationProxyForExternalTab : public AutomationProxy {
- public:
- AutomationProxyForExternalTab(int execution_timeout)
- : AutomationProxy(execution_timeout),
- messages_received_(0),
- navigate_complete_(false) {
- }
-
- int messages_received() const {
- return messages_received_;
- }
-
- const std::string& message() const {
- return message_;
- }
-
- const std::string& origin() const {
- return origin_;
- }
-
- const std::string& target() const {
- return target_;
- }
-
- // Waits for the DidNavigate event to be processed on the current thread.
- // Returns true if the event arrived, false if there was a timeout.
- bool WaitForNavigationComplete(int max_time_to_wait_ms) {
- base::TimeTicks start(base::TimeTicks::Now());
- while (!navigate_complete_) {
- PlatformThread::Sleep(50);
- MessageLoop::current()->RunAllPending();
- base::TimeTicks end(base::TimeTicks::Now());
- base::TimeDelta delta = end - start;
- if (static_cast<int>(delta.InMilliseconds()) > max_time_to_wait_ms)
- return false;
- }
- return true;
- }
-
- protected:
- virtual void OnMessageReceived(const IPC::Message& msg) {
- IPC_BEGIN_MESSAGE_MAP(AutomationProxyForExternalTab, msg)
- IPC_MESSAGE_HANDLER(AutomationMsg_DidNavigate, OnDidNavigate)
- IPC_MESSAGE_HANDLER(AutomationMsg_ForwardMessageToExternalHost,
- OnForwardMessageToExternalHost)
- IPC_END_MESSAGE_MAP()
- }
-
- void OnDidNavigate(int tab_handle, int navigation_type, int relative_offset,
- const GURL& url) {
- navigate_complete_ = true;
- }
+AutomationProxyForExternalTab::AutomationProxyForExternalTab(
+ int execution_timeout)
+ : AutomationProxy(execution_timeout),
+ messages_received_(0),
+ navigate_complete_(false) {
+}
- void OnForwardMessageToExternalHost(int tab_handle,
- const std::string& message,
- const std::string& origin,
- const std::string& target) {
- messages_received_++;
- message_ = message;
- origin_ = origin;
- target_ = target;
- PostQuitMessage(0);
+bool AutomationProxyForExternalTab::WaitForNavigationComplete(
+ int max_time_to_wait_ms) {
+ base::TimeTicks start(base::TimeTicks::Now());
+ while (!navigate_complete_) {
+ PlatformThread::Sleep(50);
+ MessageLoop::current()->RunAllPending();
+ base::TimeTicks end(base::TimeTicks::Now());
+ base::TimeDelta delta = end - start;
+ if (static_cast<int>(delta.InMilliseconds()) > max_time_to_wait_ms)
+ return false;
}
+ return true;
+}
- protected:
- bool navigate_complete_;
- int messages_received_;
- std::string message_, origin_, target_;
-};
+void AutomationProxyForExternalTab::OnMessageReceived(const IPC::Message& msg) {
+ IPC_BEGIN_MESSAGE_MAP(AutomationProxyForExternalTab, msg)
+ IPC_MESSAGE_HANDLER(AutomationMsg_DidNavigate, OnDidNavigate)
+ IPC_MESSAGE_HANDLER(AutomationMsg_ForwardMessageToExternalHost,
+ OnForwardMessageToExternalHost)
+ IPC_END_MESSAGE_MAP()
+}
-typedef CustomAutomationProxyTest<AutomationProxyForExternalTab>
- ExternalTabTestType;
+void AutomationProxyForExternalTab::OnForwardMessageToExternalHost(
+ int handle,
+ const std::string& message,
+ const std::string& origin,
+ const std::string& target) {
+ messages_received_++;
+ message_ = message;
+ origin_ = origin;
+ target_ = target;
+ PostQuitMessage(0);
+}
TEST_F(ExternalTabTestType, CreateExternalTab) {
HWND external_tab_container = NULL;
@@ -867,7 +809,7 @@ TEST_F(ExternalTabTestType, CreateExternalTab) {
EXPECT_NE(FALSE, ::IsWindow(external_tab_container));
if (tab != NULL) {
tab->NavigateInExternalTab(GURL(L"http://www.google.com"));
- EXPECT_EQ(true, ExternalTabHandler(external_tab_container, 1000));
+ EXPECT_EQ(true, ExternalTabMessageLoop(external_tab_container, 1000));
// Since the tab goes away lazily, wait a bit
PlatformThread::Sleep(1000);
EXPECT_FALSE(tab->is_valid());
@@ -925,7 +867,7 @@ TEST_F(ExternalTabTestType, ExternalTabPostMessage) {
tab->HandleMessageFromExternalHost("Hello from gtest", "null", "*");
- EXPECT_TRUE(ExternalTabHandler(external_tab_container, 10000));
+ EXPECT_TRUE(ExternalTabMessageLoop(external_tab_container, 10000));
EXPECT_NE(0, proxy->messages_received());
if (proxy->messages_received()) {
diff --git a/chrome/test/automation/automation_proxy_uitest.h b/chrome/test/automation/automation_proxy_uitest.h
new file mode 100644
index 0000000..e5e593a
--- /dev/null
+++ b/chrome/test/automation/automation_proxy_uitest.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2006-2008 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_TEST_AUTOMATION_AUTOMATION_PROXY_UITEST_H__
+#define CHROME_TEST_AUTOMATION_AUTOMATION_PROXY_UITEST_H__
+
+#include <string>
+
+#include "base/message_loop.h"
+#include "base/platform_thread.h"
+#include "base/time.h"
+#include "chrome/test/automation/automation_proxy.h"
+#include "chrome/test/ui/ui_test.h"
+#include "googleurl/src/gurl.h"
+
+// Base class for automation proxy testing.
+class AutomationProxyVisibleTest : public UITest {
+ protected:
+ AutomationProxyVisibleTest() {
+ show_window_ = true;
+ }
+};
+
+// Automation proxy UITest that allows tests to override the automation
+// proxy used by the UITest base class.
+template <class AutomationProxyClass>
+class CustomAutomationProxyTest : public AutomationProxyVisibleTest {
+ protected:
+ CustomAutomationProxyTest() {
+ }
+
+ // Override UITest's CreateAutomationProxy to provide our the unit test
+ // with our special implementation of AutomationProxy.
+ // This function is called from within UITest::LaunchBrowserAndServer.
+ virtual AutomationProxy* CreateAutomationProxy(int execution_timeout) {
+ AutomationProxyClass* proxy = new AutomationProxyClass(execution_timeout);
+ return proxy;
+ }
+};
+
+// A single-use AutomationProxy implementation that's good
+// for a single navigation and a single ForwardMessageToExternalHost
+// message. Once the ForwardMessageToExternalHost message is received
+// the class posts a quit message to the thread on which the message
+// was received.
+class AutomationProxyForExternalTab : public AutomationProxy {
+ public:
+ explicit AutomationProxyForExternalTab(int execution_timeout);
+
+ int messages_received() const {
+ return messages_received_;
+ }
+
+ const std::string& message() const {
+ return message_;
+ }
+
+ const std::string& origin() const {
+ return origin_;
+ }
+
+ const std::string& target() const {
+ return target_;
+ }
+
+ // Waits for the DidNavigate event to be processed on the current thread.
+ // Returns true if the event arrived, false if there was a timeout.
+ bool WaitForNavigationComplete(int max_time_to_wait_ms);
+
+ protected:
+ virtual void OnMessageReceived(const IPC::Message& msg);
+
+ void OnDidNavigate(int tab_handle, int navigation_type, int relative_offset,
+ const GURL& url) {
+ navigate_complete_ = true;
+ }
+
+ void OnForwardMessageToExternalHost(int handle,
+ const std::string& message,
+ const std::string& origin,
+ const std::string& target);
+
+ protected:
+ bool navigate_complete_;
+ int messages_received_;
+ std::string message_, origin_, target_;
+};
+
+// A test harness for testing external tabs.
+typedef CustomAutomationProxyTest<AutomationProxyForExternalTab>
+ ExternalTabTestType;
+
+#if defined(OS_WIN)
+// Custom message loop for external tab testing.
+//
+// Creates a window and makes external_tab_window (the external tab's
+// window handle) a child of that window.
+//
+// The time_to_wait_ms parameter is the maximum time the loop will run. To
+// end the loop earlier, post a quit message (using the Win32
+// PostQuitMessage API) to the thread.
+bool ExternalTabMessageLoop(HWND external_tab_window,
+ int time_to_wait_ms);
+#endif // defined(OS_WIN)
+
+#endif // CHROME_TEST_AUTOMATION_AUTOMATION_PROXY_UITEST_H__
diff --git a/chrome/test/automation/extension_automation_constants.cc b/chrome/test/automation/extension_automation_constants.cc
new file mode 100644
index 0000000..99ecadb
--- /dev/null
+++ b/chrome/test/automation/extension_automation_constants.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2009 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/test/automation/extension_automation_constants.h"
+
+namespace extension_automation_constants {
+
+const wchar_t kAutomationRequestIdKey[] = L"rqid";
+const wchar_t kAutomationHasCallbackKey[] = L"hascb";
+const wchar_t kAutomationErrorKey[] = L"err";
+const wchar_t kAutomationNameKey[] = L"name";
+const wchar_t kAutomationArgsKey[] = L"args";
+const wchar_t kAutomationResponseKey[] = L"res";
+const char kAutomationOrigin[] = "__priv_xtapi";
+const char kAutomationRequestTarget[] = "__priv_xtreq";
+const char kAutomationResponseTarget[] = "__priv_xtres";
+
+} // namespace extension_automation_constants
diff --git a/chrome/test/automation/extension_automation_constants.h b/chrome/test/automation/extension_automation_constants.h
new file mode 100644
index 0000000..715c379
--- /dev/null
+++ b/chrome/test/automation/extension_automation_constants.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2009 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.
+
+// Constants used to encode requests and responses for automation.
+
+#ifndef CHROME_TEST_AUTOMATION_EXTENSION_AUTOMATION_CONSTANTS_H_
+#define CHROME_TEST_AUTOMATION_EXTENSION_AUTOMATION_CONSTANTS_H_
+
+namespace extension_automation_constants {
+
+extern const wchar_t kAutomationRequestIdKey[];
+extern const wchar_t kAutomationHasCallbackKey[];
+extern const wchar_t kAutomationErrorKey[]; // not present implies success
+extern const wchar_t kAutomationNameKey[];
+extern const wchar_t kAutomationArgsKey[];
+extern const wchar_t kAutomationResponseKey[];
+extern const char kAutomationOrigin[];
+extern const char kAutomationRequestTarget[];
+extern const char kAutomationResponseTarget[];
+
+}; // namespace automation_extension_constants
+
+#endif // CHROME_TEST_AUTOMATION_EXTENSION_AUTOMATION_CONSTANTS_H_
diff --git a/chrome/test/data/extensions/uitest/roundtrip_api_call/manifest.json b/chrome/test/data/extensions/uitest/roundtrip_api_call/manifest.json
new file mode 100644
index 0000000..b071780
--- /dev/null
+++ b/chrome/test/data/extensions/uitest/roundtrip_api_call/manifest.json
@@ -0,0 +1,6 @@
+{
+ "id": "66664444789ABCDEF0123456789ABCDEF0123456",
+ "version": "1.0.0.0",
+ "name": "Roundtrip ApiCall Test Extension",
+ "description": "An extension for an extension UITest."
+}
diff --git a/chrome/test/data/extensions/uitest/roundtrip_api_call/test.html b/chrome/test/data/extensions/uitest/roundtrip_api_call/test.html
new file mode 100644
index 0000000..54d2ff3
--- /dev/null
+++ b/chrome/test/data/extensions/uitest/roundtrip_api_call/test.html
@@ -0,0 +1,9 @@
+HOWDIE!!!
+
+<script type="text/javascript">
+ function getLastFocusedCallback(num) {
+ chrome.tabs.remove(num);
+ }
+
+ chrome.windows.getLastFocused(getLastFocusedCallback);
+</script>
diff --git a/chrome/test/data/extensions/uitest/simple_api_call/manifest.json b/chrome/test/data/extensions/uitest/simple_api_call/manifest.json
new file mode 100644
index 0000000..b883fdf
--- /dev/null
+++ b/chrome/test/data/extensions/uitest/simple_api_call/manifest.json
@@ -0,0 +1,6 @@
+{
+ "id": "77774444789ABCDEF0123456789ABCDEF0123456",
+ "version": "1.0.0.0",
+ "name": "ApiCall Test Extension",
+ "description": "An extension for an extension UITest."
+}
diff --git a/chrome/test/data/extensions/uitest/simple_api_call/test.html b/chrome/test/data/extensions/uitest/simple_api_call/test.html
new file mode 100644
index 0000000..5b2af84
--- /dev/null
+++ b/chrome/test/data/extensions/uitest/simple_api_call/test.html
@@ -0,0 +1,5 @@
+HELLO!!!
+
+<script type="text/javascript">
+ chrome.tabs.remove(1);
+</script>