diff options
author | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-15 22:58:33 +0000 |
---|---|---|
committer | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-15 22:58:33 +0000 |
commit | b83e4600fc1c2f1c42598f8d89dbd36d9415309d (patch) | |
tree | 4b2ee497813a59a91105f91f6a5329224da18eac /chrome/browser/extensions | |
parent | a9a2668386addfa40ff262005d396468f54b99e2 (diff) | |
download | chromium_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.cc | 29 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_function.h | 81 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_function_dispatcher.cc | 108 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_function_dispatcher.h | 19 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_uitest.cc | 282 |
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 |