diff options
Diffstat (limited to 'chrome/browser')
-rw-r--r-- | chrome/browser/automation/automation_extension_function.cc | 115 | ||||
-rw-r--r-- | chrome/browser/automation/automation_extension_function.h | 42 | ||||
-rw-r--r-- | chrome/browser/automation/automation_provider.cc | 8 | ||||
-rw-r--r-- | chrome/browser/automation/automation_provider.h | 1 | ||||
-rw-r--r-- | chrome/browser/automation/automation_provider_win.cc | 15 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_uitest.cc | 39 | ||||
-rw-r--r-- | chrome/browser/external_tab_container.cc | 24 | ||||
-rw-r--r-- | chrome/browser/external_tab_container.h | 9 |
8 files changed, 186 insertions, 67 deletions
diff --git a/chrome/browser/automation/automation_extension_function.cc b/chrome/browser/automation/automation_extension_function.cc index 98ca3c9..6f3bb73 100644 --- a/chrome/browser/automation/automation_extension_function.cc +++ b/chrome/browser/automation/automation_extension_function.cc @@ -12,26 +12,33 @@ #include "chrome/browser/extensions/extension_function_dispatcher.h" #include "chrome/browser/renderer_host/render_view_host.h" #include "chrome/browser/renderer_host/render_view_host_delegate.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/tab_contents_delegate.h" -bool AutomationExtensionFunction::enabled_ = false; +TabContents* AutomationExtensionFunction::api_handler_tab_ = NULL; +AutomationExtensionFunction::PendingFunctionsMap + AutomationExtensionFunction::pending_functions_; void AutomationExtensionFunction::SetArgs(const Value* args) { + // Need to JSON-encode for sending over the wire to the automation user. base::JSONWriter::Write(args, false, &args_); } const std::string AutomationExtensionFunction::GetResult() { - // Our API result passing is done through InterceptMessageFromExternalHost - return ""; + // Already JSON-encoded, so override the base class's implementation. + return json_result_; } -const std::string AutomationExtensionFunction::GetError() { - // Our API result passing is done through InterceptMessageFromExternalHost - return ""; -} - -void AutomationExtensionFunction::Run() { +bool AutomationExtensionFunction::RunImpl() { namespace keys = extension_automation_constants; + DCHECK(api_handler_tab_) << + "Why is this function still enabled if no target tab?"; + if (!api_handler_tab_) { + error_ = "No longer automating functions."; + return false; + } + // 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. @@ -43,39 +50,61 @@ void AutomationExtensionFunction::Run() { std::string message; base::JSONWriter::Write(&message_to_host, false, &message); - dispatcher()->render_view_host_->delegate()->ProcessExternalHostMessage( - message, keys::kAutomationOrigin, keys::kAutomationRequestTarget); + if (api_handler_tab_->delegate()) { + api_handler_tab_->delegate()->ForwardMessageToExternalHost( + message, keys::kAutomationOrigin, keys::kAutomationRequestTarget); + } else { + NOTREACHED() << "ExternalTabContainer is supposed to correctly manage " + "lifetime of api_handler_tab_."; + } + + // Automation APIs are asynchronous so we need to stick around until + // our response comes back. Add ourselves to a static hash map keyed + // by request ID. The hash map keeps a reference count on us. + DCHECK(pending_functions_.find(request_id_) == pending_functions_.end()); + pending_functions_[request_id_] = this; + + return true; } ExtensionFunction* AutomationExtensionFunction::Factory() { return new AutomationExtensionFunction(); } -void AutomationExtensionFunction::SetEnabled( +void AutomationExtensionFunction::Enable( + TabContents* api_handler_tab, const std::vector<std::string>& functions_enabled) { - if (functions_enabled.size() > 0) { - std::vector<std::string> function_names; - if (functions_enabled.size() == 1 && functions_enabled[0] == "*") { - ExtensionFunctionDispatcher::GetAllFunctionNames(&function_names); - } else { - function_names = functions_enabled; - } + DCHECK(api_handler_tab); + if (api_handler_tab_ && api_handler_tab != api_handler_tab_) { + NOTREACHED() << "Don't call with different API handler."; + return; + } + api_handler_tab_ = api_handler_tab; - 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); - LOG_IF(WARNING, !result) << "Failed to override API function: " << *it; - } + std::vector<std::string> function_names; + if (functions_enabled.size() == 1 && functions_enabled[0] == "*") { + ExtensionFunctionDispatcher::GetAllFunctionNames(&function_names); } else { - ExtensionFunctionDispatcher::ResetFunctions(); + function_names = functions_enabled; + } + + 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); + LOG_IF(WARNING, !result) << "Failed to override API function: " << *it; } } +void AutomationExtensionFunction::Disable() { + api_handler_tab_ = NULL; + ExtensionFunctionDispatcher::ResetFunctions(); +} + bool AutomationExtensionFunction::InterceptMessageFromExternalHost( RenderViewHost* view_host, const std::string& message, @@ -83,7 +112,10 @@ bool AutomationExtensionFunction::InterceptMessageFromExternalHost( const std::string& target) { namespace keys = extension_automation_constants; - if (origin == keys::kAutomationOrigin && + // We want only specially-tagged messages passed via the conduit tab. + if (api_handler_tab_ && + view_host == api_handler_tab_->render_view_host() && + origin == keys::kAutomationOrigin && target == keys::kAutomationResponseTarget) { // This is an extension API response being sent back via postMessage, // so redirect it. @@ -107,10 +139,23 @@ bool AutomationExtensionFunction::InterceptMessageFromExternalHost( &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); + PendingFunctionsMap::iterator it = pending_functions_.find(request_id); + DCHECK(it != pending_functions_.end()); + + if (it != pending_functions_.end()) { + scoped_refptr<AutomationExtensionFunction> func = it->second; + pending_functions_.erase(it); + + // Our local ref should be the last remaining. + DCHECK(func && func->HasOneRef()); + + if (func) { + func->json_result_ = response; + func->error_ = error; + + func->SendResponse(success); + } + } return true; } } diff --git a/chrome/browser/automation/automation_extension_function.h b/chrome/browser/automation/automation_extension_function.h index ed57ca1..faf125e 100644 --- a/chrome/browser/automation/automation_extension_function.h +++ b/chrome/browser/automation/automation_extension_function.h @@ -8,38 +8,49 @@ #define CHROME_BROWSER_AUTOMATION_AUTOMATION_EXTENSION_FUNCTION_H_ #include <string> +#include <map> #include "chrome/browser/extensions/extension_function.h" class RenderViewHost; +class TabContents; // 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 { +class AutomationExtensionFunction : public AsyncExtensionFunction { public: AutomationExtensionFunction() { } // ExtensionFunction implementation. virtual void SetArgs(const Value* args); virtual const std::string GetResult(); - virtual const std::string GetError(); - virtual void Run(); + virtual bool RunImpl(); static ExtensionFunction* Factory(); + // Enable API automation of selected APIs. Overridden extension API messages + // will be routed to the automation client attached to |api_handler_tab|. + // // If the list of enabled functions is non-empty, we enable according to the // list ("*" means enable all, otherwise we enable individual named - // functions). If empty, we restore the initial functions. + // functions). An empty list makes this function a no-op. + // + // Note that all calls to this function are additive. Functions previously + // enabled will remain enabled until you call Disable(). // - // Note that all calls to this function, except a call with the empty list, - // are additive. Functions previously enabled will remain enabled until - // you clear all function forwarding by specifying the empty list. - static void SetEnabled(const std::vector<std::string>& functions_enabled); + // Calling this function after enabling one or more functions with a + // tab other than the one previously used is an error. + static void Enable(TabContents* api_handler_tab, + const std::vector<std::string>& functions_enabled); + + // Restore the default API function implementations and reset the stored + // API handler. + static void Disable(); // 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. + // the message to respond to the pending asynchronous API call 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, @@ -48,8 +59,17 @@ class AutomationExtensionFunction : public ExtensionFunction { private: ~AutomationExtensionFunction() {} - static bool enabled_; + // Weak reference, lifetime managed by the ExternalTabContainer instance + // owning the TabContents in question. + static TabContents* api_handler_tab_; + + typedef std::map<int, scoped_refptr<AutomationExtensionFunction> > + PendingFunctionsMap; + static PendingFunctionsMap pending_functions_; + std::string args_; + std::string json_result_; + DISALLOW_COPY_AND_ASSIGN(AutomationExtensionFunction); }; diff --git a/chrome/browser/automation/automation_provider.cc b/chrome/browser/automation/automation_provider.cc index 64b0133..537cc6d 100644 --- a/chrome/browser/automation/automation_provider.cc +++ b/chrome/browser/automation/automation_provider.cc @@ -418,8 +418,11 @@ void AutomationProvider::OnMessageReceived(const IPC::Message& message) { IPC_MESSAGE_HANDLER(AutomationMsg_SavePackageShouldPromptUser, SavePackageShouldPromptUser) IPC_MESSAGE_HANDLER(AutomationMsg_WindowTitle, GetWindowTitle) +#if defined(OS_WIN) + // Depends on ExternalTabContainer, so Windows-only IPC_MESSAGE_HANDLER(AutomationMsg_SetEnableExtensionAutomation, SetEnableExtensionAutomation) +#endif IPC_MESSAGE_HANDLER(AutomationMsg_SetShelfVisibility, SetShelfVisibility) IPC_MESSAGE_HANDLER(AutomationMsg_BlockedPopupCount, GetBlockedPopupCount) IPC_MESSAGE_HANDLER(AutomationMsg_SelectAll, SelectAll) @@ -1930,11 +1933,6 @@ void AutomationProvider::SavePackageShouldPromptUser(bool should_prompt) { SavePackage::SetShouldPromptUser(should_prompt); } -void AutomationProvider::SetEnableExtensionAutomation( - const std::vector<std::string>& functions_enabled) { - AutomationExtensionFunction::SetEnabled(functions_enabled); -} - void AutomationProvider::GetWindowTitle(int handle, string16* text) { gfx::NativeWindow window = window_tracker_->GetResource(handle); text->assign(platform_util::GetWindowTitle(window)); diff --git a/chrome/browser/automation/automation_provider.h b/chrome/browser/automation/automation_provider.h index 418c804..82a2d72 100644 --- a/chrome/browser/automation/automation_provider.h +++ b/chrome/browser/automation/automation_provider.h @@ -467,6 +467,7 @@ class AutomationProvider : public base::RefCounted<AutomationProvider>, // Enables extension automation (for e.g. UITests). void SetEnableExtensionAutomation( + int tab_handle, const std::vector<std::string>& functions_enabled); void GetWindowTitle(int handle, string16* text); diff --git a/chrome/browser/automation/automation_provider_win.cc b/chrome/browser/automation/automation_provider_win.cc index cfadf91..7b750ca 100644 --- a/chrome/browser/automation/automation_provider_win.cc +++ b/chrome/browser/automation/automation_provider_win.cc @@ -489,3 +489,18 @@ void AutomationProvider::TerminateSession(int handle, bool* success) { *success = (::PostMessageW(window, WM_ENDSESSION, 0, 0) == TRUE); } } + +void AutomationProvider::SetEnableExtensionAutomation( + int tab_handle, + const std::vector<std::string>& functions_enabled) { + ExternalTabContainer* external_tab = GetExternalTabForHandle(tab_handle); + if (external_tab) { + external_tab->SetEnableExtensionAutomation(functions_enabled); + } else { + // Tab must exist, and must be an external tab so that its + // delegate has an on-empty + // implementation of ForwardMessageToExternalHost. + DLOG(WARNING) << + "SetEnableExtensionAutomation called with invalid tab handle."; + } +} diff --git a/chrome/browser/extensions/extension_uitest.cc b/chrome/browser/extensions/extension_uitest.cc index ac4504c..2cfcd7c 100644 --- a/chrome/browser/extensions/extension_uitest.cc +++ b/chrome/browser/extensions/extension_uitest.cc @@ -55,38 +55,46 @@ class ExtensionUITest : public ParentTestType { void SetUp() { ParentTestType::SetUp(); - automation()->SetEnableExtensionAutomation(functions_enabled_); + + AutomationProxyForExternalTab* proxy = + static_cast<AutomationProxyForExternalTab*>(automation()); + HWND external_tab_container = NULL; + HWND tab_wnd = NULL; + tab_ = proxy->CreateTabWithHostWindow(false, + GURL(), &external_tab_container, &tab_wnd); + + tab_->SetEnableExtensionAutomation(functions_enabled_); } void TearDown() { - automation()->SetEnableExtensionAutomation(std::vector<std::string>()); + tab_->SetEnableExtensionAutomation(std::vector<std::string>()); + + AutomationProxyForExternalTab* proxy = + static_cast<AutomationProxyForExternalTab*>(automation()); + proxy->DestroyHostWindow(); + proxy->WaitForTabCleanup(tab_, action_max_timeout_ms()); + EXPECT_FALSE(tab_->is_valid()); + tab_.release(); + ParentTestType::TearDown(); } void TestWithURL(const GURL& url) { - AutomationProxyForExternalTab* proxy = + EXPECT_TRUE(tab_->is_valid()); + if (tab_) { + AutomationProxyForExternalTab* proxy = static_cast<AutomationProxyForExternalTab*>(automation()); - HWND external_tab_container = NULL; - HWND tab_wnd = NULL; - scoped_refptr<TabProxy> tab(proxy->CreateTabWithHostWindow(false, - GURL(), &external_tab_container, &tab_wnd)); - EXPECT_TRUE(tab->is_valid()); - if (tab) { // Enter a message loop to allow the tab to be created proxy->WaitForNavigation(2000); - DoAdditionalPreNavigateSetup(tab.get()); + 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, GURL()); + tab_->NavigateInExternalTab(url, GURL()); EXPECT_TRUE(proxy->WaitForMessage(action_max_timeout_ms())); - - proxy->DestroyHostWindow(); - proxy->WaitForTabCleanup(tab, action_max_timeout_ms()); - EXPECT_FALSE(tab->is_valid()); } } @@ -97,6 +105,7 @@ class ExtensionUITest : public ParentTestType { protected: // Extension API functions that we want to take over. Defaults to all. std::vector<std::string> functions_enabled_; + scoped_refptr<TabProxy> tab_; private: DISALLOW_COPY_AND_ASSIGN(ExtensionUITest); diff --git a/chrome/browser/external_tab_container.cc b/chrome/browser/external_tab_container.cc index d200891..4440294 100644 --- a/chrome/browser/external_tab_container.cc +++ b/chrome/browser/external_tab_container.cc @@ -11,6 +11,7 @@ #include "base/logging.h" #include "base/win_util.h" #include "chrome/browser/automation/automation_provider.h" +#include "chrome/browser/automation/automation_extension_function.h" #include "chrome/browser/browser_window.h" #include "chrome/browser/debugger/devtools_manager.h" #include "chrome/browser/load_notification_details.h" @@ -43,7 +44,8 @@ ExternalTabContainer::ExternalTabContainer( automation_resource_message_filter_(filter), load_requests_via_automation_(false), handle_top_level_requests_(false), - external_method_factory_(this) { + external_method_factory_(this), + enabled_extension_automation_(false) { } ExternalTabContainer::~ExternalTabContainer() { @@ -161,6 +163,10 @@ bool ExternalTabContainer::Init(Profile* profile, } void ExternalTabContainer::Uninitialize() { + if (enabled_extension_automation_) { + AutomationExtensionFunction::Disable(); + } + registrar_.RemoveAll(); if (tab_contents_) { RenderViewHost* rvh = tab_contents_->render_view_host(); @@ -659,6 +665,22 @@ ExternalTabContainer* ExternalTabContainer::RemovePendingTab(intptr_t cookie) { return NULL; } +void ExternalTabContainer::SetEnableExtensionAutomation( + const std::vector<std::string>& functions_enabled) { + if (functions_enabled.size() > 0) { + if (!tab_contents_) { + NOTREACHED() << "Being invoked via tab so should have TabContents"; + return; + } + + AutomationExtensionFunction::Enable(tab_contents_, functions_enabled); + enabled_extension_automation_ = true; + } else { + AutomationExtensionFunction::Disable(); + enabled_extension_automation_ = false; + } +} + void ExternalTabContainer::Navigate(const GURL& url, const GURL& referrer) { if (!tab_contents_) { NOTREACHED(); diff --git a/chrome/browser/external_tab_container.h b/chrome/browser/external_tab_container.h index 3c97233..95ab846 100644 --- a/chrome/browser/external_tab_container.h +++ b/chrome/browser/external_tab_container.h @@ -157,6 +157,12 @@ class ExternalTabContainer : public TabContentsDelegate, // Returns NULL if we fail to find the cookie in the map. static ExternalTabContainer* RemovePendingTab(intptr_t cookie); + // Enables extension automation (for e.g. UITests), with the current tab + // used as a conduit for the extension API messages being handled by the + // automation client. + void SetEnableExtensionAutomation( + const std::vector<std::string>& functions_enabled); + protected: // Overridden from views::WidgetWin: virtual LRESULT OnCreate(LPCREATESTRUCT create_struct); @@ -216,6 +222,9 @@ class ExternalTabContainer : public TabContentsDelegate, // Contains ExternalTabContainers that have not been connected to as yet. static PendingTabs pending_tabs_; + // True if this tab is currently the conduit for extension API automation. + bool enabled_extension_automation_; + // Allows us to run tasks on the ExternalTabContainer instance which are // bound by its lifetime. ScopedRunnableMethodFactory<ExternalTabContainer> external_method_factory_; |