diff options
21 files changed, 1201 insertions, 48 deletions
diff --git a/chrome/browser/automation/automation_extension_tracker.cc b/chrome/browser/automation/automation_extension_tracker.cc new file mode 100644 index 0000000..8f09e1f --- /dev/null +++ b/chrome/browser/automation/automation_extension_tracker.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2010 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/browser/automation/automation_extension_tracker.h" +#include "chrome/browser/extensions/extensions_service.h" +#include "chrome/browser/profile.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/notification_service.h" + +AutomationExtensionTracker::AutomationExtensionTracker( + IPC::Message::Sender* automation) + : AutomationResourceTracker<Extension*>(automation) { + registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::EXTENSION_UNLOADED_DISABLED, + NotificationService::AllSources()); +} + +AutomationExtensionTracker::~AutomationExtensionTracker() { +} + +void AutomationExtensionTracker::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type != NotificationType::EXTENSION_UNLOADED && + type != NotificationType::EXTENSION_UNLOADED_DISABLED) + return; + + Extension* extension = Details<Extension>(details).ptr(); + Profile* profile = Source<Profile>(source).ptr(); + if (profile) { + ExtensionsService* service = profile->GetExtensionsService(); + if (service) { + // Remove this extension only if it is uninstalled, not just disabled. + // If it is being uninstalled, the extension will not be in the regular + // or disabled list. + if (!service->GetExtensionById(extension->id(), true)) + CloseResource(extension); + } + } +} diff --git a/chrome/browser/automation/automation_extension_tracker.h b/chrome/browser/automation/automation_extension_tracker.h new file mode 100644 index 0000000..e55a2eb --- /dev/null +++ b/chrome/browser/automation/automation_extension_tracker.h @@ -0,0 +1,39 @@ +// Copyright (c) 2010 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_BROWSER_AUTOMATION_AUTOMATION_EXTENSION_TRACKER_H_ +#define CHROME_BROWSER_AUTOMATION_AUTOMATION_EXTENSION_TRACKER_H_ + +#include "chrome/browser/automation/automation_resource_tracker.h" + +class Extension; + +// Tracks an Extension. An Extension is removed on uninstall, not on disable. +class AutomationExtensionTracker + : public AutomationResourceTracker<Extension*> { + public: + AutomationExtensionTracker(IPC::Message::Sender* automation); + + virtual ~AutomationExtensionTracker(); + + // This is empty because we do not want to add an observer for every + // extension added to the tracker. This is because the profile, not the + // extension, is the one who sends the notification about extension + // uninstalls. Instead of using this method, one observer is added for all + // extensions in the constructor. + virtual void AddObserver(Extension* resource) {} + + // See related comment above as to why this method is empty. + virtual void RemoveObserver(Extension* resource) {} + + // Overriding AutomationResourceTracker Observe. AutomationResourceTracker's + // Observe expects the NotificationSource to be the object that is closing. + // This is not true for the relevant extension notifications, so we have to + // the observation ourselves. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); +}; + +#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_EXTENSION_TRACKER_H_ diff --git a/chrome/browser/automation/automation_provider.cc b/chrome/browser/automation/automation_provider.cc index 67e73f0..addc2fd 100644 --- a/chrome/browser/automation/automation_provider.cc +++ b/chrome/browser/automation/automation_provider.cc @@ -25,6 +25,7 @@ #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_extension_tracker.h" #include "chrome/browser/automation/automation_provider_list.h" #include "chrome/browser/automation/automation_provider_observers.h" #include "chrome/browser/automation/extension_automation_constants.h" @@ -41,8 +42,12 @@ #include "chrome/browser/download/download_manager.h" #include "chrome/browser/download/download_shelf.h" #include "chrome/browser/extensions/crx_installer.h" +#include "chrome/browser/extensions/extension_browser_event_router.h" #include "chrome/browser/extensions/extension_install_ui.h" #include "chrome/browser/extensions/extension_message_service.h" +#include "chrome/browser/extensions/extension_tabs_module.h" +#include "chrome/browser/extensions/extension_toolbar_model.h" +#include "chrome/browser/extensions/extensions_service.h" #include "chrome/browser/extensions/user_script_master.h" #include "chrome/browser/find_bar.h" #include "chrome/browser/find_bar_controller.h" @@ -63,6 +68,7 @@ #include "chrome/browser/tab_contents/tab_contents_view.h" #include "chrome/common/automation_constants.h" #include "chrome/common/chrome_paths.h" +#include "chrome/common/extensions/extension.h" #include "chrome/common/json_value_serializer.h" #include "chrome/common/notification_service.h" #include "chrome/common/platform_util.h" @@ -144,6 +150,7 @@ AutomationProvider::AutomationProvider(Profile* profile) reply_message_(NULL), popup_menu_waiter_(NULL) { browser_tracker_.reset(new AutomationBrowserTracker(this)); + extension_tracker_.reset(new AutomationExtensionTracker(this)); tab_tracker_.reset(new AutomationTabTracker(this)); window_tracker_.reset(new AutomationWindowTracker(this)); autocomplete_edit_tracker_.reset( @@ -151,6 +158,8 @@ AutomationProvider::AutomationProvider(Profile* profile) new_tab_ui_load_observer_.reset(new NewTabUILoadObserver(this)); dom_operation_observer_.reset(new DomOperationNotificationObserver(this)); metric_event_duration_observer_.reset(new MetricEventDurationObserver()); + extension_test_result_observer_.reset( + new ExtensionTestResultNotificationObserver(this)); g_browser_process->AddRefModule(); } @@ -274,6 +283,34 @@ int AutomationProvider::GetIndexForNavigationController( return parent->GetIndexOfController(controller); } +int AutomationProvider::AddExtension(Extension* extension) { + DCHECK(extension); + return extension_tracker_->Add(extension); +} + +Extension* AutomationProvider::GetExtension(int extension_handle) { + return extension_tracker_->GetResource(extension_handle); +} + +Extension* AutomationProvider::GetEnabledExtension(int extension_handle) { + Extension* extension = extension_tracker_->GetResource(extension_handle); + ExtensionsService* service = profile_->GetExtensionsService(); + if (extension && service && + service->GetExtensionById(extension->id(), false)) + return extension; + return NULL; +} + +Extension* AutomationProvider::GetDisabledExtension(int extension_handle) { + Extension* extension = extension_tracker_->GetResource(extension_handle); + ExtensionsService* service = profile_->GetExtensionsService(); + if (extension && service && + service->GetExtensionById(extension->id(), true) && + !service->GetExtensionById(extension->id(), false)) + return extension; + return NULL; +} + void AutomationProvider::OnMessageReceived(const IPC::Message& message) { IPC_BEGIN_MESSAGE_MAP(AutomationProvider, message) IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_CloseBrowser, CloseBrowser) @@ -491,6 +528,24 @@ void AutomationProvider::OnMessageReceived(const IPC::Message& message) { LoadExpandedExtension) IPC_MESSAGE_HANDLER(AutomationMsg_GetEnabledExtensions, GetEnabledExtensions) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_WaitForExtensionTestResult, + WaitForExtensionTestResult) + IPC_MESSAGE_HANDLER_DELAY_REPLY( + AutomationMsg_InstallExtensionAndGetHandle, + InstallExtensionAndGetHandle) + IPC_MESSAGE_HANDLER(AutomationMsg_UninstallExtension, + UninstallExtension) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_EnableExtension, + EnableExtension) + IPC_MESSAGE_HANDLER(AutomationMsg_DisableExtension, + DisableExtension) + IPC_MESSAGE_HANDLER_DELAY_REPLY( + AutomationMsg_ExecuteExtensionActionInActiveTabAsync, + ExecuteExtensionActionInActiveTabAsync) + IPC_MESSAGE_HANDLER(AutomationMsg_MoveExtensionBrowserAction, + MoveExtensionBrowserAction) + IPC_MESSAGE_HANDLER(AutomationMsg_GetExtensionProperty, + GetExtensionProperty) IPC_MESSAGE_HANDLER(AutomationMsg_ShutdownSessionService, ShutdownSessionService) IPC_MESSAGE_HANDLER(AutomationMsg_SaveAsAsync, SaveAsAsync) @@ -2607,9 +2662,9 @@ void AutomationProvider::InstallExtension(const FilePath& crx_path, ExtensionsService* service = profile_->GetExtensionsService(); if (service) { // The observer will delete itself when done. - new ExtensionNotificationObserver(this, - AutomationMsg_InstallExtension::ID, - reply_message); + new ExtensionInstallNotificationObserver(this, + AutomationMsg_InstallExtension::ID, + reply_message); const FilePath& install_dir = service->install_directory(); scoped_refptr<CrxInstaller> installer( @@ -2630,9 +2685,10 @@ void AutomationProvider::LoadExpandedExtension( IPC::Message* reply_message) { if (profile_->GetExtensionsService()) { // The observer will delete itself when done. - new ExtensionNotificationObserver(this, - AutomationMsg_LoadExpandedExtension::ID, - reply_message); + new ExtensionInstallNotificationObserver( + this, + AutomationMsg_LoadExpandedExtension::ID, + reply_message); profile_->GetExtensionsService()->LoadExtension(extension_dir); } else { @@ -2659,6 +2715,183 @@ void AutomationProvider::GetEnabledExtensions( } } +void AutomationProvider::WaitForExtensionTestResult( + IPC::Message* reply_message) { + DCHECK(reply_message_ == NULL); + reply_message_ = reply_message; + // Call MaybeSendResult, because the result might have come in before + // we were waiting on it. + extension_test_result_observer_->MaybeSendResult(); +} + +void AutomationProvider::InstallExtensionAndGetHandle( + const FilePath& crx_path, IPC::Message* reply_message) { + ExtensionsService* service = profile_->GetExtensionsService(); + ExtensionProcessManager* manager = profile_->GetExtensionProcessManager(); + if (service && manager) { + // The observer will delete itself when done. + new ExtensionReadyNotificationObserver( + manager, + this, + AutomationMsg_InstallExtensionAndGetHandle::ID, + reply_message); + + scoped_refptr<CrxInstaller> installer( + new CrxInstaller(service->install_directory(), + service, + NULL)); // silent install, no UI + installer->set_allow_privilege_increase(true); + installer->InstallCrx(crx_path); + } else { + AutomationMsg_InstallExtensionAndGetHandle::WriteReplyParams( + reply_message, 0); + Send(reply_message); + } +} + +void AutomationProvider::UninstallExtension(int extension_handle, + bool* success) { + *success = false; + Extension* extension = GetExtension(extension_handle); + ExtensionsService* service = profile_->GetExtensionsService(); + if (extension && service) { + ExtensionUnloadNotificationObserver observer; + service->UninstallExtension(extension->id(), false); + // The extension unload notification should have been sent synchronously + // with the uninstall. Just to be safe, check that it was received. + *success = observer.did_receive_unload_notification(); + } +} + +void AutomationProvider::EnableExtension(int extension_handle, + IPC::Message* reply_message) { + Extension* extension = GetDisabledExtension(extension_handle); + ExtensionsService* service = profile_->GetExtensionsService(); + ExtensionProcessManager* manager = profile_->GetExtensionProcessManager(); + // Only enable if this extension is disabled. + if (extension && service && manager) { + // The observer will delete itself when done. + new ExtensionReadyNotificationObserver( + manager, + this, + AutomationMsg_EnableExtension::ID, + reply_message); + service->EnableExtension(extension->id()); + } else { + AutomationMsg_EnableExtension::WriteReplyParams(reply_message, false); + Send(reply_message); + } +} + +void AutomationProvider::DisableExtension(int extension_handle, + bool* success) { + *success = false; + Extension* extension = GetEnabledExtension(extension_handle); + ExtensionsService* service = profile_->GetExtensionsService(); + if (extension && service) { + ExtensionUnloadNotificationObserver observer; + service->DisableExtension(extension->id()); + // The extension unload notification should have been sent synchronously + // with the disable. Just to be safe, check that it was received. + *success = observer.did_receive_unload_notification(); + } +} + +void AutomationProvider::ExecuteExtensionActionInActiveTabAsync( + int extension_handle, int browser_handle, + IPC::Message* reply_message) { + bool success = false; + Extension* extension = GetEnabledExtension(extension_handle); + ExtensionsService* service = profile_->GetExtensionsService(); + ExtensionMessageService* message_service = + profile_->GetExtensionMessageService(); + Browser* browser = browser_tracker_->GetResource(browser_handle); + if (extension && service && message_service && browser) { + int tab_id = ExtensionTabUtil::GetTabId(browser->GetSelectedTabContents()); + if (extension->page_action()) { + ExtensionBrowserEventRouter::GetInstance()->PageActionExecuted( + browser->profile(), extension->id(), "action", tab_id, "", 1); + success = true; + } else if (extension->browser_action()) { + ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted( + browser->profile(), extension->id(), browser); + success = true; + } + } + AutomationMsg_ExecuteExtensionActionInActiveTabAsync::WriteReplyParams( + reply_message, success); + Send(reply_message); +} + +void AutomationProvider::MoveExtensionBrowserAction( + int extension_handle, int index, bool* success) { + *success = false; + Extension* extension = GetEnabledExtension(extension_handle); + ExtensionsService* service = profile_->GetExtensionsService(); + if (extension && service) { + ExtensionToolbarModel* toolbar = service->toolbar_model(); + if (toolbar) { + if (index >= 0 && index < static_cast<int>(toolbar->size())) { + toolbar->MoveBrowserAction(extension, index); + *success = true; + } else { + DLOG(WARNING) << "Attempted to move browser action to invalid index."; + } + } + } +} + +void AutomationProvider::GetExtensionProperty( + int extension_handle, + AutomationMsg_ExtensionProperty type, + bool* success, + std::string* value) { + *success = false; + Extension* extension = GetExtension(extension_handle); + ExtensionsService* service = profile_->GetExtensionsService(); + if (extension && service) { + ExtensionToolbarModel* toolbar = service->toolbar_model(); + int found_index = -1; + int index = 0; + switch (type) { + case AUTOMATION_MSG_EXTENSION_ID: + *value = extension->id(); + *success = true; + break; + case AUTOMATION_MSG_EXTENSION_NAME: + *value = extension->name(); + *success = true; + break; + case AUTOMATION_MSG_EXTENSION_VERSION: + *value = extension->VersionString(); + *success = true; + break; + case AUTOMATION_MSG_EXTENSION_BROWSER_ACTION_INDEX: + if (toolbar) { + for (ExtensionList::const_iterator iter = toolbar->begin(); + iter != toolbar->end(); iter++) { + // Skip this extension if we are in incognito mode + // and it is not incognito-enabled. + if (profile_->IsOffTheRecord() && + !service->IsIncognitoEnabled(*iter)) + continue; + if (*iter == extension) { + found_index = index; + break; + } + index++; + } + *value = IntToString(found_index); + *success = true; + } + break; + default: + LOG(WARNING) << "Trying to get undefined extension property"; + break; + } + } +} + void AutomationProvider::SaveAsAsync(int tab_handle) { NavigationController* tab = NULL; TabContents* tab_contents = GetTabContentsForHandle(tab_handle, &tab); diff --git a/chrome/browser/automation/automation_provider.h b/chrome/browser/automation/automation_provider.h index 929bc20..f3d03cb6 100644 --- a/chrome/browser/automation/automation_provider.h +++ b/chrome/browser/automation/automation_provider.h @@ -43,7 +43,10 @@ struct Reposition_Params; struct ExternalTabSettings; } +class AutomationExtensionTracker; +class Extension; class ExtensionPortContainer; +class ExtensionTestResultNotificationObserver; class ExternalTabContainer; class LoginHandler; class MetricEventDurationObserver; @@ -130,6 +133,11 @@ class AutomationProvider : public base::RefCounted<AutomationProvider>, return reply_message; } + // Adds the extension passed in to the extension tracker, and returns + // the associated handle. If the tracker already contains the extension, + // the handle is simply returned. + int AddExtension(Extension* extension); + // Adds the external tab passed in to the tab tracker. bool AddExternalTab(ExternalTabContainer* external_tab); @@ -381,6 +389,35 @@ class AutomationProvider : public base::RefCounted<AutomationProvider>, void GetEnabledExtensions(std::vector<FilePath>* result); + void WaitForExtensionTestResult(IPC::Message* reply_message); + + void InstallExtensionAndGetHandle(const FilePath& crx_path, + IPC::Message* reply_message); + + void UninstallExtension(int extension_handle, + bool* success); + + void ReloadExtension(int extension_handle, + IPC::Message* reply_message); + + void EnableExtension(int extension_handle, + IPC::Message* reply_message); + + void DisableExtension(int extension_handle, + bool* success); + + void ExecuteExtensionActionInActiveTabAsync(int extension_handle, + int browser_handle, + IPC::Message* reply_message); + + void MoveExtensionBrowserAction(int extension_handle, int index, + bool* success); + + void GetExtensionProperty(int extension_handle, + AutomationMsg_ExtensionProperty type, + bool* success, + std::string* value); + void NavigateInExternalTab( int handle, const GURL& url, const GURL& referrer, AutomationMsg_NavigationResponseValues* status); @@ -595,6 +632,18 @@ class AutomationProvider : public base::RefCounted<AutomationProvider>, // Returns NULL on failure. RenderViewHost* GetViewForTab(int tab_handle); + // Returns the extension for the given handle. Returns NULL if there is + // no extension for the handle. + Extension* GetExtension(int extension_handle); + + // Returns the extension for the given handle, if the handle is valid and + // the associated extension is enabled. Returns NULL otherwise. + Extension* GetEnabledExtension(int extension_handle); + + // Returns the extension for the given handle, if the handle is valid and + // the associated extension is disabled. Returns NULL otherwise. + Extension* GetDisabledExtension(int extension_handle); + // Block until the focused view ID changes to something other than // previous_view_id. void WaitForFocusedViewIDToChange(int handle, @@ -621,8 +670,11 @@ class AutomationProvider : public base::RefCounted<AutomationProvider>, scoped_ptr<NotificationObserver> find_in_page_observer_; scoped_ptr<NotificationObserver> dom_operation_observer_; scoped_ptr<NotificationObserver> dom_inspector_observer_; + scoped_ptr<ExtensionTestResultNotificationObserver> + extension_test_result_observer_; scoped_ptr<MetricEventDurationObserver> metric_event_duration_observer_; scoped_ptr<AutomationBrowserTracker> browser_tracker_; + scoped_ptr<AutomationExtensionTracker> extension_tracker_; scoped_ptr<AutomationTabTracker> tab_tracker_; scoped_ptr<AutomationWindowTracker> window_tracker_; scoped_ptr<AutomationAutocompleteEditTracker> autocomplete_edit_tracker_; diff --git a/chrome/browser/automation/automation_provider_observers.cc b/chrome/browser/automation/automation_provider_observers.cc index da7537d..24b770a 100644 --- a/chrome/browser/automation/automation_provider_observers.cc +++ b/chrome/browser/automation/automation_provider_observers.cc @@ -5,15 +5,21 @@ #include "chrome/browser/automation/automation_provider_observers.h" #include "base/basictypes.h" +#include "base/string_util.h" #include "chrome/app/chrome_dll_resource.h" #include "chrome/browser/automation/automation_provider.h" #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/dom_operation_notification_details.h" +#include "chrome/browser/extensions/extension_host.h" +#include "chrome/browser/extensions/extension_process_manager.h" +#include "chrome/browser/extensions/extension_updater.h" #include "chrome/browser/login_prompt.h" #include "chrome/browser/metrics/metric_event_duration_details.h" #include "chrome/browser/tab_contents/navigation_controller.h" #include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/extensions/extension.h" #include "chrome/common/notification_service.h" +#include "chrome/test/automation/automation_constants.h" #if defined(OS_WIN) #include "chrome/browser/printing/print_job.h" @@ -282,7 +288,16 @@ void TabClosedNotificationObserver::set_for_browser_command( for_browser_command_ = for_browser_command; } -ExtensionNotificationObserver::ExtensionNotificationObserver( +bool DidExtensionHostsStopLoading(ExtensionProcessManager* manager) { + for (ExtensionProcessManager::const_iterator iter = manager->begin(); + iter != manager->end(); ++iter) { + if (!(*iter)->did_stop_loading()) + return false; + } + return true; +} + +ExtensionInstallNotificationObserver::ExtensionInstallNotificationObserver( AutomationProvider* automation, int id, IPC::Message* reply_message) : automation_(automation), id_(id), @@ -297,10 +312,10 @@ ExtensionNotificationObserver::ExtensionNotificationObserver( NotificationService::AllSources()); } -ExtensionNotificationObserver::~ExtensionNotificationObserver() { +ExtensionInstallNotificationObserver::~ExtensionInstallNotificationObserver() { } -void ExtensionNotificationObserver::Observe( +void ExtensionInstallNotificationObserver::Observe( NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { @@ -322,7 +337,7 @@ void ExtensionNotificationObserver::Observe( delete this; } -void ExtensionNotificationObserver::SendResponse( +void ExtensionInstallNotificationObserver::SendResponse( AutomationMsg_ExtensionResponseValues response) { if (reply_message_ != NULL) { switch (id_) { @@ -344,6 +359,148 @@ void ExtensionNotificationObserver::SendResponse( } } +ExtensionReadyNotificationObserver::ExtensionReadyNotificationObserver( + ExtensionProcessManager* manager, AutomationProvider* automation, int id, + IPC::Message* reply_message) + : manager_(manager), + automation_(automation), + id_(id), + reply_message_(reply_message), + extension_(NULL) { + registrar_.Add(this, NotificationType::EXTENSION_HOST_DID_STOP_LOADING, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::EXTENSION_LOADED, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::EXTENSION_INSTALL_ERROR, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::EXTENSION_OVERINSTALL_ERROR, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::EXTENSION_UPDATE_DISABLED, + NotificationService::AllSources()); +} + +ExtensionReadyNotificationObserver::~ExtensionReadyNotificationObserver() { +} + +void ExtensionReadyNotificationObserver::Observe( + NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + bool success = false; + switch (type.value) { + case NotificationType::EXTENSION_HOST_DID_STOP_LOADING: + // Only continue on with this method if our extension has been loaded + // and all the extension hosts have stopped loading. + if (!extension_ || !DidExtensionHostsStopLoading(manager_)) + return; + success = true; + break; + case NotificationType::EXTENSION_LOADED: + extension_ = Details<Extension>(details).ptr(); + if (!DidExtensionHostsStopLoading(manager_)) + return; + success = true; + break; + case NotificationType::EXTENSION_INSTALL_ERROR: + case NotificationType::EXTENSION_UPDATE_DISABLED: + case NotificationType::EXTENSION_OVERINSTALL_ERROR: + success = false; + break; + default: + NOTREACHED(); + break; + } + + if (id_ == AutomationMsg_InstallExtensionAndGetHandle::ID) { + // A handle of zero indicates an error. + int extension_handle = 0; + if (extension_) + extension_handle = automation_->AddExtension(extension_); + AutomationMsg_InstallExtensionAndGetHandle::WriteReplyParams( + reply_message_, extension_handle); + } else if (id_ == AutomationMsg_EnableExtension::ID) { + AutomationMsg_EnableExtension::WriteReplyParams(reply_message_, true); + } else { + NOTREACHED(); + LOG(ERROR) << "Cannot write reply params for unknown message id."; + } + + automation_->Send(reply_message_); + delete this; +} + +ExtensionUnloadNotificationObserver::ExtensionUnloadNotificationObserver() + : did_receive_unload_notification_(false) { + registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::EXTENSION_UNLOADED_DISABLED, + NotificationService::AllSources()); +} + +ExtensionUnloadNotificationObserver::~ExtensionUnloadNotificationObserver() { +} + +void ExtensionUnloadNotificationObserver::Observe( + NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + if (type.value == NotificationType::EXTENSION_UNLOADED || + type.value == NotificationType::EXTENSION_UNLOADED_DISABLED) { + did_receive_unload_notification_ = true; + } else { + NOTREACHED(); + } +} + +ExtensionTestResultNotificationObserver:: + ExtensionTestResultNotificationObserver(AutomationProvider* automation) + : automation_(automation) { + registrar_.Add(this, NotificationType::EXTENSION_TEST_PASSED, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::EXTENSION_TEST_FAILED, + NotificationService::AllSources()); +} + +ExtensionTestResultNotificationObserver:: + ~ExtensionTestResultNotificationObserver() { +} + +void ExtensionTestResultNotificationObserver::Observe( + NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + switch (type.value) { + case NotificationType::EXTENSION_TEST_PASSED: + results_.push_back(true); + messages_.push_back(""); + break; + + case NotificationType::EXTENSION_TEST_FAILED: + results_.push_back(false); + messages_.push_back(*(Details<std::string>(details).ptr())); + break; + + default: + NOTREACHED(); + } + // There may be a reply message waiting for this event, so check. + MaybeSendResult(); +} + +void ExtensionTestResultNotificationObserver::MaybeSendResult() { + if (results_.size() > 0) { + // This release method should return the automation's current + // reply message, or NULL if there is no current one. If it is not + // NULL, we are stating that we will handle this reply message. + IPC::Message* reply_message = automation_->reply_message_release(); + // Send the result back if we have a reply message. + if (reply_message) { + AutomationMsg_WaitForExtensionTestResult::WriteReplyParams( + reply_message, results_.front(), messages_.front()); + results_.pop_front(); + messages_.pop_front(); + automation_->Send(reply_message); + } + } +} + BrowserOpenedNotificationObserver::BrowserOpenedNotificationObserver( AutomationProvider* automation, IPC::Message* reply_message) : automation_(automation), diff --git a/chrome/browser/automation/automation_provider_observers.h b/chrome/browser/automation/automation_provider_observers.h index 892be51..6bed0f6 100644 --- a/chrome/browser/automation/automation_provider_observers.h +++ b/chrome/browser/automation/automation_provider_observers.h @@ -5,6 +5,7 @@ #ifndef CHROME_BROWSER_AUTOMATION_AUTOMATION_PROVIDER_OBSERVERS_H_ #define CHROME_BROWSER_AUTOMATION_AUTOMATION_PROVIDER_OBSERVERS_H_ +#include <deque> #include <map> #include <set> @@ -17,6 +18,8 @@ class AutomationProvider; class Browser; +class Extension; +class ExtensionProcessManager; class NavigationController; class TabContents; @@ -166,12 +169,14 @@ class TabClosedNotificationObserver : public TabStripNotificationObserver { DISALLOW_COPY_AND_ASSIGN(TabClosedNotificationObserver); }; -class ExtensionNotificationObserver : public NotificationObserver { +// Observes when an extension has finished installing or possible install +// errors. This does not guarantee that the extension is ready for use. +class ExtensionInstallNotificationObserver : public NotificationObserver { public: - ExtensionNotificationObserver(AutomationProvider* automation, - int id, - IPC::Message* reply_message); - virtual ~ExtensionNotificationObserver(); + ExtensionInstallNotificationObserver(AutomationProvider* automation, + int id, + IPC::Message* reply_message); + ~ExtensionInstallNotificationObserver(); // Implementation of NotificationObserver. virtual void Observe(NotificationType type, @@ -179,6 +184,7 @@ class ExtensionNotificationObserver : public NotificationObserver { const NotificationDetails& details); private: + // Send |response| back to the provider's client. void SendResponse(AutomationMsg_ExtensionResponseValues response); NotificationRegistrar registrar_; @@ -186,7 +192,85 @@ class ExtensionNotificationObserver : public NotificationObserver { int id_; IPC::Message* reply_message_; - DISALLOW_COPY_AND_ASSIGN(ExtensionNotificationObserver); + DISALLOW_COPY_AND_ASSIGN(ExtensionInstallNotificationObserver); +}; + +// Observes when an extension has finished loading and is ready for use. Also +// checks for possible install errors. +class ExtensionReadyNotificationObserver : public NotificationObserver { + public: + ExtensionReadyNotificationObserver(ExtensionProcessManager* manager, + AutomationProvider* automation, + int id, + IPC::Message* reply_message); + ~ExtensionReadyNotificationObserver(); + + // Implementation of NotificationObserver. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + NotificationRegistrar registrar_; + ExtensionProcessManager* manager_; + scoped_refptr<AutomationProvider> automation_; + int id_; + IPC::Message* reply_message_; + Extension* extension_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionReadyNotificationObserver); +}; + +class ExtensionUnloadNotificationObserver : public NotificationObserver { + public: + ExtensionUnloadNotificationObserver(); + ~ExtensionUnloadNotificationObserver(); + + // Implementation of NotificationObserver. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + bool did_receive_unload_notification() { + return did_receive_unload_notification_; + } + + private: + NotificationRegistrar registrar_; + bool did_receive_unload_notification_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionUnloadNotificationObserver); +}; + +class ExtensionTestResultNotificationObserver : public NotificationObserver { + public: + explicit ExtensionTestResultNotificationObserver( + AutomationProvider* automation); + ~ExtensionTestResultNotificationObserver(); + + // Implementation of NotificationObserver. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Sends a test result back to the provider's client, if there is a pending + // provider message and there is a result in the queue. + void MaybeSendResult(); + + private: + NotificationRegistrar registrar_; + AutomationProvider* automation_; + // Two queues containing the test results. Although typically only + // one result will be in each queue, there are cases where a queue is + // needed. + // For example, perhaps two events occur asynchronously and their + // order of completion is not guaranteed. If the test wants to make sure + // both finish before continuing, a queue is needed. The test would then + // need to wait twice. + std::deque<bool> results_; + std::deque<std::string> messages_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionTestResultNotificationObserver); }; class BrowserOpenedNotificationObserver : public NotificationObserver { diff --git a/chrome/browser/automation/automation_resource_tracker.h b/chrome/browser/automation/automation_resource_tracker.h index 8500aea..9f6df8b 100644 --- a/chrome/browser/automation/automation_resource_tracker.h +++ b/chrome/browser/automation/automation_resource_tracker.h @@ -129,10 +129,18 @@ class AutomationResourceTracker : public NotificationObserver, T resource = Source<typename AutomationResourceTraits<T>::ValueType>(source).ptr(); - HandleCloseNotification(resource); + CloseResource(resource); } protected: + // Removes |resource| from the tracker, and handles sending the close + // notification back to the client. This typically should not be called + // directly, unless there is no appropriate notification available + // for the resource type. + void CloseResource(T resource) { + HandleCloseNotification(resource); + } + NotificationRegistrar registrar_; private: diff --git a/chrome/browser/gtk/browser_actions_toolbar_gtk.cc b/chrome/browser/gtk/browser_actions_toolbar_gtk.cc index 7f3ec625..1d25c2a 100644 --- a/chrome/browser/gtk/browser_actions_toolbar_gtk.cc +++ b/chrome/browser/gtk/browser_actions_toolbar_gtk.cc @@ -359,7 +359,7 @@ BrowserActionsToolbarGtk::BrowserActionsToolbarGtk(Browser* browser) G_CALLBACK(OnOverflowButtonPressThunk), this); gtk_box_pack_start(GTK_BOX(hbox_.get()), gripper, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(hbox_.get()), button_hbox_, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(hbox_.get()), button_hbox_.get(), TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(hbox_.get()), overflow_button_.widget(), FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(hbox_.get()), separator_, FALSE, FALSE, 0); @@ -378,7 +378,7 @@ BrowserActionsToolbarGtk::BrowserActionsToolbarGtk(Browser* browser) g_signal_connect(widget(), "hierarchy-changed", G_CALLBACK(OnHierarchyChangedThunk), this); - ViewIDUtil::SetID(button_hbox_, VIEW_ID_BROWSER_ACTION_TOOLBAR); + ViewIDUtil::SetID(button_hbox_.get(), VIEW_ID_BROWSER_ACTION_TOOLBAR); } BrowserActionsToolbarGtk::~BrowserActionsToolbarGtk() { @@ -390,6 +390,7 @@ BrowserActionsToolbarGtk::~BrowserActionsToolbarGtk() { if (model_) model_->RemoveObserver(this); + button_hbox_.Destroy(); hbox_.Destroy(); } @@ -410,10 +411,10 @@ void BrowserActionsToolbarGtk::Update() { void BrowserActionsToolbarGtk::SetupDrags() { GtkTargetEntry drag_target = GetDragTargetEntry(); - gtk_drag_dest_set(button_hbox_, GTK_DEST_DEFAULT_DROP, &drag_target, 1, + gtk_drag_dest_set(button_hbox_.get(), GTK_DEST_DEFAULT_DROP, &drag_target, 1, GDK_ACTION_MOVE); - g_signal_connect(button_hbox_, "drag-motion", + g_signal_connect(button_hbox_.get(), "drag-motion", G_CALLBACK(OnDragMotionThunk), this); } @@ -448,8 +449,8 @@ void BrowserActionsToolbarGtk::CreateButtonForExtension(Extension* extension, linked_ptr<BrowserActionButton> button( new BrowserActionButton(this, extension)); gtk_chrome_shrinkable_hbox_pack_start( - GTK_CHROME_SHRINKABLE_HBOX(button_hbox_), button->widget(), 0); - gtk_box_reorder_child(GTK_BOX(button_hbox_), button->widget(), index); + GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get()), button->widget(), 0); + gtk_box_reorder_child(GTK_BOX(button_hbox_.get()), button->widget(), index); gtk_widget_show(button->widget()); extension_button_map_[extension->id()] = button; @@ -529,7 +530,7 @@ void BrowserActionsToolbarGtk::BrowserActionRemoved(Extension* extension) { if (drag_button_ != NULL) { // Break the current drag. - gtk_grab_remove(button_hbox_); + gtk_grab_remove(button_hbox_.get()); } RemoveButtonForExtension(extension); @@ -558,7 +559,7 @@ void BrowserActionsToolbarGtk::BrowserActionMoved(Extension* extension, if (profile_->IsOffTheRecord()) index = model_->OriginalIndexToIncognito(index); - gtk_box_reorder_child(GTK_BOX(button_hbox_), button->widget(), index); + gtk_box_reorder_child(GTK_BOX(button_hbox_.get()), button->widget(), index); } void BrowserActionsToolbarGtk::ModelLoaded() { @@ -568,14 +569,14 @@ void BrowserActionsToolbarGtk::ModelLoaded() { void BrowserActionsToolbarGtk::AnimationProgressed(const Animation* animation) { int width = start_width_ + (desired_width_ - start_width_) * animation->GetCurrentValue(); - gtk_widget_set_size_request(button_hbox_, width, -1); + gtk_widget_set_size_request(button_hbox_.get(), width, -1); if (width == desired_width_) resize_animation_.Reset(); } void BrowserActionsToolbarGtk::AnimationEnded(const Animation* animation) { - gtk_widget_set_size_request(button_hbox_, desired_width_, -1); + gtk_widget_set_size_request(button_hbox_.get(), desired_width_, -1); } void BrowserActionsToolbarGtk::ExecuteCommandById(int command_id) { @@ -619,11 +620,11 @@ void BrowserActionsToolbarGtk::SetButtonHBoxWidth(int new_width) { gint max_width = WidthForIconCount(model_->size()); new_width = std::min(max_width, new_width); new_width = std::max(new_width, 0); - gtk_widget_set_size_request(button_hbox_, new_width, -1); + gtk_widget_set_size_request(button_hbox_.get(), new_width, -1); int showing_icon_count = gtk_chrome_shrinkable_hbox_get_visible_child_count( - GTK_CHROME_SHRINKABLE_HBOX(button_hbox_)); + GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get())); if (model_->size() > static_cast<size_t>(showing_icon_count)) { if (!GTK_WIDGET_VISIBLE(overflow_button_.widget())) { @@ -633,7 +634,7 @@ void BrowserActionsToolbarGtk::SetButtonHBoxWidth(int new_width) { gtk_widget_size_request(overflow_button_.widget(), &req); new_width -= req.width; new_width = std::max(new_width, 0); - gtk_widget_set_size_request(button_hbox_, new_width, -1); + gtk_widget_set_size_request(button_hbox_.get(), new_width, -1); gtk_widget_show(overflow_button_.widget()); } @@ -654,7 +655,7 @@ gboolean BrowserActionsToolbarGtk::OnDragMotion(GtkWidget* widget, // We will go ahead and reorder the child in order to provide visual feedback // to the user. We don't inform the model that it has moved until the drag // ends. - gtk_box_reorder_child(GTK_BOX(button_hbox_), drag_button_->widget(), + gtk_box_reorder_child(GTK_BOX(button_hbox_.get()), drag_button_->widget(), drop_index_); gdk_drag_status(drag_context, GDK_ACTION_MOVE, time); @@ -765,7 +766,7 @@ gboolean BrowserActionsToolbarGtk::OnGripperButtonRelease( // the perfect size to fit the buttons. int visible_icon_count = gtk_chrome_shrinkable_hbox_get_visible_child_count( - GTK_CHROME_SHRINKABLE_HBOX(button_hbox_)); + GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get())); AnimateToShowNIcons(visible_icon_count); model_->SetVisibleIconCount(visible_icon_count); @@ -785,7 +786,7 @@ gboolean BrowserActionsToolbarGtk::OnOverflowButtonPress( int visible_icon_count = gtk_chrome_shrinkable_hbox_get_visible_child_count( - GTK_CHROME_SHRINKABLE_HBOX(button_hbox_)); + GTK_CHROME_SHRINKABLE_HBOX(button_hbox_.get())); for (int i = visible_icon_count; i < static_cast<int>(model_->size()); ++i) { Extension* extension = model_->GetExtensionByIndex(i); BrowserActionButton* button = extension_button_map_[extension->id()].get(); diff --git a/chrome/browser/gtk/browser_actions_toolbar_gtk.h b/chrome/browser/gtk/browser_actions_toolbar_gtk.h index 955f55e..0f8376b 100644 --- a/chrome/browser/gtk/browser_actions_toolbar_gtk.h +++ b/chrome/browser/gtk/browser_actions_toolbar_gtk.h @@ -152,7 +152,7 @@ class BrowserActionsToolbarGtk : public ExtensionToolbarModel::Observer, OwnedWidgetGtk hbox_; // Contains the browser action buttons. - GtkWidget* button_hbox_; + OwnedWidgetGtk button_hbox_; OverflowButton overflow_button_; scoped_ptr<MenuGtk> overflow_menu_; diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 2fb4b38..fef2da9 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -1454,6 +1454,7 @@ 'dependencies': [ 'theme_resources', '../skia/skia.gyp:skia', + '../testing/gtest.gyp:gtest', ], 'include_dirs': [ '..', @@ -1470,6 +1471,8 @@ 'test/automation/automation_proxy.h', 'test/automation/browser_proxy.cc', 'test/automation/browser_proxy.h', + 'test/automation/extension_proxy.cc', + 'test/automation/extension_proxy.h', 'test/automation/tab_proxy.cc', 'test/automation/tab_proxy.h', 'test/automation/window_proxy.cc', diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 31a0109..70c3868 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -175,6 +175,8 @@ 'browser/automation/extension_automation_constants.cc', 'browser/automation/automation_extension_function.h', 'browser/automation/automation_extension_function.cc', + 'browser/automation/automation_extension_tracker.cc', + 'browser/automation/automation_extension_tracker.h', 'browser/automation/automation_profile_impl.cc', 'browser/automation/automation_profile_impl.h', 'browser/automation/automation_provider_chromeos.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index cb73e8e..cc21528 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -102,6 +102,8 @@ 'test/automation/browser_proxy.h', 'test/automation/dom_element_proxy.cc', 'test/automation/dom_element_proxy.h', + 'test/automation/extension_proxy.cc', + 'test/automation/extension_proxy.h', 'test/automation/javascript_execution_controller.cc', 'test/automation/javascript_execution_controller.h', 'test/automation/tab_proxy.cc', @@ -305,6 +307,7 @@ 'browser/views/find_bar_host_uitest.cc', 'common/logging_chrome_uitest.cc', 'test/automation/automation_proxy_uitest.cc', + 'test/automation/extension_proxy_uitest.cc', 'test/automated_ui_tests/automated_ui_test_test.cc', 'test/chrome_process_util_uitest.cc', 'test/ui/dom_checker_uitest.cc', diff --git a/chrome/test/automation/automation_constants.h b/chrome/test/automation/automation_constants.h index cf54a39..f5e3b6f 100644 --- a/chrome/test/automation/automation_constants.h +++ b/chrome/test/automation/automation_constants.h @@ -31,4 +31,14 @@ enum AutomationMsg_ExtensionResponseValues { AUTOMATION_MSG_EXTENSION_ALREADY_INSTALLED, }; +// Used in the AutomationMsg_GetExtensionProperty to identify which extension +// property should be retrieved, instead of having separate messages for each +// property. +enum AutomationMsg_ExtensionProperty { + AUTOMATION_MSG_EXTENSION_ID = 0, + AUTOMATION_MSG_EXTENSION_NAME, + AUTOMATION_MSG_EXTENSION_VERSION, + AUTOMATION_MSG_EXTENSION_BROWSER_ACTION_INDEX, +}; + #endif // CHROME_TEST_AUTOMATION_AUTOMATION_CONSTANTS_H_ diff --git a/chrome/test/automation/automation_messages.h b/chrome/test/automation/automation_messages.h index 22ec4d6..ddd1c83 100644 --- a/chrome/test/automation/automation_messages.h +++ b/chrome/test/automation/automation_messages.h @@ -127,6 +127,43 @@ struct ParamTraits<AutomationMsg_ExtensionResponseValues> { }; template <> +struct ParamTraits<AutomationMsg_ExtensionProperty> { + typedef AutomationMsg_ExtensionProperty param_type; + static void Write(Message* m, const param_type& p) { + m->WriteInt(p); + } + static bool Read(const Message* m, void** iter, param_type* p) { + int type; + if (!m->ReadInt(iter, &type)) + return false; + *p = static_cast<AutomationMsg_ExtensionProperty>(type); + return true; + } + static void Log(const param_type& p, std::wstring* l) { + std::wstring control; + switch (p) { + case AUTOMATION_MSG_EXTENSION_ID: + control = L"AUTOMATION_MSG_EXTENSION_ID"; + break; + case AUTOMATION_MSG_EXTENSION_NAME: + control = L"AUTOMATION_MSG_EXTENSION_NAME"; + break; + case AUTOMATION_MSG_EXTENSION_VERSION: + control = L"AUTOMATION_MSG_EXTENSION_VERSION"; + break; + case AUTOMATION_MSG_EXTENSION_BROWSER_ACTION_INDEX: + control = L"AUTOMATION_MSG_EXTENSION_BROWSER_ACTION_INDEX"; + break; + default: + control = L"UNKNOWN"; + break; + } + + LogParam(control, l); + } +}; + +template <> struct ParamTraits<SecurityStyle> { typedef SecurityStyle param_type; static void Write(Message* m, const param_type& p) { diff --git a/chrome/test/automation/automation_messages_internal.h b/chrome/test/automation/automation_messages_internal.h index 74dabad..8430172 100644 --- a/chrome/test/automation/automation_messages_internal.h +++ b/chrome/test/automation/automation_messages_internal.h @@ -13,6 +13,7 @@ #include "base/basictypes.h" #include "base/string16.h" +#include "base/values.h" #include "chrome/common/content_settings.h" #include "chrome/common/navigation_types.h" #include "chrome/test/automation/autocomplete_edit_proxy.h" @@ -1321,4 +1322,56 @@ IPC_BEGIN_MESSAGES(Automation) std::string /* JSON response */, bool /* success */) + // Installs an extension from the crx file and returns its id. + // On error, |extension handle| will be 0. + IPC_SYNC_MESSAGE_ROUTED1_1(AutomationMsg_InstallExtensionAndGetHandle, + FilePath /* full path to crx file */, + int /* extension handle */) + + // Waits for the next extension test result. Sets |test result| as the + // received result and |message| as any accompanying message with the + // result, which could be the empty string. + IPC_SYNC_MESSAGE_ROUTED0_2(AutomationMsg_WaitForExtensionTestResult, + bool /* test result */, + std::string /* message */) + + // Uninstalls an extension. On success |success| is true. + IPC_SYNC_MESSAGE_ROUTED1_1(AutomationMsg_UninstallExtension, + int /* extension handle */, + bool /* success */) + + // Enables an extension. On success |success| is true. + IPC_SYNC_MESSAGE_ROUTED1_1(AutomationMsg_EnableExtension, + int /* extension handle */, + bool /* success */) + + // Disables an extension. On success |success| is true. + IPC_SYNC_MESSAGE_ROUTED1_1(AutomationMsg_DisableExtension, + int /* extension handle */, + bool /* success */) + + // Executes the action associated with the given extension. This executes + // the extension's page or browser action in the given browser, but does + // not open popups. On success |success| is true. + IPC_SYNC_MESSAGE_ROUTED2_1( + AutomationMsg_ExecuteExtensionActionInActiveTabAsync, + int /* extension handle */, + int /* browser handle */, + bool /* success */) + + // Moves the browser action to the given index in the browser action toolbar. + // On success |success| is true. + IPC_SYNC_MESSAGE_ROUTED2_1(AutomationMsg_MoveExtensionBrowserAction, + int /* extension handle */, + int /* index */, + bool /* success */) + + // Gets an extension property |property type|. On success |success| is true, + // and |property value| is set. + IPC_SYNC_MESSAGE_ROUTED2_2(AutomationMsg_GetExtensionProperty, + int /* extension handle */, + AutomationMsg_ExtensionProperty /* property type */, + bool /* success */, + std::string /* property value */) + IPC_END_MESSAGES(Automation) diff --git a/chrome/test/automation/automation_proxy.cc b/chrome/test/automation/automation_proxy.cc index 46fcc3b..04bc7ea 100644 --- a/chrome/test/automation/automation_proxy.cc +++ b/chrome/test/automation/automation_proxy.cc @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include <sstream> - #include "chrome/test/automation/automation_proxy.h" +#include <sstream> + #include "base/basictypes.h" #include "base/file_version_info.h" #include "base/logging.h" @@ -16,8 +16,10 @@ #include "chrome/test/automation/automation_constants.h" #include "chrome/test/automation/automation_messages.h" #include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/automation/extension_proxy.h" #include "chrome/test/automation/tab_proxy.h" #include "chrome/test/automation/window_proxy.h" +#include <gtest/gtest.h> #include "ipc/ipc_descriptors.h" #if defined(OS_WIN) // TODO(port): Enable when dialog_delegate is ported. @@ -230,11 +232,25 @@ bool AutomationProxy::SavePackageShouldPromptUser(bool should_prompt) { return Send(new AutomationMsg_SavePackageShouldPromptUser(0, should_prompt)); } -bool AutomationProxy::InstallExtension(const FilePath& crx_file) { - AutomationMsg_ExtensionResponseValues response; - if (!Send(new AutomationMsg_InstallExtension(0, crx_file, &response))) - return false; - return response == AUTOMATION_MSG_EXTENSION_INSTALL_SUCCEEDED; +scoped_refptr<ExtensionProxy> AutomationProxy::InstallExtension( + const FilePath& crx_file) { + int handle = 0; + if (!Send(new AutomationMsg_InstallExtensionAndGetHandle(0, crx_file, + &handle))) + return NULL; + + return ProxyObjectFromHandle<ExtensionProxy>(handle); +} + +void AutomationProxy::EnsureExtensionTestResult() { + bool result; + std::string message; + if (!Send(new AutomationMsg_WaitForExtensionTestResult(0, &result, + &message))) { + FAIL() << "Could not send WaitForExtensionTestResult message"; + return; + } + ASSERT_TRUE(result) << "Extension test message: " << message; } bool AutomationProxy::GetEnabledExtensions( diff --git a/chrome/test/automation/automation_proxy.h b/chrome/test/automation/automation_proxy.h index 7809ec1..f0a6cd8 100644 --- a/chrome/test/automation/automation_proxy.h +++ b/chrome/test/automation/automation_proxy.h @@ -26,6 +26,7 @@ #include "ipc/ipc_sync_channel.h" class BrowserProxy; +class ExtensionProxy; class TabProxy; class WindowProxy; @@ -181,10 +182,13 @@ class AutomationProxy : public IPC::Channel::Listener, // sent. bool SavePackageShouldPromptUser(bool should_prompt) WARN_UNUSED_RESULT; - // Installs the extension crx. Returns true only if extension was installed - // and loaded successfully. - // Note: Overinstalls will fail. - bool InstallExtension(const FilePath& crx_file) WARN_UNUSED_RESULT; + // Installs the extension crx. Returns the ExtensionProxy for the + // installed extension, or NULL on failure. + // Note: Overinstalls and downgrades will return NULL. + scoped_refptr<ExtensionProxy> InstallExtension(const FilePath& crx_file); + + // Asserts that the next extension test result is true. + void EnsureExtensionTestResult(); // Gets a list of all enabled extensions' base directories. // Returns true on success. @@ -196,6 +200,7 @@ class AutomationProxy : public IPC::Channel::Listener, bool LoginWithUserAndPass(const std::string& username, const std::string& password) WARN_UNUSED_RESULT; #endif + // Returns the ID of the automation IPC channel, so that it can be // passed to the app as a launch parameter. const std::string& channel_id() const { return channel_id_; } diff --git a/chrome/test/automation/extension_proxy.cc b/chrome/test/automation/extension_proxy.cc new file mode 100644 index 0000000..66aa8e8 --- /dev/null +++ b/chrome/test/automation/extension_proxy.cc @@ -0,0 +1,141 @@ +// Copyright (c) 2010 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_proxy.h" + +#include "chrome/test/automation/automation_messages.h" +#include "chrome/test/automation/automation_proxy.h" +#include "chrome/test/automation/browser_proxy.h" +#include "testing/gtest/include/gtest/gtest.h" + +ExtensionProxy::ExtensionProxy(AutomationMessageSender* sender, + AutomationHandleTracker* tracker, + int handle) + : AutomationResourceProxy(tracker, sender, handle) { +} + +bool ExtensionProxy::Uninstall() { + if (!is_valid()) + return false; + + bool success = false; + if (!sender_->Send(new AutomationMsg_UninstallExtension(0, handle_, + &success))) + return false; + return success; +} + +bool ExtensionProxy::Enable() { + if (!is_valid()) + return false; + + bool success = false; + if (!sender_->Send(new AutomationMsg_EnableExtension(0, handle_, + &success))) + return false; + return success; +} + +bool ExtensionProxy::Disable() { + if (!is_valid()) + return false; + + bool success = false; + if (!sender_->Send(new AutomationMsg_DisableExtension(0, handle_, + &success))) + return false; + return success; +} + +bool ExtensionProxy::ExecuteActionInActiveTabAsync(BrowserProxy* browser) { + if (!is_valid()) + return false; + + bool success = false; + if (!sender_->Send(new AutomationMsg_ExecuteExtensionActionInActiveTabAsync( + 0, handle_, browser->handle(), &success))) + return false; + return success; +} + +bool ExtensionProxy::MoveBrowserAction(int index) { + if (!is_valid()) + return false; + bool success = false; + if (!sender_->Send( + new AutomationMsg_MoveExtensionBrowserAction(0, handle_, index, + &success))) + return false; + return success; +} + +bool ExtensionProxy::GetId(std::string* id) { + DCHECK(id); + return GetProperty(AUTOMATION_MSG_EXTENSION_ID, id); +} + +bool ExtensionProxy::GetName(std::string* name) { + DCHECK(name); + return GetProperty(AUTOMATION_MSG_EXTENSION_NAME, name); +} + +bool ExtensionProxy::GetVersion(std::string* version) { + DCHECK(version); + return GetProperty(AUTOMATION_MSG_EXTENSION_VERSION, version); +} + +bool ExtensionProxy::GetBrowserActionIndex(int* index) { + DCHECK(index); + std::string index_string; + if (!GetProperty(AUTOMATION_MSG_EXTENSION_BROWSER_ACTION_INDEX, + &index_string)) + return false; + // Do not modify |index| until we are sure we can get the value, just to be + // nice to the caller. + int converted_index; + if (!StringToInt(index_string, &converted_index)) { + LOG(ERROR) << "Received index string could not be converted to int: " + << index_string; + return false; + } + *index = converted_index; + return true; +} + +void ExtensionProxy::EnsureIdMatches(const std::string& expected_id) { + std::string id; + ASSERT_TRUE(GetId(&id)); + ASSERT_EQ(expected_id, id); +} + +void ExtensionProxy::EnsureNameMatches(const std::string& expected_name) { + std::string name; + ASSERT_TRUE(GetName(&name)); + ASSERT_EQ(expected_name, name); +} + +void ExtensionProxy::EnsureVersionMatches(const std::string& expected_version) { + std::string version; + ASSERT_TRUE(GetVersion(&version)); + ASSERT_EQ(expected_version, version); +} + +void ExtensionProxy::EnsureBrowserActionIndexMatches(int expected_index) { + int index; + ASSERT_TRUE(GetBrowserActionIndex(&index)); + ASSERT_EQ(expected_index, index); +} + +bool ExtensionProxy::GetProperty(AutomationMsg_ExtensionProperty type, + std::string* value) { + DCHECK(value); + if (!is_valid()) + return false; + + bool success = false; + if (!sender_->Send(new AutomationMsg_GetExtensionProperty(0, handle_, type, + &success, value))) + return false; + return success; +} diff --git a/chrome/test/automation/extension_proxy.h b/chrome/test/automation/extension_proxy.h new file mode 100644 index 0000000..c6ae6dda --- /dev/null +++ b/chrome/test/automation/extension_proxy.h @@ -0,0 +1,88 @@ +// Copyright (c) 2010 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_EXTENSION_PROXY_H_ +#define CHROME_TEST_AUTOMATION_EXTENSION_PROXY_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "base/weak_ptr.h" +#include "chrome/test/automation/automation_constants.h" +#include "chrome/test/automation/automation_handle_tracker.h" + +class AutomationMessageSender; +class BrowserProxy; + +// This class presents the interface to actions that can be performed on +// a given extension. This refers to a particular version of that extension. +// For example, it refers to Google Translate 1.0. If the extension is +// updated to a newer version, this proxy is invalidated. +class ExtensionProxy : public AutomationResourceProxy { + public: + // Creates an extension proxy referring to an extension id. + ExtensionProxy(AutomationMessageSender* sender, + AutomationHandleTracker* tracker, + int handle); + + // Uninstalls this extension. Returns true on success. + bool Uninstall() WARN_UNUSED_RESULT; + + // Enables this extension. Returns true on success. The extension + // should be disabled when this is called. + bool Enable() WARN_UNUSED_RESULT; + + // Disables this extension. Returns true on success. The extension + // should be enabled when this is called. + bool Disable() WARN_UNUSED_RESULT; + + // Executes the action associated with this extension. This may be a page + // action or a browser action. This is similar to clicking, but does not + // work with popups. Also, for page actions, this will execute the action + // even if the page action is not shown for the active tab. Returns true on + // success. + // TODO(kkania): Add support for popups. + bool ExecuteActionInActiveTabAsync(BrowserProxy* browser) + WARN_UNUSED_RESULT; + + // Moves the browser action from its current location in the browser action + // toolbar to a new |index|. Index should be less than the number of browser + // actions in the toolbar. Returns true on success. + bool MoveBrowserAction(int index) WARN_UNUSED_RESULT; + + // Gets the id of this extension. Returns true on success. + bool GetId(std::string* id) WARN_UNUSED_RESULT; + + // Gets the name of this extension. Returns true on success. + bool GetName(std::string* name) WARN_UNUSED_RESULT; + + // Gets the version string of this extension. Returns true on success. + bool GetVersion(std::string* version) WARN_UNUSED_RESULT; + + // Gets the index (zero-based) of this extension's browser action in + // the browser action toolbar. |index| will be set to -1 if the extension + // does not have a browser action in the toolbar. Returns true on success. + bool GetBrowserActionIndex(int* index) WARN_UNUSED_RESULT; + + // Asserts that |expected_id| matches this extension's id. + void EnsureIdMatches(const std::string& expected_id); + + // Asserts that |expected_name| matches this extension's name. + void EnsureNameMatches(const std::string& expected_name); + + // Asserts that |expected_version| matches this extension's name. + void EnsureVersionMatches(const std::string& expected_version); + + // Asserts that |expected_index| matches the index (zero-based) of this + // extension's browser action in the browser action toolbar. + void EnsureBrowserActionIndexMatches(int expected_index); + + private: + // Gets the string value of the property of type |type|. Returns true on + // success. + bool GetProperty(AutomationMsg_ExtensionProperty type, std::string* value) + WARN_UNUSED_RESULT; +}; + +#endif // CHROME_TEST_AUTOMATION_EXTENSION_PROXY_H_ diff --git a/chrome/test/automation/extension_proxy_uitest.cc b/chrome/test/automation/extension_proxy_uitest.cc new file mode 100644 index 0000000..18d385c --- /dev/null +++ b/chrome/test/automation/extension_proxy_uitest.cc @@ -0,0 +1,178 @@ +// Copyright (c) 2010 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/path_service.h" +#include "base/ref_counted.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/test/automation/automation_proxy.h" +#include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/automation/extension_proxy.h" +#include "chrome/test/automation/tab_proxy.h" +#include "chrome/test/ui/ui_test.h" + +namespace { + +// These tests are not meant to test the extension system itself, but to verify +// the correctness of ExtensionProxy and the AutomationProvider implementation +// behind it. +class ExtensionProxyUITest : public UITest { + public: + ExtensionProxyUITest() {} + + virtual void SetUp() { + UITest::SetUp(); + simple_extension_= InstallSimpleBrowserActionExtension(); + ASSERT_TRUE(simple_extension_.get()); + } + + protected: + // Installs a simple browser action extension from the sample_extensions + // folder. Returns an ExtensionProxy, which could be NULL. + scoped_refptr<ExtensionProxy> InstallSimpleBrowserActionExtension() { + return automation()->InstallExtension( + test_data_directory_.AppendASCII("extensions").AppendASCII("uitest"). + AppendASCII("simple_browser_action.crx")); + } + + // Installs a extension which, when clicking the browser action, renames + // the current tab to the tab's index. Returns an ExtensionProxy, + // which could be NULL. + scoped_refptr<ExtensionProxy> InstallRenameTabExtension() { + return automation()->InstallExtension( + test_data_directory_.AppendASCII("extensions").AppendASCII("uitest"). + AppendASCII("rename_tab.crx")); + } + + // The google translate extension, which is installed on test setup. + scoped_refptr<ExtensionProxy> simple_extension_; + + private: + DISALLOW_COPY_AND_ASSIGN(ExtensionProxyUITest); +}; + +TEST_F(ExtensionProxyUITest, NoSuchExtension) { + ASSERT_TRUE(simple_extension_->Uninstall()); + + // A proxy referring to an uninstalled extension should return false for all + // calls without actually invoking the extension system. + ASSERT_FALSE(simple_extension_->Uninstall()); + ASSERT_FALSE(simple_extension_->Enable()); + ASSERT_FALSE(simple_extension_->Disable()); + scoped_refptr<BrowserProxy> browser = automation()->GetBrowserWindow(0); + ASSERT_TRUE(browser.get()); + ASSERT_FALSE(simple_extension_-> + ExecuteActionInActiveTabAsync(browser.get())); + ASSERT_FALSE(simple_extension_->MoveBrowserAction(0)); + std::string name, version; + int index; + ASSERT_FALSE(simple_extension_->GetName(&name)); + ASSERT_FALSE(simple_extension_->GetVersion(&version)); + ASSERT_FALSE(simple_extension_->GetBrowserActionIndex(&index)); +} + +TEST_F(ExtensionProxyUITest, EnableDisable) { + ASSERT_TRUE(simple_extension_->Disable()); + ASSERT_TRUE(simple_extension_->Enable()); + ASSERT_TRUE(simple_extension_->Disable()); +} + +TEST_F(ExtensionProxyUITest, Uninstall) { + ASSERT_TRUE(simple_extension_->Uninstall()); + + // Uninstall a disabled extension. + simple_extension_ = InstallSimpleBrowserActionExtension(); + ASSERT_TRUE(simple_extension_.get()); + ASSERT_TRUE(simple_extension_->Disable()); + ASSERT_TRUE(simple_extension_->Uninstall()); + + // Uninstall a just enabled extension. + simple_extension_ = InstallSimpleBrowserActionExtension(); + ASSERT_TRUE(simple_extension_.get()); + ASSERT_TRUE(simple_extension_->Disable()); + ASSERT_TRUE(simple_extension_->Enable()); + ASSERT_TRUE(simple_extension_->Uninstall()); +} + +TEST_F(ExtensionProxyUITest, ExecuteBrowserActionInActiveTabAsync) { + scoped_refptr<BrowserProxy> browser = automation()->GetBrowserWindow(0); + ASSERT_TRUE(browser.get()); + scoped_refptr<ExtensionProxy> rename_tab_extension = + InstallRenameTabExtension(); + ASSERT_TRUE(rename_tab_extension.get()); + + // Need to use http in order for the extension to be able to inject + // javascript into the page. Extensions do not have permissions for + // chrome://* urls. + FilePath path; + // The root directory for the http server does not matter in this case, + // but we have to pick something. + PathService::Get(chrome::DIR_TEST_DATA, &path); + StartHttpServerWithPort(path, L"1365"); + GURL localhost = GURL("http://localhost:1365"); + NavigateToURL(localhost); + + // Click the browser action, which should rename the tab title to + // the tab's index. + ASSERT_TRUE(rename_tab_extension-> + ExecuteActionInActiveTabAsync(browser.get())); + ASSERT_NO_FATAL_FAILURE(automation()->EnsureExtensionTestResult()); + + scoped_refptr<TabProxy> display_tab = browser->GetTab(0); + ASSERT_TRUE(display_tab); + std::wstring title_wstring; + ASSERT_TRUE(display_tab->GetTabTitle(&title_wstring)); + ASSERT_STREQ(L"0", title_wstring.c_str()); + + // Click the action again right after navigating to a new page. + ASSERT_TRUE(browser->AppendTab(localhost)); + display_tab = browser->GetTab(1); + ASSERT_TRUE(display_tab); + ASSERT_TRUE(rename_tab_extension-> + ExecuteActionInActiveTabAsync(browser.get())); + ASSERT_NO_FATAL_FAILURE(automation()->EnsureExtensionTestResult()); + ASSERT_TRUE(display_tab->GetTabTitle(&title_wstring)); + ASSERT_STREQ(L"1", title_wstring.c_str()); + + // Do not forget to stop the server. + StopHttpServer(); +} + +TEST_F(ExtensionProxyUITest, MoveBrowserAction) { + scoped_refptr<ExtensionProxy> rename_tab_extension = + InstallRenameTabExtension(); + ASSERT_TRUE(rename_tab_extension.get()); + ASSERT_NO_FATAL_FAILURE(simple_extension_-> + EnsureBrowserActionIndexMatches(0)); + ASSERT_NO_FATAL_FAILURE(rename_tab_extension-> + EnsureBrowserActionIndexMatches(1)); + + // Move google translate to the end, then beginning, and verify. + ASSERT_TRUE(simple_extension_->MoveBrowserAction(1)); + ASSERT_NO_FATAL_FAILURE(simple_extension_-> + EnsureBrowserActionIndexMatches(1)); + ASSERT_NO_FATAL_FAILURE(rename_tab_extension-> + EnsureBrowserActionIndexMatches(0)); + ASSERT_TRUE(simple_extension_->MoveBrowserAction(0)); + ASSERT_NO_FATAL_FAILURE(simple_extension_-> + EnsureBrowserActionIndexMatches(0)); + ASSERT_NO_FATAL_FAILURE(rename_tab_extension-> + EnsureBrowserActionIndexMatches(1)); + + // Try moving browser action to invalid index. + ASSERT_FALSE(simple_extension_->MoveBrowserAction(-1)); + ASSERT_FALSE(simple_extension_->MoveBrowserAction(2)); +} + +TEST_F(ExtensionProxyUITest, GetProperty) { + ASSERT_NO_FATAL_FAILURE(simple_extension_-> + EnsureIdMatches("aiglobglfckejlcpcbdokbkbjeemfhno")); + ASSERT_NO_FATAL_FAILURE(simple_extension_-> + EnsureNameMatches("Browser Action")); + ASSERT_NO_FATAL_FAILURE(simple_extension_-> + EnsureVersionMatches("0.1.1")); + ASSERT_NO_FATAL_FAILURE(simple_extension_-> + EnsureBrowserActionIndexMatches(0)); +} + +} // namespace diff --git a/chrome/test/pyautolib/pyautolib.cc b/chrome/test/pyautolib/pyautolib.cc index 84bdd18..39a5b32 100644 --- a/chrome/test/pyautolib/pyautolib.cc +++ b/chrome/test/pyautolib/pyautolib.cc @@ -5,6 +5,7 @@ #include "base/scoped_ptr.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" +#include "chrome/test/automation/extension_proxy.h" #include "chrome/test/automation/tab_proxy.h" #include "chrome/test/pyautolib/pyautolib.h" #include "googleurl/src/gurl.h" @@ -156,7 +157,9 @@ bool PyUITestBase::OpenNewBrowserWindow(bool show) { } bool PyUITestBase::InstallExtension(const FilePath& crx_file) { - return automation()->InstallExtension(crx_file); + scoped_refptr<ExtensionProxy> proxy = + automation()->InstallExtension(crx_file); + return proxy.get() != NULL; } bool PyUITestBase::GetBookmarkBarVisibility() { @@ -274,7 +277,6 @@ scoped_refptr<BrowserProxy> PyUITestBase::GetBrowserWindow(int window_index) { return automation()->GetBrowserWindow(window_index); } - std::string PyUITestBase::_SendJSONRequest(int window_index, std::string& request) { scoped_refptr<BrowserProxy> browser_proxy = @@ -286,4 +288,3 @@ std::string PyUITestBase::_SendJSONRequest(int window_index, } return response; } - |