summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions
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 /chrome/browser/extensions
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
Diffstat (limited to 'chrome/browser/extensions')
-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
5 files changed, 442 insertions, 77 deletions
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