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 | |
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
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> |