diff options
author | Ben Murdoch <benm@google.com> | 2010-07-29 17:14:53 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2010-08-04 14:29:45 +0100 |
commit | c407dc5cd9bdc5668497f21b26b09d988ab439de (patch) | |
tree | 7eaf8707c0309516bdb042ad976feedaf72b0bb1 /chrome/browser/automation | |
parent | 0998b1cdac5733f299c12d88bc31ef9c8035b8fa (diff) | |
download | external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.zip external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.gz external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.bz2 |
Merge Chromium src@r53293
Change-Id: Ia79acf8670f385cee48c45b0a75371d8e950af34
Diffstat (limited to 'chrome/browser/automation')
44 files changed, 12138 insertions, 0 deletions
diff --git a/chrome/browser/automation/automation_autocomplete_edit_tracker.h b/chrome/browser/automation/automation_autocomplete_edit_tracker.h new file mode 100644 index 0000000..3f741f3 --- /dev/null +++ b/chrome/browser/automation/automation_autocomplete_edit_tracker.h @@ -0,0 +1,33 @@ +// 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_BROWSER_AUTOMATION_AUTOMATION_AUTOCOMPLETE_EDIT_TRACKER_H_ +#define CHROME_BROWSER_AUTOMATION_AUTOMATION_AUTOCOMPLETE_EDIT_TRACKER_H_ + +#include "chrome/browser/autocomplete/autocomplete_edit_view.h" +#include "chrome/browser/automation/automation_resource_tracker.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/notification_type.h" + +class AutomationAutocompleteEditTracker + : public AutomationResourceTracker<AutocompleteEditView*> { + public: + explicit AutomationAutocompleteEditTracker(IPC::Message::Sender* automation) + : AutomationResourceTracker<AutocompleteEditView*>(automation) { } + + virtual ~AutomationAutocompleteEditTracker() { + } + + virtual void AddObserver(AutocompleteEditView* resource) { + registrar_.Add(this, NotificationType::AUTOCOMPLETE_EDIT_DESTROYED, + Source<AutocompleteEditView>(resource)); + } + + virtual void RemoveObserver(AutocompleteEditView* resource) { + registrar_.Remove(this, NotificationType::AUTOCOMPLETE_EDIT_DESTROYED, + Source<AutocompleteEditView>(resource)); + } +}; + +#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_AUTOCOMPLETE_EDIT_TRACKER_H_ diff --git a/chrome/browser/automation/automation_browser_tracker.h b/chrome/browser/automation/automation_browser_tracker.h new file mode 100644 index 0000000..aa6d034 --- /dev/null +++ b/chrome/browser/automation/automation_browser_tracker.h @@ -0,0 +1,32 @@ +// 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_BROWSER_AUTOMATION_AUTOMATION_BROWSER_TRACKER_H__ +#define CHROME_BROWSER_AUTOMATION_AUTOMATION_BROWSER_TRACKER_H__ + +#include "chrome/browser/automation/automation_resource_tracker.h" +#include "chrome/browser/browser.h" +#include "chrome/common/notification_source.h" + +// Tracks Browser objects. +class AutomationBrowserTracker : public AutomationResourceTracker<Browser*> { + public: + explicit AutomationBrowserTracker(IPC::Message::Sender* automation) + : AutomationResourceTracker<Browser*>(automation) { } + + virtual ~AutomationBrowserTracker() { + } + + virtual void AddObserver(Browser* resource) { + registrar_.Add(this, NotificationType::BROWSER_CLOSED, + Source<Browser>(resource)); + } + + virtual void RemoveObserver(Browser* resource) { + registrar_.Remove(this, NotificationType::BROWSER_CLOSED, + Source<Browser>(resource)); + } +}; + +#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_BROWSER_TRACKER_H__ diff --git a/chrome/browser/automation/automation_extension_function.cc b/chrome/browser/automation/automation_extension_function.cc new file mode 100644 index 0000000..c58e45c --- /dev/null +++ b/chrome/browser/automation/automation_extension_function.cc @@ -0,0 +1,165 @@ +// 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/json_reader.h" +#include "base/json/json_writer.h" +#include "chrome/browser/automation/extension_automation_constants.h" +#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" + +TabContents* AutomationExtensionFunction::api_handler_tab_ = NULL; +AutomationExtensionFunction::PendingFunctionsMap + AutomationExtensionFunction::pending_functions_; + +void AutomationExtensionFunction::SetArgs(const ListValue* 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() { + // Already JSON-encoded, so override the base class's implementation. + return json_result_; +} + +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. + 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; + base::JSONWriter::Write(&message_to_host, false, &message); + 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::Enable( + TabContents* api_handler_tab, + const std::vector<std::string>& 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; + + std::vector<std::string> function_names; + if (functions_enabled.size() == 1 && functions_enabled[0] == "*") { + ExtensionFunctionDispatcher::GetAllFunctionNames(&function_names); + } else { + 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, + const std::string& origin, + const std::string& target) { + namespace keys = extension_automation_constants; + + // 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. + scoped_ptr<Value> message_value(base::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); + + 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; + } + } + } + + 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..5e15e9e --- /dev/null +++ b/chrome/browser/automation/automation_extension_function.h @@ -0,0 +1,77 @@ +// 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 <map> +#include <string> +#include <vector> + +#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 AsyncExtensionFunction { + public: + AutomationExtensionFunction() { } + + // ExtensionFunction implementation. + virtual void SetArgs(const ListValue* args); + virtual const std::string GetResult(); + 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). 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(). + // + // 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 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, + const std::string& target); + + private: + ~AutomationExtensionFunction() {} + + // 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); +}; + +#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_EXTENSION_FUNCTION_H_ 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_profile_impl.cc b/chrome/browser/automation/automation_profile_impl.cc new file mode 100644 index 0000000..751019a --- /dev/null +++ b/chrome/browser/automation/automation_profile_impl.cc @@ -0,0 +1,216 @@ +// Copyright (c) 2006-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/browser/automation/automation_profile_impl.h" + +#include <map> + +#include "chrome/browser/automation/automation_resource_message_filter.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/net/chrome_url_request_context.h" +#include "chrome/browser/profile.h" +#include "net/base/net_errors.h" +#include "net/url_request/url_request_context.h" +#include "chrome/test/automation/automation_messages.h" + +namespace AutomationRequestContext { + +// A special Request context for automation. Substitute a few things +// like cookie store, proxy settings etc to handle them differently +// for automation. +class AutomationURLRequestContext : public ChromeURLRequestContext { + public: + AutomationURLRequestContext(ChromeURLRequestContext* original_context, + net::CookieStore* automation_cookie_store, + net::CookiePolicy* automation_cookie_policy) + : ChromeURLRequestContext(original_context), + // We must hold a reference to |original_context|, since many + // of the dependencies that ChromeURLRequestContext(original_context) + // copied are scoped to |original_context|. + original_context_(original_context) { + cookie_policy_ = automation_cookie_policy; + cookie_store_ = automation_cookie_store; + } + + virtual bool IsExternal() const { + return true; + } + + private: + virtual ~AutomationURLRequestContext() { + // Clear out members before calling base class dtor since we don't + // own any of them. + + // Clear URLRequestContext members. + host_resolver_ = NULL; + proxy_service_ = NULL; + http_transaction_factory_ = NULL; + ftp_transaction_factory_ = NULL; + cookie_store_ = NULL; + transport_security_state_ = NULL; + } + + scoped_refptr<ChromeURLRequestContext> original_context_; + DISALLOW_COPY_AND_ASSIGN(AutomationURLRequestContext); +}; + +// CookieStore specialization to have automation specific +// behavior for cookies. +class AutomationCookieStore : public net::CookieStore { + public: + AutomationCookieStore(net::CookieStore* original_cookie_store, + AutomationResourceMessageFilter* automation_client, + int tab_handle) + : original_cookie_store_(original_cookie_store), + automation_client_(automation_client), + tab_handle_(tab_handle) { + } + + virtual ~AutomationCookieStore() { + DLOG(INFO) << "In " << __FUNCTION__; + } + + // CookieStore implementation. + virtual bool SetCookieWithOptions(const GURL& url, + const std::string& cookie_line, + const net::CookieOptions& options) { + // The cookie_string_ is available only once, i.e. once it is read by + // it is invalidated. + cookie_string_ = cookie_line; + return true; + } + + virtual std::string GetCookiesWithOptions(const GURL& url, + const net::CookieOptions& options) { + return cookie_string_; + } + + virtual void DeleteCookie(const GURL& url, + const std::string& cookie_name) { + NOTREACHED() << "Should not get called for an automation profile"; + } + + virtual net::CookieMonster* GetCookieMonster() { + NOTREACHED() << "Should not get called for an automation profile"; + return NULL; + } + + protected: + void SendIPCMessageOnIOThread(IPC::Message* m) { + if (ChromeThread::CurrentlyOn(ChromeThread::IO)) { + automation_client_->Send(m); + } else { + Task* task = NewRunnableMethod(this, + &AutomationCookieStore::SendIPCMessageOnIOThread, m); + ChromeThread::PostTask(ChromeThread::IO, FROM_HERE, task); + } + } + + net::CookieStore* original_cookie_store_; + scoped_refptr<AutomationResourceMessageFilter> automation_client_; + int tab_handle_; + std::string cookie_string_; + + private: + DISALLOW_COPY_AND_ASSIGN(AutomationCookieStore); +}; + +// CookiePolicy specialization for automation specific cookie policies. +class AutomationCookiePolicy : public net::CookiePolicy { + public: + AutomationCookiePolicy(AutomationResourceMessageFilter* automation_client, + int tab_handle, net::CookieStore* cookie_store) + : automation_client_(automation_client), + tab_handle_(tab_handle), + cookie_store_(cookie_store) {} + + virtual int CanGetCookies(const GURL& url, + const GURL& first_party_for_cookies, + net::CompletionCallback* callback) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + + if (automation_client_.get()) { + automation_client_->GetCookiesForUrl(tab_handle_, url, callback, + cookie_store_.get()); + return net::ERR_IO_PENDING; + } + return net::ERR_ACCESS_DENIED; + } + + virtual int CanSetCookie(const GURL& url, + const GURL& first_party_for_cookies, + const std::string& cookie_line, + net::CompletionCallback* callback) { + if (automation_client_.get()) { + automation_client_->Send(new AutomationMsg_SetCookieAsync(0, + tab_handle_, url, cookie_line)); + } + return net::ERR_ACCESS_DENIED; + } + + private: + scoped_refptr<AutomationResourceMessageFilter> automation_client_; + int tab_handle_; + scoped_refptr<net::CookieStore> cookie_store_; + + DISALLOW_COPY_AND_ASSIGN(AutomationCookiePolicy); +}; + +class Factory : public ChromeURLRequestContextFactory { + public: + Factory(ChromeURLRequestContextGetter* original_context_getter, + Profile* profile, + AutomationResourceMessageFilter* automation_client, + int tab_handle) + : ChromeURLRequestContextFactory(profile), + original_context_getter_(original_context_getter), + automation_client_(automation_client), + tab_handle_(tab_handle) { + } + + virtual ChromeURLRequestContext* Create() { + ChromeURLRequestContext* original_context = + original_context_getter_->GetIOContext(); + + // Create an automation cookie store. + scoped_refptr<net::CookieStore> automation_cookie_store = + new AutomationCookieStore(original_context->cookie_store(), + automation_client_, + tab_handle_); + + // Create an automation cookie policy. + AutomationCookiePolicy* automation_cookie_policy = + new AutomationCookiePolicy(automation_client_, + tab_handle_, + automation_cookie_store); + + return new AutomationURLRequestContext(original_context, + automation_cookie_store, + automation_cookie_policy); + } + + private: + scoped_refptr<ChromeURLRequestContextGetter> original_context_getter_; + scoped_refptr<AutomationResourceMessageFilter> automation_client_; + int tab_handle_; +}; + +ChromeURLRequestContextGetter* CreateAutomationURLRequestContextForTab( + int tab_handle, + Profile* profile, + AutomationResourceMessageFilter* automation_client) { + ChromeURLRequestContextGetter* original_context = + static_cast<ChromeURLRequestContextGetter*>( + profile->GetRequestContext()); + + ChromeURLRequestContextGetter* request_context = + new ChromeURLRequestContextGetter( + NULL, // Don't register an observer on PrefService. + new Factory(original_context, profile, automation_client, + tab_handle)); + return request_context; +} + +} // namespace AutomationRequestContext + diff --git a/chrome/browser/automation/automation_profile_impl.h b/chrome/browser/automation/automation_profile_impl.h new file mode 100644 index 0000000..5bfba8e --- /dev/null +++ b/chrome/browser/automation/automation_profile_impl.h @@ -0,0 +1,25 @@ +// 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. + +#ifndef CHROME_BROWSER_AUTOMATION_AUTOMATION_PROFILE_IMPL_H_ +#define CHROME_BROWSER_AUTOMATION_AUTOMATION_PROFILE_IMPL_H_ + +#include "ipc/ipc_message.h" + +class Profile; +class ChromeURLRequestContextGetter; +class AutomationResourceMessageFilter; + +namespace AutomationRequestContext { + +// Returns the URL request context to be used by HTTP requests handled over +// the automation channel. +ChromeURLRequestContextGetter* CreateAutomationURLRequestContextForTab( + int tab_handle, + Profile* profile, + AutomationResourceMessageFilter* automation_client); + +} + +#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_PROFILE_IMPL_H_ diff --git a/chrome/browser/automation/automation_provider.cc b/chrome/browser/automation/automation_provider.cc new file mode 100644 index 0000000..6c66931 --- /dev/null +++ b/chrome/browser/automation/automation_provider.cc @@ -0,0 +1,4133 @@ +// 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_provider.h" + +#include <set> + +#include "app/l10n_util.h" +#include "app/message_box_flags.h" +#include "base/callback.h" +#include "base/file_path.h" +#include "base/file_version_info.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/json/string_escape.h" +#include "base/keyboard_codes.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/stl_util-inl.h" +#include "base/string_util.h" +#include "base/task.h" +#include "base/thread.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "base/waitable_event.h" +#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/autofill/autofill_manager.h" +#include "chrome/browser/automation/automation_autocomplete_edit_tracker.h" +#include "chrome/browser/automation/automation_browser_tracker.h" +#include "chrome/browser/automation/automation_extension_tracker.h" +#include "chrome/browser/automation/automation_provider_json.h" +#include "chrome/browser/automation/automation_provider_list.h" +#include "chrome/browser/automation/automation_provider_observers.h" +#include "chrome/browser/automation/automation_resource_message_filter.h" +#include "chrome/browser/automation/automation_tab_tracker.h" +#include "chrome/browser/automation/automation_window_tracker.h" +#include "chrome/browser/automation/extension_port_container.h" +#include "chrome/browser/autocomplete/autocomplete_edit.h" +#include "chrome/browser/blocked_popup_container.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/bookmarks/bookmark_storage.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/browser_window.h" +#include "chrome/browser/browsing_data_remover.h" +#include "chrome/browser/character_encoding.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/dom_operation_notification_details.h" +#include "chrome/browser/debugger/devtools_manager.h" +#include "chrome/browser/download/download_item.h" +#include "chrome/browser/download/download_shelf.h" +#include "chrome/browser/download/save_package.h" +#include "chrome/browser/extensions/crx_installer.h" +#include "chrome/browser/extensions/extension_browser_event_router.h" +#include "chrome/browser/extensions/extension_host.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" +#include "chrome/browser/find_notification_details.h" +#include "chrome/browser/host_content_settings_map.h" +#include "chrome/browser/importer/importer.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "chrome/browser/io_thread.h" +#include "chrome/browser/location_bar.h" +#include "chrome/browser/login_prompt.h" +#include "chrome/browser/net/url_request_mock_util.h" +#include "chrome/browser/platform_util.h" +#include "chrome/browser/pref_service.h" +#include "chrome/browser/printing/print_job.h" +#include "chrome/browser/profile_manager.h" +#include "chrome/browser/renderer_host/render_process_host.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/ssl/ssl_manager.h" +#include "chrome/browser/ssl/ssl_blocking_page.h" +#include "chrome/browser/tab_contents/infobar_delegate.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/tab_contents_view.h" +#include "chrome/browser/translate/translate_infobar_delegate.h" +#include "chrome/common/automation_constants.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/chrome_version_info.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/json_value_serializer.h" +#include "chrome/common/net/url_request_context_getter.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "chrome/test/automation/automation_messages.h" +#include "chrome/test/automation/tab_proxy.h" +#include "net/proxy/proxy_service.h" +#include "net/proxy/proxy_config_service_fixed.h" +#include "net/url_request/url_request_context.h" +#include "chrome/browser/automation/ui_controls.h" +#include "views/event.h" +#include "webkit/glue/password_form.h" +#include "webkit/glue/plugins/plugin_list.h" + +#if defined(OS_WIN) +#include "chrome/browser/external_tab_container_win.h" +#endif // defined(OS_WIN) + +using base::Time; + +class AutomationInterstitialPage : public InterstitialPage { + public: + AutomationInterstitialPage(TabContents* tab, + const GURL& url, + const std::string& contents) + : InterstitialPage(tab, true, url), + contents_(contents) { + } + + virtual std::string GetHTMLContents() { return contents_; } + + private: + std::string contents_; + + DISALLOW_COPY_AND_ASSIGN(AutomationInterstitialPage); +}; + +class ClickTask : public Task { + public: + explicit ClickTask(int flags) : flags_(flags) {} + virtual ~ClickTask() {} + + virtual void Run() { + ui_controls::MouseButton button = ui_controls::LEFT; + if ((flags_ & views::Event::EF_LEFT_BUTTON_DOWN) == + views::Event::EF_LEFT_BUTTON_DOWN) { + button = ui_controls::LEFT; + } else if ((flags_ & views::Event::EF_RIGHT_BUTTON_DOWN) == + views::Event::EF_RIGHT_BUTTON_DOWN) { + button = ui_controls::RIGHT; + } else if ((flags_ & views::Event::EF_MIDDLE_BUTTON_DOWN) == + views::Event::EF_MIDDLE_BUTTON_DOWN) { + button = ui_controls::MIDDLE; + } else { + NOTREACHED(); + } + + ui_controls::SendMouseClick(button); + } + + private: + int flags_; + + DISALLOW_COPY_AND_ASSIGN(ClickTask); +}; + +AutomationProvider::AutomationProvider(Profile* profile) + : redirect_query_(0), + 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( + new AutomationAutocompleteEditTracker(this)); + 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(); +} + +AutomationProvider::~AutomationProvider() { + STLDeleteContainerPairSecondPointers(port_containers_.begin(), + port_containers_.end()); + port_containers_.clear(); + + // Make sure that any outstanding NotificationObservers also get destroyed. + ObserverList<NotificationObserver>::Iterator it(notification_observer_list_); + NotificationObserver* observer; + while ((observer = it.GetNext()) != NULL) + delete observer; + + if (channel_.get()) { + channel_->Close(); + } + g_browser_process->ReleaseModule(); +} + +void AutomationProvider::ConnectToChannel(const std::string& channel_id) { + automation_resource_message_filter_ = new AutomationResourceMessageFilter; + channel_.reset( + new IPC::SyncChannel(channel_id, IPC::Channel::MODE_CLIENT, this, + automation_resource_message_filter_, + g_browser_process->io_thread()->message_loop(), + true, g_browser_process->shutdown_event())); + scoped_ptr<FileVersionInfo> version_info(chrome::GetChromeVersionInfo()); + std::string version_string; + if (version_info != NULL) { + version_string = WideToASCII(version_info->file_version()); + } + + // Send a hello message with our current automation protocol version. + channel_->Send(new AutomationMsg_Hello(0, version_string.c_str())); +} + +void AutomationProvider::SetExpectedTabCount(size_t expected_tabs) { + if (expected_tabs == 0) { + Send(new AutomationMsg_InitialLoadsComplete(0)); + } else { + initial_load_observer_.reset(new InitialLoadObserver(expected_tabs, this)); + } +} + +NotificationObserver* AutomationProvider::AddNavigationStatusListener( + NavigationController* tab, IPC::Message* reply_message, + int number_of_navigations, bool include_current_navigation) { + NotificationObserver* observer = + new NavigationNotificationObserver(tab, this, reply_message, + number_of_navigations, + include_current_navigation); + + notification_observer_list_.AddObserver(observer); + return observer; +} + +void AutomationProvider::RemoveNavigationStatusListener( + NotificationObserver* obs) { + notification_observer_list_.RemoveObserver(obs); +} + +NotificationObserver* AutomationProvider::AddTabStripObserver( + Browser* parent, + IPC::Message* reply_message) { + NotificationObserver* observer = + new TabAppendedNotificationObserver(parent, this, reply_message); + notification_observer_list_.AddObserver(observer); + + return observer; +} + +void AutomationProvider::RemoveTabStripObserver(NotificationObserver* obs) { + notification_observer_list_.RemoveObserver(obs); +} + +void AutomationProvider::AddLoginHandler(NavigationController* tab, + LoginHandler* handler) { + login_handler_map_[tab] = handler; +} + +void AutomationProvider::RemoveLoginHandler(NavigationController* tab) { + DCHECK(login_handler_map_[tab]); + login_handler_map_.erase(tab); +} + +void AutomationProvider::AddPortContainer(ExtensionPortContainer* port) { + int port_id = port->port_id(); + DCHECK_NE(-1, port_id); + DCHECK(port_containers_.find(port_id) == port_containers_.end()); + + port_containers_[port_id] = port; +} + +void AutomationProvider::RemovePortContainer(ExtensionPortContainer* port) { + int port_id = port->port_id(); + DCHECK_NE(-1, port_id); + + PortContainerMap::iterator it = port_containers_.find(port_id); + DCHECK(it != port_containers_.end()); + + if (it != port_containers_.end()) { + delete it->second; + port_containers_.erase(it); + } +} + +ExtensionPortContainer* AutomationProvider::GetPortContainer( + int port_id) const { + PortContainerMap::const_iterator it = port_containers_.find(port_id); + if (it == port_containers_.end()) + return NULL; + + return it->second; +} + +int AutomationProvider::GetIndexForNavigationController( + const NavigationController* controller, const Browser* parent) const { + DCHECK(parent); + 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) + IPC_MESSAGE_HANDLER(AutomationMsg_CloseBrowserRequestAsync, + CloseBrowserAsync) + IPC_MESSAGE_HANDLER(AutomationMsg_ActivateTab, ActivateTab) + IPC_MESSAGE_HANDLER(AutomationMsg_ActiveTabIndex, GetActiveTabIndex) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_AppendTab, AppendTab) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_CloseTab, CloseTab) + IPC_MESSAGE_HANDLER(AutomationMsg_GetCookies, GetCookies) + IPC_MESSAGE_HANDLER(AutomationMsg_SetCookie, SetCookie) + IPC_MESSAGE_HANDLER(AutomationMsg_DeleteCookie, DeleteCookie) + IPC_MESSAGE_HANDLER(AutomationMsg_ShowCollectedCookiesDialog, + ShowCollectedCookiesDialog) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_NavigateToURL, NavigateToURL) + IPC_MESSAGE_HANDLER_DELAY_REPLY( + AutomationMsg_NavigateToURLBlockUntilNavigationsComplete, + NavigateToURLBlockUntilNavigationsComplete) + IPC_MESSAGE_HANDLER(AutomationMsg_NavigationAsync, NavigationAsync) + IPC_MESSAGE_HANDLER(AutomationMsg_NavigationAsyncWithDisposition, + NavigationAsyncWithDisposition) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_GoBack, GoBack) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_GoForward, GoForward) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_Reload, Reload) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_SetAuth, SetAuth) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_CancelAuth, CancelAuth) + IPC_MESSAGE_HANDLER(AutomationMsg_NeedsAuth, NeedsAuth) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_RedirectsFrom, + GetRedirectsFrom) + IPC_MESSAGE_HANDLER(AutomationMsg_BrowserWindowCount, GetBrowserWindowCount) + IPC_MESSAGE_HANDLER(AutomationMsg_NormalBrowserWindowCount, + GetNormalBrowserWindowCount) + IPC_MESSAGE_HANDLER(AutomationMsg_BrowserWindow, GetBrowserWindow) + IPC_MESSAGE_HANDLER(AutomationMsg_GetBrowserLocale, GetBrowserLocale) + IPC_MESSAGE_HANDLER(AutomationMsg_LastActiveBrowserWindow, + GetLastActiveBrowserWindow) + IPC_MESSAGE_HANDLER(AutomationMsg_ActiveWindow, GetActiveWindow) + IPC_MESSAGE_HANDLER(AutomationMsg_FindNormalBrowserWindow, + FindNormalBrowserWindow) + IPC_MESSAGE_HANDLER(AutomationMsg_IsWindowActive, IsWindowActive) + IPC_MESSAGE_HANDLER(AutomationMsg_ActivateWindow, ActivateWindow) + IPC_MESSAGE_HANDLER(AutomationMsg_IsWindowMaximized, IsWindowMaximized) + IPC_MESSAGE_HANDLER(AutomationMsg_WindowExecuteCommandAsync, + ExecuteBrowserCommandAsync) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_WindowExecuteCommand, + ExecuteBrowserCommand) + IPC_MESSAGE_HANDLER(AutomationMsg_TerminateSession, TerminateSession) + IPC_MESSAGE_HANDLER(AutomationMsg_WindowViewBounds, WindowGetViewBounds) + IPC_MESSAGE_HANDLER(AutomationMsg_GetWindowBounds, GetWindowBounds) + IPC_MESSAGE_HANDLER(AutomationMsg_SetWindowBounds, SetWindowBounds) + IPC_MESSAGE_HANDLER(AutomationMsg_SetWindowVisible, SetWindowVisible) + IPC_MESSAGE_HANDLER(AutomationMsg_WindowClick, WindowSimulateClick) + IPC_MESSAGE_HANDLER(AutomationMsg_WindowMouseMove, WindowSimulateMouseMove) + IPC_MESSAGE_HANDLER(AutomationMsg_WindowKeyPress, WindowSimulateKeyPress) +#if !defined(OS_MACOSX) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_WindowDrag, + WindowSimulateDrag) +#endif // !defined(OS_MACOSX) + IPC_MESSAGE_HANDLER(AutomationMsg_TabCount, GetTabCount) + IPC_MESSAGE_HANDLER(AutomationMsg_Type, GetType) + IPC_MESSAGE_HANDLER(AutomationMsg_Tab, GetTab) +#if defined(OS_WIN) + IPC_MESSAGE_HANDLER(AutomationMsg_TabHWND, GetTabHWND) +#endif // defined(OS_WIN) + IPC_MESSAGE_HANDLER(AutomationMsg_TabProcessID, GetTabProcessID) + IPC_MESSAGE_HANDLER(AutomationMsg_TabTitle, GetTabTitle) + IPC_MESSAGE_HANDLER(AutomationMsg_TabIndex, GetTabIndex) + IPC_MESSAGE_HANDLER(AutomationMsg_TabURL, GetTabURL) + IPC_MESSAGE_HANDLER(AutomationMsg_ShelfVisibility, GetShelfVisibility) + IPC_MESSAGE_HANDLER(AutomationMsg_IsFullscreen, IsFullscreen) + IPC_MESSAGE_HANDLER(AutomationMsg_IsFullscreenBubbleVisible, + GetFullscreenBubbleVisibility) + IPC_MESSAGE_HANDLER(AutomationMsg_HandleUnused, HandleUnused) + IPC_MESSAGE_HANDLER(AutomationMsg_ApplyAccelerator, ApplyAccelerator) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_DomOperation, + ExecuteJavascript) + IPC_MESSAGE_HANDLER(AutomationMsg_ConstrainedWindowCount, + GetConstrainedWindowCount) + IPC_MESSAGE_HANDLER(AutomationMsg_FindInPage, HandleFindInPageRequest) + IPC_MESSAGE_HANDLER(AutomationMsg_GetFocusedViewID, GetFocusedViewID) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_InspectElement, + HandleInspectElementRequest) + IPC_MESSAGE_HANDLER(AutomationMsg_DownloadDirectory, GetDownloadDirectory) + IPC_MESSAGE_HANDLER(AutomationMsg_SetProxyConfig, SetProxyConfig); + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_OpenNewBrowserWindow, + OpenNewBrowserWindow) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_OpenNewBrowserWindowOfType, + OpenNewBrowserWindowOfType) + IPC_MESSAGE_HANDLER(AutomationMsg_WindowForBrowser, GetWindowForBrowser) + IPC_MESSAGE_HANDLER(AutomationMsg_AutocompleteEditForBrowser, + GetAutocompleteEditForBrowser) + IPC_MESSAGE_HANDLER(AutomationMsg_BrowserForWindow, GetBrowserForWindow) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_ShowInterstitialPage, + ShowInterstitialPage) + IPC_MESSAGE_HANDLER(AutomationMsg_HideInterstitialPage, + HideInterstitialPage) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_WaitForTabToBeRestored, + WaitForTabToBeRestored) + IPC_MESSAGE_HANDLER(AutomationMsg_GetSecurityState, GetSecurityState) + IPC_MESSAGE_HANDLER(AutomationMsg_GetPageType, GetPageType) + IPC_MESSAGE_HANDLER(AutomationMsg_GetMetricEventDuration, + GetMetricEventDuration) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_ActionOnSSLBlockingPage, + ActionOnSSLBlockingPage) + IPC_MESSAGE_HANDLER(AutomationMsg_BringBrowserToFront, BringBrowserToFront) + IPC_MESSAGE_HANDLER(AutomationMsg_IsMenuCommandEnabled, + IsMenuCommandEnabled) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_PrintNow, PrintNow) + IPC_MESSAGE_HANDLER(AutomationMsg_PrintAsync, PrintAsync) + IPC_MESSAGE_HANDLER(AutomationMsg_SavePage, SavePage) + IPC_MESSAGE_HANDLER(AutomationMsg_AutocompleteEditGetText, + GetAutocompleteEditText) + IPC_MESSAGE_HANDLER(AutomationMsg_AutocompleteEditSetText, + SetAutocompleteEditText) + IPC_MESSAGE_HANDLER(AutomationMsg_AutocompleteEditIsQueryInProgress, + AutocompleteEditIsQueryInProgress) + IPC_MESSAGE_HANDLER(AutomationMsg_AutocompleteEditGetMatches, + AutocompleteEditGetMatches) + IPC_MESSAGE_HANDLER(AutomationMsg_OpenFindInPage, + HandleOpenFindInPageRequest) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_Find, HandleFindRequest) + IPC_MESSAGE_HANDLER(AutomationMsg_FindWindowVisibility, + GetFindWindowVisibility) + IPC_MESSAGE_HANDLER(AutomationMsg_FindWindowLocation, + HandleFindWindowLocationRequest) + IPC_MESSAGE_HANDLER(AutomationMsg_BookmarkBarVisibility, + GetBookmarkBarVisibility) + IPC_MESSAGE_HANDLER(AutomationMsg_GetBookmarksAsJSON, + GetBookmarksAsJSON) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_WaitForBookmarkModelToLoad, + WaitForBookmarkModelToLoad) + IPC_MESSAGE_HANDLER(AutomationMsg_AddBookmarkGroup, + AddBookmarkGroup) + IPC_MESSAGE_HANDLER(AutomationMsg_AddBookmarkURL, + AddBookmarkURL) + IPC_MESSAGE_HANDLER(AutomationMsg_ReparentBookmark, + ReparentBookmark) + IPC_MESSAGE_HANDLER(AutomationMsg_SetBookmarkTitle, + SetBookmarkTitle) + IPC_MESSAGE_HANDLER(AutomationMsg_SetBookmarkURL, + SetBookmarkURL) + IPC_MESSAGE_HANDLER(AutomationMsg_RemoveBookmark, + RemoveBookmark) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_SendJSONRequest, + SendJSONRequest) + IPC_MESSAGE_HANDLER(AutomationMsg_GetInfoBarCount, GetInfoBarCount) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_ClickInfoBarAccept, + ClickInfoBarAccept) + IPC_MESSAGE_HANDLER(AutomationMsg_GetLastNavigationTime, + GetLastNavigationTime) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_WaitForNavigation, + WaitForNavigation) + IPC_MESSAGE_HANDLER(AutomationMsg_SetIntPreference, SetIntPreference) + IPC_MESSAGE_HANDLER(AutomationMsg_ShowingAppModalDialog, + GetShowingAppModalDialog) + IPC_MESSAGE_HANDLER(AutomationMsg_ClickAppModalDialogButton, + ClickAppModalDialogButton) + IPC_MESSAGE_HANDLER(AutomationMsg_SetStringPreference, SetStringPreference) + IPC_MESSAGE_HANDLER(AutomationMsg_GetBooleanPreference, + GetBooleanPreference) + IPC_MESSAGE_HANDLER(AutomationMsg_SetBooleanPreference, + SetBooleanPreference) + IPC_MESSAGE_HANDLER(AutomationMsg_GetPageCurrentEncoding, + GetPageCurrentEncoding) + IPC_MESSAGE_HANDLER(AutomationMsg_OverrideEncoding, OverrideEncoding) + IPC_MESSAGE_HANDLER(AutomationMsg_SavePackageShouldPromptUser, + SavePackageShouldPromptUser) + IPC_MESSAGE_HANDLER(AutomationMsg_WindowTitle, GetWindowTitle) + IPC_MESSAGE_HANDLER(AutomationMsg_SetShelfVisibility, SetShelfVisibility) + IPC_MESSAGE_HANDLER(AutomationMsg_BlockedPopupCount, GetBlockedPopupCount) + IPC_MESSAGE_HANDLER(AutomationMsg_SelectAll, SelectAll) + IPC_MESSAGE_HANDLER(AutomationMsg_Cut, Cut) + IPC_MESSAGE_HANDLER(AutomationMsg_Copy, Copy) + IPC_MESSAGE_HANDLER(AutomationMsg_Paste, Paste) + IPC_MESSAGE_HANDLER(AutomationMsg_ReloadAsync, ReloadAsync) + IPC_MESSAGE_HANDLER(AutomationMsg_StopAsync, StopAsync) + IPC_MESSAGE_HANDLER_DELAY_REPLY( + AutomationMsg_WaitForBrowserWindowCountToBecome, + WaitForBrowserWindowCountToBecome) + IPC_MESSAGE_HANDLER_DELAY_REPLY( + AutomationMsg_WaitForAppModalDialogToBeShown, + WaitForAppModalDialogToBeShown) + IPC_MESSAGE_HANDLER_DELAY_REPLY( + AutomationMsg_GoBackBlockUntilNavigationsComplete, + GoBackBlockUntilNavigationsComplete) + IPC_MESSAGE_HANDLER_DELAY_REPLY( + AutomationMsg_GoForwardBlockUntilNavigationsComplete, + GoForwardBlockUntilNavigationsComplete) + IPC_MESSAGE_HANDLER(AutomationMsg_SetPageFontSize, OnSetPageFontSize) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_InstallExtension, + InstallExtension) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_LoadExpandedExtension, + 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) + IPC_MESSAGE_HANDLER(AutomationMsg_SetContentSetting, SetContentSetting) + IPC_MESSAGE_HANDLER(AutomationMsg_RemoveBrowsingData, RemoveBrowsingData) + IPC_MESSAGE_HANDLER(AutomationMsg_ResetToDefaultTheme, ResetToDefaultTheme) +#if defined(TOOLKIT_VIEWS) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_WaitForFocusedViewIDToChange, + WaitForFocusedViewIDToChange) + IPC_MESSAGE_HANDLER(AutomationMsg_StartTrackingPopupMenus, + StartTrackingPopupMenus) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_WaitForPopupMenuToOpen, + WaitForPopupMenuToOpen) +#endif // defined(TOOLKIT_VIEWS) +#if defined(OS_WIN) + // These are for use with external tabs. + IPC_MESSAGE_HANDLER(AutomationMsg_CreateExternalTab, CreateExternalTab) + IPC_MESSAGE_HANDLER(AutomationMsg_ProcessUnhandledAccelerator, + ProcessUnhandledAccelerator) + IPC_MESSAGE_HANDLER(AutomationMsg_SetInitialFocus, SetInitialFocus) + IPC_MESSAGE_HANDLER(AutomationMsg_TabReposition, OnTabReposition) + IPC_MESSAGE_HANDLER(AutomationMsg_ForwardContextMenuCommandToChrome, + OnForwardContextMenuCommandToChrome) + IPC_MESSAGE_HANDLER(AutomationMsg_NavigateInExternalTab, + NavigateInExternalTab) + IPC_MESSAGE_HANDLER(AutomationMsg_NavigateExternalTabAtIndex, + NavigateExternalTabAtIndex) + IPC_MESSAGE_HANDLER(AutomationMsg_ConnectExternalTab, ConnectExternalTab) + IPC_MESSAGE_HANDLER(AutomationMsg_SetEnableExtensionAutomation, + SetEnableExtensionAutomation) + IPC_MESSAGE_HANDLER(AutomationMsg_HandleMessageFromExternalHost, + OnMessageFromExternalHost) + IPC_MESSAGE_HANDLER(AutomationMsg_BrowserMove, OnBrowserMoved) + IPC_MESSAGE_HANDLER(AutomationMsg_RunUnloadHandlers, OnRunUnloadHandlers) +#endif // defined(OS_WIN) +#if defined(OS_CHROMEOS) + IPC_MESSAGE_HANDLER_DELAY_REPLY(AutomationMsg_LoginWithUserAndPass, + LoginWithUserAndPass) +#endif // defined(OS_CHROMEOS) + IPC_END_MESSAGE_MAP() +} + +void AutomationProvider::ActivateTab(int handle, int at_index, int* status) { + *status = -1; + if (browser_tracker_->ContainsHandle(handle) && at_index > -1) { + Browser* browser = browser_tracker_->GetResource(handle); + if (at_index >= 0 && at_index < browser->tab_count()) { + browser->SelectTabContentsAt(at_index, true); + *status = 0; + } + } +} + +void AutomationProvider::AppendTab(int handle, const GURL& url, + IPC::Message* reply_message) { + int append_tab_response = -1; // -1 is the error code + NotificationObserver* observer = NULL; + + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + observer = AddTabStripObserver(browser, reply_message); + TabContents* tab_contents = browser->AddTabWithURL( + url, GURL(), PageTransition::TYPED, -1, TabStripModel::ADD_SELECTED, + NULL, std::string()); + if (tab_contents) { + append_tab_response = + GetIndexForNavigationController(&tab_contents->controller(), browser); + } + } + + if (append_tab_response < 0) { + // The append tab failed. Remove the TabStripObserver + if (observer) { + RemoveTabStripObserver(observer); + delete observer; + } + + AutomationMsg_AppendTab::WriteReplyParams(reply_message, + append_tab_response); + Send(reply_message); + } +} + +void AutomationProvider::NavigateToURL(int handle, const GURL& url, + IPC::Message* reply_message) { + NavigateToURLBlockUntilNavigationsComplete(handle, url, 1, reply_message); +} + +void AutomationProvider::NavigateToURLBlockUntilNavigationsComplete( + int handle, const GURL& url, int number_of_navigations, + IPC::Message* reply_message) { + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + + // Simulate what a user would do. Activate the tab and then navigate. + // We could allow navigating in a background tab in future. + Browser* browser = FindAndActivateTab(tab); + + if (browser) { + AddNavigationStatusListener(tab, reply_message, number_of_navigations, + false); + + // TODO(darin): avoid conversion to GURL + browser->OpenURL(url, GURL(), CURRENT_TAB, PageTransition::TYPED); + return; + } + } + + AutomationMsg_NavigateToURL::WriteReplyParams( + reply_message, AUTOMATION_MSG_NAVIGATION_ERROR); + Send(reply_message); +} + +void AutomationProvider::NavigationAsync(int handle, + const GURL& url, + bool* status) { + NavigationAsyncWithDisposition(handle, url, CURRENT_TAB, status); +} + +void AutomationProvider::NavigationAsyncWithDisposition( + int handle, + const GURL& url, + WindowOpenDisposition disposition, + bool* status) { + *status = false; + + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + + // Simulate what a user would do. Activate the tab and then navigate. + // We could allow navigating in a background tab in future. + Browser* browser = FindAndActivateTab(tab); + + if (browser) { + // Don't add any listener unless a callback mechanism is desired. + // TODO(vibhor): Do this if such a requirement arises in future. + browser->OpenURL(url, GURL(), disposition, PageTransition::TYPED); + *status = true; + } + } +} + +void AutomationProvider::GoBack(int handle, IPC::Message* reply_message) { + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + Browser* browser = FindAndActivateTab(tab); + if (browser && browser->command_updater()->IsCommandEnabled(IDC_BACK)) { + AddNavigationStatusListener(tab, reply_message, 1, false); + browser->GoBack(CURRENT_TAB); + return; + } + } + + AutomationMsg_GoBack::WriteReplyParams( + reply_message, AUTOMATION_MSG_NAVIGATION_ERROR); + Send(reply_message); +} + +void AutomationProvider::GoForward(int handle, IPC::Message* reply_message) { + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + Browser* browser = FindAndActivateTab(tab); + if (browser && browser->command_updater()->IsCommandEnabled(IDC_FORWARD)) { + AddNavigationStatusListener(tab, reply_message, 1, false); + browser->GoForward(CURRENT_TAB); + return; + } + } + + AutomationMsg_GoForward::WriteReplyParams( + reply_message, AUTOMATION_MSG_NAVIGATION_ERROR); + Send(reply_message); +} + +void AutomationProvider::Reload(int handle, IPC::Message* reply_message) { + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + Browser* browser = FindAndActivateTab(tab); + if (browser && browser->command_updater()->IsCommandEnabled(IDC_RELOAD)) { + AddNavigationStatusListener(tab, reply_message, 1, false); + browser->Reload(CURRENT_TAB); + return; + } + } + + AutomationMsg_Reload::WriteReplyParams( + reply_message, AUTOMATION_MSG_NAVIGATION_ERROR); + Send(reply_message); +} + +void AutomationProvider::SetAuth(int tab_handle, + const std::wstring& username, + const std::wstring& password, + IPC::Message* reply_message) { + if (tab_tracker_->ContainsHandle(tab_handle)) { + NavigationController* tab = tab_tracker_->GetResource(tab_handle); + LoginHandlerMap::iterator iter = login_handler_map_.find(tab); + + if (iter != login_handler_map_.end()) { + // If auth is needed again after this, assume login has failed. This is + // not strictly correct, because a navigation can require both proxy and + // server auth, but it should be OK for now. + LoginHandler* handler = iter->second; + AddNavigationStatusListener(tab, reply_message, 1, false); + handler->SetAuth(username, password); + return; + } + } + + AutomationMsg_SetAuth::WriteReplyParams( + reply_message, AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED); + Send(reply_message); +} + +void AutomationProvider::CancelAuth(int tab_handle, + IPC::Message* reply_message) { + if (tab_tracker_->ContainsHandle(tab_handle)) { + NavigationController* tab = tab_tracker_->GetResource(tab_handle); + LoginHandlerMap::iterator iter = login_handler_map_.find(tab); + + if (iter != login_handler_map_.end()) { + // If auth is needed again after this, something is screwy. + LoginHandler* handler = iter->second; + AddNavigationStatusListener(tab, reply_message, 1, false); + handler->CancelAuth(); + return; + } + } + + AutomationMsg_CancelAuth::WriteReplyParams( + reply_message, AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED); + Send(reply_message); +} + +void AutomationProvider::NeedsAuth(int tab_handle, bool* needs_auth) { + *needs_auth = false; + + if (tab_tracker_->ContainsHandle(tab_handle)) { + NavigationController* tab = tab_tracker_->GetResource(tab_handle); + LoginHandlerMap::iterator iter = login_handler_map_.find(tab); + + if (iter != login_handler_map_.end()) { + // The LoginHandler will be in our map IFF the tab needs auth. + *needs_auth = true; + } + } +} + +void AutomationProvider::GetRedirectsFrom(int tab_handle, + const GURL& source_url, + IPC::Message* reply_message) { + DCHECK(!redirect_query_) << "Can only handle one redirect query at once."; + if (tab_tracker_->ContainsHandle(tab_handle)) { + NavigationController* tab = tab_tracker_->GetResource(tab_handle); + HistoryService* history_service = + tab->profile()->GetHistoryService(Profile::EXPLICIT_ACCESS); + + DCHECK(history_service) << "Tab " << tab_handle << "'s profile " << + "has no history service"; + if (history_service) { + DCHECK(reply_message_ == NULL); + reply_message_ = reply_message; + // Schedule a history query for redirects. The response will be sent + // asynchronously from the callback the history system uses to notify us + // that it's done: OnRedirectQueryComplete. + redirect_query_ = history_service->QueryRedirectsFrom( + source_url, &consumer_, + NewCallback(this, &AutomationProvider::OnRedirectQueryComplete)); + return; // Response will be sent when query completes. + } + } + + // Send failure response. + std::vector<GURL> empty; + AutomationMsg_RedirectsFrom::WriteReplyParams(reply_message, false, empty); + Send(reply_message); +} + +void AutomationProvider::GetActiveTabIndex(int handle, int* active_tab_index) { + *active_tab_index = -1; // -1 is the error code + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + *active_tab_index = browser->selected_index(); + } +} + +void AutomationProvider::GetBrowserLocale(string16* locale) { + DCHECK(g_browser_process); + *locale = ASCIIToUTF16(g_browser_process->GetApplicationLocale()); +} + +void AutomationProvider::GetBrowserWindowCount(int* window_count) { + *window_count = static_cast<int>(BrowserList::size()); +} + +void AutomationProvider::GetNormalBrowserWindowCount(int* window_count) { + *window_count = static_cast<int>( + BrowserList::GetBrowserCountForType(profile_, Browser::TYPE_NORMAL)); +} + +void AutomationProvider::GetShowingAppModalDialog(bool* showing_dialog, + int* dialog_button) { + AppModalDialog* dialog_delegate = + Singleton<AppModalDialogQueue>()->active_dialog(); + *showing_dialog = (dialog_delegate != NULL); + if (*showing_dialog) + *dialog_button = dialog_delegate->GetDialogButtons(); + else + *dialog_button = MessageBoxFlags::DIALOGBUTTON_NONE; +} + +void AutomationProvider::ClickAppModalDialogButton(int button, bool* success) { + *success = false; + + AppModalDialog* dialog_delegate = + Singleton<AppModalDialogQueue>()->active_dialog(); + if (dialog_delegate && + (dialog_delegate->GetDialogButtons() & button) == button) { + if ((button & MessageBoxFlags::DIALOGBUTTON_OK) == + MessageBoxFlags::DIALOGBUTTON_OK) { + dialog_delegate->AcceptWindow(); + *success = true; + } + if ((button & MessageBoxFlags::DIALOGBUTTON_CANCEL) == + MessageBoxFlags::DIALOGBUTTON_CANCEL) { + DCHECK(!*success) << "invalid param, OK and CANCEL specified"; + dialog_delegate->CancelWindow(); + *success = true; + } + } +} + +void AutomationProvider::ShutdownSessionService(int handle, bool* result) { + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + browser->profile()->ShutdownSessionService(); + *result = true; + } else { + *result = false; + } +} + +void AutomationProvider::GetBrowserWindow(int index, int* handle) { + *handle = 0; + if (index >= 0) { + BrowserList::const_iterator iter = BrowserList::begin(); + for (; (iter != BrowserList::end()) && (index > 0); ++iter, --index) {} + if (iter != BrowserList::end()) { + *handle = browser_tracker_->Add(*iter); + } + } +} + +void AutomationProvider::FindNormalBrowserWindow(int* handle) { + *handle = 0; + Browser* browser = BrowserList::FindBrowserWithType(profile_, + Browser::TYPE_NORMAL, + false); + if (browser) + *handle = browser_tracker_->Add(browser); +} + +void AutomationProvider::GetLastActiveBrowserWindow(int* handle) { + *handle = 0; + Browser* browser = BrowserList::GetLastActive(); + if (browser) + *handle = browser_tracker_->Add(browser); +} + +#if defined(OS_POSIX) +// TODO(estade): use this implementation for all platforms? +void AutomationProvider::GetActiveWindow(int* handle) { + gfx::NativeWindow window = + BrowserList::GetLastActive()->window()->GetNativeHandle(); + *handle = window_tracker_->Add(window); +} +#endif + +void AutomationProvider::ExecuteBrowserCommandAsync(int handle, int command, + bool* success) { + *success = false; + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + if (browser->command_updater()->SupportsCommand(command) && + browser->command_updater()->IsCommandEnabled(command)) { + browser->ExecuteCommand(command); + *success = true; + } + } +} + +void AutomationProvider::ExecuteBrowserCommand( + int handle, int command, IPC::Message* reply_message) { + // List of commands which just finish synchronously and don't require + // setting up an observer. + static const int kSynchronousCommands[] = { + IDC_HOME, + IDC_SELECT_NEXT_TAB, + IDC_SELECT_PREVIOUS_TAB, + IDC_SHOW_BOOKMARK_MANAGER, + }; + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + if (browser->command_updater()->SupportsCommand(command) && + browser->command_updater()->IsCommandEnabled(command)) { + // First check if we can handle the command without using an observer. + for (size_t i = 0; i < arraysize(kSynchronousCommands); i++) { + if (command == kSynchronousCommands[i]) { + browser->ExecuteCommand(command); + AutomationMsg_WindowExecuteCommand::WriteReplyParams(reply_message, + true); + Send(reply_message); + return; + } + } + + // Use an observer if we have one, otherwise fail. + if (ExecuteBrowserCommandObserver::CreateAndRegisterObserver( + this, browser, command, reply_message)) { + browser->ExecuteCommand(command); + return; + } + } + } + AutomationMsg_WindowExecuteCommand::WriteReplyParams(reply_message, false); + Send(reply_message); +} + +// This task just adds another task to the event queue. This is useful if +// you want to ensure that any tasks added to the event queue after this one +// have already been processed by the time |task| is run. +class InvokeTaskLaterTask : public Task { + public: + explicit InvokeTaskLaterTask(Task* task) : task_(task) {} + virtual ~InvokeTaskLaterTask() {} + + virtual void Run() { + MessageLoop::current()->PostTask(FROM_HERE, task_); + } + + private: + Task* task_; + + DISALLOW_COPY_AND_ASSIGN(InvokeTaskLaterTask); +}; + +void AutomationProvider::WindowSimulateClick(const IPC::Message& message, + int handle, + const gfx::Point& click, + int flags) { + if (window_tracker_->ContainsHandle(handle)) { + ui_controls::SendMouseMoveNotifyWhenDone(click.x(), click.y(), + new ClickTask(flags)); + } +} + +void AutomationProvider::WindowSimulateMouseMove(const IPC::Message& message, + int handle, + const gfx::Point& location) { + if (window_tracker_->ContainsHandle(handle)) + ui_controls::SendMouseMove(location.x(), location.y()); +} + +void AutomationProvider::WindowSimulateKeyPress(const IPC::Message& message, + int handle, + int key, + int flags) { + if (!window_tracker_->ContainsHandle(handle)) + return; + + gfx::NativeWindow window = window_tracker_->GetResource(handle); + // The key event is sent to whatever window is active. + ui_controls::SendKeyPress(window, static_cast<base::KeyboardCode>(key), + ((flags & views::Event::EF_CONTROL_DOWN) == + views::Event::EF_CONTROL_DOWN), + ((flags & views::Event::EF_SHIFT_DOWN) == + views::Event::EF_SHIFT_DOWN), + ((flags & views::Event::EF_ALT_DOWN) == + views::Event::EF_ALT_DOWN), + ((flags & views::Event::EF_COMMAND_DOWN) == + views::Event::EF_COMMAND_DOWN)); +} + +void AutomationProvider::IsWindowActive(int handle, bool* success, + bool* is_active) { + if (window_tracker_->ContainsHandle(handle)) { + *is_active = + platform_util::IsWindowActive(window_tracker_->GetResource(handle)); + *success = true; + } else { + *success = false; + *is_active = false; + } +} + +void AutomationProvider::GetTabCount(int handle, int* tab_count) { + *tab_count = -1; // -1 is the error code + + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + *tab_count = browser->tab_count(); + } +} + +void AutomationProvider::GetType(int handle, int* type_as_int) { + *type_as_int = -1; // -1 is the error code + + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + *type_as_int = static_cast<int>(browser->type()); + } +} + +void AutomationProvider::GetTab(int win_handle, int tab_index, + int* tab_handle) { + *tab_handle = 0; + if (browser_tracker_->ContainsHandle(win_handle) && (tab_index >= 0)) { + Browser* browser = browser_tracker_->GetResource(win_handle); + if (tab_index < browser->tab_count()) { + TabContents* tab_contents = + browser->GetTabContentsAt(tab_index); + *tab_handle = tab_tracker_->Add(&tab_contents->controller()); + } + } +} + +void AutomationProvider::GetTabTitle(int handle, int* title_string_size, + std::wstring* title) { + *title_string_size = -1; // -1 is the error code + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + NavigationEntry* entry = tab->GetActiveEntry(); + if (entry != NULL) { + *title = UTF16ToWideHack(entry->title()); + } else { + *title = std::wstring(); + } + *title_string_size = static_cast<int>(title->size()); + } +} + +void AutomationProvider::GetTabIndex(int handle, int* tabstrip_index) { + *tabstrip_index = -1; // -1 is the error code + + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + Browser* browser = Browser::GetBrowserForController(tab, NULL); + *tabstrip_index = browser->tabstrip_model()->GetIndexOfController(tab); + } +} + +void AutomationProvider::HandleUnused(const IPC::Message& message, int handle) { + if (window_tracker_->ContainsHandle(handle)) { + window_tracker_->Remove(window_tracker_->GetResource(handle)); + } +} + +void AutomationProvider::OnChannelError() { + LOG(INFO) << "AutomationProxy went away, shutting down app."; + AutomationProviderList::GetInstance()->RemoveProvider(this); +} + +// TODO(brettw) change this to accept GURLs when history supports it +void AutomationProvider::OnRedirectQueryComplete( + HistoryService::Handle request_handle, + GURL from_url, + bool success, + history::RedirectList* redirects) { + DCHECK(request_handle == redirect_query_); + DCHECK(reply_message_ != NULL); + + std::vector<GURL> redirects_gurl; + reply_message_->WriteBool(success); + if (success) { + for (size_t i = 0; i < redirects->size(); i++) + redirects_gurl.push_back(redirects->at(i)); + } + + IPC::ParamTraits<std::vector<GURL> >::Write(reply_message_, redirects_gurl); + + Send(reply_message_); + redirect_query_ = 0; + reply_message_ = NULL; +} + +bool AutomationProvider::Send(IPC::Message* msg) { + DCHECK(channel_.get()); + return channel_->Send(msg); +} + +Browser* AutomationProvider::FindAndActivateTab( + NavigationController* controller) { + int tab_index; + Browser* browser = Browser::GetBrowserForController(controller, &tab_index); + if (browser) + browser->SelectTabContentsAt(tab_index, true); + + return browser; +} + +namespace { + +class GetCookiesTask : public Task { + public: + GetCookiesTask(const GURL& url, + URLRequestContextGetter* context_getter, + base::WaitableEvent* event, + std::string* cookies) + : url_(url), + context_getter_(context_getter), + event_(event), + cookies_(cookies) {} + + virtual void Run() { + *cookies_ = context_getter_->GetCookieStore()->GetCookies(url_); + event_->Signal(); + } + + private: + const GURL& url_; + URLRequestContextGetter* const context_getter_; + base::WaitableEvent* const event_; + std::string* const cookies_; + + DISALLOW_COPY_AND_ASSIGN(GetCookiesTask); +}; + +std::string GetCookiesForURL( + const GURL& url, + URLRequestContextGetter* context_getter) { + std::string cookies; + base::WaitableEvent event(true /* manual reset */, + false /* not initially signaled */); + CHECK(ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + new GetCookiesTask(url, context_getter, &event, &cookies))); + event.Wait(); + return cookies; +} + +class SetCookieTask : public Task { + public: + SetCookieTask(const GURL& url, + const std::string& value, + URLRequestContextGetter* context_getter, + base::WaitableEvent* event, + bool* rv) + : url_(url), + value_(value), + context_getter_(context_getter), + event_(event), + rv_(rv) {} + + virtual void Run() { + *rv_ = context_getter_->GetCookieStore()->SetCookie(url_, value_); + event_->Signal(); + } + + private: + const GURL& url_; + const std::string& value_; + URLRequestContextGetter* const context_getter_; + base::WaitableEvent* const event_; + bool* const rv_; + + DISALLOW_COPY_AND_ASSIGN(SetCookieTask); +}; + +bool SetCookieForURL( + const GURL& url, + const std::string& value, + URLRequestContextGetter* context_getter) { + base::WaitableEvent event(true /* manual reset */, + false /* not initially signaled */); + bool rv = false; + CHECK(ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + new SetCookieTask(url, value, context_getter, &event, &rv))); + event.Wait(); + return rv; +} + +class DeleteCookieTask : public Task { + public: + DeleteCookieTask(const GURL& url, + const std::string& name, + const scoped_refptr<URLRequestContextGetter>& context_getter) + : url_(url), + name_(name), + context_getter_(context_getter) {} + + virtual void Run() { + net::CookieStore* cookie_store = context_getter_->GetCookieStore(); + cookie_store->DeleteCookie(url_, name_); + } + + private: + const GURL url_; + const std::string name_; + const scoped_refptr<URLRequestContextGetter> context_getter_; + + DISALLOW_COPY_AND_ASSIGN(DeleteCookieTask); +}; + +} // namespace + +void AutomationProvider::GetCookies(const GURL& url, int handle, + int* value_size, + std::string* value) { + *value_size = -1; + if (url.is_valid() && tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + + // Since we are running on the UI thread don't call GetURLRequestContext(). + scoped_refptr<URLRequestContextGetter> request_context = + tab->tab_contents()->request_context(); + if (!request_context.get()) + request_context = tab->profile()->GetRequestContext(); + + *value = GetCookiesForURL(url, request_context.get()); + *value_size = static_cast<int>(value->size()); + } +} + +void AutomationProvider::SetCookie(const GURL& url, + const std::string value, + int handle, + int* response_value) { + *response_value = -1; + + if (url.is_valid() && tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + + scoped_refptr<URLRequestContextGetter> request_context = + tab->tab_contents()->request_context(); + if (!request_context.get()) + request_context = tab->profile()->GetRequestContext(); + + if (SetCookieForURL(url, value, request_context.get())) + *response_value = 1; + } +} + +void AutomationProvider::DeleteCookie(const GURL& url, + const std::string& cookie_name, + int handle, bool* success) { + *success = false; + if (url.is_valid() && tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + new DeleteCookieTask(url, cookie_name, + tab->profile()->GetRequestContext())); + *success = true; + } +} + +void AutomationProvider::ShowCollectedCookiesDialog( + int handle, bool* success) { + *success = false; + if (tab_tracker_->ContainsHandle(handle)) { + TabContents* tab_contents = + tab_tracker_->GetResource(handle)->tab_contents(); + tab_contents->delegate()->ShowCollectedCookiesDialog(tab_contents); + *success = true; + } +} + +void AutomationProvider::GetTabURL(int handle, bool* success, GURL* url) { + *success = false; + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + // Return what the user would see in the location bar. + *url = tab->GetActiveEntry()->virtual_url(); + *success = true; + } +} + +void AutomationProvider::GetTabProcessID(int handle, int* process_id) { + *process_id = -1; + + if (tab_tracker_->ContainsHandle(handle)) { + *process_id = 0; + TabContents* tab_contents = + tab_tracker_->GetResource(handle)->tab_contents(); + RenderProcessHost* rph = tab_contents->GetRenderProcessHost(); + if (rph) + *process_id = base::GetProcId(rph->GetHandle()); + } +} + +void AutomationProvider::ApplyAccelerator(int handle, int id) { + NOTREACHED() << "This function has been deprecated. " + << "Please use ExecuteBrowserCommandAsync instead."; +} + +void AutomationProvider::ExecuteJavascript(int handle, + const std::wstring& frame_xpath, + const std::wstring& script, + IPC::Message* reply_message) { + bool succeeded = false; + TabContents* tab_contents = GetTabContentsForHandle(handle, NULL); + if (tab_contents) { + // Set the routing id of this message with the controller. + // This routing id needs to be remembered for the reverse + // communication while sending back the response of + // this javascript execution. + std::wstring set_automation_id; + SStringPrintf(&set_automation_id, + L"window.domAutomationController.setAutomationId(%d);", + reply_message->routing_id()); + + DCHECK(reply_message_ == NULL); + reply_message_ = reply_message; + + tab_contents->render_view_host()->ExecuteJavascriptInWebFrame( + frame_xpath, set_automation_id); + tab_contents->render_view_host()->ExecuteJavascriptInWebFrame( + frame_xpath, script); + succeeded = true; + } + + if (!succeeded) { + AutomationMsg_DomOperation::WriteReplyParams(reply_message, std::string()); + Send(reply_message); + } +} + +void AutomationProvider::GetShelfVisibility(int handle, bool* visible) { + *visible = false; + + if (browser_tracker_->ContainsHandle(handle)) { +#if defined(OS_CHROMEOS) + // Chromium OS shows FileBrowse ui rather than download shelf. So we + // enumerate all browsers and look for a chrome://filebrowse... pop up. + for (BrowserList::const_iterator it = BrowserList::begin(); + it != BrowserList::end(); ++it) { + if ((*it)->type() == Browser::TYPE_POPUP) { + const GURL& url = + (*it)->GetTabContentsAt((*it)->selected_index())->GetURL(); + + if (url.SchemeIs(chrome::kChromeUIScheme) && + url.host() == chrome::kChromeUIFileBrowseHost) { + *visible = true; + break; + } + } + } +#else + Browser* browser = browser_tracker_->GetResource(handle); + if (browser) { + *visible = browser->window()->IsDownloadShelfVisible(); + } +#endif + } +} + +void AutomationProvider::SetShelfVisibility(int handle, bool visible) { + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + if (browser) { + if (visible) + browser->window()->GetDownloadShelf()->Show(); + else + browser->window()->GetDownloadShelf()->Close(); + } + } +} + +void AutomationProvider::IsFullscreen(int handle, bool* visible) { + *visible = false; + + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + if (browser) + *visible = browser->window()->IsFullscreen(); + } +} + +void AutomationProvider::GetFullscreenBubbleVisibility(int handle, + bool* visible) { + *visible = false; + + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + if (browser) + *visible = browser->window()->IsFullscreenBubbleVisible(); + } +} + +void AutomationProvider::GetConstrainedWindowCount(int handle, int* count) { + *count = -1; // -1 is the error code + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* nav_controller = tab_tracker_->GetResource(handle); + TabContents* tab_contents = nav_controller->tab_contents(); + if (tab_contents) { + *count = static_cast<int>(tab_contents->child_windows_.size()); + } + } +} + +void AutomationProvider::HandleFindInPageRequest( + int handle, const std::wstring& find_request, + int forward, int match_case, int* active_ordinal, int* matches_found) { + NOTREACHED() << "This function has been deprecated." + << "Please use HandleFindRequest instead."; + *matches_found = -1; + return; +} + +void AutomationProvider::HandleFindRequest( + int handle, + const AutomationMsg_Find_Params& params, + IPC::Message* reply_message) { + if (!tab_tracker_->ContainsHandle(handle)) { + AutomationMsg_FindInPage::WriteReplyParams(reply_message, -1, -1); + Send(reply_message); + return; + } + + NavigationController* nav = tab_tracker_->GetResource(handle); + TabContents* tab_contents = nav->tab_contents(); + + find_in_page_observer_.reset(new + FindInPageNotificationObserver(this, tab_contents, reply_message)); + + tab_contents->set_current_find_request_id( + FindInPageNotificationObserver::kFindInPageRequestId); + tab_contents->render_view_host()->StartFinding( + FindInPageNotificationObserver::kFindInPageRequestId, + params.search_string, params.forward, params.match_case, + params.find_next); +} + +void AutomationProvider::HandleOpenFindInPageRequest( + const IPC::Message& message, int handle) { + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + browser->FindInPage(false, false); + } +} + +void AutomationProvider::GetFindWindowVisibility(int handle, bool* visible) { + *visible = false; + Browser* browser = browser_tracker_->GetResource(handle); + if (browser) { + FindBarTesting* find_bar = + browser->GetFindBarController()->find_bar()->GetFindBarTesting(); + find_bar->GetFindBarWindowInfo(NULL, visible); + } +} + +void AutomationProvider::HandleFindWindowLocationRequest(int handle, int* x, + int* y) { + gfx::Point position(0, 0); + bool visible = false; + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + FindBarTesting* find_bar = + browser->GetFindBarController()->find_bar()->GetFindBarTesting(); + find_bar->GetFindBarWindowInfo(&position, &visible); + } + + *x = position.x(); + *y = position.y(); +} + +// Bookmark bar visibility is based on the pref (e.g. is it in the toolbar). +// Presence in the NTP is NOT considered visible by this call. +void AutomationProvider::GetBookmarkBarVisibility(int handle, + bool* visible, + bool* animating) { + *visible = false; + *animating = false; + + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + if (browser) { +#if 0 // defined(TOOLKIT_VIEWS) && defined(OS_LINUX) + // TODO(jrg): Was removed in rev43789 for perf. Need to investigate. + + // IsBookmarkBarVisible() line looks correct but is not + // consistent across platforms. Specifically, on Mac/Linux, it + // returns false if the bar is hidden in a pref (even if visible + // on the NTP). On ChromeOS, it returned true if on NTP + // independent of the pref. Making the code more consistent + // caused a perf bot regression on Windows (which shares views). + // See http://crbug.com/40225 + *visible = browser->profile()->GetPrefs()->GetBoolean( + prefs::kShowBookmarkBar); +#else + *visible = browser->window()->IsBookmarkBarVisible(); +#endif + *animating = browser->window()->IsBookmarkBarAnimating(); + } + } +} + +void AutomationProvider::GetBookmarksAsJSON(int handle, + std::string* bookmarks_as_json, + bool *success) { + *success = false; + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + if (browser) { + if (!browser->profile()->GetBookmarkModel()->IsLoaded()) { + return; + } + scoped_refptr<BookmarkStorage> storage = new BookmarkStorage( + browser->profile(), + browser->profile()->GetBookmarkModel()); + *success = storage->SerializeData(bookmarks_as_json); + } + } +} + +void AutomationProvider::WaitForBookmarkModelToLoad( + int handle, + IPC::Message* reply_message) { + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + BookmarkModel* model = browser->profile()->GetBookmarkModel(); + if (model->IsLoaded()) { + AutomationMsg_WaitForBookmarkModelToLoad::WriteReplyParams( + reply_message, true); + Send(reply_message); + } else { + // The observer will delete itself when done. + new AutomationProviderBookmarkModelObserver(this, reply_message, + model); + } + } +} + +void AutomationProvider::AddBookmarkGroup(int handle, + int64 parent_id, int index, + std::wstring title, + bool* success) { + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + if (browser) { + BookmarkModel* model = browser->profile()->GetBookmarkModel(); + if (!model->IsLoaded()) { + *success = false; + return; + } + const BookmarkNode* parent = model->GetNodeByID(parent_id); + DCHECK(parent); + if (parent) { + const BookmarkNode* child = model->AddGroup(parent, index, + WideToUTF16(title)); + DCHECK(child); + if (child) + *success = true; + } + } + } + *success = false; +} + +void AutomationProvider::AddBookmarkURL(int handle, + int64 parent_id, int index, + std::wstring title, const GURL& url, + bool* success) { + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + if (browser) { + BookmarkModel* model = browser->profile()->GetBookmarkModel(); + if (!model->IsLoaded()) { + *success = false; + return; + } + const BookmarkNode* parent = model->GetNodeByID(parent_id); + DCHECK(parent); + if (parent) { + const BookmarkNode* child = model->AddURL(parent, index, + WideToUTF16(title), url); + DCHECK(child); + if (child) + *success = true; + } + } + } + *success = false; +} + +void AutomationProvider::ReparentBookmark(int handle, + int64 id, int64 new_parent_id, + int index, + bool* success) { + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + if (browser) { + BookmarkModel* model = browser->profile()->GetBookmarkModel(); + if (!model->IsLoaded()) { + *success = false; + return; + } + const BookmarkNode* node = model->GetNodeByID(id); + DCHECK(node); + const BookmarkNode* new_parent = model->GetNodeByID(new_parent_id); + DCHECK(new_parent); + if (node && new_parent) { + model->Move(node, new_parent, index); + *success = true; + } + } + } + *success = false; +} + +void AutomationProvider::SetBookmarkTitle(int handle, + int64 id, std::wstring title, + bool* success) { + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + if (browser) { + BookmarkModel* model = browser->profile()->GetBookmarkModel(); + if (!model->IsLoaded()) { + *success = false; + return; + } + const BookmarkNode* node = model->GetNodeByID(id); + DCHECK(node); + if (node) { + model->SetTitle(node, WideToUTF16(title)); + *success = true; + } + } + } + *success = false; +} + +void AutomationProvider::SetBookmarkURL(int handle, + int64 id, const GURL& url, + bool* success) { + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + if (browser) { + BookmarkModel* model = browser->profile()->GetBookmarkModel(); + if (!model->IsLoaded()) { + *success = false; + return; + } + const BookmarkNode* node = model->GetNodeByID(id); + DCHECK(node); + if (node) { + model->SetURL(node, url); + *success = true; + } + } + } + *success = false; +} + +void AutomationProvider::RemoveBookmark(int handle, + int64 id, + bool* success) { + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + if (browser) { + BookmarkModel* model = browser->profile()->GetBookmarkModel(); + if (!model->IsLoaded()) { + *success = false; + return; + } + const BookmarkNode* node = model->GetNodeByID(id); + DCHECK(node); + if (node) { + const BookmarkNode* parent = node->GetParent(); + DCHECK(parent); + model->Remove(parent, parent->IndexOfChild(node)); + *success = true; + } + } + } + *success = false; +} + +// Sample json input: { "command": "SetWindowDimensions", +// "x": 20, # optional +// "y": 20, # optional +// "width": 800, # optional +// "height": 600 } # optional +void AutomationProvider::SetWindowDimensions(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + gfx::Rect rect = browser->window()->GetRestoredBounds(); + int x, y, width, height; + if (args->GetInteger(L"x", &x)) + rect.set_x(x); + if (args->GetInteger(L"y", &y)) + rect.set_y(y); + if (args->GetInteger(L"width", &width)) + rect.set_width(width); + if (args->GetInteger(L"height", &height)) + rect.set_height(height); + browser->window()->SetBounds(rect); + AutomationJSONReply(this, reply_message).SendSuccess(NULL); +} + +ListValue* AutomationProvider::GetInfobarsInfo(TabContents* tc) { + // Each infobar may have different properties depending on the type. + ListValue* infobars = new ListValue; + for (int infobar_index = 0; + infobar_index < tc->infobar_delegate_count(); + ++infobar_index) { + DictionaryValue* infobar_item = new DictionaryValue; + InfoBarDelegate* infobar = tc->GetInfoBarDelegateAt(infobar_index); + if (infobar->AsConfirmInfoBarDelegate()) { + // Also covers ThemeInstalledInfoBarDelegate and + // CrashedExtensionInfoBarDelegate. + infobar_item->SetString(L"type", "confirm_infobar"); + ConfirmInfoBarDelegate* confirm_infobar = + infobar->AsConfirmInfoBarDelegate(); + infobar_item->SetString(L"text", confirm_infobar->GetMessageText()); + infobar_item->SetString(L"link_text", confirm_infobar->GetLinkText()); + ListValue* buttons_list = new ListValue; + int buttons = confirm_infobar->GetButtons(); + if (ConfirmInfoBarDelegate::BUTTON_OK & buttons) { + StringValue* button_label = new StringValue( + confirm_infobar->GetButtonLabel( + ConfirmInfoBarDelegate::BUTTON_OK)); + buttons_list->Append(button_label); + } + if (ConfirmInfoBarDelegate::BUTTON_CANCEL & buttons) { + StringValue* button_label = new StringValue( + confirm_infobar->GetButtonLabel( + ConfirmInfoBarDelegate::BUTTON_CANCEL)); + buttons_list->Append(button_label); + } + infobar_item->Set(L"buttons", buttons_list); + } else if (infobar->AsAlertInfoBarDelegate()) { + infobar_item->SetString(L"type", "alert_infobar"); + AlertInfoBarDelegate* alert_infobar = + infobar->AsAlertInfoBarDelegate(); + infobar_item->SetString(L"text", alert_infobar->GetMessageText()); + } else if (infobar->AsLinkInfoBarDelegate()) { + infobar_item->SetString(L"type", "link_infobar"); + LinkInfoBarDelegate* link_infobar = infobar->AsLinkInfoBarDelegate(); + infobar_item->SetString(L"link_text", link_infobar->GetLinkText()); + } else if (infobar->AsTranslateInfoBarDelegate()) { + infobar_item->SetString(L"type", "translate_infobar"); + TranslateInfoBarDelegate* translate_infobar = + infobar->AsTranslateInfoBarDelegate(); + infobar_item->SetString(L"original_lang_code", + translate_infobar->GetOriginalLanguageCode()); + infobar_item->SetString(L"target_lang_code", + translate_infobar->GetTargetLanguageCode()); + } else if (infobar->AsExtensionInfoBarDelegate()) { + infobar_item->SetString(L"type", "extension_infobar"); + } else { + infobar_item->SetString(L"type", "unknown_infobar"); + } + infobars->Append(infobar_item); + } + return infobars; +} + +// Sample json input: { "command": "WaitForInfobarCount", +// "count": COUNT, +// "tab_index": INDEX } +// Sample output: {} +void AutomationProvider::WaitForInfobarCount(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + int tab_index; + int count; + if (!args->GetInteger(L"count", &count) || count < 0 || + !args->GetInteger(L"tab_index", &tab_index) || tab_index < 0) { + AutomationJSONReply(this, reply_message).SendError( + "Missing or invalid args: 'count', 'tab_index'."); + return; + } + + TabContents* tab_contents = browser->GetTabContentsAt(tab_index); + // Observer deletes itself. + new WaitForInfobarCountObserver(this, reply_message, tab_contents, count); +} + +namespace { + +// Task to get info about BrowserChildProcessHost. Must run on IO thread to +// honor the semantics of BrowserChildProcessHost. +// Used by AutomationProvider::GetBrowserInfo(). +class GetChildProcessHostInfoTask : public Task { + public: + GetChildProcessHostInfoTask(base::WaitableEvent* event, + ListValue* child_processes) + : event_(event), + child_processes_(child_processes) {} + + virtual void Run() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + for (BrowserChildProcessHost::Iterator iter; !iter.Done(); ++iter) { + // Only add processes which are already started, + // since we need their handle. + if ((*iter)->handle() == base::kNullProcessHandle) { + continue; + } + ChildProcessInfo* info = *iter; + DictionaryValue* item = new DictionaryValue; + item->SetString(L"name", info->name()); + item->SetString(L"type", + ChildProcessInfo::GetTypeNameInEnglish(info->type())); + item->SetInteger(L"pid", base::GetProcId(info->handle())); + child_processes_->Append(item); + } + event_->Signal(); + } + + private: + base::WaitableEvent* const event_; // weak + ListValue* child_processes_; + + DISALLOW_COPY_AND_ASSIGN(GetChildProcessHostInfoTask); +}; + +} // namespace + +// Sample json input: { "command": "GetBrowserInfo" } +// Refer to GetBrowserInfo() in chrome/test/pyautolib/pyauto.py for +// sample json output. +void AutomationProvider::GetBrowserInfo(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + DictionaryValue* properties = new DictionaryValue; + properties->SetString(L"ChromeVersion", chrome::kChromeVersion); + properties->SetString(L"BrowserProcessExecutableName", + chrome::kBrowserProcessExecutableName); + properties->SetString(L"HelperProcessExecutableName", + chrome::kHelperProcessExecutableName); + properties->SetString(L"BrowserProcessExecutablePath", + chrome::kBrowserProcessExecutablePath); + properties->SetString(L"HelperProcessExecutablePath", + chrome::kHelperProcessExecutablePath); + properties->SetString(L"command_line_string", + CommandLine::ForCurrentProcess()->command_line_string()); + + std::string branding; +#if defined(GOOGLE_CHROME_BUILD) + branding = "Google Chrome"; +#elif defined(CHROMIUM_BUILD) + branding = "Chromium"; +#else + branding = "Unknown Branding"; +#endif + properties->SetString(L"branding", branding); + + scoped_ptr<DictionaryValue> return_value(new DictionaryValue); + return_value->Set(L"properties", properties); + + return_value->SetInteger(L"browser_pid", base::GetCurrentProcId()); + // Add info about all windows in a list of dictionaries, one dictionary + // item per window. + ListValue* windows = new ListValue; + int windex = 0; + for (BrowserList::const_iterator it = BrowserList::begin(); + it != BrowserList::end(); + ++it, ++windex) { + DictionaryValue* browser_item = new DictionaryValue; + browser = *it; + browser_item->SetInteger(L"index", windex); + // Window properties + gfx::Rect rect = browser->window()->GetRestoredBounds(); + browser_item->SetInteger(L"x", rect.x()); + browser_item->SetInteger(L"y", rect.y()); + browser_item->SetInteger(L"width", rect.width()); + browser_item->SetInteger(L"height", rect.height()); + browser_item->SetBoolean(L"fullscreen", + browser->window()->IsFullscreen()); + browser_item->SetInteger(L"selected_tab", browser->selected_index()); + browser_item->SetBoolean(L"incognito", + browser->profile()->IsOffTheRecord()); + // For each window, add info about all tabs in a list of dictionaries, + // one dictionary item per tab. + ListValue* tabs = new ListValue; + for (int i = 0; i < browser->tab_count(); ++i) { + TabContents* tc = browser->GetTabContentsAt(i); + DictionaryValue* tab = new DictionaryValue; + tab->SetInteger(L"index", i); + tab->SetString(L"url", tc->GetURL().spec()); + tab->SetInteger(L"renderer_pid", + base::GetProcId(tc->GetRenderProcessHost()->GetHandle())); + tab->Set(L"infobars", GetInfobarsInfo(tc)); + tabs->Append(tab); + } + browser_item->Set(L"tabs", tabs); + + windows->Append(browser_item); + } + return_value->Set(L"windows", windows); + + return_value->SetString(L"child_process_path", + ChildProcessHost::GetChildPath(true).value()); + // Child processes are the processes for plugins and other workers. + // Add all child processes in a list of dictionaries, one dictionary item + // per child process. + ListValue* child_processes = new ListValue; + base::WaitableEvent event(true /* manual reset */, + false /* not initially signaled */); + CHECK(ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + new GetChildProcessHostInfoTask(&event, child_processes))); + event.Wait(); + return_value->Set(L"child_processes", child_processes); + + // Add all extension processes in a list of dictionaries, one dictionary + // item per extension process. + ListValue* extension_processes = new ListValue; + ProfileManager* profile_manager = g_browser_process->profile_manager(); + for (ProfileManager::const_iterator it = profile_manager->begin(); + it != profile_manager->end(); ++it) { + ExtensionProcessManager* process_manager = + (*it)->GetExtensionProcessManager(); + ExtensionProcessManager::const_iterator jt; + for (jt = process_manager->begin(); jt != process_manager->end(); ++jt) { + ExtensionHost* ex_host = *jt; + // Don't add dead extension processes. + if (!ex_host->IsRenderViewLive()) + continue; + DictionaryValue* item = new DictionaryValue; + item->SetString(L"name", ex_host->extension()->name()); + item->SetInteger( + L"pid", + base::GetProcId(ex_host->render_process_host()->GetHandle())); + extension_processes->Append(item); + } + } + return_value->Set(L"extension_processes", extension_processes); + AutomationJSONReply(this, reply_message).SendSuccess(return_value.get()); +} + +// Sample json input: { "command": "GetHistoryInfo", +// "search_text": "some text" } +// Refer chrome/test/pyautolib/history_info.py for sample json output. +void AutomationProvider::GetHistoryInfo(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + consumer_.CancelAllRequests(); + + string16 search_text; + args->GetString("search_text", &search_text); + + // Fetch history. + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + history::QueryOptions options; + // The observer owns itself. It deletes itself after it fetches history. + AutomationProviderHistoryObserver* history_observer = + new AutomationProviderHistoryObserver(this, reply_message); + hs->QueryHistory( + search_text, + options, + &consumer_, + NewCallback(history_observer, + &AutomationProviderHistoryObserver::HistoryQueryComplete)); +} + +// Sample json input: { "command": "AddHistoryItem", +// "item": { "URL": "http://www.google.com", +// "title": "Google", # optional +// "time": 12345 # optional (time_t) +// } } +// Refer chrome/test/pyautolib/pyauto.py for details on input. +void AutomationProvider::AddHistoryItem(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + DictionaryValue* item = NULL; + args->GetDictionary(L"item", &item); + string16 url_text; + string16 title; + base::Time time = base::Time::Now(); + AutomationJSONReply reply(this, reply_message); + + if (!item->GetString("url", &url_text)) { + reply.SendError("bad args (no URL in dict?)"); + return; + } + GURL gurl(url_text); + item->GetString("title", &title); // Don't care if it fails. + int it; + double dt; + if (item->GetInteger(L"time", &it)) + time = base::Time::FromTimeT(it); + else if (item->GetReal(L"time", &dt)) + time = base::Time::FromDoubleT(dt); + + // Ideas for "dummy" values (e.g. id_scope) came from + // chrome/browser/autocomplete/history_contents_provider_unittest.cc + HistoryService* hs = profile_->GetHistoryService(Profile::EXPLICIT_ACCESS); + const void* id_scope = reinterpret_cast<void*>(1); + hs->AddPage(gurl, time, + id_scope, + 0, + GURL(), + PageTransition::LINK, + history::RedirectList(), + false); + if (title.length()) + hs->SetPageTitle(gurl, title); + reply.SendSuccess(NULL); +} + +// Sample json input: { "command": "GetDownloadsInfo" } +// Refer chrome/test/pyautolib/download_info.py for sample json output. +void AutomationProvider::GetDownloadsInfo(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + AutomationProviderDownloadManagerObserver observer; + std::vector<DownloadItem*> downloads; + scoped_ptr<DictionaryValue> return_value(new DictionaryValue); + AutomationJSONReply reply(this, reply_message); + + if (!profile_->HasCreatedDownloadManager()) { + reply.SendError("no download manager"); + return; + } + // Use DownloadManager's GetDownloads() method and not GetCurrentDownloads() + // since that would be transient; a download might enter and empty out + // the current download queue too soon to be noticed. + profile_->GetDownloadManager()->GetDownloads(&observer, L""); + downloads = observer.Downloads(); + + std::map<DownloadItem::DownloadState, std::string> state_to_string; + state_to_string[DownloadItem::IN_PROGRESS] = std::string("IN_PROGRESS"); + state_to_string[DownloadItem::CANCELLED] = std::string("CANCELLED"); + state_to_string[DownloadItem::REMOVING] = std::string("REMOVING"); + state_to_string[DownloadItem::COMPLETE] = std::string("COMPLETE"); + + std::map<DownloadItem::SafetyState, std::string> safety_state_to_string; + safety_state_to_string[DownloadItem::SAFE] = std::string("SAFE"); + safety_state_to_string[DownloadItem::DANGEROUS] = std::string("DANGEROUS"); + safety_state_to_string[DownloadItem::DANGEROUS_BUT_VALIDATED] = + std::string("DANGEROUS_BUT_VALIDATED"); + + ListValue* list_of_downloads = new ListValue; + for (std::vector<DownloadItem*>::iterator it = downloads.begin(); + it != downloads.end(); + it++) { // Fill info about each download item. + DictionaryValue* dl_item_value = new DictionaryValue; + dl_item_value->SetInteger(L"id", static_cast<int>((*it)->id())); + dl_item_value->SetString(L"url", (*it)->url().spec()); + dl_item_value->SetString(L"referrer_url", (*it)->referrer_url().spec()); + dl_item_value->SetString(L"file_name", (*it)->file_name().value()); + dl_item_value->SetString(L"full_path", (*it)->full_path().value()); + dl_item_value->SetBoolean(L"is_paused", (*it)->is_paused()); + dl_item_value->SetBoolean(L"open_when_complete", + (*it)->open_when_complete()); + dl_item_value->SetBoolean(L"is_extension_install", + (*it)->is_extension_install()); + dl_item_value->SetBoolean(L"is_temporary", (*it)->is_temporary()); + dl_item_value->SetBoolean(L"is_otr", (*it)->is_otr()); // off-the-record + dl_item_value->SetString(L"state", state_to_string[(*it)->state()]); + dl_item_value->SetString(L"safety_state", + safety_state_to_string[(*it)->safety_state()]); + dl_item_value->SetInteger(L"PercentComplete", (*it)->PercentComplete()); + list_of_downloads->Append(dl_item_value); + } + return_value->Set(L"downloads", list_of_downloads); + + reply.SendSuccess(return_value.get()); + // All value objects allocated above are owned by |return_value| + // and get freed by it. +} + +void AutomationProvider::WaitForDownloadsToComplete( + Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + AutomationProviderDownloadManagerObserver observer; + std::vector<DownloadItem*> downloads; + AutomationJSONReply reply(this, reply_message); + + // Look for a quick return. + if (!profile_->HasCreatedDownloadManager()) { + reply.SendSuccess(NULL); // No download manager. + return; + } + profile_->GetDownloadManager()->GetCurrentDownloads(&observer, FilePath()); + downloads = observer.Downloads(); + if (downloads.size() == 0) { + reply.SendSuccess(NULL); + return; + } + + // The observer owns itself. When the last observed item pings, it + // deletes itself. + AutomationProviderDownloadItemObserver* item_observer = + new AutomationProviderDownloadItemObserver( + this, reply_message, downloads.size()); + for (std::vector<DownloadItem*>::iterator i = downloads.begin(); + i != downloads.end(); + i++) { + (*i)->AddObserver(item_observer); + } +} + +// Sample json input: { "command": "GetPrefsInfo" } +// Refer chrome/test/pyautolib/prefs_info.py for sample json output. +void AutomationProvider::GetPrefsInfo(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + const PrefService::PreferenceSet& prefs = + profile_->GetPrefs()->preference_set(); + DictionaryValue* items = new DictionaryValue; + for (PrefService::PreferenceSet::const_iterator it = prefs.begin(); + it != prefs.end(); ++it) { + items->Set((*it)->name(), (*it)->GetValue()->DeepCopy()); + } + scoped_ptr<DictionaryValue> return_value(new DictionaryValue); + return_value->Set(L"prefs", items); // return_value owns items. + AutomationJSONReply(this, reply_message).SendSuccess(return_value.get()); +} + +// Sample json input: { "command": "SetPrefs", "path": path, "value": value } +void AutomationProvider::SetPrefs(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + std::wstring path; + Value* val; + AutomationJSONReply reply(this, reply_message); + if (args->GetString(L"path", &path) && args->Get(L"value", &val)) { + PrefService* pref_service = profile_->GetPrefs(); + const PrefService::Preference* pref = + pref_service->FindPreference(path.c_str()); + if (!pref) { // Not a registered pref. + reply.SendError("pref not registered."); + return; + } else if (pref->IsManaged()) { // Do not attempt to change a managed pref. + reply.SendError("pref is managed. cannot be changed."); + return; + } else { // Set the pref. + pref_service->Set(path.c_str(), *val); + } + } else { + reply.SendError("no pref path or value given."); + return; + } + + reply.SendSuccess(NULL); +} + +// Sample json input: { "command": "GetOmniboxInfo" } +// Refer chrome/test/pyautolib/omnibox_info.py for sample json output. +void AutomationProvider::GetOmniboxInfo(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + scoped_ptr<DictionaryValue> return_value(new DictionaryValue); + + LocationBar* loc_bar = browser->window()->GetLocationBar(); + AutocompleteEditView* edit_view = loc_bar->location_entry(); + AutocompleteEditModel* model = edit_view->model(); + + // Fill up matches. + ListValue* matches = new ListValue; + const AutocompleteResult& result = model->result(); + for (AutocompleteResult::const_iterator i = result.begin(); + i != result.end(); ++i) { + const AutocompleteMatch& match = *i; + DictionaryValue* item = new DictionaryValue; // owned by return_value + item->SetString(L"type", AutocompleteMatch::TypeToString(match.type)); + item->SetBoolean(L"starred", match.starred); + item->SetString(L"destination_url", match.destination_url.spec()); + item->SetString(L"contents", match.contents); + item->SetString(L"description", match.description); + matches->Append(item); + } + return_value->Set(L"matches", matches); + + // Fill up other properties. + DictionaryValue* properties = new DictionaryValue; // owned by return_value + properties->SetBoolean(L"has_focus", model->has_focus()); + properties->SetBoolean(L"query_in_progress", model->query_in_progress()); + properties->SetString(L"keyword", model->keyword()); + properties->SetString(L"text", edit_view->GetText()); + return_value->Set(L"properties", properties); + + AutomationJSONReply(this, reply_message).SendSuccess(return_value.get()); +} + +// Sample json input: { "command": "SetOmniboxText", +// "text": "goog" } +void AutomationProvider::SetOmniboxText(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + std::wstring text; + AutomationJSONReply reply(this, reply_message); + if (!args->GetString(L"text", &text)) { + reply.SendError("text missing"); + return; + } + browser->FocusLocationBar(); + LocationBar* loc_bar = browser->window()->GetLocationBar(); + AutocompleteEditView* edit_view = loc_bar->location_entry(); + edit_view->model()->OnSetFocus(false); + edit_view->SetUserText(text); + reply.SendSuccess(NULL); +} + +// Sample json input: { "command": "OmniboxMovePopupSelection", +// "count": 1 } +// Negative count implies up, positive implies down. Count values will be +// capped by the size of the popup list. +void AutomationProvider::OmniboxMovePopupSelection( + Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + int count; + AutomationJSONReply reply(this, reply_message); + if (!args->GetInteger(L"count", &count)) { + reply.SendError("count missing"); + return; + } + LocationBar* loc_bar = browser->window()->GetLocationBar(); + AutocompleteEditModel* model = loc_bar->location_entry()->model(); + model->OnUpOrDownKeyPressed(count); + reply.SendSuccess(NULL); +} + +// Sample json input: { "command": "OmniboxAcceptInput" } +void AutomationProvider::OmniboxAcceptInput(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + NavigationController& controller = + browser->GetSelectedTabContents()->controller(); + // Setup observer to wait until the selected item loads. + NotificationObserver* observer = + new OmniboxAcceptNotificationObserver(&controller, this, reply_message); + notification_observer_list_.AddObserver(observer); + + browser->window()->GetLocationBar()->AcceptInput(); +} + +// Sample json input: { "command": "GetInitialLoadTimes" } +// Refer to InitialLoadObserver::GetTimingInformation() for sample output. +void AutomationProvider::GetInitialLoadTimes( + Browser*, + DictionaryValue*, + IPC::Message* reply_message) { + scoped_ptr<DictionaryValue> return_value( + initial_load_observer_->GetTimingInformation()); + + std::string json_return; + base::JSONWriter::Write(return_value.get(), false, &json_return); + AutomationMsg_SendJSONRequest::WriteReplyParams( + reply_message, json_return, true); + Send(reply_message); +} + +// Sample json input: { "command": "GetPluginsInfo" } +// Refer chrome/test/pyautolib/plugins_info.py for sample json output. +void AutomationProvider::GetPluginsInfo(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + std::vector<WebPluginInfo> plugins; + NPAPI::PluginList::Singleton()->GetPlugins(false, &plugins); + ListValue* items = new ListValue; + for (std::vector<WebPluginInfo>::const_iterator it = plugins.begin(); + it != plugins.end(); + ++it) { + DictionaryValue* item = new DictionaryValue; + item->SetStringFromUTF16(L"name", it->name); + item->SetString(L"path", it->path.value()); + item->SetStringFromUTF16(L"version", it->version); + item->SetStringFromUTF16(L"desc", it->desc); + item->SetBoolean(L"enabled", it->enabled); + // Add info about mime types. + ListValue* mime_types = new ListValue(); + for (std::vector<WebPluginMimeType>::const_iterator type_it = + it->mime_types.begin(); + type_it != it->mime_types.end(); + ++type_it) { + DictionaryValue* mime_type = new DictionaryValue(); + mime_type->SetString(L"mimeType", type_it->mime_type); + mime_type->SetStringFromUTF16(L"description", type_it->description); + + ListValue* file_extensions = new ListValue(); + for (std::vector<std::string>::const_iterator ext_it = + type_it->file_extensions.begin(); + ext_it != type_it->file_extensions.end(); + ++ext_it) { + file_extensions->Append(new StringValue(*ext_it)); + } + mime_type->Set(L"fileExtensions", file_extensions); + + mime_types->Append(mime_type); + } + item->Set(L"mimeTypes", mime_types); + items->Append(item); + } + scoped_ptr<DictionaryValue> return_value(new DictionaryValue); + return_value->Set(L"plugins", items); // return_value owns items. + + AutomationJSONReply(this, reply_message).SendSuccess(return_value.get()); +} + +// Sample json input: +// { "command": "EnablePlugin", +// "path": "/Library/Internet Plug-Ins/Flash Player.plugin" } +void AutomationProvider::EnablePlugin(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + FilePath::StringType path; + AutomationJSONReply reply(this, reply_message); + if (!args->GetString(L"path", &path)) { + reply.SendError("path not specified."); + return; + } else if (!NPAPI::PluginList::Singleton()->EnablePlugin(FilePath(path))) { + reply.SendError(StringPrintf("Could not enable plugin for path %s.", + path.c_str())); + return; + } + reply.SendSuccess(NULL); +} + +// Sample json input: +// { "command": "DisablePlugin", +// "path": "/Library/Internet Plug-Ins/Flash Player.plugin" } +void AutomationProvider::DisablePlugin(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + FilePath::StringType path; + AutomationJSONReply reply(this, reply_message); + if (!args->GetString(L"path", &path)) { + reply.SendError("path not specified."); + return; + } else if (!NPAPI::PluginList::Singleton()->DisablePlugin(FilePath(path))) { + reply.SendError(StringPrintf("Could not disable plugin for path %s.", + path.c_str())); + return; + } + reply.SendSuccess(NULL); +} + +// Sample json input: +// { "command": "SaveTabContents", +// "tab_index": 0, +// "filename": <a full pathname> } +// Sample json output: +// {} +void AutomationProvider::SaveTabContents(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + int tab_index = 0; + FilePath::StringType filename; + FilePath::StringType parent_directory; + TabContents* tab_contents = NULL; + + if (!args->GetInteger(L"tab_index", &tab_index) || + !args->GetString(L"filename", &filename)) { + AutomationJSONReply(this, reply_message).SendError( + "tab_index or filename param missing"); + return; + } else { + tab_contents = browser->GetTabContentsAt(tab_index); + if (!tab_contents) { + AutomationJSONReply(this, reply_message).SendError( + "no tab at tab_index"); + return; + } + } + // We're doing a SAVE_AS_ONLY_HTML so the the directory path isn't + // used. Nevertheless, SavePackage requires it be valid. Sigh. + parent_directory = FilePath(filename).DirName().value(); + if (!tab_contents->SavePage(FilePath(filename), FilePath(parent_directory), + SavePackage::SAVE_AS_ONLY_HTML)) { + AutomationJSONReply(this, reply_message).SendError( + "Could not initiate SavePage"); + return; + } + // The observer will delete itself when done. + new SavePackageNotificationObserver(tab_contents->save_package(), + this, reply_message); +} + +// Refer to ImportSettings() in chrome/test/pyautolib/pyauto.py for sample +// json input. +// Sample json output: "{}" +void AutomationProvider::ImportSettings(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + // Map from the json string passed over to the import item masks. + std::map<std::string, ImportItem> string_to_import_item; + string_to_import_item["HISTORY"] = importer::HISTORY; + string_to_import_item["FAVORITES"] = importer::FAVORITES; + string_to_import_item["COOKIES"] = importer::COOKIES; + string_to_import_item["PASSWORDS"] = importer::PASSWORDS; + string_to_import_item["SEARCH_ENGINES"] = importer::SEARCH_ENGINES; + string_to_import_item["HOME_PAGE"] = importer::HOME_PAGE; + string_to_import_item["ALL"] = importer::ALL; + + std::wstring browser_name; + int import_items = 0; + ListValue* import_items_list = NULL; + bool first_run; + + if (!args->GetString(L"import_from", &browser_name) || + !args->GetBoolean(L"first_run", &first_run) || + !args->GetList(L"import_items", &import_items_list)) { + AutomationJSONReply(this, reply_message).SendError( + "Incorrect type for one or more of the arguments."); + return; + } + + int num_items = import_items_list->GetSize(); + for (int i = 0; i < num_items; i++) { + std::string item; + import_items_list->GetString(i, &item); + // If the provided string is not part of the map, error out. + if (!ContainsKey(string_to_import_item, item)) { + AutomationJSONReply(this, reply_message).SendError( + "Invalid item string found in import_items."); + return; + } + import_items |= string_to_import_item[item]; + } + + ImporterHost* importer_host = new ImporterHost(); + // Get the correct ProfileInfo based on the browser they user provided. + importer::ProfileInfo profile_info; + int num_browsers = importer_host->GetAvailableProfileCount(); + int i = 0; + for ( ; i < num_browsers; i++) { + std::wstring name = importer_host->GetSourceProfileNameAt(i); + if (name == browser_name) { + profile_info = importer_host->GetSourceProfileInfoAt(i); + break; + } + } + // If we made it to the end of the loop, then the input was bad. + if (i == num_browsers) { + AutomationJSONReply(this, reply_message).SendError( + "Invalid browser name string found."); + return; + } + + Profile* profile = browser->profile(); + + importer_host->SetObserver( + new AutomationProviderImportSettingsObserver(this, reply_message)); + importer_host->StartImportSettings(profile_info, profile, import_items, + new ProfileWriter(profile), first_run); +} + +// See AddSavedPassword() in chrome/test/functional/pyauto.py for sample json +// input. +// Sample json output: { "password_added": true } +void AutomationProvider::AddSavedPassword(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + string16 username; + string16 password; + base::Time time = base::Time::Now(); + AutomationJSONReply reply(this, reply_message); + + if (!args->GetStringAsUTF16(L"password", &password) || + !args->GetStringAsUTF16(L"username", &username)) { + reply.SendError("Username and password must be strings."); + return; + } + + // If the time is specified, change time to the specified time. + int it; + double dt; + if (args->GetInteger(L"time", &it)) + time = base::Time::FromTimeT(it); + else if (args->GetReal(L"time", &dt)) + time = base::Time::FromDoubleT(dt); + + webkit_glue::PasswordForm new_password; + new_password.username_value = username; + new_password.password_value = password; + new_password.date_created = time; + + Profile* profile = browser->profile(); + // Use IMPLICIT_ACCESS since new passwords aren't added off the record. + PasswordStore* password_store = + profile->GetPasswordStore(Profile::IMPLICIT_ACCESS); + + // Set the return based on whether setting the password succeeded. + scoped_ptr<DictionaryValue> return_value(new DictionaryValue); + + // It will be null if it's accessed in an incognito window. + if (password_store != NULL) { + password_store->AddLogin(new_password); + return_value->SetBoolean(L"password_added", true); + } else { + return_value->SetBoolean(L"password_added", false); + } + + reply.SendSuccess(return_value.get()); +} + +// Sample json input: { "command": "GetSavedPasswords" } +// Refer to GetSavedPasswords() in chrome/test/pyautolib/pyauto.py for sample +// json output. +void AutomationProvider::GetSavedPasswords(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + Profile* profile = browser->profile(); + // Use EXPLICIT_ACCESS since saved passwords can be retreived off the record. + PasswordStore* password_store = + profile->GetPasswordStore(Profile::EXPLICIT_ACCESS); + password_store->GetAutofillableLogins( + new AutomationProviderGetPasswordsObserver(this, reply_message)); + // Observer deletes itself after returning. +} + +// Refer to ClearBrowsingData() in chrome/test/pyautolib/pyauto.py for sample +// json input. +// Sample json output: {} +void AutomationProvider::ClearBrowsingData(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + std::map<std::string, BrowsingDataRemover::TimePeriod> string_to_time_period; + string_to_time_period["LAST_HOUR"] = BrowsingDataRemover::LAST_HOUR; + string_to_time_period["LAST_DAY"] = BrowsingDataRemover::LAST_DAY; + string_to_time_period["LAST_WEEK"] = BrowsingDataRemover::LAST_WEEK; + string_to_time_period["FOUR_WEEKS"] = BrowsingDataRemover::FOUR_WEEKS; + string_to_time_period["EVERYTHING"] = BrowsingDataRemover::EVERYTHING; + + std::map<std::string, int> string_to_mask_value; + string_to_mask_value["HISTORY"] = BrowsingDataRemover::REMOVE_HISTORY; + string_to_mask_value["DOWNLOADS"] = BrowsingDataRemover::REMOVE_DOWNLOADS; + string_to_mask_value["COOKIES"] = BrowsingDataRemover::REMOVE_COOKIES; + string_to_mask_value["PASSWORDS"] = BrowsingDataRemover::REMOVE_PASSWORDS; + string_to_mask_value["FORM_DATA"] = BrowsingDataRemover::REMOVE_FORM_DATA; + string_to_mask_value["CACHE"] = BrowsingDataRemover::REMOVE_CACHE; + + std::string time_period; + ListValue* to_remove; + if (!args->GetString(L"time_period", &time_period) || + !args->GetList(L"to_remove", &to_remove)) { + AutomationJSONReply(this, reply_message).SendError( + "time_period must be a string and to_remove a list."); + return; + } + + int remove_mask = 0; + int num_removals = to_remove->GetSize(); + for (int i = 0; i < num_removals; i++) { + std::string removal; + to_remove->GetString(i, &removal); + // If the provided string is not part of the map, then error out. + if (!ContainsKey(string_to_mask_value, removal)) { + AutomationJSONReply(this, reply_message).SendError( + "Invalid browsing data string found in to_remove."); + return; + } + remove_mask |= string_to_mask_value[removal]; + } + + if (!ContainsKey(string_to_time_period, time_period)) { + AutomationJSONReply(this, reply_message).SendError( + "Invalid string for time_period."); + return; + } + + BrowsingDataRemover* remover = new BrowsingDataRemover( + profile(), string_to_time_period[time_period], base::Time()); + + remover->AddObserver( + new AutomationProviderBrowsingDataObserver(this, reply_message)); + remover->Remove(remove_mask); + // BrowsingDataRemover deletes itself using DeleteTask. + // The observer also deletes itself after sending the reply. +} + +// Sample json input: { "command": "GetThemeInfo" } +// Refer GetThemeInfo() in chrome/test/pyautolib/pyauto.py for sample output. +void AutomationProvider::GetThemeInfo(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + scoped_ptr<DictionaryValue> return_value(new DictionaryValue); + Extension* theme = browser->profile()->GetTheme(); + if (theme) { + return_value->SetString(L"name", theme->name()); + return_value->Set(L"images", theme->GetThemeImages()->DeepCopy()); + return_value->Set(L"colors", theme->GetThemeColors()->DeepCopy()); + return_value->Set(L"tints", theme->GetThemeTints()->DeepCopy()); + } + AutomationJSONReply(this, reply_message).SendSuccess(return_value.get()); +} + +// Sample json input: +// { "command": "GetAutoFillProfile" } +// Refer to GetAutoFillProfile() in chrome/test/pyautolib/pyauto.py for sample +// json output. +void AutomationProvider::GetAutoFillProfile(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + // Get the AutoFillProfiles currently in the database. + int tab_index = 0; + args->GetInteger(L"tab_index", &tab_index); + TabContents* tab_contents = browser->GetTabContentsAt(tab_index); + AutomationJSONReply reply(this, reply_message); + + if (tab_contents) { + PersonalDataManager* pdm = tab_contents->profile()->GetOriginalProfile() + ->GetPersonalDataManager(); + if (pdm) { + std::vector<AutoFillProfile*> autofill_profiles = pdm->profiles(); + std::vector<CreditCard*> credit_cards = pdm->credit_cards(); + + ListValue* profiles = GetListFromAutoFillProfiles(autofill_profiles); + ListValue* cards = GetListFromCreditCards(credit_cards); + + scoped_ptr<DictionaryValue> return_value(new DictionaryValue); + + return_value->Set(L"profiles", profiles); + return_value->Set(L"credit_cards", cards); + reply.SendSuccess(return_value.get()); + } else { + reply.SendError("No PersonalDataManager."); + return; + } + } else { + reply.SendError("No tab at that index."); + return; + } +} + +// Refer to FillAutoFillProfile() in chrome/test/pyautolib/pyauto.py for sample +// json input. +// Sample json output: {} +void AutomationProvider::FillAutoFillProfile(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message) { + AutomationJSONReply reply(this, reply_message); + ListValue* profiles = NULL; + ListValue* cards = NULL; + args->GetList(L"profiles", &profiles); + args->GetList(L"credit_cards", &cards); + std::string error_mesg; + + std::vector<AutoFillProfile> autofill_profiles; + std::vector<CreditCard> credit_cards; + // Create an AutoFillProfile for each of the dictionary profiles. + if (profiles) { + autofill_profiles = GetAutoFillProfilesFromList(*profiles, &error_mesg); + } + // Create a CreditCard for each of the dictionary values. + if (cards) { + credit_cards = GetCreditCardsFromList(*cards, &error_mesg); + } + if (!error_mesg.empty()) { + reply.SendError(error_mesg); + return; + } + + // Save the AutoFillProfiles. + int tab_index = 0; + args->GetInteger(L"tab_index", &tab_index); + TabContents* tab_contents = browser->GetTabContentsAt(tab_index); + + if (tab_contents) { + PersonalDataManager* pdm = tab_contents->profile() + ->GetPersonalDataManager(); + if (pdm) { + pdm->OnAutoFillDialogApply(profiles? &autofill_profiles : NULL, + cards? &credit_cards : NULL); + } else { + reply.SendError("No PersonalDataManager."); + return; + } + } else { + reply.SendError("No tab at that index."); + return; + } + reply.SendSuccess(NULL); +} + +/* static */ +ListValue* AutomationProvider::GetListFromAutoFillProfiles( + std::vector<AutoFillProfile*> autofill_profiles) { + ListValue* profiles = new ListValue; + + std::map<AutoFillFieldType, std::wstring> autofill_type_to_string + = GetAutoFillFieldToStringMap(); + + // For each AutoFillProfile, transform it to a dictionary object to return. + for (std::vector<AutoFillProfile*>::iterator it = autofill_profiles.begin(); + it != autofill_profiles.end(); ++it) { + AutoFillProfile* profile = *it; + DictionaryValue* profile_info = new DictionaryValue; + profile_info->SetStringFromUTF16(L"label", profile->Label()); + // For each of the types, if it has a value, add it to the dictionary. + for (std::map<AutoFillFieldType, std::wstring>::iterator + type_it = autofill_type_to_string.begin(); + type_it != autofill_type_to_string.end(); ++type_it) { + string16 value = profile->GetFieldText(AutoFillType(type_it->first)); + if (value.length()) { // If there was something stored for that value. + profile_info->SetStringFromUTF16(type_it->second, value); + } + } + profiles->Append(profile_info); + } + return profiles; +} + +/* static */ +ListValue* AutomationProvider::GetListFromCreditCards( + std::vector<CreditCard*> credit_cards) { + ListValue* cards = new ListValue; + + std::map<AutoFillFieldType, std::wstring> credit_card_type_to_string = + GetCreditCardFieldToStringMap(); + + // For each AutoFillProfile, transform it to a dictionary object to return. + for (std::vector<CreditCard*>::iterator it = credit_cards.begin(); + it != credit_cards.end(); ++it) { + CreditCard* card = *it; + DictionaryValue* card_info = new DictionaryValue; + card_info->SetStringFromUTF16(L"label", card->Label()); + // For each of the types, if it has a value, add it to the dictionary. + for (std::map<AutoFillFieldType, std::wstring>::iterator type_it = + credit_card_type_to_string.begin(); + type_it != credit_card_type_to_string.end(); ++type_it) { + string16 value = card->GetFieldText(AutoFillType(type_it->first)); + // If there was something stored for that value. + if (value.length()) { + card_info->SetStringFromUTF16(type_it->second, value); + } + } + cards->Append(card_info); + } + return cards; +} + +/* static */ +std::vector<AutoFillProfile> AutomationProvider::GetAutoFillProfilesFromList( + const ListValue& profiles, std::string* error_message) { + std::vector<AutoFillProfile> autofill_profiles; + DictionaryValue* profile_info = NULL; + string16 profile_label; + string16 current_value; + + std::map<AutoFillFieldType, std::wstring> autofill_type_to_string = + GetAutoFillFieldToStringMap(); + + int num_profiles = profiles.GetSize(); + for (int i = 0; i < num_profiles; i++) { + profiles.GetDictionary(i, &profile_info); + profile_info->GetString("label", &profile_label); + // Choose an id of 0 so that a unique id will be created. + AutoFillProfile profile(profile_label, 0); + // Loop through the possible profile types and add those provided. + for (std::map<AutoFillFieldType, std::wstring>::iterator type_it = + autofill_type_to_string.begin(); + type_it != autofill_type_to_string.end(); ++type_it) { + if (profile_info->HasKey(type_it->second)) { + if (profile_info->GetStringAsUTF16(type_it->second, ¤t_value)) { + profile.SetInfo(AutoFillType(type_it->first), current_value); + } else { + *error_message= "All values must be strings"; + break; + } + } + } + autofill_profiles.push_back(profile); + } + return autofill_profiles; +} + +/* static */ +std::vector<CreditCard> AutomationProvider::GetCreditCardsFromList( + const ListValue& cards, std::string* error_message) { + std::vector<CreditCard> credit_cards; + DictionaryValue* card_info = NULL; + string16 card_label; + string16 current_value; + + std::map<AutoFillFieldType, std::wstring> credit_card_type_to_string = + GetCreditCardFieldToStringMap(); + + int num_credit_cards = cards.GetSize(); + for (int i = 0; i < num_credit_cards; i++) { + cards.GetDictionary(i, &card_info); + card_info->GetString("label", &card_label); + CreditCard card(card_label, 0); + // Loop through the possible credit card fields and add those provided. + for (std::map<AutoFillFieldType, std::wstring>::iterator type_it = + credit_card_type_to_string.begin(); + type_it != credit_card_type_to_string.end(); ++type_it) { + if (card_info->HasKey(type_it->second)) { + if (card_info->GetStringAsUTF16(type_it->second, ¤t_value)) { + card.SetInfo(AutoFillType(type_it->first), current_value); + } else { + *error_message= "All values must be strings"; + break; + } + } + } + credit_cards.push_back(card); + } + return credit_cards; +} + +/* static */ +std::map<AutoFillFieldType, std::wstring> + AutomationProvider::GetAutoFillFieldToStringMap() { + std::map<AutoFillFieldType, std::wstring> autofill_type_to_string; + autofill_type_to_string[NAME_FIRST] = L"NAME_FIRST"; + autofill_type_to_string[NAME_MIDDLE] = L"NAME_MIDDLE"; + autofill_type_to_string[NAME_LAST] = L"NAME_LAST"; + autofill_type_to_string[COMPANY_NAME] = L"COMPANY_NAME"; + autofill_type_to_string[EMAIL_ADDRESS] = L"EMAIL_ADDRESS"; + autofill_type_to_string[ADDRESS_HOME_LINE1] = L"ADDRESS_HOME_LINE1"; + autofill_type_to_string[ADDRESS_HOME_LINE2] = L"ADDRESS_HOME_LINE2"; + autofill_type_to_string[ADDRESS_HOME_CITY] = L"ADDRESS_HOME_CITY"; + autofill_type_to_string[ADDRESS_HOME_STATE] = L"ADDRESS_HOME_STATE"; + autofill_type_to_string[ADDRESS_HOME_ZIP] = L"ADDRESS_HOME_ZIP"; + autofill_type_to_string[ADDRESS_HOME_COUNTRY] = L"ADDRESS_HOME_COUNTRY"; + autofill_type_to_string[PHONE_HOME_NUMBER] = L"PHONE_HOME_NUMBER"; + autofill_type_to_string[PHONE_FAX_NUMBER] = L"PHONE_FAX_NUMBER"; + autofill_type_to_string[NAME_FIRST] = L"NAME_FIRST"; + return autofill_type_to_string; +} + +/* static */ +std::map<AutoFillFieldType, std::wstring> + AutomationProvider::GetCreditCardFieldToStringMap() { + std::map<AutoFillFieldType, std::wstring> credit_card_type_to_string; + credit_card_type_to_string[CREDIT_CARD_NAME] = L"CREDIT_CARD_NAME"; + credit_card_type_to_string[CREDIT_CARD_NUMBER] = L"CREDIT_CARD_NUMBER"; + credit_card_type_to_string[CREDIT_CARD_TYPE] = L"CREDIT_CARD_TYPE"; + credit_card_type_to_string[CREDIT_CARD_EXP_MONTH] = L"CREDIT_CARD_EXP_MONTH"; + credit_card_type_to_string[CREDIT_CARD_EXP_4_DIGIT_YEAR] = + L"CREDIT_CARD_EXP_4_DIGIT_YEAR"; + return credit_card_type_to_string; +} + +void AutomationProvider::SendJSONRequest(int handle, + std::string json_request, + IPC::Message* reply_message) { + Browser* browser = NULL; + scoped_ptr<Value> values; + + // Basic error checking. + if (browser_tracker_->ContainsHandle(handle)) { + browser = browser_tracker_->GetResource(handle); + } + if (!browser) { + AutomationJSONReply(this, reply_message).SendError("no browser object"); + return; + } + base::JSONReader reader; + std::string error; + values.reset(reader.ReadAndReturnError(json_request, true, NULL, &error)); + if (!error.empty()) { + AutomationJSONReply(this, reply_message).SendError(error); + return; + } + + // Make sure input is a dict with a string command. + std::string command; + DictionaryValue* dict_value = NULL; + if (values->GetType() != Value::TYPE_DICTIONARY) { + AutomationJSONReply(this, reply_message).SendError("not a dict"); + return; + } + // Ownership remains with "values" variable. + dict_value = static_cast<DictionaryValue*>(values.get()); + if (!dict_value->GetStringASCII(std::string("command"), &command)) { + AutomationJSONReply(this, reply_message).SendError( + "no command key in dict or not a string command"); + return; + } + + // Map json commands to their handlers. + std::map<std::string, JsonHandler> handler_map; + handler_map["DisablePlugin"] = &AutomationProvider::DisablePlugin; + handler_map["EnablePlugin"] = &AutomationProvider::EnablePlugin; + handler_map["GetPluginsInfo"] = &AutomationProvider::GetPluginsInfo; + + handler_map["GetBrowserInfo"] = &AutomationProvider::GetBrowserInfo; + + handler_map["WaitForInfobarCount"] = &AutomationProvider::WaitForInfobarCount; + + handler_map["GetHistoryInfo"] = &AutomationProvider::GetHistoryInfo; + handler_map["AddHistoryItem"] = &AutomationProvider::AddHistoryItem; + + handler_map["GetOmniboxInfo"] = &AutomationProvider::GetOmniboxInfo; + handler_map["SetOmniboxText"] = &AutomationProvider::SetOmniboxText; + handler_map["OmniboxAcceptInput"] = &AutomationProvider::OmniboxAcceptInput; + handler_map["OmniboxMovePopupSelection"] = + &AutomationProvider::OmniboxMovePopupSelection; + + handler_map["GetPrefsInfo"] = &AutomationProvider::GetPrefsInfo; + handler_map["SetPrefs"] = &AutomationProvider::SetPrefs; + + handler_map["SetWindowDimensions"] = &AutomationProvider::SetWindowDimensions; + + handler_map["GetDownloadsInfo"] = &AutomationProvider::GetDownloadsInfo; + handler_map["WaitForAllDownloadsToComplete"] = + &AutomationProvider::WaitForDownloadsToComplete; + + handler_map["GetInitialLoadTimes"] = &AutomationProvider::GetInitialLoadTimes; + + handler_map["SaveTabContents"] = &AutomationProvider::SaveTabContents; + + handler_map["ImportSettings"] = &AutomationProvider::ImportSettings; + + handler_map["AddSavedPassword"] = &AutomationProvider::AddSavedPassword; + handler_map["GetSavedPasswords"] = &AutomationProvider::GetSavedPasswords; + + handler_map["ClearBrowsingData"] = &AutomationProvider::ClearBrowsingData; + + // SetTheme() implemented using InstallExtension(). + handler_map["GetThemeInfo"] = &AutomationProvider::GetThemeInfo; + + handler_map["GetAutoFillProfile"] = &AutomationProvider::GetAutoFillProfile; + handler_map["FillAutoFillProfile"] = &AutomationProvider::FillAutoFillProfile; + + if (handler_map.find(std::string(command)) != handler_map.end()) { + (this->*handler_map[command])(browser, dict_value, reply_message); + } else { + std::string error_string = "Unknown command. Options: "; + for (std::map<std::string, JsonHandler>::const_iterator it = + handler_map.begin(); it != handler_map.end(); ++it) { + error_string += it->first + ", "; + } + AutomationJSONReply(this, reply_message).SendError(error_string); + } +} + +void AutomationProvider::HandleInspectElementRequest( + int handle, int x, int y, IPC::Message* reply_message) { + TabContents* tab_contents = GetTabContentsForHandle(handle, NULL); + if (tab_contents) { + DCHECK(reply_message_ == NULL); + reply_message_ = reply_message; + + DevToolsManager::GetInstance()->InspectElement( + tab_contents->render_view_host(), x, y); + } else { + AutomationMsg_InspectElement::WriteReplyParams(reply_message, -1); + Send(reply_message); + } +} + +void AutomationProvider::ReceivedInspectElementResponse(int num_resources) { + if (reply_message_) { + AutomationMsg_InspectElement::WriteReplyParams(reply_message_, + num_resources); + Send(reply_message_); + reply_message_ = NULL; + } +} + +class SetProxyConfigTask : public Task { + public: + SetProxyConfigTask(URLRequestContextGetter* request_context_getter, + const std::string& new_proxy_config) + : request_context_getter_(request_context_getter), + proxy_config_(new_proxy_config) {} + virtual void Run() { + // First, deserialize the JSON string. If this fails, log and bail. + JSONStringValueSerializer deserializer(proxy_config_); + std::string error_msg; + scoped_ptr<Value> root(deserializer.Deserialize(NULL, &error_msg)); + if (!root.get() || root->GetType() != Value::TYPE_DICTIONARY) { + DLOG(WARNING) << "Received bad JSON string for ProxyConfig: " + << error_msg; + return; + } + + scoped_ptr<DictionaryValue> dict( + static_cast<DictionaryValue*>(root.release())); + // Now put together a proxy configuration from the deserialized string. + net::ProxyConfig pc; + PopulateProxyConfig(*dict.get(), &pc); + + net::ProxyService* proxy_service = + request_context_getter_->GetURLRequestContext()->proxy_service(); + DCHECK(proxy_service); + scoped_ptr<net::ProxyConfigService> proxy_config_service( + new net::ProxyConfigServiceFixed(pc)); + proxy_service->ResetConfigService(proxy_config_service.release()); + } + + void PopulateProxyConfig(const DictionaryValue& dict, net::ProxyConfig* pc) { + DCHECK(pc); + bool no_proxy = false; + if (dict.GetBoolean(automation::kJSONProxyNoProxy, &no_proxy)) { + // Make no changes to the ProxyConfig. + return; + } + bool auto_config; + if (dict.GetBoolean(automation::kJSONProxyAutoconfig, &auto_config)) { + pc->set_auto_detect(true); + } + std::string pac_url; + if (dict.GetString(automation::kJSONProxyPacUrl, &pac_url)) { + pc->set_pac_url(GURL(pac_url)); + } + std::string proxy_bypass_list; + if (dict.GetString(automation::kJSONProxyBypassList, &proxy_bypass_list)) { + pc->proxy_rules().bypass_rules.ParseFromString(proxy_bypass_list); + } + std::string proxy_server; + if (dict.GetString(automation::kJSONProxyServer, &proxy_server)) { + pc->proxy_rules().ParseFromString(proxy_server); + } + } + + private: + scoped_refptr<URLRequestContextGetter> request_context_getter_; + std::string proxy_config_; +}; + + +void AutomationProvider::SetProxyConfig(const std::string& new_proxy_config) { + URLRequestContextGetter* context_getter = Profile::GetDefaultRequestContext(); + if (!context_getter) { + FilePath user_data_dir; + PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); + ProfileManager* profile_manager = g_browser_process->profile_manager(); + DCHECK(profile_manager); + Profile* profile = profile_manager->GetDefaultProfile(user_data_dir); + DCHECK(profile); + context_getter = profile->GetRequestContext(); + } + DCHECK(context_getter); + + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + new SetProxyConfigTask(context_getter, new_proxy_config)); +} + +void AutomationProvider::GetDownloadDirectory( + int handle, FilePath* download_directory) { + DLOG(INFO) << "Handling download directory request"; + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + DownloadManager* dlm = tab->profile()->GetDownloadManager(); + DCHECK(dlm); + *download_directory = dlm->download_path(); + } +} + +void AutomationProvider::OpenNewBrowserWindow(bool show, + IPC::Message* reply_message) { + OpenNewBrowserWindowOfType(static_cast<int>(Browser::TYPE_NORMAL), show, + reply_message); +} + +void AutomationProvider::OpenNewBrowserWindowOfType( + int type, bool show, IPC::Message* reply_message) { + new BrowserOpenedNotificationObserver(this, reply_message); + // We may have no current browser windows open so don't rely on + // asking an existing browser to execute the IDC_NEWWINDOW command + Browser* browser = new Browser(static_cast<Browser::Type>(type), profile_); + browser->CreateBrowserWindow(); + browser->AddBlankTab(true); + if (show) + browser->window()->Show(); +} + +void AutomationProvider::GetWindowForBrowser(int browser_handle, + bool* success, + int* handle) { + *success = false; + *handle = 0; + + if (browser_tracker_->ContainsHandle(browser_handle)) { + Browser* browser = browser_tracker_->GetResource(browser_handle); + gfx::NativeWindow win = browser->window()->GetNativeHandle(); + // Add() returns the existing handle for the resource if any. + *handle = window_tracker_->Add(win); + *success = true; + } +} + +void AutomationProvider::GetAutocompleteEditForBrowser( + int browser_handle, + bool* success, + int* autocomplete_edit_handle) { + *success = false; + *autocomplete_edit_handle = 0; + + if (browser_tracker_->ContainsHandle(browser_handle)) { + Browser* browser = browser_tracker_->GetResource(browser_handle); + LocationBar* loc_bar = browser->window()->GetLocationBar(); + AutocompleteEditView* edit_view = loc_bar->location_entry(); + // Add() returns the existing handle for the resource if any. + *autocomplete_edit_handle = autocomplete_edit_tracker_->Add(edit_view); + *success = true; + } +} + +void AutomationProvider::ShowInterstitialPage(int tab_handle, + const std::string& html_text, + IPC::Message* reply_message) { + if (tab_tracker_->ContainsHandle(tab_handle)) { + NavigationController* controller = tab_tracker_->GetResource(tab_handle); + TabContents* tab_contents = controller->tab_contents(); + + AddNavigationStatusListener(controller, reply_message, 1, false); + AutomationInterstitialPage* interstitial = + new AutomationInterstitialPage(tab_contents, + GURL("about:interstitial"), + html_text); + interstitial->Show(); + return; + } + + AutomationMsg_ShowInterstitialPage::WriteReplyParams( + reply_message, AUTOMATION_MSG_NAVIGATION_ERROR); + Send(reply_message); +} + +void AutomationProvider::HideInterstitialPage(int tab_handle, + bool* success) { + *success = false; + TabContents* tab_contents = GetTabContentsForHandle(tab_handle, NULL); + if (tab_contents && tab_contents->interstitial_page()) { + tab_contents->interstitial_page()->DontProceed(); + *success = true; + } +} + +void AutomationProvider::CloseTab(int tab_handle, + bool wait_until_closed, + IPC::Message* reply_message) { + if (tab_tracker_->ContainsHandle(tab_handle)) { + NavigationController* controller = tab_tracker_->GetResource(tab_handle); + int index; + Browser* browser = Browser::GetBrowserForController(controller, &index); + DCHECK(browser); + new TabClosedNotificationObserver(this, wait_until_closed, reply_message); + browser->CloseContents(controller->tab_contents()); + return; + } + + AutomationMsg_CloseTab::WriteReplyParams(reply_message, false); + Send(reply_message); +} + +void AutomationProvider::CloseBrowser(int browser_handle, + IPC::Message* reply_message) { + if (browser_tracker_->ContainsHandle(browser_handle)) { + Browser* browser = browser_tracker_->GetResource(browser_handle); + new BrowserClosedNotificationObserver(browser, this, + reply_message); + browser->window()->Close(); + } else { + NOTREACHED(); + } +} + +void AutomationProvider::CloseBrowserAsync(int browser_handle) { + if (browser_tracker_->ContainsHandle(browser_handle)) { + Browser* browser = browser_tracker_->GetResource(browser_handle); + browser->window()->Close(); + } else { + NOTREACHED(); + } +} + +void AutomationProvider::WaitForTabToBeRestored(int tab_handle, + IPC::Message* reply_message) { + if (tab_tracker_->ContainsHandle(tab_handle)) { + NavigationController* tab = tab_tracker_->GetResource(tab_handle); + restore_tracker_.reset( + new NavigationControllerRestoredObserver(this, tab, reply_message)); + } +} + +void AutomationProvider::GetSecurityState(int handle, bool* success, + SecurityStyle* security_style, + int* ssl_cert_status, + int* insecure_content_status) { + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + NavigationEntry* entry = tab->GetActiveEntry(); + *success = true; + *security_style = entry->ssl().security_style(); + *ssl_cert_status = entry->ssl().cert_status(); + *insecure_content_status = entry->ssl().content_status(); + } else { + *success = false; + *security_style = SECURITY_STYLE_UNKNOWN; + *ssl_cert_status = 0; + *insecure_content_status = 0; + } +} + +void AutomationProvider::GetPageType(int handle, bool* success, + NavigationEntry::PageType* page_type) { + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + NavigationEntry* entry = tab->GetActiveEntry(); + *page_type = entry->page_type(); + *success = true; + // In order to return the proper result when an interstitial is shown and + // no navigation entry were created for it we need to ask the TabContents. + if (*page_type == NavigationEntry::NORMAL_PAGE && + tab->tab_contents()->showing_interstitial_page()) + *page_type = NavigationEntry::INTERSTITIAL_PAGE; + } else { + *success = false; + *page_type = NavigationEntry::NORMAL_PAGE; + } +} + +void AutomationProvider::GetMetricEventDuration(const std::string& event_name, + int* duration_ms) { + *duration_ms = metric_event_duration_observer_->GetEventDurationMs( + event_name); +} + +void AutomationProvider::ActionOnSSLBlockingPage(int handle, bool proceed, + IPC::Message* reply_message) { + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + NavigationEntry* entry = tab->GetActiveEntry(); + if (entry->page_type() == NavigationEntry::INTERSTITIAL_PAGE) { + TabContents* tab_contents = tab->tab_contents(); + InterstitialPage* ssl_blocking_page = + InterstitialPage::GetInterstitialPage(tab_contents); + if (ssl_blocking_page) { + if (proceed) { + AddNavigationStatusListener(tab, reply_message, 1, false); + ssl_blocking_page->Proceed(); + return; + } + ssl_blocking_page->DontProceed(); + AutomationMsg_ActionOnSSLBlockingPage::WriteReplyParams( + reply_message, AUTOMATION_MSG_NAVIGATION_SUCCESS); + Send(reply_message); + return; + } + } + } + // We failed. + AutomationMsg_ActionOnSSLBlockingPage::WriteReplyParams( + reply_message, AUTOMATION_MSG_NAVIGATION_ERROR); + Send(reply_message); +} + +void AutomationProvider::BringBrowserToFront(int browser_handle, + bool* success) { + if (browser_tracker_->ContainsHandle(browser_handle)) { + Browser* browser = browser_tracker_->GetResource(browser_handle); + browser->window()->Activate(); + *success = true; + } else { + *success = false; + } +} + +void AutomationProvider::IsMenuCommandEnabled(int browser_handle, + int message_num, + bool* menu_item_enabled) { + if (browser_tracker_->ContainsHandle(browser_handle)) { + Browser* browser = browser_tracker_->GetResource(browser_handle); + *menu_item_enabled = + browser->command_updater()->IsCommandEnabled(message_num); + } else { + *menu_item_enabled = false; + } +} + +void AutomationProvider::PrintNow(int tab_handle, + IPC::Message* reply_message) { + NavigationController* tab = NULL; + TabContents* tab_contents = GetTabContentsForHandle(tab_handle, &tab); + if (tab_contents) { + FindAndActivateTab(tab); + notification_observer_list_.AddObserver( + new DocumentPrintedNotificationObserver(this, reply_message)); + if (tab_contents->PrintNow()) + return; + } + AutomationMsg_PrintNow::WriteReplyParams(reply_message, false); + Send(reply_message); +} + +void AutomationProvider::SavePage(int tab_handle, + const FilePath& file_name, + const FilePath& dir_path, + int type, + bool* success) { + if (!tab_tracker_->ContainsHandle(tab_handle)) { + *success = false; + return; + } + + NavigationController* nav = tab_tracker_->GetResource(tab_handle); + Browser* browser = FindAndActivateTab(nav); + DCHECK(browser); + if (!browser->command_updater()->IsCommandEnabled(IDC_SAVE_PAGE)) { + *success = false; + return; + } + + SavePackage::SavePackageType save_type = + static_cast<SavePackage::SavePackageType>(type); + DCHECK(save_type >= SavePackage::SAVE_AS_ONLY_HTML && + save_type <= SavePackage::SAVE_AS_COMPLETE_HTML); + nav->tab_contents()->SavePage(file_name, dir_path, save_type); + + *success = true; +} + +void AutomationProvider::GetAutocompleteEditText(int autocomplete_edit_handle, + bool* success, + std::wstring* text) { + *success = false; + if (autocomplete_edit_tracker_->ContainsHandle(autocomplete_edit_handle)) { + *text = autocomplete_edit_tracker_->GetResource(autocomplete_edit_handle)-> + GetText(); + *success = true; + } +} + +void AutomationProvider::SetAutocompleteEditText(int autocomplete_edit_handle, + const std::wstring& text, + bool* success) { + *success = false; + if (autocomplete_edit_tracker_->ContainsHandle(autocomplete_edit_handle)) { + autocomplete_edit_tracker_->GetResource(autocomplete_edit_handle)-> + SetUserText(text); + *success = true; + } +} + +void AutomationProvider::AutocompleteEditGetMatches( + int autocomplete_edit_handle, + bool* success, + std::vector<AutocompleteMatchData>* matches) { + *success = false; + if (autocomplete_edit_tracker_->ContainsHandle(autocomplete_edit_handle)) { + const AutocompleteResult& result = autocomplete_edit_tracker_-> + GetResource(autocomplete_edit_handle)->model()->result(); + for (AutocompleteResult::const_iterator i = result.begin(); + i != result.end(); ++i) + matches->push_back(AutocompleteMatchData(*i)); + *success = true; + } +} + +void AutomationProvider::AutocompleteEditIsQueryInProgress( + int autocomplete_edit_handle, + bool* success, + bool* query_in_progress) { + *success = false; + *query_in_progress = false; + if (autocomplete_edit_tracker_->ContainsHandle(autocomplete_edit_handle)) { + *query_in_progress = autocomplete_edit_tracker_-> + GetResource(autocomplete_edit_handle)->model()->query_in_progress(); + *success = true; + } +} + +#if !defined(OS_MACOSX) + +#endif // !defined(OS_MACOSX) + +TabContents* AutomationProvider::GetTabContentsForHandle( + int handle, NavigationController** tab) { + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* nav_controller = tab_tracker_->GetResource(handle); + if (tab) + *tab = nav_controller; + return nav_controller->tab_contents(); + } + return NULL; +} + +TestingAutomationProvider::TestingAutomationProvider(Profile* profile) + : AutomationProvider(profile) { + BrowserList::AddObserver(this); + registrar_.Add(this, NotificationType::SESSION_END, + NotificationService::AllSources()); +} + +TestingAutomationProvider::~TestingAutomationProvider() { + BrowserList::RemoveObserver(this); +} + +void TestingAutomationProvider::OnChannelError() { + BrowserList::CloseAllBrowsersAndExit(); + AutomationProvider::OnChannelError(); +} + +void TestingAutomationProvider::OnBrowserAdded(const Browser* browser) { +} + +void TestingAutomationProvider::OnBrowserRemoving(const Browser* browser) { + // For backwards compatibility with the testing automation interface, we + // want the automation provider (and hence the process) to go away when the + // last browser goes away. + if (BrowserList::size() == 1 && !CommandLine::ForCurrentProcess()->HasSwitch( + switches::kKeepAliveForTest)) { + // If you change this, update Observer for NotificationType::SESSION_END + // below. + MessageLoop::current()->PostTask(FROM_HERE, + NewRunnableMethod(this, &TestingAutomationProvider::OnRemoveProvider)); + } +} + +void TestingAutomationProvider::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NotificationType::SESSION_END); + // OnBrowserRemoving does a ReleaseLater. When session end is received we exit + // before the task runs resulting in this object not being deleted. This + // Release balance out the Release scheduled by OnBrowserRemoving. + Release(); +} + +void TestingAutomationProvider::OnRemoveProvider() { + AutomationProviderList::GetInstance()->RemoveProvider(this); +} + +void AutomationProvider::GetInfoBarCount(int handle, int* count) { + *count = -1; // -1 means error. + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* nav_controller = tab_tracker_->GetResource(handle); + if (nav_controller) + *count = nav_controller->tab_contents()->infobar_delegate_count(); + } +} + +void AutomationProvider::ClickInfoBarAccept(int handle, + int info_bar_index, + bool wait_for_navigation, + IPC::Message* reply_message) { + bool success = false; + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* nav_controller = tab_tracker_->GetResource(handle); + if (nav_controller) { + int count = nav_controller->tab_contents()->infobar_delegate_count(); + if (info_bar_index >= 0 && info_bar_index < count) { + if (wait_for_navigation) { + AddNavigationStatusListener(nav_controller, reply_message, 1, false); + } + InfoBarDelegate* delegate = + nav_controller->tab_contents()->GetInfoBarDelegateAt( + info_bar_index); + if (delegate->AsConfirmInfoBarDelegate()) + delegate->AsConfirmInfoBarDelegate()->Accept(); + success = true; + } + } + } + + // This "!wait_for_navigation || !success condition" logic looks suspicious. + // It will send a failure message when success is true but + // |wait_for_navigation| is false. + // TODO(phajdan.jr): investgate whether the reply param (currently + // AUTOMATION_MSG_NAVIGATION_ERROR) should depend on success. + if (!wait_for_navigation || !success) + AutomationMsg_ClickInfoBarAccept::WriteReplyParams( + reply_message, AUTOMATION_MSG_NAVIGATION_ERROR); +} + +void AutomationProvider::GetLastNavigationTime(int handle, + int64* last_navigation_time) { + Time time = tab_tracker_->GetLastNavigationTime(handle); + *last_navigation_time = time.ToInternalValue(); +} + +void AutomationProvider::WaitForNavigation(int handle, + int64 last_navigation_time, + IPC::Message* reply_message) { + NavigationController* controller = tab_tracker_->GetResource(handle); + Time time = tab_tracker_->GetLastNavigationTime(handle); + + if (time.ToInternalValue() > last_navigation_time || !controller) { + AutomationMsg_WaitForNavigation::WriteReplyParams(reply_message, + controller == NULL ? AUTOMATION_MSG_NAVIGATION_ERROR : + AUTOMATION_MSG_NAVIGATION_SUCCESS); + Send(reply_message); + return; + } + + AddNavigationStatusListener(controller, reply_message, 1, true); +} + +void AutomationProvider::SetIntPreference(int handle, + const std::wstring& name, + int value, + bool* success) { + *success = false; + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + browser->profile()->GetPrefs()->SetInteger(name.c_str(), value); + *success = true; + } +} + +void AutomationProvider::SetStringPreference(int handle, + const std::wstring& name, + const std::string& value, + bool* success) { + *success = false; + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + browser->profile()->GetPrefs()->SetString(name.c_str(), value); + *success = true; + } +} + +void AutomationProvider::GetBooleanPreference(int handle, + const std::wstring& name, + bool* success, + bool* value) { + *success = false; + *value = false; + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + *value = browser->profile()->GetPrefs()->GetBoolean(name.c_str()); + *success = true; + } +} + +void AutomationProvider::SetBooleanPreference(int handle, + const std::wstring& name, + bool value, + bool* success) { + *success = false; + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + browser->profile()->GetPrefs()->SetBoolean(name.c_str(), value); + *success = true; + } +} + +// Gets the current used encoding name of the page in the specified tab. +void AutomationProvider::GetPageCurrentEncoding( + int tab_handle, std::string* current_encoding) { + if (tab_tracker_->ContainsHandle(tab_handle)) { + NavigationController* nav = tab_tracker_->GetResource(tab_handle); + Browser* browser = FindAndActivateTab(nav); + DCHECK(browser); + + if (browser->command_updater()->IsCommandEnabled(IDC_ENCODING_MENU)) + *current_encoding = nav->tab_contents()->encoding(); + } +} + +// Gets the current used encoding name of the page in the specified tab. +void AutomationProvider::OverrideEncoding(int tab_handle, + const std::string& encoding_name, + bool* success) { + *success = false; + if (tab_tracker_->ContainsHandle(tab_handle)) { + NavigationController* nav = tab_tracker_->GetResource(tab_handle); + if (!nav) + return; + Browser* browser = FindAndActivateTab(nav); + + // If the browser has UI, simulate what a user would do. + // Activate the tab and then click the encoding menu. + if (browser && + browser->command_updater()->IsCommandEnabled(IDC_ENCODING_MENU)) { + int selected_encoding_id = + CharacterEncoding::GetCommandIdByCanonicalEncodingName(encoding_name); + if (selected_encoding_id) { + browser->OverrideEncoding(selected_encoding_id); + *success = true; + } + } else { + // There is no UI, Chrome probably runs as Chrome-Frame mode. + // Try to get TabContents and call its override_encoding method. + TabContents* contents = nav->tab_contents(); + if (!contents) + return; + const std::string selected_encoding = + CharacterEncoding::GetCanonicalEncodingNameByAliasName(encoding_name); + if (selected_encoding.empty()) + return; + contents->SetOverrideEncoding(selected_encoding); + } + } +} + +void AutomationProvider::SavePackageShouldPromptUser(bool should_prompt) { + SavePackage::SetShouldPromptUser(should_prompt); +} + +void AutomationProvider::GetBlockedPopupCount(int handle, int* count) { + *count = -1; // -1 is the error code + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* nav_controller = tab_tracker_->GetResource(handle); + TabContents* tab_contents = nav_controller->tab_contents(); + if (tab_contents) { + BlockedPopupContainer* container = + tab_contents->blocked_popup_container(); + if (container) { + *count = static_cast<int>(container->GetBlockedPopupCount()); + } else { + // If we don't have a container, we don't have any blocked popups to + // contain! + *count = 0; + } + } + } +} + +void AutomationProvider::SelectAll(int tab_handle) { + RenderViewHost* view = GetViewForTab(tab_handle); + if (!view) { + NOTREACHED(); + return; + } + + view->SelectAll(); +} + +void AutomationProvider::Cut(int tab_handle) { + RenderViewHost* view = GetViewForTab(tab_handle); + if (!view) { + NOTREACHED(); + return; + } + + view->Cut(); +} + +void AutomationProvider::Copy(int tab_handle) { + RenderViewHost* view = GetViewForTab(tab_handle); + if (!view) { + NOTREACHED(); + return; + } + + view->Copy(); +} + +void AutomationProvider::Paste(int tab_handle) { + RenderViewHost* view = GetViewForTab(tab_handle); + if (!view) { + NOTREACHED(); + return; + } + + view->Paste(); +} + +void AutomationProvider::ReloadAsync(int tab_handle) { + if (tab_tracker_->ContainsHandle(tab_handle)) { + NavigationController* tab = tab_tracker_->GetResource(tab_handle); + if (!tab) { + NOTREACHED(); + return; + } + + const bool check_for_repost = true; + tab->Reload(check_for_repost); + } +} + +void AutomationProvider::StopAsync(int tab_handle) { + RenderViewHost* view = GetViewForTab(tab_handle); + if (!view) { + // We tolerate StopAsync being called even before a view has been created. + // So just log a warning instead of a NOTREACHED(). + DLOG(WARNING) << "StopAsync: no view for handle " << tab_handle; + return; + } + + view->Stop(); +} + +void AutomationProvider::OnSetPageFontSize(int tab_handle, + int font_size) { + AutomationPageFontSize automation_font_size = + static_cast<AutomationPageFontSize>(font_size); + + if (automation_font_size < SMALLEST_FONT || + automation_font_size > LARGEST_FONT) { + DLOG(ERROR) << "Invalid font size specified : " + << font_size; + return; + } + + if (tab_tracker_->ContainsHandle(tab_handle)) { + NavigationController* tab = tab_tracker_->GetResource(tab_handle); + DCHECK(tab != NULL); + if (tab && tab->tab_contents()) { + DCHECK(tab->tab_contents()->profile() != NULL); + tab->tab_contents()->profile()->GetPrefs()->SetInteger( + prefs::kWebKitDefaultFontSize, font_size); + } + } +} + +void AutomationProvider::RemoveBrowsingData(int remove_mask) { + BrowsingDataRemover* remover; + remover = new BrowsingDataRemover(profile(), + BrowsingDataRemover::EVERYTHING, // All time periods. + base::Time()); + remover->Remove(remove_mask); + // BrowsingDataRemover deletes itself. +} + +void AutomationProvider::WaitForBrowserWindowCountToBecome( + int target_count, IPC::Message* reply_message) { + if (static_cast<int>(BrowserList::size()) == target_count) { + AutomationMsg_WaitForBrowserWindowCountToBecome::WriteReplyParams( + reply_message, true); + Send(reply_message); + return; + } + + // Set up an observer (it will delete itself). + new BrowserCountChangeNotificationObserver(target_count, this, reply_message); +} + +void AutomationProvider::WaitForAppModalDialogToBeShown( + IPC::Message* reply_message) { + if (Singleton<AppModalDialogQueue>()->HasActiveDialog()) { + AutomationMsg_WaitForAppModalDialogToBeShown::WriteReplyParams( + reply_message, true); + Send(reply_message); + return; + } + + // Set up an observer (it will delete itself). + new AppModalDialogShownObserver(this, reply_message); +} + +void AutomationProvider::GoBackBlockUntilNavigationsComplete( + int handle, int number_of_navigations, IPC::Message* reply_message) { + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + Browser* browser = FindAndActivateTab(tab); + if (browser && browser->command_updater()->IsCommandEnabled(IDC_BACK)) { + AddNavigationStatusListener(tab, reply_message, number_of_navigations, + false); + browser->GoBack(CURRENT_TAB); + return; + } + } + + AutomationMsg_GoBackBlockUntilNavigationsComplete::WriteReplyParams( + reply_message, AUTOMATION_MSG_NAVIGATION_ERROR); + Send(reply_message); +} + +void AutomationProvider::GoForwardBlockUntilNavigationsComplete( + int handle, int number_of_navigations, IPC::Message* reply_message) { + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + Browser* browser = FindAndActivateTab(tab); + if (browser && browser->command_updater()->IsCommandEnabled(IDC_FORWARD)) { + AddNavigationStatusListener(tab, reply_message, number_of_navigations, + false); + browser->GoForward(CURRENT_TAB); + return; + } + } + + AutomationMsg_GoForwardBlockUntilNavigationsComplete::WriteReplyParams( + reply_message, AUTOMATION_MSG_NAVIGATION_ERROR); + Send(reply_message); +} + +RenderViewHost* AutomationProvider::GetViewForTab(int tab_handle) { + if (tab_tracker_->ContainsHandle(tab_handle)) { + NavigationController* tab = tab_tracker_->GetResource(tab_handle); + if (!tab) { + NOTREACHED(); + return NULL; + } + + TabContents* tab_contents = tab->tab_contents(); + if (!tab_contents) { + NOTREACHED(); + return NULL; + } + + RenderViewHost* view_host = tab_contents->render_view_host(); + return view_host; + } + + return NULL; +} + +void AutomationProvider::GetBrowserForWindow(int window_handle, + bool* success, + int* browser_handle) { + *success = false; + *browser_handle = 0; + + gfx::NativeWindow window = window_tracker_->GetResource(window_handle); + if (!window) + return; + + BrowserList::const_iterator iter = BrowserList::begin(); + for (;iter != BrowserList::end(); ++iter) { + gfx::NativeWindow this_window = (*iter)->window()->GetNativeHandle(); + if (window == this_window) { + // Add() returns the existing handle for the resource if any. + *browser_handle = browser_tracker_->Add(*iter); + *success = true; + return; + } + } +} + +void AutomationProvider::InstallExtension(const FilePath& crx_path, + IPC::Message* reply_message) { + ExtensionsService* service = profile_->GetExtensionsService(); + if (service) { + // The observer will delete itself when done. + new ExtensionInstallNotificationObserver(this, + AutomationMsg_InstallExtension::ID, + reply_message); + + const FilePath& install_dir = service->install_directory(); + scoped_refptr<CrxInstaller> installer( + new CrxInstaller(install_dir, + service, + NULL)); // silent install, no UI + installer->set_allow_privilege_increase(true); + installer->InstallCrx(crx_path); + } else { + AutomationMsg_InstallExtension::WriteReplyParams( + reply_message, AUTOMATION_MSG_EXTENSION_INSTALL_FAILED); + Send(reply_message); + } +} + +void AutomationProvider::LoadExpandedExtension( + const FilePath& extension_dir, + IPC::Message* reply_message) { + if (profile_->GetExtensionsService()) { + // The observer will delete itself when done. + new ExtensionInstallNotificationObserver( + this, + AutomationMsg_LoadExpandedExtension::ID, + reply_message); + + profile_->GetExtensionsService()->LoadExtension(extension_dir); + } else { + AutomationMsg_LoadExpandedExtension::WriteReplyParams( + reply_message, AUTOMATION_MSG_EXTENSION_INSTALL_FAILED); + Send(reply_message); + } +} + +void AutomationProvider::GetEnabledExtensions( + std::vector<FilePath>* result) { + ExtensionsService* service = profile_->GetExtensionsService(); + DCHECK(service); + if (service->extensions_enabled()) { + const ExtensionList* extensions = service->extensions(); + DCHECK(extensions); + for (size_t i = 0; i < extensions->size(); ++i) { + Extension* extension = (*extensions)[i]; + DCHECK(extension); + if (extension->location() == Extension::INTERNAL || + extension->location() == Extension::LOAD) { + result->push_back(extension->path()); + } + } + } +} + +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, bool with_ui, 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); + + ExtensionInstallUI* client = + (with_ui ? new ExtensionInstallUI(profile_) : NULL); + scoped_refptr<CrxInstaller> installer( + new CrxInstaller(service->install_directory(), + service, + client)); + 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); + if (tab_contents) + tab_contents->OnSavePage(); +} + +void AutomationProvider::SetContentSetting( + int handle, + const std::string& host, + ContentSettingsType content_type, + ContentSetting setting, + bool* success) { + *success = false; + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + HostContentSettingsMap* map = + browser->profile()->GetHostContentSettingsMap(); + if (host.empty()) { + map->SetDefaultContentSetting(content_type, setting); + } else { + map->SetContentSetting(HostContentSettingsMap::Pattern(host), + content_type, setting); + } + *success = true; + } +} + +#if !defined(TOOLKIT_VIEWS) +void AutomationProvider::GetFocusedViewID(int handle, int* view_id) { + NOTIMPLEMENTED(); +}; + +void AutomationProvider::WaitForFocusedViewIDToChange( + int handle, int previous_view_id, IPC::Message* reply_message) { + NOTIMPLEMENTED(); +} + +void AutomationProvider::StartTrackingPopupMenus( + int browser_handle, bool* success) { + NOTIMPLEMENTED(); +} + +void AutomationProvider::WaitForPopupMenuToOpen(IPC::Message* reply_message) { + NOTIMPLEMENTED(); +} +#endif // !defined(TOOLKIT_VIEWS) + +void AutomationProvider::ResetToDefaultTheme() { + profile_->ClearTheme(); +} diff --git a/chrome/browser/automation/automation_provider.h b/chrome/browser/automation/automation_provider.h new file mode 100644 index 0000000..f166771 --- /dev/null +++ b/chrome/browser/automation/automation_provider.h @@ -0,0 +1,953 @@ +// 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. + +// This implements a browser-side endpoint for UI automation activity. +// The client-side endpoint is implemented by AutomationProxy. +// The entire lifetime of this object should be contained within that of +// the BrowserProcess, and in particular the NotificationService that's +// hung off of it. + +#ifndef CHROME_BROWSER_AUTOMATION_AUTOMATION_PROVIDER_H_ +#define CHROME_BROWSER_AUTOMATION_AUTOMATION_PROVIDER_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/autofill/field_types.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/common/content_settings.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/test/automation/automation_constants.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_channel.h" +#if defined(OS_WIN) +#include "views/event.h" +#endif // defined(OS_WIN) + +struct AutomationMsg_Find_Params; +class PopupMenuWaiter; + +namespace IPC { +struct Reposition_Params; +struct ExternalTabSettings; +class ChannelProxy; +} + +class AutoFillProfile; +class AutomationAutocompleteEditTracker; +class AutomationBrowserTracker; +class AutomationExtensionTracker; +class AutomationResourceMessageFilter; +class AutomationTabTracker; +class AutomationWindowTracker; +class CreditCard; +class DictionaryValue; +class Extension; +class ExtensionPortContainer; +class ExtensionTestResultNotificationObserver; +class ExternalTabContainer; +class LoginHandler; +class MetricEventDurationObserver; +class InitialLoadObserver; +class NavigationControllerRestoredObserver; +struct AutocompleteMatchData; + +namespace gfx { +class Point; +} + +class AutomationProvider : public base::RefCounted<AutomationProvider>, + public IPC::Channel::Listener, + public IPC::Message::Sender { + public: + explicit AutomationProvider(Profile* profile); + + Profile* profile() const { return profile_; } + + // Establishes a connection to an automation client, if present. + // An AutomationProxy should be established (probably in a different process) + // before calling this. + void ConnectToChannel(const std::string& channel_id); + + // Sets the number of tabs that we expect; when this number of tabs has + // loaded, an AutomationMsg_InitialLoadsComplete message is sent. + void SetExpectedTabCount(size_t expected_tabs); + + // Add a listener for navigation status notification. Currently only + // navigation completion is observed; when the |number_of_navigations| + // complete, the completed_response object is sent; if the server requires + // authentication, we instead send the auth_needed_response object. A pointer + // to the added navigation observer is returned. This object should NOT be + // deleted and should be released by calling the corresponding + // RemoveNavigationStatusListener method. + NotificationObserver* AddNavigationStatusListener( + NavigationController* tab, IPC::Message* reply_message, + int number_of_navigations, bool include_current_navigation); + + void RemoveNavigationStatusListener(NotificationObserver* obs); + + // Add an observer for the TabStrip. Currently only Tab append is observed. A + // navigation listener is created on successful notification of tab append. A + // pointer to the added navigation observer is returned. This object should + // NOT be deleted and should be released by calling the corresponding + // RemoveTabStripObserver method. + NotificationObserver* AddTabStripObserver(Browser* parent, + IPC::Message* reply_message); + void RemoveTabStripObserver(NotificationObserver* obs); + + // Get the index of a particular NavigationController object + // in the given parent window. This method uses + // TabStrip::GetIndexForNavigationController to get the index. + int GetIndexForNavigationController(const NavigationController* controller, + const Browser* parent) const; + + // Add or remove a non-owning reference to a tab's LoginHandler. This is for + // when a login prompt is shown for HTTP/FTP authentication. + // TODO(mpcomplete): The login handling is a fairly special purpose feature. + // Eventually we'll probably want ways to interact with the ChromeView of the + // login window in a generic manner, such that it can be used for anything, + // not just logins. + void AddLoginHandler(NavigationController* tab, LoginHandler* handler); + void RemoveLoginHandler(NavigationController* tab); + + // Add an extension port container. + // Takes ownership of the container. + void AddPortContainer(ExtensionPortContainer* port); + // Remove and delete the port container. + void RemovePortContainer(ExtensionPortContainer* port); + // Get the port container for the given port id. + ExtensionPortContainer* GetPortContainer(int port_id) const; + + // IPC implementations + virtual bool Send(IPC::Message* msg); + virtual void OnMessageReceived(const IPC::Message& msg); + virtual void OnChannelError(); + + // Received response from inspector controller + void ReceivedInspectElementResponse(int num_resources); + + IPC::Message* reply_message_release() { + IPC::Message* reply_message = reply_message_; + reply_message_ = NULL; + 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); + +#if defined(OS_WIN) + // Adds the external tab passed in to the tab tracker. + bool AddExternalTab(ExternalTabContainer* external_tab); +#endif + + protected: + friend class base::RefCounted<AutomationProvider>; + friend class PopupMenuWaiter; + virtual ~AutomationProvider(); + + private: + // IPC Message callbacks. + void CloseBrowser(int handle, IPC::Message* reply_message); + void CloseBrowserAsync(int browser_handle); + void ActivateTab(int handle, int at_index, int* status); + void AppendTab(int handle, const GURL& url, IPC::Message* reply_message); + void CloseTab(int tab_handle, bool wait_until_closed, + IPC::Message* reply_message); + + void GetActiveTabIndex(int handle, int* active_tab_index); + void GetCookies(const GURL& url, int handle, int* value_size, + std::string* value); + void SetCookie(const GURL& url, + const std::string value, + int handle, + int* response_value); + void DeleteCookie(const GURL& url, const std::string& cookie_name, + int handle, bool* success); + void ShowCollectedCookiesDialog(int handle, bool* success); + void GetBrowserWindowCount(int* window_count); + void GetBrowserLocale(string16* locale); + void GetNormalBrowserWindowCount(int* window_count); + void GetShowingAppModalDialog(bool* showing_dialog, int* dialog_button); + void ClickAppModalDialogButton(int button, bool* success); + void ShutdownSessionService(int handle, bool* result); + // Be aware that the browser window returned might be of non TYPE_NORMAL + // or in incognito mode. + void GetBrowserWindow(int index, int* handle); + void FindNormalBrowserWindow(int* handle); + void GetLastActiveBrowserWindow(int* handle); + void GetActiveWindow(int* handle); + void ExecuteBrowserCommandAsync(int handle, int command, bool* success); + void ExecuteBrowserCommand(int handle, int command, + IPC::Message* reply_message); + void TerminateSession(int handle, bool* success); + void WindowGetViewBounds(int handle, int view_id, bool screen_coordinates, + bool* success, gfx::Rect* bounds); + void WindowSimulateDrag(int handle, + std::vector<gfx::Point> drag_path, + int flags, + bool press_escape_en_route, + IPC::Message* reply_message); + void WindowSimulateClick(const IPC::Message& message, + int handle, + const gfx::Point& click, + int flags); + void WindowSimulateMouseMove(const IPC::Message& message, + int handle, + const gfx::Point& location); + void WindowSimulateKeyPress(const IPC::Message& message, + int handle, + int key, + int flags); + void GetWindowBounds(int handle, gfx::Rect* bounds, bool* result); + void SetWindowBounds(int handle, const gfx::Rect& bounds, bool* result); + void SetWindowVisible(int handle, bool visible, bool* result); + void IsWindowActive(int handle, bool* success, bool* is_active); + void ActivateWindow(int handle); + void IsWindowMaximized(int handle, bool* is_maximized, bool* success); + + void GetTabCount(int handle, int* tab_count); + void GetType(int handle, int* type_as_int); + void GetTab(int win_handle, int tab_index, int* tab_handle); +#if defined(OS_WIN) + // TODO(port): Replace HWND. + void GetTabHWND(int handle, HWND* tab_hwnd); +#endif // defined(OS_WIN) + void GetTabProcessID(int handle, int* process_id); + void GetTabTitle(int handle, int* title_string_size, std::wstring* title); + void GetTabIndex(int handle, int* tabstrip_index); + void GetTabURL(int handle, bool* success, GURL* url); + void HandleUnused(const IPC::Message& message, int handle); + void NavigateToURL(int handle, const GURL& url, IPC::Message* reply_message); + void NavigateToURLBlockUntilNavigationsComplete(int handle, const GURL& url, + int number_of_navigations, + IPC::Message* reply_message); + void NavigationAsync(int handle, const GURL& url, bool* status); + void NavigationAsyncWithDisposition(int handle, + const GURL& url, + WindowOpenDisposition disposition, + bool* status); + void GoBack(int handle, IPC::Message* reply_message); + void GoForward(int handle, IPC::Message* reply_message); + void Reload(int handle, IPC::Message* reply_message); + void SetAuth(int tab_handle, const std::wstring& username, + const std::wstring& password, IPC::Message* reply_message); + void CancelAuth(int tab_handle, IPC::Message* reply_message); + void NeedsAuth(int tab_handle, bool* needs_auth); + void GetRedirectsFrom(int tab_handle, + const GURL& source_url, + IPC::Message* reply_message); + void ExecuteJavascript(int handle, + const std::wstring& frame_xpath, + const std::wstring& script, + IPC::Message* reply_message); + void GetShelfVisibility(int handle, bool* visible); + void SetShelfVisibility(int handle, bool visible); + void SetFilteredInet(const IPC::Message& message, bool enabled); + void GetFilteredInetHitCount(int* hit_count); + void SetProxyConfig(const std::string& new_proxy_config); + void IsFullscreen(int handle, bool* is_fullscreen); + void GetFullscreenBubbleVisibility(int handle, bool* is_visible); + void SetContentSetting(int handle, + const std::string& host, + ContentSettingsType content_type, + ContentSetting setting, + bool* success); + + void GetFocusedViewID(int handle, int* view_id); + + // Helper function to find the browser window that contains a given + // NavigationController and activate that tab. + // Returns the Browser if found. + Browser* FindAndActivateTab(NavigationController* contents); + + // Deprecated. + void ApplyAccelerator(int handle, int id); + + void GetConstrainedWindowCount(int handle, int* count); + + // This function has been deprecated, please use HandleFindRequest. + void HandleFindInPageRequest(int handle, + const std::wstring& find_request, + int forward, + int match_case, + int* active_ordinal, + int* matches_found); + + // Responds to the FindInPage request, retrieves the search query parameters, + // launches an observer to listen for results and issues a StartFind request. + void HandleFindRequest(int handle, + const AutomationMsg_Find_Params& params, + IPC::Message* reply_message); + + // Responds to requests to open the FindInPage window. + void HandleOpenFindInPageRequest(const IPC::Message& message, + int handle); + + // Get the visibility state of the Find window. + void GetFindWindowVisibility(int handle, bool* visible); + + // Responds to requests to find the location of the Find window. + void HandleFindWindowLocationRequest(int handle, int* x, int* y); + + // Get the visibility state of the Bookmark bar. + void GetBookmarkBarVisibility(int handle, bool* visible, bool* animating); + + // Get the bookmarks as a JSON string. + void GetBookmarksAsJSON(int handle, std::string* bookmarks_as_json, + bool *success); + + // Wait for the bookmark model to load. + void WaitForBookmarkModelToLoad(int handle, IPC::Message* reply_message); + + // Set |loaded| to true if the bookmark model has loaded, else false. + void BookmarkModelHasLoaded(int handle, bool* loaded); + + // Editing, modification, and removal of bookmarks. + // Bookmarks are referenced by id. + void AddBookmarkGroup(int handle, + int64 parent_id, int index, std::wstring title, + bool* success); + void AddBookmarkURL(int handle, + int64 parent_id, int index, + std::wstring title, const GURL& url, + bool* success); + void ReparentBookmark(int handle, + int64 id, int64 new_parent_id, int index, + bool* success); + void SetBookmarkTitle(int handle, + int64 id, std::wstring title, + bool* success); + void SetBookmarkURL(int handle, + int64 id, const GURL& url, + bool* success); + void RemoveBookmark(int handle, + int64 id, + bool* success); + + // Set window dimensions. + // Uses the JSON interface for input/output. + void SetWindowDimensions(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Get info about infobars in the given TabContents object. + // This includes info about the type of infobars, the message text, + // buttons, etc. + // Caller owns the returned object. + ListValue* GetInfobarsInfo(TabContents* tc); + + // Wait for infobar count in a given tab to become a certain value. + // Uses the JSON interface for input/output. + void WaitForInfobarCount(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Get info about the chromium/chrome in use. + // This includes things like version, executable name, executable path. + // Uses the JSON interface for input/output. + void GetBrowserInfo(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Get info about downloads. This includes only ones that have been + // registered by the history system. + // Uses the JSON interface for input/output. + void GetDownloadsInfo(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Wait for all downloads to complete. + // Uses the JSON interface for input/output. + void WaitForDownloadsToComplete(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Get info about history. + // Uses the JSON interface for input/output. + void GetHistoryInfo(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Add an item to the history service. + // Uses the JSON interface for input/output. + void AddHistoryItem(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Get info about preferences. + // Uses the JSON interface for input/output. + void GetPrefsInfo(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Set prefs. + // Uses the JSON interface for input/output. + void SetPrefs(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Return load times of initial tabs. + // Uses the JSON interface for input/output. + // Only includes tabs from command line arguments or session restore. + // See declaration of InitialLoadObserver in automation_provider_observers.h + // for example response. + void GetInitialLoadTimes(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Get info about plugins. + // Uses the JSON interface for input/output. + void GetPluginsInfo(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Enable a plugin. + // Uses the JSON interface for input/output. + void EnablePlugin(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Disable a plugin. + // Uses the JSON interface for input/output. + void DisablePlugin(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Get info about omnibox. + // Contains data about the matches (url, content, description) + // in the omnibox popup, the text in the omnibox. + // Uses the JSON interface for input/output. + void GetOmniboxInfo(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Set text in the omnibox. This sets focus to the omnibox. + // Uses the JSON interface for input/output. + void SetOmniboxText(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Move omnibox popup selection up or down. + // Uses the JSON interface for input/output. + void OmniboxMovePopupSelection(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Accept the current string of text in the omnibox. + // This is equivalent to clicking or hiting enter on a popup selection. + // Blocks until the page loads. + // Uses the JSON interface for input/output. + void OmniboxAcceptInput(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Save the contents of a tab into a file. + // Uses the JSON interface for input/output. + void SaveTabContents(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Import the given settings from the given browser. + // Uses the JSON interface for input/output. + void ImportSettings(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Add a new username-password combination to the saved passwords. + // Uses the JSON interface for input/output. + void AddSavedPassword(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Return the saved username/password combinations. + // Uses the JSON interface for input/output. + void GetSavedPasswords(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Clear the specified browsing data. This call provides similar + // functionality to RemoveBrowsingData but is synchronous. + // Uses the JSON interface for input/output. + void ClearBrowsingData(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Get info about theme. + // Uses the JSON interface for input/output. + void GetThemeInfo(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Get the profiles that are currently saved to the DB. + // Uses the JSON interface for input/output. + void GetAutoFillProfile(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Fill in an AutoFillProfile with the given profile information. + // Uses the JSON interface for input/output. + void FillAutoFillProfile(Browser* browser, + DictionaryValue* args, + IPC::Message* reply_message); + + // Translate DictionaryValues of autofill profiles and credit cards to the + // data structure used in the browser. + // Args: + // profiles/cards: the ListValue of profiles/credit cards to translate. + // error_message: a pointer to the return string in case of error. + static std::vector<AutoFillProfile> GetAutoFillProfilesFromList( + const ListValue& profiles, std::string* error_message); + static std::vector<CreditCard> GetCreditCardsFromList( + const ListValue& cards, std::string* error_message); + + // The opposite of the above: translates from the internal data structure + // for profiles and credit cards to a ListValue of DictionaryValues. The + // caller owns the returned object. + static ListValue* GetListFromAutoFillProfiles( + std::vector<AutoFillProfile*> autofill_profiles); + static ListValue* GetListFromCreditCards( + std::vector<CreditCard*> credit_cards); + + // Return the map from the internal data representation to the string value + // of auto fill fields and credit card fields. + static std::map<AutoFillFieldType, std::wstring> + GetAutoFillFieldToStringMap(); + static std::map<AutoFillFieldType, std::wstring> + GetCreditCardFieldToStringMap(); + + // Generic pattern for pyautolib + // Uses the JSON interface for input/output. + void SendJSONRequest(int handle, + std::string json_request, + IPC::Message* reply_message); + + // Method ptr for json handlers. + // Uses the JSON interface for input/output. + typedef void (AutomationProvider::*JsonHandler)(Browser* browser, + DictionaryValue*, + IPC::Message*); + + // Responds to InspectElement request + void HandleInspectElementRequest(int handle, + int x, + int y, + IPC::Message* reply_message); + + void GetDownloadDirectory(int handle, FilePath* download_directory); + + // Retrieves a Browser from a Window and vice-versa. + void GetWindowForBrowser(int window_handle, bool* success, int* handle); + void GetBrowserForWindow(int window_handle, bool* success, + int* browser_handle); + + void GetAutocompleteEditForBrowser(int browser_handle, bool* success, + int* autocomplete_edit_handle); + + // If |show| is true, call Show() on the new window after creating it. + void OpenNewBrowserWindow(bool show, IPC::Message* reply_message); + void OpenNewBrowserWindowOfType(int type, + bool show, + IPC::Message* reply_message); + + void ShowInterstitialPage(int tab_handle, + const std::string& html_text, + IPC::Message* reply_message); + void HideInterstitialPage(int tab_handle, bool* success); + + void OnSetPageFontSize(int tab_handle, int font_size); + + // See browsing_data_remover.h for explanation of bitmap fields. + void RemoveBrowsingData(int remove_mask); + + void InstallExtension(const FilePath& crx_path, + IPC::Message* reply_message); + + void LoadExpandedExtension(const FilePath& extension_dir, + IPC::Message* reply_message); + + void GetEnabledExtensions(std::vector<FilePath>* result); + + void WaitForExtensionTestResult(IPC::Message* reply_message); + + void InstallExtensionAndGetHandle(const FilePath& crx_path, + bool with_ui, + 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); + + + // See comment in AutomationMsg_WaitForTabToBeRestored. + void WaitForTabToBeRestored(int tab_handle, IPC::Message* reply_message); + + // Gets the security state for the tab associated to the specified |handle|. + void GetSecurityState(int handle, bool* success, + SecurityStyle* security_style, int* ssl_cert_status, + int* insecure_content_status); + + // Gets the page type for the tab associated to the specified |handle|. + void GetPageType(int handle, bool* success, + NavigationEntry::PageType* page_type); + + // Gets the duration in ms of the last event matching |event_name|. + // |duration_ms| is -1 if the event hasn't occurred yet. + void GetMetricEventDuration(const std::string& event_name, int* duration_ms); + + // Simulates an action on the SSL blocking page at the tab specified by + // |handle|. If |proceed| is true, it is equivalent to the user pressing the + // 'Proceed' button, if false the 'Get me out of there button'. + // Not that this fails if the tab is not displaying a SSL blocking page. + void ActionOnSSLBlockingPage(int handle, + bool proceed, + IPC::Message* reply_message); + + // Brings the browser window to the front and activates it. + void BringBrowserToFront(int browser_handle, bool* success); + + // Checks to see if a command on the browser's CommandController is enabled. + void IsMenuCommandEnabled(int browser_handle, + int message_num, + bool* menu_item_enabled); + + // Prints the current tab immediately. + void PrintNow(int tab_handle, IPC::Message* reply_message); + + // Asynchronous request for printing the current tab. + void PrintAsync(int tab_handle); + + // Save the current web page. + void SavePage(int tab_handle, + const FilePath& file_name, + const FilePath& dir_path, + int type, + bool* success); + + // Retrieves the visible text from the autocomplete edit. + void GetAutocompleteEditText(int autocomplete_edit_handle, + bool* success, std::wstring* text); + + // Sets the visible text from the autocomplete edit. + void SetAutocompleteEditText(int autocomplete_edit_handle, + const std::wstring& text, + bool* success); + + // Retrieves if a query to an autocomplete provider is in progress. + void AutocompleteEditIsQueryInProgress(int autocomplete_edit_handle, + bool* success, + bool* query_in_progress); + + // Retrieves the individual autocomplete matches displayed by the popup. + void AutocompleteEditGetMatches(int autocomplete_edit_handle, + bool* success, + std::vector<AutocompleteMatchData>* matches); + + + // Retrieves the number of info-bars currently showing in |count|. + void GetInfoBarCount(int handle, int* count); + + // Causes a click on the "accept" button of the info-bar at |info_bar_index|. + // If |wait_for_navigation| is true, it sends the reply after a navigation has + // occurred. + void ClickInfoBarAccept(int handle, int info_bar_index, + bool wait_for_navigation, + IPC::Message* reply_message); + + // Retrieves the last time a navigation occurred for the tab. + void GetLastNavigationTime(int handle, int64* last_navigation_time); + + // Waits for a new navigation in the tab if none has happened since + // |last_navigation_time|. + void WaitForNavigation(int handle, + int64 last_navigation_time, + IPC::Message* reply_message); + + // Sets the int value for preference with name |name|. + void SetIntPreference(int handle, + const std::wstring& name, + int value, + bool* success); + + // Sets the string value for preference with name |name|. + void SetStringPreference(int handle, + const std::wstring& name, + const std::string& value, + bool* success); + + // Gets the bool value for preference with name |name|. + void GetBooleanPreference(int handle, + const std::wstring& name, + bool* success, + bool* value); + + // Sets the bool value for preference with name |name|. + void SetBooleanPreference(int handle, + const std::wstring& name, + bool value, + bool* success); + + // Resets to the default theme. + void ResetToDefaultTheme(); + + // Gets the current used encoding name of the page in the specified tab. + void GetPageCurrentEncoding(int tab_handle, std::string* current_encoding); + + // Uses the specified encoding to override the encoding of the page in the + // specified tab. + void OverrideEncoding(int tab_handle, + const std::string& encoding_name, + bool* success); + + void SavePackageShouldPromptUser(bool should_prompt); + + // 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); + + // Returns the number of blocked popups in the tab |handle|. + void GetBlockedPopupCount(int handle, int* count); + + // Selects all contents on the page. + void SelectAll(int tab_handle); + + // Edit operations on the page. + void Cut(int tab_handle); + void Copy(int tab_handle); + void Paste(int tab_handle); + + void ReloadAsync(int tab_handle); + void StopAsync(int tab_handle); + void SaveAsAsync(int tab_handle); + + void WaitForBrowserWindowCountToBecome(int target_count, + IPC::Message* reply_message); + + void WaitForAppModalDialogToBeShown(IPC::Message* reply_message); + + void GoBackBlockUntilNavigationsComplete(int handle, + int number_of_navigations, + IPC::Message* reply_message); + + void GoForwardBlockUntilNavigationsComplete(int handle, + int number_of_navigations, + IPC::Message* reply_message); + + // Convert a tab handle into a TabContents. If |tab| is non-NULL a pointer + // to the tab is also returned. Returns NULL in case of failure or if the tab + // is not of the TabContents type. + TabContents* GetTabContentsForHandle(int handle, NavigationController** tab); + +#if defined(OS_CHROMEOS) + // Logs in through the Chrome OS Login Wizard with given |username| and + // password. Returns true via |reply_message| on success. + void LoginWithUserAndPass(const std::string& username, + const std::string& password, + IPC::Message* reply_message); +#endif + + // Callback for history redirect queries. + virtual void OnRedirectQueryComplete( + HistoryService::Handle request_handle, + GURL from_url, + bool success, + history::RedirectList* redirects); + + // Returns the associated view for the tab handle passed in. + // 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, + int previous_view_id, + IPC::Message* reply_message); + + // Start tracking popup menus. Must be called before executing the + // command that might open the popup menu; then call WaitForPopupMenuToOpen. + void StartTrackingPopupMenus(int browser_handle, bool* success); + + // Wait until a popup menu has opened. + void WaitForPopupMenuToOpen(IPC::Message* reply_message); + + // Method called by the popup menu tracker when a popup menu is opened. + void NotifyPopupMenuOpened(); + +#if defined(OS_WIN) + // The functions in this block are for use with external tabs, so they are + // Windows only. + + // The container of an externally hosted tab calls this to reflect any + // accelerator keys that it did not process. This gives the tab a chance + // to handle the keys + void ProcessUnhandledAccelerator(const IPC::Message& message, int handle, + const MSG& msg); + + void SetInitialFocus(const IPC::Message& message, int handle, bool reverse, + bool restore_focus_to_view); + + void OnTabReposition(int tab_handle, + const IPC::Reposition_Params& params); + + void OnForwardContextMenuCommandToChrome(int tab_handle, int command); + + void CreateExternalTab(const IPC::ExternalTabSettings& settings, + gfx::NativeWindow* tab_container_window, + gfx::NativeWindow* tab_window, + int* tab_handle); + + void ConnectExternalTab(uint64 cookie, + bool allow, + gfx::NativeWindow parent_window, + gfx::NativeWindow* tab_container_window, + gfx::NativeWindow* tab_window, + int* tab_handle); + + void NavigateInExternalTab( + int handle, const GURL& url, const GURL& referrer, + AutomationMsg_NavigationResponseValues* status); + void NavigateExternalTabAtIndex( + int handle, int index, AutomationMsg_NavigationResponseValues* status); + + // Handler for a message sent by the automation client. + void OnMessageFromExternalHost(int handle, const std::string& message, + const std::string& origin, + const std::string& target); + + // Determine if the message from the external host represents a browser + // event, and if so dispatch it. + bool InterceptBrowserEventMessageFromExternalHost(const std::string& message, + const std::string& origin, + const std::string& target); + + void OnBrowserMoved(int handle); + + void OnRunUnloadHandlers(int handle, gfx::NativeWindow notification_window, + int notification_message); + + ExternalTabContainer* GetExternalTabForHandle(int handle); +#endif // defined(OS_WIN) + + typedef ObserverList<NotificationObserver> NotificationObserverList; + typedef std::map<NavigationController*, LoginHandler*> LoginHandlerMap; + typedef std::map<int, ExtensionPortContainer*> PortContainerMap; + + scoped_ptr<IPC::ChannelProxy> channel_; + scoped_ptr<InitialLoadObserver> initial_load_observer_; + scoped_ptr<NotificationObserver> new_tab_ui_load_observer_; + 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_; + scoped_ptr<NavigationControllerRestoredObserver> restore_tracker_; + LoginHandlerMap login_handler_map_; + PortContainerMap port_containers_; + NotificationObserverList notification_observer_list_; + scoped_refptr<AutomationResourceMessageFilter> + automation_resource_message_filter_; + + // Handle for an in-process redirect query. We expect only one redirect query + // at a time (we should have only one caller, and it will block while waiting + // for the results) so there is only one handle. When non-0, indicates a + // query in progress. + HistoryService::Handle redirect_query_; + + // Consumer for asynchronous history queries. + CancelableRequestConsumer consumer_; + + Profile* profile_; + + IPC::Message* reply_message_; + + // Keep track of whether a popup menu has been opened since the last time + // that StartTrackingPopupMenus has been called. + bool popup_menu_opened_; + + // A temporary object that receives a notification when a popup menu opens. + PopupMenuWaiter* popup_menu_waiter_; + + DISALLOW_COPY_AND_ASSIGN(AutomationProvider); +}; + +// When life started, the AutomationProvider class was a singleton and was meant +// only for UI tests. It had specific behavior (like for example, when the +// channel was shut down. it closed all open Browsers). The new +// AutomationProvider serves other purposes than just UI testing. This class is +// meant to provide the OLD functionality for backward compatibility +class TestingAutomationProvider : public AutomationProvider, + public BrowserList::Observer, + public NotificationObserver { + public: + explicit TestingAutomationProvider(Profile* profile); + + // BrowserList::Observer implementation + // Called immediately after a browser is added to the list + virtual void OnBrowserAdded(const Browser* browser); + // Called immediately before a browser is removed from the list + virtual void OnBrowserRemoving(const Browser* browser); + + // IPC implementations + virtual void OnChannelError(); + + private: + virtual ~TestingAutomationProvider(); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + void OnRemoveProvider(); // Called via PostTask + + NotificationRegistrar registrar_; +}; + +#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_PROVIDER_H_ diff --git a/chrome/browser/automation/automation_provider_chromeos.cc b/chrome/browser/automation/automation_provider_chromeos.cc new file mode 100644 index 0000000..6e1cc2b --- /dev/null +++ b/chrome/browser/automation/automation_provider_chromeos.cc @@ -0,0 +1,27 @@ +// 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_provider.h" + +#include "chrome/browser/automation/automation_provider_observers.h" +#include "chrome/browser/chromeos/login/login_screen.h" +#include "chrome/browser/chromeos/login/user_manager.h" +#include "chrome/browser/chromeos/login/wizard_controller.h" +#include "views/window/window_gtk.h" + +void AutomationProvider::LoginWithUserAndPass(const std::string& username, + const std::string& password, + IPC::Message* reply_message) { + WizardController* controller = WizardController::default_controller(); + chromeos::NewUserView* new_user_view = + controller->GetLoginScreen()->view(); + + new_user_view->SetUsername(username); + new_user_view->SetPassword(password); + + // Set up an observer (it will delete itself). + new LoginManagerObserver(this, reply_message); + + new_user_view->Login(); +} diff --git a/chrome/browser/automation/automation_provider_gtk.cc b/chrome/browser/automation/automation_provider_gtk.cc new file mode 100644 index 0000000..8b391ee --- /dev/null +++ b/chrome/browser/automation/automation_provider_gtk.cc @@ -0,0 +1,221 @@ +// 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/browser/automation/automation_provider.h" + +#include <gtk/gtk.h> + +#include "chrome/browser/automation/ui_controls.h" +#include "chrome/browser/automation/automation_browser_tracker.h" +#include "chrome/browser/automation/automation_window_tracker.h" +#include "chrome/browser/gtk/browser_window_gtk.h" +#include "chrome/browser/gtk/gtk_util.h" +#include "chrome/browser/gtk/view_id_util.h" +#include "chrome/test/automation/automation_messages.h" +#include "gfx/point.h" +#include "gfx/rect.h" + +void AutomationProvider::SetWindowBounds(int handle, const gfx::Rect& bounds, + bool* success) { + *success = false; + GtkWindow* window = window_tracker_->GetResource(handle); + if (window) { + gtk_window_move(window, bounds.x(), bounds.height()); + gtk_window_resize(window, bounds.width(), bounds.height()); + *success = true; + } +} + +void AutomationProvider::SetWindowVisible(int handle, bool visible, + bool* result) { + *result = false; + GtkWindow* window = window_tracker_->GetResource(handle); + if (window) { + if (visible) { + gtk_window_present(window); + } else { + gtk_widget_hide(GTK_WIDGET(window)); + } + *result = true; + } +} + +#if !defined(TOOLKIT_VIEWS) +void AutomationProvider::WindowGetViewBounds(int handle, int view_id, + bool screen_coordinates, + bool* success, + gfx::Rect* bounds) { + *success = false; + + GtkWindow* window = window_tracker_->GetResource(handle); + if (window) { + GtkWidget* widget = ViewIDUtil::GetWidget(GTK_WIDGET(window), + static_cast<ViewID>(view_id)); + if (!widget) + return; + *success = true; + *bounds = gfx::Rect(widget->allocation.width, widget->allocation.height); + gint x, y; + if (screen_coordinates) { + gfx::Point point = gtk_util::GetWidgetScreenPosition(widget); + x = point.x(); + y = point.y(); + } else { + gtk_widget_translate_coordinates(widget, GTK_WIDGET(window), + 0, 0, &x, &y); + } + bounds->set_origin(gfx::Point(x, y)); + } +} +#endif + +void AutomationProvider::ActivateWindow(int handle) { + NOTIMPLEMENTED(); +} + +void AutomationProvider::IsWindowMaximized(int handle, bool* is_maximized, + bool* success) { + *success = false; + NOTIMPLEMENTED(); +} + +void AutomationProvider::PrintAsync(int tab_handle) { + NOTIMPLEMENTED(); +} + +// This task sends a WindowDragResponse message with the appropriate +// routing ID to the automation proxy. This is implemented as a task so that +// we know that the mouse events (and any tasks that they spawn on the message +// loop) have been processed by the time this is sent. +class WindowDragResponseTask : public Task { + public: + WindowDragResponseTask(AutomationProvider* provider, + IPC::Message* reply_message) + : provider_(provider), + reply_message_(reply_message) { + DCHECK(provider_); + DCHECK(reply_message_); + } + + virtual ~WindowDragResponseTask() { + } + + virtual void Run() { + AutomationMsg_WindowDrag::WriteReplyParams(reply_message_, true); + provider_->Send(reply_message_); + } + + private: + AutomationProvider* provider_; + IPC::Message* reply_message_; + + DISALLOW_COPY_AND_ASSIGN(WindowDragResponseTask); +}; + +// A task that just runs a SendMouseEvent and performs another task when done. +class MouseEventTask : public Task { + public: + MouseEventTask(Task* next_task, ui_controls::MouseButtonState state) + : next_task_(next_task), + state_(state) {} + + virtual ~MouseEventTask() { + } + + virtual void Run() { + ui_controls::SendMouseEventsNotifyWhenDone(ui_controls::LEFT, state_, + next_task_); + } + + private: + // The task to execute when we are done. + Task* next_task_; + + // Mouse press or mouse release. + ui_controls::MouseButtonState state_; + + DISALLOW_COPY_AND_ASSIGN(MouseEventTask); +}; + +// A task that just runs a SendMouseMove and performs another task when done. +class MouseMoveTask : public Task { + public: + MouseMoveTask(Task* next_task, int absolute_x, int absolute_y) + : next_task_(next_task), + x_(absolute_x), + y_(absolute_y) { + } + + virtual ~MouseMoveTask() { + } + + virtual void Run() { + ui_controls::SendMouseMoveNotifyWhenDone(x_, y_, next_task_); + } + + private: + // The task to execute when we are done. + Task* next_task_; + + // Coordinates of the press. + int x_; + int y_; + + DISALLOW_COPY_AND_ASSIGN(MouseMoveTask); +}; + +void AutomationProvider::WindowSimulateDrag(int handle, + std::vector<gfx::Point> drag_path, + int flags, + bool press_escape_en_route, + IPC::Message* reply_message) { + // TODO(estade): don't ignore |flags| or |escape_en_route|. + gfx::NativeWindow window = + browser_tracker_->GetResource(handle)->window()->GetNativeHandle(); + if (window && (drag_path.size() > 1)) { + int x, y; + gdk_window_get_position(GTK_WIDGET(window)->window, &x, &y); + + // Create a nested stack of tasks to run. + Task* next_task = new WindowDragResponseTask(this, reply_message); + next_task = new MouseEventTask(next_task, ui_controls::UP); + next_task = new MouseEventTask(next_task, ui_controls::UP); + for (size_t i = drag_path.size() - 1; i > 0; --i) { + // Smooth out the mouse movements by adding intermediate points. This + // better simulates a real user drag. + int dest_x = drag_path[i].x() + x; + int dest_y = drag_path[i].y() + y; + int half_step_x = (dest_x + drag_path[i - 1].x() + x) / 2; + int half_step_y = (dest_y + drag_path[i - 1].y() + y) / 2; + + next_task = new MouseMoveTask(next_task, dest_x, dest_y); + next_task = new MouseMoveTask(next_task, half_step_x, half_step_y); + } + next_task = new MouseEventTask(next_task, ui_controls::DOWN); + + ui_controls::SendMouseMoveNotifyWhenDone(x + drag_path[0].x(), + y + drag_path[0].y(), + next_task); + } else { + AutomationMsg_WindowDrag::WriteReplyParams(reply_message, false); + Send(reply_message); + } +} + +void AutomationProvider::TerminateSession(int handle, bool* success) { + *success = false; + NOTIMPLEMENTED(); +} + +void AutomationProvider::GetWindowBounds(int handle, gfx::Rect* bounds, + bool* result) { + *result = false; + NOTIMPLEMENTED(); +} + +void AutomationProvider::GetWindowTitle(int handle, string16* text) { + gfx::NativeWindow window = window_tracker_->GetResource(handle); + const gchar* title = gtk_window_get_title(window); + text->assign(UTF8ToUTF16(title)); +} diff --git a/chrome/browser/automation/automation_provider_json.cc b/chrome/browser/automation/automation_provider_json.cc new file mode 100644 index 0000000..05f3a1b --- /dev/null +++ b/chrome/browser/automation/automation_provider_json.cc @@ -0,0 +1,55 @@ +// 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_provider_json.h" + +#include "base/json/json_writer.h" +#include "base/json/string_escape.h" +#include "chrome/test/automation/automation_messages.h" + +namespace { + +// Util for creating a JSON error return string (dict with key +// 'error' and error string value). No need to quote input. +std::string JSONErrorString(const std::string& err) { + std::string prefix = "{\"error\": \""; + std::string no_quote_err; + std::string suffix = "\"}"; + + base::JsonDoubleQuote(err, false, &no_quote_err); + return prefix + no_quote_err + suffix; +} + +} // namespace + +AutomationJSONReply::AutomationJSONReply(AutomationProvider* provider, + IPC::Message* reply_message) + : provider_(provider), + message_(reply_message) { +} + +AutomationJSONReply::~AutomationJSONReply() { + DCHECK(!message_) << "JSON automation request not replied!"; +} + +void AutomationJSONReply::SendSuccess(const Value* value) { + DCHECK(message_) << "Resending reply for JSON automation request"; + std::string json_string = "{}"; + if (value) + base::JSONWriter::Write(value, false, &json_string); + AutomationMsg_SendJSONRequest::WriteReplyParams( + message_, json_string, true); + provider_->Send(message_); + message_ = NULL; +} + +void AutomationJSONReply::SendError(const std::string& error_message) { + DCHECK(message_) << "Resending reply for JSON automation request"; + std::string json_string = JSONErrorString(error_message); + AutomationMsg_SendJSONRequest::WriteReplyParams( + message_, json_string, false); + provider_->Send(message_); + message_ = NULL; +} + diff --git a/chrome/browser/automation/automation_provider_json.h b/chrome/browser/automation/automation_provider_json.h new file mode 100644 index 0000000..a203c58 --- /dev/null +++ b/chrome/browser/automation/automation_provider_json.h @@ -0,0 +1,40 @@ +// 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. + +// Support utilities for the JSON automation interface used by PyAuto. + +#ifndef CHROME_BROWSER_AUTOMATION_AUTOMATION_PROVIDER_JSON_H_ +#define CHROME_BROWSER_AUTOMATION_AUTOMATION_PROVIDER_JSON_H_ + +#include <string> + +#include "base/values.h" +#include "ipc/ipc_message.h" +#include "chrome/browser/automation/automation_provider.h" + +// Helper to ensure we always send a reply message for JSON automation requests. +class AutomationJSONReply { + public: + // Creates a new reply object for the IPC message |reply_message| for + // |provider|. The caller is expected to call SendSuccess() or SendError() + // before destroying this object. + AutomationJSONReply(AutomationProvider* provider, + IPC::Message* reply_message); + + ~AutomationJSONReply(); + + // Send a success reply along with data contained in |value|. + // An empty message will be sent if |value| is NULL. + void SendSuccess(const Value* value); + + // Send an error reply along with error message |error_message|. + void SendError(const std::string& error_message); + + private: + AutomationProvider* provider_; + IPC::Message* message_; +}; + +#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_PROVIDER_JSON_H_ + diff --git a/chrome/browser/automation/automation_provider_list.cc b/chrome/browser/automation/automation_provider_list.cc new file mode 100644 index 0000000..6676626 --- /dev/null +++ b/chrome/browser/automation/automation_provider_list.cc @@ -0,0 +1,52 @@ +// 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. + +#include "chrome/browser/automation/automation_provider_list.h" + +#include <algorithm> + +#include "base/logging.h" +#include "chrome/browser/automation/automation_provider.h" +#include "chrome/browser/browser_process.h" + +AutomationProviderList* AutomationProviderList::instance_ = NULL; + +AutomationProviderList::AutomationProviderList() { +} + +AutomationProviderList::~AutomationProviderList() { + iterator iter = automation_providers_.begin(); + while (iter != automation_providers_.end()) { + (*iter)->Release(); + iter = automation_providers_.erase(iter); + } + instance_ = NULL; +} + +bool AutomationProviderList::AddProvider(AutomationProvider* provider) { + provider->AddRef(); + automation_providers_.push_back(provider); + return true; +} + +bool AutomationProviderList::RemoveProvider(AutomationProvider* provider) { + const iterator remove_provider = + find(automation_providers_.begin(), automation_providers_.end(), provider); + if (remove_provider != automation_providers_.end()) { + (*remove_provider)->Release(); + automation_providers_.erase(remove_provider); + if (automation_providers_.empty()) + OnLastProviderRemoved(); + return true; + } + return false; +} + +AutomationProviderList* AutomationProviderList::GetInstance() { + if (!instance_) { + instance_ = new AutomationProviderList; + } + DCHECK(NULL != instance_); + return instance_; +} diff --git a/chrome/browser/automation/automation_provider_list.h b/chrome/browser/automation/automation_provider_list.h new file mode 100644 index 0000000..058dc34 --- /dev/null +++ b/chrome/browser/automation/automation_provider_list.h @@ -0,0 +1,48 @@ +// 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_BROWSER_AUTOMATION_AUTOMATION_PROVIDER_LIST_H_ +#define CHROME_BROWSER_AUTOMATION_AUTOMATION_PROVIDER_LIST_H_ + +#include <vector> +#include "base/basictypes.h" + +class AutomationProvider; + +// Stores a list of all AutomationProvider objects. +class AutomationProviderList { + public: + ~AutomationProviderList(); + typedef std::vector<AutomationProvider*> list_type; + typedef list_type::iterator iterator; + typedef list_type::const_iterator const_iterator; + + // Adds and removes automation providers from the global list. + bool AddProvider(AutomationProvider* provider); + bool RemoveProvider(AutomationProvider* provider); + + const_iterator begin() { + return automation_providers_.begin(); + } + + const_iterator end() { + return automation_providers_.end(); + } + + size_t size() { + return automation_providers_.size(); + } + + static AutomationProviderList* GetInstance(); + + private: + AutomationProviderList(); + void OnLastProviderRemoved(); + list_type automation_providers_; + static AutomationProviderList* instance_; + + DISALLOW_COPY_AND_ASSIGN(AutomationProviderList); +}; + +#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_PROVIDER_LIST_H_ diff --git a/chrome/browser/automation/automation_provider_list_generic.cc b/chrome/browser/automation/automation_provider_list_generic.cc new file mode 100644 index 0000000..868d509 --- /dev/null +++ b/chrome/browser/automation/automation_provider_list_generic.cc @@ -0,0 +1,8 @@ +// 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/browser/automation/automation_provider_list.h" + +void AutomationProviderList::OnLastProviderRemoved() { +} diff --git a/chrome/browser/automation/automation_provider_list_mac.mm b/chrome/browser/automation/automation_provider_list_mac.mm new file mode 100644 index 0000000..2cfdb3f --- /dev/null +++ b/chrome/browser/automation/automation_provider_list_mac.mm @@ -0,0 +1,13 @@ +// 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/browser/automation/automation_provider_list.h" + +#import <AppKit/AppKit.h> + +void AutomationProviderList::OnLastProviderRemoved() { + // We need to explicitly quit the application here because on Mac + // the controller holds an additional reference to g_browser_process. + [NSApp terminate:nil]; +} diff --git a/chrome/browser/automation/automation_provider_mac.mm b/chrome/browser/automation/automation_provider_mac.mm new file mode 100644 index 0000000..ed55d2d --- /dev/null +++ b/chrome/browser/automation/automation_provider_mac.mm @@ -0,0 +1,114 @@ +// 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/browser/automation/automation_provider.h" + +#import <Cocoa/Cocoa.h> + +#include "app/l10n_util.h" +#include "app/l10n_util_mac.h" +#include "base/sys_string_conversions.h" +#include "chrome/browser/automation/automation_browser_tracker.h" +#include "chrome/browser/automation/automation_window_tracker.h" +#include "chrome/browser/cocoa/tab_window_controller.h" +#include "chrome/test/automation/automation_messages.h" +#include "gfx/point.h" +#include "gfx/rect.h" +#include "grit/generated_resources.h" + +void AutomationProvider::SetWindowBounds(int handle, const gfx::Rect& bounds, + bool* success) { + *success = false; + NSWindow* window = window_tracker_->GetResource(handle); + if (window) { + NSRect new_bounds = NSRectFromCGRect(bounds.ToCGRect()); + + if ([[NSScreen screens] count] > 0) { + new_bounds.origin.y = + [[[NSScreen screens] objectAtIndex:0] frame].size.height - + new_bounds.origin.y - new_bounds.size.height; + } + + [window setFrame:new_bounds display:NO]; + *success = true; + } +} + +void AutomationProvider::SetWindowVisible(int handle, bool visible, + bool* result) { + *result = false; + NSWindow* window = window_tracker_->GetResource(handle); + if (window) { + if (visible) { + [window orderFront:nil]; + } else { + [window orderOut:nil]; + } + *result = true; + } +} + +void AutomationProvider::WindowGetViewBounds(int handle, int view_id, + bool screen_coordinates, + bool* success, + gfx::Rect* bounds) { + // AutomationProxyVisibleTest claims that this is used only by Chrome Views + // which we don't use on the Mac. Is this true? + + *success = false; + NOTIMPLEMENTED(); +} + +void AutomationProvider::ActivateWindow(int handle) { NOTIMPLEMENTED(); } + +void AutomationProvider::IsWindowMaximized(int handle, bool* is_maximized, + bool* success) { + *success = false; + NOTIMPLEMENTED(); +} + +void AutomationProvider::PrintAsync(int tab_handle) { + NOTIMPLEMENTED(); +} + +void AutomationProvider::WindowSimulateDrag(int handle, + std::vector<gfx::Point> drag_path, + int flags, + bool press_escape_en_route, + IPC::Message* reply_message) { + NOTIMPLEMENTED(); + AutomationMsg_WindowDrag::WriteReplyParams(reply_message, false); + Send(reply_message); +} + +void AutomationProvider::TerminateSession(int handle, bool* success) { + *success = false; + NOTIMPLEMENTED(); +} + +void AutomationProvider::GetWindowBounds(int handle, gfx::Rect* bounds, + bool* result) { + *result = false; + NOTIMPLEMENTED(); +} + +void AutomationProvider::GetWindowTitle(int handle, string16* text) { + gfx::NativeWindow window = window_tracker_->GetResource(handle); + NSString* title = nil; + if ([[window delegate] isKindOfClass:[TabWindowController class]]) { + TabWindowController* delegate = + reinterpret_cast<TabWindowController*>([window delegate]); + title = [delegate selectedTabTitle]; + } else { + title = [window title]; + } + // If we don't yet have a title, use "Untitled". + if (![title length]) { + text->assign(WideToUTF16(l10n_util::GetString( + IDS_BROWSER_WINDOW_MAC_TAB_UNTITLED))); + return; + } + + text->assign(base::SysNSStringToUTF16(title)); +} diff --git a/chrome/browser/automation/automation_provider_observers.cc b/chrome/browser/automation/automation_provider_observers.cc new file mode 100644 index 0000000..f731309 --- /dev/null +++ b/chrome/browser/automation/automation_provider_observers.cc @@ -0,0 +1,1134 @@ +// 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_provider_observers.h" + +#include "base/basictypes.h" +#include "base/json/json_writer.h" +#include "base/string_util.h" +#include "chrome/app/chrome_dll_resource.h" +#include "chrome/browser/automation/automation_provider.h" +#include "chrome/browser/automation/automation_provider_json.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/dom_operation_notification_details.h" +#include "chrome/browser/download/download_item.h" +#include "chrome/browser/download/save_package.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/printing/print_job.h" +#include "chrome/browser/profile.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_CHROMEOS) +#include "chrome/browser/chromeos/login/authentication_notification_details.h" +#endif + +// Holds onto start and stop timestamps for a particular tab +class InitialLoadObserver::TabTime { + public: + explicit TabTime(base::TimeTicks started) + : load_start_time_(started) { + } + void set_stop_time(base::TimeTicks stopped) { + load_stop_time_ = stopped; + } + base::TimeTicks stop_time() const { + return load_stop_time_; + } + base::TimeTicks start_time() const { + return load_start_time_; + } + private: + base::TimeTicks load_start_time_; + base::TimeTicks load_stop_time_; +}; + +InitialLoadObserver::InitialLoadObserver(size_t tab_count, + AutomationProvider* automation) + : automation_(automation), + outstanding_tab_count_(tab_count), + init_time_(base::TimeTicks::Now()) { + if (outstanding_tab_count_ > 0) { + registrar_.Add(this, NotificationType::LOAD_START, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::LOAD_STOP, + NotificationService::AllSources()); + } +} + +InitialLoadObserver::~InitialLoadObserver() { +} + +void InitialLoadObserver::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::LOAD_START) { + if (outstanding_tab_count_ > loading_tabs_.size()) + loading_tabs_.insert(TabTimeMap::value_type( + source.map_key(), + TabTime(base::TimeTicks::Now()))); + } else if (type == NotificationType::LOAD_STOP) { + if (outstanding_tab_count_ > finished_tabs_.size()) { + TabTimeMap::iterator iter = loading_tabs_.find(source.map_key()); + if (iter != loading_tabs_.end()) { + finished_tabs_.insert(source.map_key()); + iter->second.set_stop_time(base::TimeTicks::Now()); + } + if (outstanding_tab_count_ == finished_tabs_.size()) + ConditionMet(); + } + } else { + NOTREACHED(); + } +} + +DictionaryValue* InitialLoadObserver::GetTimingInformation() const { + ListValue* items = new ListValue; + for (TabTimeMap::const_iterator it = loading_tabs_.begin(); + it != loading_tabs_.end(); + ++it) { + DictionaryValue* item = new DictionaryValue; + base::TimeDelta delta_start = it->second.start_time() - init_time_; + + item->SetReal(L"load_start_ms", delta_start.InMillisecondsF()); + if (it->second.stop_time().is_null()) { + item->Set(L"load_stop_ms", Value::CreateNullValue()); + } else { + base::TimeDelta delta_stop = it->second.stop_time() - init_time_; + item->SetReal(L"load_stop_ms", delta_stop.InMillisecondsF()); + } + items->Append(item); + } + DictionaryValue* return_value = new DictionaryValue; + return_value->Set(L"tabs", items); + return return_value; +} + +void InitialLoadObserver::ConditionMet() { + registrar_.RemoveAll(); + automation_->Send(new AutomationMsg_InitialLoadsComplete(0)); +} + +NewTabUILoadObserver::NewTabUILoadObserver(AutomationProvider* automation) + : automation_(automation) { + registrar_.Add(this, NotificationType::INITIAL_NEW_TAB_UI_LOAD, + NotificationService::AllSources()); +} + +NewTabUILoadObserver::~NewTabUILoadObserver() { +} + +void NewTabUILoadObserver::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::INITIAL_NEW_TAB_UI_LOAD) { + Details<int> load_time(details); + automation_->Send( + new AutomationMsg_InitialNewTabUILoadComplete(0, *load_time.ptr())); + } else { + NOTREACHED(); + } +} + +NavigationControllerRestoredObserver::NavigationControllerRestoredObserver( + AutomationProvider* automation, + NavigationController* controller, + IPC::Message* reply_message) + : automation_(automation), + controller_(controller), + reply_message_(reply_message) { + if (FinishedRestoring()) { + SendDone(); + } else { + registrar_.Add(this, NotificationType::LOAD_STOP, + NotificationService::AllSources()); + } +} + +NavigationControllerRestoredObserver::~NavigationControllerRestoredObserver() { +} + +void NavigationControllerRestoredObserver::Observe( + NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + if (FinishedRestoring()) { + SendDone(); + registrar_.RemoveAll(); + } +} + +bool NavigationControllerRestoredObserver::FinishedRestoring() { + return (!controller_->needs_reload() && !controller_->pending_entry() && + !controller_->tab_contents()->is_loading()); +} + +void NavigationControllerRestoredObserver::SendDone() { + DCHECK(reply_message_ != NULL); + automation_->Send(reply_message_); +} + +NavigationNotificationObserver::NavigationNotificationObserver( + NavigationController* controller, + AutomationProvider* automation, + IPC::Message* reply_message, + int number_of_navigations, + bool include_current_navigation) + : automation_(automation), + reply_message_(reply_message), + controller_(controller), + navigations_remaining_(number_of_navigations), + navigation_started_(false) { + DCHECK_LT(0, navigations_remaining_); + Source<NavigationController> source(controller_); + registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, source); + registrar_.Add(this, NotificationType::LOAD_START, source); + registrar_.Add(this, NotificationType::LOAD_STOP, source); + registrar_.Add(this, NotificationType::AUTH_NEEDED, source); + registrar_.Add(this, NotificationType::AUTH_SUPPLIED, source); + registrar_.Add(this, NotificationType::AUTH_CANCELLED, source); + + if (include_current_navigation && controller->tab_contents()->is_loading()) + navigation_started_ = true; +} + +NavigationNotificationObserver::~NavigationNotificationObserver() { + if (reply_message_) { + // This means we did not receive a notification for this navigation. + // Send over a failed navigation status back to the caller to ensure that + // the caller does not hang waiting for the response. + IPC::ParamTraits<AutomationMsg_NavigationResponseValues>::Write( + reply_message_, AUTOMATION_MSG_NAVIGATION_ERROR); + automation_->Send(reply_message_); + reply_message_ = NULL; + } + + automation_->RemoveNavigationStatusListener(this); +} + +void NavigationNotificationObserver::Observe( + NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + // We listen for 2 events to determine when the navigation started because: + // - when this is used by the WaitForNavigation method, we might be invoked + // afer the load has started (but not after the entry was committed, as + // WaitForNavigation compares times of the last navigation). + // - when this is used with a page requiring authentication, we will not get + // a NotificationType::NAV_ENTRY_COMMITTED until after we authenticate, so + // we need the NotificationType::LOAD_START. + if (type == NotificationType::NAV_ENTRY_COMMITTED || + type == NotificationType::LOAD_START) { + navigation_started_ = true; + } else if (type == NotificationType::LOAD_STOP) { + if (navigation_started_) { + navigation_started_ = false; + if (--navigations_remaining_ == 0) + ConditionMet(AUTOMATION_MSG_NAVIGATION_SUCCESS); + } + } else if (type == NotificationType::AUTH_SUPPLIED || + type == NotificationType::AUTH_CANCELLED) { + // The LoginHandler for this tab is no longer valid. + automation_->RemoveLoginHandler(controller_); + + // Treat this as if navigation started again, since load start/stop don't + // occur while authentication is ongoing. + navigation_started_ = true; + } else if (type == NotificationType::AUTH_NEEDED) { + // Remember the login handler that wants authentication. + // We do this in all cases (not just when navigation_started_ == true) so + // tests can still wait for auth dialogs outside of navigation. + LoginHandler* handler = + Details<LoginNotificationDetails>(details)->handler(); + automation_->AddLoginHandler(controller_, handler); + + // Respond that authentication is needed. + navigation_started_ = false; + ConditionMet(AUTOMATION_MSG_NAVIGATION_AUTH_NEEDED); + } else { + NOTREACHED(); + } +} + +void NavigationNotificationObserver::ConditionMet( + AutomationMsg_NavigationResponseValues navigation_result) { + DCHECK(reply_message_ != NULL); + + IPC::ParamTraits<AutomationMsg_NavigationResponseValues>::Write( + reply_message_, navigation_result); + automation_->Send(reply_message_); + reply_message_ = NULL; + + delete this; +} + +TabStripNotificationObserver::TabStripNotificationObserver( + NotificationType notification, AutomationProvider* automation) + : automation_(automation), + notification_(notification) { + registrar_.Add(this, notification_, NotificationService::AllSources()); +} + +TabStripNotificationObserver::~TabStripNotificationObserver() { +} + +void TabStripNotificationObserver::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == notification_) { + ObserveTab(Source<NavigationController>(source).ptr()); + + // If verified, no need to observe anymore + automation_->RemoveTabStripObserver(this); + delete this; + } else { + NOTREACHED(); + } +} + +TabAppendedNotificationObserver::TabAppendedNotificationObserver( + Browser* parent, AutomationProvider* automation, + IPC::Message* reply_message) + : TabStripNotificationObserver(NotificationType::TAB_PARENTED, automation), + parent_(parent), + reply_message_(reply_message) { +} + +void TabAppendedNotificationObserver::ObserveTab( + NavigationController* controller) { + if (automation_->GetIndexForNavigationController(controller, parent_) == + TabStripModel::kNoTab) { + // This tab notification doesn't belong to the parent_. + return; + } + + automation_->AddNavigationStatusListener(controller, reply_message_, 1, + false); +} + +TabClosedNotificationObserver::TabClosedNotificationObserver( + AutomationProvider* automation, bool wait_until_closed, + IPC::Message* reply_message) + : TabStripNotificationObserver(wait_until_closed ? + NotificationType::TAB_CLOSED : NotificationType::TAB_CLOSING, + automation), + reply_message_(reply_message), + for_browser_command_(false) { +} + +void TabClosedNotificationObserver::ObserveTab( + NavigationController* controller) { + if (for_browser_command_) { + AutomationMsg_WindowExecuteCommand::WriteReplyParams(reply_message_, + true); + } else { + AutomationMsg_CloseTab::WriteReplyParams(reply_message_, true); + } + automation_->Send(reply_message_); +} + +void TabClosedNotificationObserver::set_for_browser_command( + bool for_browser_command) { + for_browser_command_ = for_browser_command; +} + +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), + reply_message_(reply_message) { + registrar_.Add(this, NotificationType::EXTENSION_LOADED, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::EXTENSION_INSTALL_ERROR, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::EXTENSION_UPDATE_DISABLED, + NotificationService::AllSources()); +} + +ExtensionInstallNotificationObserver::~ExtensionInstallNotificationObserver() { +} + +void ExtensionInstallNotificationObserver::Observe( + NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + switch (type.value) { + case NotificationType::EXTENSION_LOADED: + SendResponse(AUTOMATION_MSG_EXTENSION_INSTALL_SUCCEEDED); + break; + case NotificationType::EXTENSION_INSTALL_ERROR: + case NotificationType::EXTENSION_UPDATE_DISABLED: + SendResponse(AUTOMATION_MSG_EXTENSION_INSTALL_FAILED); + break; + default: + NOTREACHED(); + break; + } + + delete this; +} + +void ExtensionInstallNotificationObserver::SendResponse( + AutomationMsg_ExtensionResponseValues response) { + if (reply_message_ != NULL) { + switch (id_) { + case AutomationMsg_InstallExtension::ID: + AutomationMsg_InstallExtension::WriteReplyParams(reply_message_, + response); + break; + case AutomationMsg_LoadExpandedExtension::ID: + AutomationMsg_LoadExpandedExtension::WriteReplyParams(reply_message_, + response); + break; + default: + NOTREACHED(); + break; + } + + automation_->Send(reply_message_); + reply_message_ = NULL; + } +} + +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_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: + 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), + reply_message_(reply_message), + for_browser_command_(false) { + registrar_.Add(this, NotificationType::BROWSER_OPENED, + NotificationService::AllSources()); +} + +BrowserOpenedNotificationObserver::~BrowserOpenedNotificationObserver() { +} + +void BrowserOpenedNotificationObserver::Observe( + NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::BROWSER_OPENED) { + if (for_browser_command_) { + AutomationMsg_WindowExecuteCommand::WriteReplyParams(reply_message_, + true); + } + automation_->Send(reply_message_); + delete this; + } else { + NOTREACHED(); + } +} + +void BrowserOpenedNotificationObserver::set_for_browser_command( + bool for_browser_command) { + for_browser_command_ = for_browser_command; +} + +BrowserClosedNotificationObserver::BrowserClosedNotificationObserver( + Browser* browser, + AutomationProvider* automation, + IPC::Message* reply_message) + : automation_(automation), + reply_message_(reply_message), + for_browser_command_(false) { + registrar_.Add(this, NotificationType::BROWSER_CLOSED, + Source<Browser>(browser)); +} + +void BrowserClosedNotificationObserver::Observe( + NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NotificationType::BROWSER_CLOSED); + Details<bool> close_app(details); + DCHECK(reply_message_ != NULL); + if (for_browser_command_) { + AutomationMsg_WindowExecuteCommand::WriteReplyParams(reply_message_, + true); + } else { + AutomationMsg_CloseBrowser::WriteReplyParams(reply_message_, true, + *(close_app.ptr())); + } + automation_->Send(reply_message_); + reply_message_ = NULL; + delete this; +} + +void BrowserClosedNotificationObserver::set_for_browser_command( + bool for_browser_command) { + for_browser_command_ = for_browser_command; +} + +BrowserCountChangeNotificationObserver::BrowserCountChangeNotificationObserver( + int target_count, + AutomationProvider* automation, + IPC::Message* reply_message) + : target_count_(target_count), + automation_(automation), + reply_message_(reply_message) { + registrar_.Add(this, NotificationType::BROWSER_OPENED, + NotificationService::AllSources()); + registrar_.Add(this, NotificationType::BROWSER_CLOSED, + NotificationService::AllSources()); +} + +void BrowserCountChangeNotificationObserver::Observe( + NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NotificationType::BROWSER_OPENED || + type == NotificationType::BROWSER_CLOSED); + int current_count = static_cast<int>(BrowserList::size()); + if (type == NotificationType::BROWSER_CLOSED) { + // At the time of the notification the browser being closed is not removed + // from the list. The real count is one less than the reported count. + DCHECK_LT(0, current_count); + current_count--; + } + if (current_count == target_count_) { + AutomationMsg_WaitForBrowserWindowCountToBecome::WriteReplyParams( + reply_message_, true); + automation_->Send(reply_message_); + reply_message_ = NULL; + delete this; + } +} + +AppModalDialogShownObserver::AppModalDialogShownObserver( + AutomationProvider* automation, IPC::Message* reply_message) + : automation_(automation), + reply_message_(reply_message) { + registrar_.Add(this, NotificationType::APP_MODAL_DIALOG_SHOWN, + NotificationService::AllSources()); +} + +AppModalDialogShownObserver::~AppModalDialogShownObserver() { +} + +void AppModalDialogShownObserver::Observe( + NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NotificationType::APP_MODAL_DIALOG_SHOWN); + + AutomationMsg_WaitForAppModalDialogToBeShown::WriteReplyParams( + reply_message_, true); + automation_->Send(reply_message_); + reply_message_ = NULL; + delete this; +} + +namespace { + +// Define mapping from command to notification +struct CommandNotification { + int command; + NotificationType::Type notification_type; +}; + +const struct CommandNotification command_notifications[] = { + {IDC_DUPLICATE_TAB, NotificationType::TAB_PARENTED}, + {IDC_NEW_TAB, NotificationType::INITIAL_NEW_TAB_UI_LOAD}, + + // Returns as soon as the restored tab is created. To further wait until + // the content page is loaded, use WaitForTabToBeRestored. + {IDC_RESTORE_TAB, NotificationType::TAB_PARENTED}, + + // For the following commands, we need to wait for a new tab to be created, + // load to finish, and title to change. + {IDC_MANAGE_EXTENSIONS, NotificationType::TAB_CONTENTS_TITLE_UPDATED}, + {IDC_OPTIONS, NotificationType::TAB_CONTENTS_TITLE_UPDATED}, + {IDC_SHOW_DOWNLOADS, NotificationType::TAB_CONTENTS_TITLE_UPDATED}, + {IDC_SHOW_HISTORY, NotificationType::TAB_CONTENTS_TITLE_UPDATED}, +}; + +} // namespace + +ExecuteBrowserCommandObserver::~ExecuteBrowserCommandObserver() { +} + +// static +bool ExecuteBrowserCommandObserver::CreateAndRegisterObserver( + AutomationProvider* automation, Browser* browser, int command, + IPC::Message* reply_message) { + bool result = true; + switch (command) { + case IDC_NEW_WINDOW: + case IDC_NEW_INCOGNITO_WINDOW: { + BrowserOpenedNotificationObserver* observer = + new BrowserOpenedNotificationObserver(automation, reply_message); + observer->set_for_browser_command(true); + break; + } + case IDC_CLOSE_WINDOW: { + BrowserClosedNotificationObserver* observer = + new BrowserClosedNotificationObserver(browser, automation, + reply_message); + observer->set_for_browser_command(true); + break; + } + case IDC_CLOSE_TAB: { + TabClosedNotificationObserver* observer = + new TabClosedNotificationObserver(automation, true, reply_message); + observer->set_for_browser_command(true); + break; + } + case IDC_BACK: + case IDC_FORWARD: + case IDC_RELOAD: { + automation->AddNavigationStatusListener( + &browser->GetSelectedTabContents()->controller(), + reply_message, 1, false); + break; + } + default: { + ExecuteBrowserCommandObserver* observer = + new ExecuteBrowserCommandObserver(automation, reply_message); + if (!observer->Register(command)) { + delete observer; + result = false; + } + break; + } + } + return result; +} + +void ExecuteBrowserCommandObserver::Observe( + NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + if (type == notification_type_) { + AutomationMsg_WindowExecuteCommand::WriteReplyParams(reply_message_, + true); + automation_->Send(reply_message_); + delete this; + } else { + NOTREACHED(); + } +} + +ExecuteBrowserCommandObserver::ExecuteBrowserCommandObserver( + AutomationProvider* automation, IPC::Message* reply_message) + : automation_(automation), + reply_message_(reply_message) { +} + +bool ExecuteBrowserCommandObserver::Register(int command) { + if (!GetNotificationType(command, ¬ification_type_)) + return false; + registrar_.Add(this, notification_type_, NotificationService::AllSources()); + return true; +} + +bool ExecuteBrowserCommandObserver::GetNotificationType( + int command, NotificationType::Type* type) { + if (!type) + return false; + bool found = false; + for (unsigned int i = 0; i < arraysize(command_notifications); i++) { + if (command_notifications[i].command == command) { + *type = command_notifications[i].notification_type; + found = true; + break; + } + } + return found; +} + +FindInPageNotificationObserver::FindInPageNotificationObserver( + AutomationProvider* automation, TabContents* parent_tab, + IPC::Message* reply_message) + : automation_(automation), + active_match_ordinal_(-1), + reply_message_(reply_message) { + registrar_.Add(this, NotificationType::FIND_RESULT_AVAILABLE, + Source<TabContents>(parent_tab)); +} + +FindInPageNotificationObserver::~FindInPageNotificationObserver() { +} + +void FindInPageNotificationObserver::Observe( + NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::FIND_RESULT_AVAILABLE) { + Details<FindNotificationDetails> find_details(details); + if (find_details->request_id() == kFindInPageRequestId) { + // We get multiple responses and one of those will contain the ordinal. + // This message comes to us before the final update is sent. + if (find_details->active_match_ordinal() > -1) + active_match_ordinal_ = find_details->active_match_ordinal(); + if (find_details->final_update()) { + if (reply_message_ != NULL) { + AutomationMsg_FindInPage::WriteReplyParams(reply_message_, + active_match_ordinal_, find_details->number_of_matches()); + automation_->Send(reply_message_); + reply_message_ = NULL; + } else { + DLOG(WARNING) << "Multiple final Find messages observed."; + } + } else { + DLOG(INFO) << "Ignoring, since we only care about the final message"; + } + } + } else { + NOTREACHED(); + } +} + +// static +const int FindInPageNotificationObserver::kFindInPageRequestId = -1; + +DomOperationNotificationObserver::DomOperationNotificationObserver( + AutomationProvider* automation) + : automation_(automation) { + registrar_.Add(this, NotificationType::DOM_OPERATION_RESPONSE, + NotificationService::AllSources()); +} + +DomOperationNotificationObserver::~DomOperationNotificationObserver() { +} + +void DomOperationNotificationObserver::Observe( + NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + if (NotificationType::DOM_OPERATION_RESPONSE == type) { + Details<DomOperationNotificationDetails> dom_op_details(details); + + IPC::Message* reply_message = automation_->reply_message_release(); + if (reply_message) { + AutomationMsg_DomOperation::WriteReplyParams(reply_message, + dom_op_details->json()); + automation_->Send(reply_message); + } + } +} + +DocumentPrintedNotificationObserver::DocumentPrintedNotificationObserver( + AutomationProvider* automation, IPC::Message* reply_message) + : automation_(automation), + success_(false), + reply_message_(reply_message) { + registrar_.Add(this, NotificationType::PRINT_JOB_EVENT, + NotificationService::AllSources()); +} + +DocumentPrintedNotificationObserver::~DocumentPrintedNotificationObserver() { + DCHECK(reply_message_ != NULL); + AutomationMsg_PrintNow::WriteReplyParams(reply_message_, success_); + automation_->Send(reply_message_); + automation_->RemoveNavigationStatusListener(this); +} + +void DocumentPrintedNotificationObserver::Observe( + NotificationType type, const NotificationSource& source, + const NotificationDetails& details) { + using namespace printing; + DCHECK(type == NotificationType::PRINT_JOB_EVENT); + switch (Details<JobEventDetails>(details)->type()) { + case JobEventDetails::JOB_DONE: { + // Succeeded. + success_ = true; + delete this; + break; + } + case JobEventDetails::USER_INIT_CANCELED: + case JobEventDetails::FAILED: { + // Failed. + delete this; + break; + } + case JobEventDetails::NEW_DOC: + case JobEventDetails::USER_INIT_DONE: + case JobEventDetails::DEFAULT_INIT_DONE: + case JobEventDetails::NEW_PAGE: + case JobEventDetails::PAGE_DONE: + case JobEventDetails::DOC_DONE: + case JobEventDetails::ALL_PAGES_REQUESTED: { + // Don't care. + break; + } + default: { + NOTREACHED(); + break; + } + } +} + +MetricEventDurationObserver::MetricEventDurationObserver() { + registrar_.Add(this, NotificationType::METRIC_EVENT_DURATION, + NotificationService::AllSources()); +} + +int MetricEventDurationObserver::GetEventDurationMs( + const std::string& event_name) { + EventDurationMap::const_iterator it = durations_.find(event_name); + if (it == durations_.end()) + return -1; + return it->second; +} + +void MetricEventDurationObserver::Observe(NotificationType type, + const NotificationSource& source, const NotificationDetails& details) { + if (type != NotificationType::METRIC_EVENT_DURATION) { + NOTREACHED(); + return; + } + MetricEventDurationDetails* metric_event_duration = + Details<MetricEventDurationDetails>(details).ptr(); + durations_[metric_event_duration->event_name] = + metric_event_duration->duration_ms; +} + +#if defined(OS_CHROMEOS) +LoginManagerObserver::LoginManagerObserver( + AutomationProvider* automation, + IPC::Message* reply_message) + : automation_(automation), + reply_message_(reply_message) { + + registrar_.Add(this, NotificationType::LOGIN_AUTHENTICATION, + NotificationService::AllSources()); +} + +void LoginManagerObserver::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NotificationType::LOGIN_AUTHENTICATION); + Details<AuthenticationNotificationDetails> auth_details(details); + AutomationMsg_LoginWithUserAndPass::WriteReplyParams(reply_message_, + auth_details->success()); + automation_->Send(reply_message_); + delete this; +} +#endif + +AutomationProviderBookmarkModelObserver::AutomationProviderBookmarkModelObserver( + AutomationProvider* provider, + IPC::Message* reply_message, + BookmarkModel* model) { + automation_provider_ = provider; + reply_message_ = reply_message; + model_ = model; + model_->AddObserver(this); +} + +AutomationProviderBookmarkModelObserver::~AutomationProviderBookmarkModelObserver() { + model_->RemoveObserver(this); +} + +void AutomationProviderBookmarkModelObserver::ReplyAndDelete(bool success) { + AutomationMsg_WaitForBookmarkModelToLoad::WriteReplyParams( + reply_message_, success); + automation_provider_->Send(reply_message_); + delete this; +} + +void AutomationProviderDownloadItemObserver::OnDownloadFileCompleted( + DownloadItem* download) { + download->RemoveObserver(this); + if (--downloads_ == 0) { + AutomationJSONReply(provider_, reply_message_).SendSuccess(NULL); + delete this; + } +} + +void AutomationProviderHistoryObserver::HistoryQueryComplete( + HistoryService::Handle request_handle, + history::QueryResults* results) { + scoped_ptr<DictionaryValue> return_value(new DictionaryValue); + + ListValue* history_list = new ListValue; + for (size_t i = 0; i < results->size(); ++i) { + DictionaryValue* page_value = new DictionaryValue; + history::URLResult const &page = (*results)[i]; + page_value->SetStringFromUTF16(L"title", page.title()); + page_value->SetString(L"url", page.url().spec()); + page_value->SetReal(L"time", + static_cast<double>(page.visit_time().ToDoubleT())); + page_value->SetStringFromUTF16(L"snippet", page.snippet().text()); + page_value->SetBoolean( + L"starred", + provider_->profile()->GetBookmarkModel()->IsBookmarked(page.url())); + history_list->Append(page_value); + } + + return_value->Set(L"history", history_list); + // Return history info. + AutomationJSONReply reply(provider_, reply_message_); + reply.SendSuccess(return_value.get()); + delete this; +} + +void AutomationProviderImportSettingsObserver::ImportEnded() { + // Send back an empty success message. + AutomationJSONReply(provider_, reply_message_).SendSuccess(NULL); + delete this; +} + +void AutomationProviderGetPasswordsObserver::OnPasswordStoreRequestDone( + int handle, const std::vector<webkit_glue::PasswordForm*>& result) { + scoped_ptr<DictionaryValue> return_value(new DictionaryValue); + + ListValue* passwords = new ListValue; + for (std::vector<webkit_glue::PasswordForm*>::const_iterator it = + result.begin(); it != result.end(); ++it) { + DictionaryValue* password_val = new DictionaryValue; + webkit_glue::PasswordForm* password_form = *it; + password_val->SetStringFromUTF16(L"username", + password_form->username_value); + password_val->SetStringFromUTF16(L"password", + password_form->password_value); + password_val->SetReal( + L"time", static_cast<double>( + password_form->date_created.ToDoubleT())); + passwords->Append(password_val); + } + + return_value->Set(L"passwords", passwords); + AutomationJSONReply(provider_, reply_message_).SendSuccess( + return_value.get()); + delete this; +} + +void AutomationProviderBrowsingDataObserver::OnBrowsingDataRemoverDone() { + // Send back an empty success message + AutomationJSONReply(provider_, reply_message_).SendSuccess(NULL); + delete this; +} + +OmniboxAcceptNotificationObserver::OmniboxAcceptNotificationObserver( + NavigationController* controller, + AutomationProvider* automation, + IPC::Message* reply_message) + : automation_(automation), + reply_message_(reply_message), + controller_(controller) { + Source<NavigationController> source(controller_); + registrar_.Add(this, NotificationType::LOAD_STOP, source); + // Pages requiring auth don't send LOAD_STOP. + registrar_.Add(this, NotificationType::AUTH_NEEDED, source); +} + +OmniboxAcceptNotificationObserver::~OmniboxAcceptNotificationObserver() { + automation_->RemoveNavigationStatusListener(this); +} + +void OmniboxAcceptNotificationObserver::Observe( + NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::LOAD_STOP || + type == NotificationType::AUTH_NEEDED) { + AutomationJSONReply(automation_, reply_message_).SendSuccess(NULL); + delete this; + } else { + NOTREACHED(); + } +} + +SavePackageNotificationObserver::SavePackageNotificationObserver( + SavePackage* save_package, + AutomationProvider* automation, + IPC::Message* reply_message) : automation_(automation), + reply_message_(reply_message) { + Source<SavePackage> source(save_package); + registrar_.Add(this, NotificationType::SAVE_PACKAGE_SUCCESSFULLY_FINISHED, + source); +} + +void SavePackageNotificationObserver::Observe( + NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::SAVE_PACKAGE_SUCCESSFULLY_FINISHED) { + AutomationJSONReply(automation_, reply_message_).SendSuccess(NULL); + delete this; + } else { + NOTREACHED(); + } +} + +WaitForInfobarCountObserver::WaitForInfobarCountObserver( + AutomationProvider* automation, + IPC::Message* reply_message, + TabContents* tab_contents, + int count) + : automation_(automation), + reply_message_(reply_message), + tab_contents_(tab_contents), + count_(count) { + if (tab_contents->infobar_delegate_count() == count) { + ConditionMet(); + return; + } + registrar_.Add(this, NotificationType::TAB_CONTENTS_INFOBAR_ADDED, + Source<TabContents>(tab_contents_)); + registrar_.Add(this, NotificationType::TAB_CONTENTS_INFOBAR_REMOVED, + Source<TabContents>(tab_contents_)); +} + +void WaitForInfobarCountObserver::Observe( + NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NotificationType::TAB_CONTENTS_INFOBAR_ADDED || + type == NotificationType::TAB_CONTENTS_INFOBAR_REMOVED); + if (tab_contents_->infobar_delegate_count() == count_) { + ConditionMet(); + } +} + +void WaitForInfobarCountObserver::ConditionMet() { + registrar_.RemoveAll(); + AutomationJSONReply(automation_, reply_message_).SendSuccess(NULL); + delete this; +} diff --git a/chrome/browser/automation/automation_provider_observers.h b/chrome/browser/automation/automation_provider_observers.h new file mode 100644 index 0000000..732d1fd --- /dev/null +++ b/chrome/browser/automation/automation_provider_observers.h @@ -0,0 +1,738 @@ +// 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. + +#ifndef CHROME_BROWSER_AUTOMATION_AUTOMATION_PROVIDER_OBSERVERS_H_ +#define CHROME_BROWSER_AUTOMATION_AUTOMATION_PROVIDER_OBSERVERS_H_ + +#include <deque> +#include <map> +#include <set> + +#include "chrome/browser/bookmarks/bookmark_model_observer.h" +#include "chrome/browser/browsing_data_remover.h" +#include "chrome/browser/download/download_item.h" +#include "chrome/browser/download/download_manager.h" +#include "chrome/browser/importer/importer.h" +#include "chrome/browser/importer/importer_data_types.h" +#include "chrome/browser/password_manager/password_store.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_type.h" +#include "chrome/test/automation/automation_messages.h" + +class AutomationProvider; +class Browser; +class Extension; +class ExtensionProcessManager; +class NavigationController; +class SavePackage; +class TabContents; + +namespace IPC { +class Message; +} + +class InitialLoadObserver : public NotificationObserver { + public: + InitialLoadObserver(size_t tab_count, AutomationProvider* automation); + ~InitialLoadObserver(); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Caller owns the return value and is responsible for deleting it. + // Example return value: + // {'tabs': [{'start_time_ms': 1, 'stop_time_ms': 2.5}, + // {'start_time_ms': 0.5, 'stop_time_ms': 3}]} + // stop_time_ms values may be null if WaitForInitialLoads has not finished. + // Only includes entries for the |tab_count| tabs we are monitoring. + // There is no defined ordering of the return value. + DictionaryValue* GetTimingInformation() const; + + private: + class TabTime; + typedef std::map<uintptr_t, TabTime> TabTimeMap; + typedef std::set<uintptr_t> TabSet; + + void ConditionMet(); + + NotificationRegistrar registrar_; + + AutomationProvider* automation_; + size_t outstanding_tab_count_; + base::TimeTicks init_time_; + TabTimeMap loading_tabs_; + TabSet finished_tabs_; + + DISALLOW_COPY_AND_ASSIGN(InitialLoadObserver); +}; + +// Watches for NewTabUI page loads for performance timing purposes. +class NewTabUILoadObserver : public NotificationObserver { + public: + explicit NewTabUILoadObserver(AutomationProvider* automation); + ~NewTabUILoadObserver(); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + NotificationRegistrar registrar_; + AutomationProvider* automation_; + + DISALLOW_COPY_AND_ASSIGN(NewTabUILoadObserver); +}; + +class NavigationControllerRestoredObserver : public NotificationObserver { + public: + NavigationControllerRestoredObserver(AutomationProvider* automation, + NavigationController* controller, + IPC::Message* reply_message); + ~NavigationControllerRestoredObserver(); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + bool FinishedRestoring(); + void SendDone(); + + NotificationRegistrar registrar_; + AutomationProvider* automation_; + NavigationController* controller_; + IPC::Message* reply_message_; + + DISALLOW_COPY_AND_ASSIGN(NavigationControllerRestoredObserver); +}; + +class NavigationNotificationObserver : public NotificationObserver { + public: + NavigationNotificationObserver(NavigationController* controller, + AutomationProvider* automation, + IPC::Message* reply_message, + int number_of_navigations, + bool include_current_navigation); + ~NavigationNotificationObserver(); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + void ConditionMet(AutomationMsg_NavigationResponseValues navigation_result); + + NotificationRegistrar registrar_; + AutomationProvider* automation_; + IPC::Message* reply_message_; + NavigationController* controller_; + int navigations_remaining_; + bool navigation_started_; + + DISALLOW_COPY_AND_ASSIGN(NavigationNotificationObserver); +}; + +class TabStripNotificationObserver : public NotificationObserver { + public: + TabStripNotificationObserver(NotificationType notification, + AutomationProvider* automation); + virtual ~TabStripNotificationObserver(); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + virtual void ObserveTab(NavigationController* controller) = 0; + + protected: + NotificationRegistrar registrar_; + AutomationProvider* automation_; + NotificationType notification_; +}; + +class TabAppendedNotificationObserver : public TabStripNotificationObserver { + public: + TabAppendedNotificationObserver(Browser* parent, + AutomationProvider* automation, + IPC::Message* reply_message); + + virtual void ObserveTab(NavigationController* controller); + + protected: + Browser* parent_; + IPC::Message* reply_message_; + + private: + DISALLOW_COPY_AND_ASSIGN(TabAppendedNotificationObserver); +}; + +class TabClosedNotificationObserver : public TabStripNotificationObserver { + public: + TabClosedNotificationObserver(AutomationProvider* automation, + bool wait_until_closed, + IPC::Message* reply_message); + + virtual void ObserveTab(NavigationController* controller); + + void set_for_browser_command(bool for_browser_command); + + protected: + IPC::Message* reply_message_; + bool for_browser_command_; + + private: + DISALLOW_COPY_AND_ASSIGN(TabClosedNotificationObserver); +}; + +// 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: + ExtensionInstallNotificationObserver(AutomationProvider* automation, + int id, + IPC::Message* reply_message); + ~ExtensionInstallNotificationObserver(); + + // Implementation of NotificationObserver. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + // Send |response| back to the provider's client. + void SendResponse(AutomationMsg_ExtensionResponseValues response); + + NotificationRegistrar registrar_; + scoped_refptr<AutomationProvider> automation_; + int id_; + IPC::Message* reply_message_; + + 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 { + public: + BrowserOpenedNotificationObserver(AutomationProvider* automation, + IPC::Message* reply_message); + ~BrowserOpenedNotificationObserver(); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + void set_for_browser_command(bool for_browser_command); + + private: + NotificationRegistrar registrar_; + AutomationProvider* automation_; + IPC::Message* reply_message_; + bool for_browser_command_; + + DISALLOW_COPY_AND_ASSIGN(BrowserOpenedNotificationObserver); +}; + +class BrowserClosedNotificationObserver : public NotificationObserver { + public: + BrowserClosedNotificationObserver(Browser* browser, + AutomationProvider* automation, + IPC::Message* reply_message); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + void set_for_browser_command(bool for_browser_command); + + private: + NotificationRegistrar registrar_; + AutomationProvider* automation_; + IPC::Message* reply_message_; + bool for_browser_command_; + + DISALLOW_COPY_AND_ASSIGN(BrowserClosedNotificationObserver); +}; + +class BrowserCountChangeNotificationObserver : public NotificationObserver { + public: + BrowserCountChangeNotificationObserver(int target_count, + AutomationProvider* automation, + IPC::Message* reply_message); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + int target_count_; + NotificationRegistrar registrar_; + AutomationProvider* automation_; + IPC::Message* reply_message_; + + DISALLOW_COPY_AND_ASSIGN(BrowserCountChangeNotificationObserver); +}; + +class AppModalDialogShownObserver : public NotificationObserver { + public: + AppModalDialogShownObserver(AutomationProvider* automation, + IPC::Message* reply_message); + ~AppModalDialogShownObserver(); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + NotificationRegistrar registrar_; + AutomationProvider* automation_; + IPC::Message* reply_message_; + + DISALLOW_COPY_AND_ASSIGN(AppModalDialogShownObserver); +}; + +class ExecuteBrowserCommandObserver : public NotificationObserver { + public: + ~ExecuteBrowserCommandObserver(); + + static bool CreateAndRegisterObserver(AutomationProvider* automation, + Browser* browser, + int command, + IPC::Message* reply_message); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + ExecuteBrowserCommandObserver(AutomationProvider* automation, + IPC::Message* reply_message); + + bool Register(int command); + + bool GetNotificationType(int command, NotificationType::Type* type); + + NotificationRegistrar registrar_; + AutomationProvider* automation_; + NotificationType::Type notification_type_; + IPC::Message* reply_message_; + + DISALLOW_COPY_AND_ASSIGN(ExecuteBrowserCommandObserver); +}; + +class FindInPageNotificationObserver : public NotificationObserver { + public: + FindInPageNotificationObserver(AutomationProvider* automation, + TabContents* parent_tab, + IPC::Message* reply_message); + ~FindInPageNotificationObserver(); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // The Find mechanism is over asynchronous IPC, so a search is kicked off and + // we wait for notification to find out what the results are. As the user is + // typing, new search requests can be issued and the Request ID helps us make + // sense of whether this is the current request or an old one. The unit tests, + // however, which uses this constant issues only one search at a time, so we + // don't need a rolling id to identify each search. But, we still need to + // specify one, so we just use a fixed one - its value does not matter. + static const int kFindInPageRequestId; + + private: + NotificationRegistrar registrar_; + AutomationProvider* automation_; + // We will at some point (before final update) be notified of the ordinal and + // we need to preserve it so we can send it later. + int active_match_ordinal_; + IPC::Message* reply_message_; + + DISALLOW_COPY_AND_ASSIGN(FindInPageNotificationObserver); +}; + +class DomOperationNotificationObserver : public NotificationObserver { + public: + explicit DomOperationNotificationObserver(AutomationProvider* automation); + ~DomOperationNotificationObserver(); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + NotificationRegistrar registrar_; + AutomationProvider* automation_; + + DISALLOW_COPY_AND_ASSIGN(DomOperationNotificationObserver); +}; + +class DocumentPrintedNotificationObserver : public NotificationObserver { + public: + DocumentPrintedNotificationObserver(AutomationProvider* automation, + IPC::Message* reply_message); + ~DocumentPrintedNotificationObserver(); + + virtual void Observe(NotificationType type, const NotificationSource& source, + const NotificationDetails& details); + + private: + NotificationRegistrar registrar_; + scoped_refptr<AutomationProvider> automation_; + bool success_; + IPC::Message* reply_message_; + + DISALLOW_COPY_AND_ASSIGN(DocumentPrintedNotificationObserver); +}; + +// Collects METRIC_EVENT_DURATION notifications and keep track of the times. +class MetricEventDurationObserver : public NotificationObserver { + public: + MetricEventDurationObserver(); + + // Get the duration of an event. Returns -1 if we haven't seen the event. + int GetEventDurationMs(const std::string& event_name); + + // NotificationObserver interface. + virtual void Observe(NotificationType type, const NotificationSource& source, + const NotificationDetails& details); + + private: + NotificationRegistrar registrar_; + + typedef std::map<std::string, int> EventDurationMap; + EventDurationMap durations_; + + DISALLOW_COPY_AND_ASSIGN(MetricEventDurationObserver); +}; + +#if defined(OS_CHROMEOS) +// Collects LOGIN_AUTHENTICATION notifications and returns +// whether authentication succeeded to the automation provider. +class LoginManagerObserver : public NotificationObserver { + public: + LoginManagerObserver(AutomationProvider* automation, + IPC::Message* reply_message); + + // NotificationObserver interface. + virtual void Observe(NotificationType type, const NotificationSource& source, + const NotificationDetails& details); + + private: + NotificationRegistrar registrar_; + AutomationProvider* automation_; + IPC::Message* reply_message_; + + DISALLOW_COPY_AND_ASSIGN(LoginManagerObserver); +}; +#endif + +// Waits for the bookmark model to load. +class AutomationProviderBookmarkModelObserver : BookmarkModelObserver { + public: + AutomationProviderBookmarkModelObserver(AutomationProvider* provider, + IPC::Message* reply_message, + BookmarkModel* model); + virtual ~AutomationProviderBookmarkModelObserver(); + + virtual void Loaded(BookmarkModel* model) { + ReplyAndDelete(true); + } + virtual void BookmarkModelBeingDeleted(BookmarkModel* model) { + ReplyAndDelete(false); + } + virtual void BookmarkNodeMoved(BookmarkModel* model, + const BookmarkNode* old_parent, + int old_index, + const BookmarkNode* new_parent, + int new_index) {} + virtual void BookmarkNodeAdded(BookmarkModel* model, + const BookmarkNode* parent, + int index) {} + virtual void BookmarkNodeRemoved(BookmarkModel* model, + const BookmarkNode* parent, + int old_index, + const BookmarkNode* node) {} + virtual void BookmarkNodeChanged(BookmarkModel* model, + const BookmarkNode* node) {} + virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model, + const BookmarkNode* node) {} + virtual void BookmarkNodeChildrenReordered(BookmarkModel* model, + const BookmarkNode* node) {} + + private: + // Reply to the automation message with the given success value, + // then delete myself (which removes myself from the bookmark model + // observer list). + void ReplyAndDelete(bool success); + + scoped_refptr<AutomationProvider> automation_provider_; + IPC::Message* reply_message_; + BookmarkModel* model_; + + DISALLOW_COPY_AND_ASSIGN(AutomationProviderBookmarkModelObserver); +}; + +// When asked for pending downloads, the DownloadManager places +// results in a DownloadManager::Observer. +class AutomationProviderDownloadManagerObserver + : public DownloadManager::Observer { + public: + AutomationProviderDownloadManagerObserver() : DownloadManager::Observer() {} + virtual ~AutomationProviderDownloadManagerObserver() {} + virtual void ModelChanged() {} + virtual void SetDownloads(std::vector<DownloadItem*>& downloads) { + downloads_ = downloads; + } + std::vector<DownloadItem*> Downloads() { + return downloads_; + } + private: + std::vector<DownloadItem*> downloads_; + + DISALLOW_COPY_AND_ASSIGN(AutomationProviderDownloadManagerObserver); +}; + + +// Allows the automation provider to wait for all downloads to finish. +class AutomationProviderDownloadItemObserver : public DownloadItem::Observer { + public: + AutomationProviderDownloadItemObserver( + AutomationProvider* provider, + IPC::Message* reply_message, + int downloads) { + provider_ = provider; + reply_message_ = reply_message; + downloads_ = downloads; + } + virtual ~AutomationProviderDownloadItemObserver() {} + + virtual void OnDownloadUpdated(DownloadItem* download) { } + virtual void OnDownloadFileCompleted(DownloadItem* download); + virtual void OnDownloadOpened(DownloadItem* download) { } + + private: + AutomationProvider* provider_; + IPC::Message* reply_message_; + int downloads_; + + DISALLOW_COPY_AND_ASSIGN(AutomationProviderDownloadItemObserver); +}; + +// Allows the automation provider to wait for history queries to finish. +class AutomationProviderHistoryObserver { + public: + AutomationProviderHistoryObserver( + AutomationProvider* provider, + IPC::Message* reply_message) { + provider_ = provider; + reply_message_ = reply_message; + } + ~AutomationProviderHistoryObserver() {} + void HistoryQueryComplete(HistoryService::Handle request_handle, + history::QueryResults* results); + + private: + AutomationProvider* provider_; + IPC::Message* reply_message_; +}; + +// Allows the automation provider to wait for import queries to finish. +class AutomationProviderImportSettingsObserver + : public ImporterHost::Observer { + public: + AutomationProviderImportSettingsObserver( + AutomationProvider* provider, + IPC::Message* reply_message) + : provider_(provider), + reply_message_(reply_message) {} + void ImportStarted() {} + void ImportItemStarted(importer::ImportItem item) {} + void ImportItemEnded(importer::ImportItem item) {} + void ImportEnded(); + private: + AutomationProvider* provider_; + IPC::Message* reply_message_; +}; + +// Allows automation provider to wait for getting passwords to finish. +class AutomationProviderGetPasswordsObserver : + public PasswordStoreConsumer { + public: + AutomationProviderGetPasswordsObserver( + AutomationProvider* provider, + IPC::Message* reply_message) + : provider_(provider), + reply_message_(reply_message) {} + + void OnPasswordStoreRequestDone( + int handle, const std::vector<webkit_glue::PasswordForm*>& result); + + private: + AutomationProvider* provider_; + IPC::Message* reply_message_; +}; + +// Allows the automation provider to wait for clearing browser data to finish. +class AutomationProviderBrowsingDataObserver + : public BrowsingDataRemover::Observer { + public: + AutomationProviderBrowsingDataObserver( + AutomationProvider* provider, + IPC::Message* reply_message) + : provider_(provider), + reply_message_(reply_message) {} + void OnBrowsingDataRemoverDone(); + + private: + AutomationProvider* provider_; + IPC::Message* reply_message_; +}; + +// Allows automation provider to wait until page load after selecting an item +// in the omnibox popup. +class OmniboxAcceptNotificationObserver : public NotificationObserver { + public: + OmniboxAcceptNotificationObserver(NavigationController* controller, + AutomationProvider* automation, + IPC::Message* reply_message); + ~OmniboxAcceptNotificationObserver(); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + NotificationRegistrar registrar_; + AutomationProvider* automation_; + IPC::Message* reply_message_; + NavigationController* controller_; + + DISALLOW_COPY_AND_ASSIGN(OmniboxAcceptNotificationObserver); +}; + +// Allows the automation provider to wait for a save package notification. +class SavePackageNotificationObserver : public NotificationObserver { + public: + SavePackageNotificationObserver(SavePackage* save_package, + AutomationProvider* automation, + IPC::Message* reply_message); + virtual ~SavePackageNotificationObserver() {} + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + NotificationRegistrar registrar_; + AutomationProvider* automation_; + IPC::Message* reply_message_; + + DISALLOW_COPY_AND_ASSIGN(SavePackageNotificationObserver); +}; + +// Allows the automation provider to wait for a given number of infobars. +class WaitForInfobarCountObserver : public NotificationObserver { + public: + WaitForInfobarCountObserver(AutomationProvider* automation, + IPC::Message* reply_message, + TabContents* tab_contents, + int count); + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + void ConditionMet(); + + NotificationRegistrar registrar_; + AutomationProvider* automation_; + IPC::Message* reply_message_; + TabContents* tab_contents_; + int count_; + + DISALLOW_COPY_AND_ASSIGN(WaitForInfobarCountObserver); +}; + +#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_PROVIDER_OBSERVERS_H_ diff --git a/chrome/browser/automation/automation_provider_unittest.cc b/chrome/browser/automation/automation_provider_unittest.cc new file mode 100644 index 0000000..356d0f4 --- /dev/null +++ b/chrome/browser/automation/automation_provider_unittest.cc @@ -0,0 +1,32 @@ +// 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/browser/automation/chrome_frame_automation_provider.h" +#include "ipc/ipc_message.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +class MockChromeFrameAutomationProvider + : public ChromeFrameAutomationProvider { + public: + explicit MockChromeFrameAutomationProvider(Profile* profile) + : ChromeFrameAutomationProvider(profile) {} + + virtual ~MockChromeFrameAutomationProvider() {} + + MOCK_METHOD1(OnUnhandledMessage, + void (const IPC::Message& message)); // NOLINT +}; + +TEST(AutomationProviderTest, TestInvalidChromeFrameMessage) { + IPC::Message bad_msg(1, -1, IPC::Message::PRIORITY_NORMAL); + + scoped_refptr<MockChromeFrameAutomationProvider> + mock(new MockChromeFrameAutomationProvider(NULL)); + + EXPECT_CALL(*mock, OnUnhandledMessage(testing::Property(&IPC::Message::type, + -1))).Times(1); + mock->OnMessageReceived(bad_msg); +} + diff --git a/chrome/browser/automation/automation_provider_views.cc b/chrome/browser/automation/automation_provider_views.cc new file mode 100644 index 0000000..f130079 --- /dev/null +++ b/chrome/browser/automation/automation_provider_views.cc @@ -0,0 +1,190 @@ +// 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/browser/automation/automation_provider.h" + +#include "chrome/browser/automation/automation_browser_tracker.h" +#include "chrome/browser/automation/automation_window_tracker.h" +#include "chrome/browser/views/frame/browser_view.h" +#include "chrome/browser/views/toolbar_view.h" +#include "chrome/test/automation/automation_messages.h" +#include "gfx/point.h" +#include "views/controls/menu/menu_wrapper.h" +#include "views/focus/focus_manager.h" +#include "views/view.h" +#include "views/widget/root_view.h" +#include "views/widget/widget.h" + +void AutomationProvider::WindowGetViewBounds(int handle, int view_id, + bool screen_coordinates, + bool* success, + gfx::Rect* bounds) { + *success = false; + + if (window_tracker_->ContainsHandle(handle)) { + gfx::NativeWindow window = window_tracker_->GetResource(handle); + views::RootView* root_view = views::Widget::FindRootView(window); + if (root_view) { + views::View* view = root_view->GetViewByID(view_id); + if (view) { + *success = true; + gfx::Point point; + if (screen_coordinates) + views::View::ConvertPointToScreen(view, &point); + else + views::View::ConvertPointToView(view, root_view, &point); + *bounds = view->GetLocalBounds(false); + bounds->set_origin(point); + } + } + } +} + +void AutomationProvider::GetFocusedViewID(int handle, int* view_id) { + *view_id = -1; + if (window_tracker_->ContainsHandle(handle)) { + gfx::NativeWindow window = window_tracker_->GetResource(handle); + views::FocusManager* focus_manager = + views::FocusManager::GetFocusManagerForNativeWindow(window); + DCHECK(focus_manager); + views::View* focused_view = focus_manager->GetFocusedView(); + if (focused_view) + *view_id = focused_view->GetID(); + } +} + +// Helper class that waits until the focus has changed to a view other +// than the one with the provided view id. +class ViewFocusChangeWaiter : public views::FocusChangeListener { + public: + ViewFocusChangeWaiter(views::FocusManager* focus_manager, + int previous_view_id, + AutomationProvider* automation, + IPC::Message* reply_message) + : focus_manager_(focus_manager), + previous_view_id_(previous_view_id), + automation_(automation), + reply_message_(reply_message), + ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { + focus_manager_->AddFocusChangeListener(this); + // Call the focus change notification once in case the focus has + // already changed. + FocusWillChange(NULL, focus_manager_->GetFocusedView()); + } + + ~ViewFocusChangeWaiter() { + focus_manager_->RemoveFocusChangeListener(this); + } + + // Inherited from FocusChangeListener + virtual void FocusWillChange(views::View* focused_before, + views::View* focused_now) { + // This listener is called before focus actually changes. Post a task + // that will get run after focus changes. + MessageLoop::current()->PostTask( + FROM_HERE, + method_factory_.NewRunnableMethod( + &ViewFocusChangeWaiter::FocusChanged, + focused_before, + focused_now)); + } + + private: + void FocusChanged(views::View* focused_before, + views::View* focused_now) { + if (focused_now && focused_now->GetID() != previous_view_id_) { + AutomationMsg_WaitForFocusedViewIDToChange::WriteReplyParams( + reply_message_, true, focused_now->GetID()); + + automation_->Send(reply_message_); + delete this; + } + } + + views::FocusManager* focus_manager_; + int previous_view_id_; + AutomationProvider* automation_; + IPC::Message* reply_message_; + ScopedRunnableMethodFactory<ViewFocusChangeWaiter> method_factory_; + + DISALLOW_COPY_AND_ASSIGN(ViewFocusChangeWaiter); +}; + +void AutomationProvider::WaitForFocusedViewIDToChange( + int handle, int previous_view_id, IPC::Message* reply_message) { + if (!window_tracker_->ContainsHandle(handle)) + return; + gfx::NativeWindow window = window_tracker_->GetResource(handle); + views::FocusManager* focus_manager = + views::FocusManager::GetFocusManagerForNativeWindow(window); + + // The waiter will respond to the IPC and delete itself when done. + new ViewFocusChangeWaiter(focus_manager, + previous_view_id, + this, + reply_message); +} + +class PopupMenuWaiter : public views::MenuListener { + public: + PopupMenuWaiter(ToolbarView* toolbar_view, + AutomationProvider* automation) + : toolbar_view_(toolbar_view), + automation_(automation), + reply_message_(NULL) { + toolbar_view_->AddMenuListener(this); + } + + // Implementation of views::MenuListener + virtual void OnMenuOpened() { + toolbar_view_->RemoveMenuListener(this); + automation_->popup_menu_opened_ = true; + automation_->popup_menu_waiter_ = NULL; + if (reply_message_) { + AutomationMsg_WaitForPopupMenuToOpen::WriteReplyParams( + reply_message_, true); + automation_->Send(reply_message_); + } + delete this; + } + + void set_reply_message(IPC::Message* reply_message) { + reply_message_ = reply_message; + } + + private: + ToolbarView* toolbar_view_; + AutomationProvider* automation_; + IPC::Message* reply_message_; + + DISALLOW_COPY_AND_ASSIGN(PopupMenuWaiter); +}; + +void AutomationProvider::StartTrackingPopupMenus( + int browser_handle, bool* success) { + if (browser_tracker_->ContainsHandle(browser_handle)) { + Browser* browser = browser_tracker_->GetResource(browser_handle); + BrowserView* browser_view = reinterpret_cast<BrowserView*>( + browser->window()); + ToolbarView* toolbar_view = browser_view->GetToolbarView(); + popup_menu_opened_ = false; + popup_menu_waiter_ = new PopupMenuWaiter(toolbar_view, this); + *success = true; + } +} + +void AutomationProvider::WaitForPopupMenuToOpen(IPC::Message* reply_message) { + // See if the menu already opened and return true if so. + if (popup_menu_opened_) { + AutomationMsg_WaitForPopupMenuToOpen::WriteReplyParams( + reply_message, true); + Send(reply_message); + return; + } + + // Otherwise, register this reply message with the waiter, + // which will handle responding to this IPC when the popup + // menu opens. + popup_menu_waiter_->set_reply_message(reply_message); +} diff --git a/chrome/browser/automation/automation_provider_win.cc b/chrome/browser/automation/automation_provider_win.cc new file mode 100644 index 0000000..51959bd --- /dev/null +++ b/chrome/browser/automation/automation_provider_win.cc @@ -0,0 +1,605 @@ +// 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/browser/automation/automation_provider.h" + +#include "base/json/json_reader.h" +#include "base/keyboard_codes.h" +#include "chrome/browser/automation/automation_browser_tracker.h" +#include "chrome/browser/automation/automation_extension_function.h" +#include "chrome/browser/automation/automation_tab_tracker.h" +#include "chrome/browser/automation/automation_window_tracker.h" +#include "chrome/browser/automation/extension_automation_constants.h" +#include "chrome/browser/automation/extension_port_container.h" +#include "chrome/browser/automation/ui_controls.h" +#include "chrome/browser/browser_window.h" +#include "chrome/browser/extensions/extension_message_service.h" +#include "chrome/browser/external_tab_container_win.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/views/bookmark_bar_view.h" +#include "chrome/test/automation/automation_messages.h" +#include "views/widget/root_view.h" +#include "views/widget/widget_win.h" +#include "views/window/window.h" + +// This task just adds another task to the event queue. This is useful if +// you want to ensure that any tasks added to the event queue after this one +// have already been processed by the time |task| is run. +class InvokeTaskLaterTask : public Task { + public: + explicit InvokeTaskLaterTask(Task* task) : task_(task) {} + virtual ~InvokeTaskLaterTask() {} + + virtual void Run() { + MessageLoop::current()->PostTask(FROM_HERE, task_); + } + + private: + Task* task_; + + DISALLOW_COPY_AND_ASSIGN(InvokeTaskLaterTask); +}; + +static void MoveMouse(const POINT& point) { + SetCursorPos(point.x, point.y); + + // Now, make sure that GetMessagePos returns the values we just set by + // simulating a mouse move. The value returned by GetMessagePos is updated + // when a mouse move event is removed from the event queue. + PostMessage(NULL, WM_MOUSEMOVE, 0, MAKELPARAM(point.x, point.y)); + MSG msg; + while (PeekMessage(&msg, NULL, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE)) { + } + + // Verify +#ifndef NDEBUG + DWORD pos = GetMessagePos(); + gfx::Point cursor_point(pos); + DCHECK_EQ(point.x, cursor_point.x()); + DCHECK_EQ(point.y, cursor_point.y()); +#endif +} + +BOOL CALLBACK EnumThreadWndProc(HWND hwnd, LPARAM l_param) { + if (hwnd == reinterpret_cast<HWND>(l_param)) { + return FALSE; + } + return TRUE; +} + +void AutomationProvider::GetActiveWindow(int* handle) { + HWND window = GetForegroundWindow(); + + // Let's make sure this window belongs to our process. + if (EnumThreadWindows(::GetCurrentThreadId(), + EnumThreadWndProc, + reinterpret_cast<LPARAM>(window))) { + // We enumerated all the windows and did not find the foreground window, + // it is not our window, ignore it. + *handle = 0; + return; + } + + *handle = window_tracker_->Add(window); +} + +// This task enqueues a mouse event on the event loop, so that the view +// that it's being sent to can do the requisite post-processing. +class MouseEventTask : public Task { + public: + MouseEventTask(views::View* view, + views::Event::EventType type, + const gfx::Point& point, + int flags) + : view_(view), type_(type), point_(point), flags_(flags) {} + virtual ~MouseEventTask() {} + + virtual void Run() { + views::MouseEvent event(type_, point_.x(), point_.y(), flags_); + // We need to set the cursor position before we process the event because + // some code (tab dragging, for instance) queries the actual cursor location + // rather than the location of the mouse event. Note that the reason why + // the drag code moved away from using mouse event locations was because + // our conversion to screen location doesn't work well with multiple + // monitors, so this only works reliably in a single monitor setup. + gfx::Point screen_location(point_.x(), point_.y()); + view_->ConvertPointToScreen(view_, &screen_location); + MoveMouse(screen_location.ToPOINT()); + switch (type_) { + case views::Event::ET_MOUSE_PRESSED: + view_->OnMousePressed(event); + break; + + case views::Event::ET_MOUSE_DRAGGED: + view_->OnMouseDragged(event); + break; + + case views::Event::ET_MOUSE_RELEASED: + view_->OnMouseReleased(event, false); + break; + + default: + NOTREACHED(); + } + } + + private: + views::View* view_; + views::Event::EventType type_; + gfx::Point point_; + int flags_; + + DISALLOW_COPY_AND_ASSIGN(MouseEventTask); +}; + +// This task sends a WindowDragResponse message with the appropriate +// routing ID to the automation proxy. This is implemented as a task so that +// we know that the mouse events (and any tasks that they spawn on the message +// loop) have been processed by the time this is sent. +class WindowDragResponseTask : public Task { + public: + WindowDragResponseTask(AutomationProvider* provider, + IPC::Message* reply_message) + : provider_(provider), reply_message_(reply_message) {} + virtual ~WindowDragResponseTask() {} + + virtual void Run() { + DCHECK(reply_message_ != NULL); + AutomationMsg_WindowDrag::WriteReplyParams(reply_message_, true); + provider_->Send(reply_message_); + } + + private: + AutomationProvider* provider_; + IPC::Message* reply_message_; + + DISALLOW_COPY_AND_ASSIGN(WindowDragResponseTask); +}; + +void AutomationProvider::WindowSimulateDrag(int handle, + std::vector<gfx::Point> drag_path, + int flags, + bool press_escape_en_route, + IPC::Message* reply_message) { + if (browser_tracker_->ContainsHandle(handle) && (drag_path.size() > 1)) { + gfx::NativeWindow window = + browser_tracker_->GetResource(handle)->window()->GetNativeHandle(); + + UINT down_message = 0; + UINT up_message = 0; + WPARAM wparam_flags = 0; + if (flags & views::Event::EF_SHIFT_DOWN) + wparam_flags |= MK_SHIFT; + if (flags & views::Event::EF_CONTROL_DOWN) + wparam_flags |= MK_CONTROL; + if (flags & views::Event::EF_LEFT_BUTTON_DOWN) { + wparam_flags |= MK_LBUTTON; + down_message = WM_LBUTTONDOWN; + up_message = WM_LBUTTONUP; + } + if (flags & views::Event::EF_MIDDLE_BUTTON_DOWN) { + wparam_flags |= MK_MBUTTON; + down_message = WM_MBUTTONDOWN; + up_message = WM_MBUTTONUP; + } + if (flags & views::Event::EF_RIGHT_BUTTON_DOWN) { + wparam_flags |= MK_RBUTTON; + down_message = WM_LBUTTONDOWN; + up_message = WM_LBUTTONUP; + } + + Browser* browser = browser_tracker_->GetResource(handle); + DCHECK(browser); + HWND top_level_hwnd = + reinterpret_cast<HWND>(browser->window()->GetNativeHandle()); + POINT temp = drag_path[0].ToPOINT(); + MapWindowPoints(top_level_hwnd, HWND_DESKTOP, &temp, 1); + MoveMouse(temp); + SendMessage(top_level_hwnd, down_message, wparam_flags, + MAKELPARAM(drag_path[0].x(), drag_path[0].y())); + for (int i = 1; i < static_cast<int>(drag_path.size()); ++i) { + temp = drag_path[i].ToPOINT(); + MapWindowPoints(top_level_hwnd, HWND_DESKTOP, &temp, 1); + MoveMouse(temp); + SendMessage(top_level_hwnd, WM_MOUSEMOVE, wparam_flags, + MAKELPARAM(drag_path[i].x(), drag_path[i].y())); + } + POINT end = drag_path[drag_path.size() - 1].ToPOINT(); + MapWindowPoints(top_level_hwnd, HWND_DESKTOP, &end, 1); + MoveMouse(end); + + if (press_escape_en_route) { + // Press Escape. + ui_controls::SendKeyPress(window, base::VKEY_ESCAPE, + ((flags & views::Event::EF_CONTROL_DOWN) + == views::Event::EF_CONTROL_DOWN), + ((flags & views::Event::EF_SHIFT_DOWN) == + views::Event::EF_SHIFT_DOWN), + ((flags & views::Event::EF_ALT_DOWN) == + views::Event::EF_ALT_DOWN), + false); + } + SendMessage(top_level_hwnd, up_message, wparam_flags, + MAKELPARAM(end.x, end.y)); + + MessageLoop::current()->PostTask(FROM_HERE, new InvokeTaskLaterTask( + new WindowDragResponseTask(this, reply_message))); + } else { + AutomationMsg_WindowDrag::WriteReplyParams(reply_message, false); + Send(reply_message); + } +} + +void AutomationProvider::GetWindowBounds(int handle, gfx::Rect* bounds, + bool* success) { + *success = false; + HWND hwnd = window_tracker_->GetResource(handle); + if (hwnd) { + *success = true; + WINDOWPLACEMENT window_placement; + GetWindowPlacement(hwnd, &window_placement); + *bounds = window_placement.rcNormalPosition; + } +} + +void AutomationProvider::SetWindowBounds(int handle, const gfx::Rect& bounds, + bool* success) { + *success = false; + if (window_tracker_->ContainsHandle(handle)) { + HWND hwnd = window_tracker_->GetResource(handle); + if (::MoveWindow(hwnd, bounds.x(), bounds.y(), bounds.width(), + bounds.height(), true)) { + *success = true; + } + } +} + +void AutomationProvider::SetWindowVisible(int handle, bool visible, + bool* result) { + if (window_tracker_->ContainsHandle(handle)) { + HWND hwnd = window_tracker_->GetResource(handle); + ::ShowWindow(hwnd, visible ? SW_SHOW : SW_HIDE); + *result = true; + } else { + *result = false; + } +} + +void AutomationProvider::ActivateWindow(int handle) { + if (window_tracker_->ContainsHandle(handle)) { + ::SetActiveWindow(window_tracker_->GetResource(handle)); + } +} + +void AutomationProvider::IsWindowMaximized(int handle, bool* is_maximized, + bool* success) { + *success = false; + + HWND hwnd = window_tracker_->GetResource(handle); + if (hwnd) { + *success = true; + WINDOWPLACEMENT window_placement; + GetWindowPlacement(hwnd, &window_placement); + *is_maximized = (window_placement.showCmd == SW_MAXIMIZE); + } +} + +void AutomationProvider::GetTabHWND(int handle, HWND* tab_hwnd) { + *tab_hwnd = NULL; + + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + *tab_hwnd = tab->tab_contents()->GetNativeView(); + } +} + +void AutomationProvider::CreateExternalTab( + const IPC::ExternalTabSettings& settings, + gfx::NativeWindow* tab_container_window, gfx::NativeWindow* tab_window, + int* tab_handle) { + *tab_handle = 0; + *tab_container_window = NULL; + *tab_window = NULL; + scoped_refptr<ExternalTabContainer> external_tab_container = + new ExternalTabContainer(this, automation_resource_message_filter_); + + Profile* profile = settings.is_off_the_record ? + profile_->GetOffTheRecordProfile() : profile_; + + // When the ExternalTabContainer window is created we grab a reference on it + // which is released when the window is destroyed. + external_tab_container->Init(profile, settings.parent, settings.dimensions, + settings.style, settings.load_requests_via_automation, + settings.handle_top_level_requests, NULL, settings.initial_url, + settings.referrer, settings.infobars_enabled); + + if (AddExternalTab(external_tab_container)) { + TabContents* tab_contents = external_tab_container->tab_contents(); + *tab_handle = external_tab_container->tab_handle(); + *tab_container_window = external_tab_container->GetNativeView(); + *tab_window = tab_contents->GetNativeView(); + } else { + external_tab_container->Uninitialize(); + } +} + +bool AutomationProvider::AddExternalTab(ExternalTabContainer* external_tab) { + DCHECK(external_tab != NULL); + + TabContents* tab_contents = external_tab->tab_contents(); + if (tab_contents) { + int tab_handle = tab_tracker_->Add(&tab_contents->controller()); + external_tab->SetTabHandle(tab_handle); + return true; + } + + return false; +} + +void AutomationProvider::ProcessUnhandledAccelerator( + const IPC::Message& message, int handle, const MSG& msg) { + ExternalTabContainer* external_tab = GetExternalTabForHandle(handle); + if (external_tab) { + external_tab->ProcessUnhandledAccelerator(msg); + } + // This message expects no response. +} + +void AutomationProvider::SetInitialFocus(const IPC::Message& message, + int handle, bool reverse, + bool restore_focus_to_view) { + ExternalTabContainer* external_tab = GetExternalTabForHandle(handle); + if (external_tab) { + external_tab->FocusThroughTabTraversal(reverse, restore_focus_to_view); + } + // This message expects no response. +} + +void AutomationProvider::PrintAsync(int tab_handle) { + NavigationController* tab = NULL; + TabContents* tab_contents = GetTabContentsForHandle(tab_handle, &tab); + if (tab_contents) { + if (tab_contents->PrintNow()) + return; + } +} + +ExternalTabContainer* AutomationProvider::GetExternalTabForHandle(int handle) { + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + return ExternalTabContainer::GetContainerForTab( + tab->tab_contents()->GetNativeView()); + } + + return NULL; +} + +void AutomationProvider::OnTabReposition( + int tab_handle, const IPC::Reposition_Params& params) { + if (!tab_tracker_->ContainsHandle(tab_handle)) + return; + + if (!IsWindow(params.window)) + return; + + unsigned long process_id = 0; + unsigned long thread_id = 0; + + thread_id = GetWindowThreadProcessId(params.window, &process_id); + + if (thread_id != GetCurrentThreadId()) { + DCHECK_EQ(thread_id, GetCurrentThreadId()); + return; + } + + SetWindowPos(params.window, params.window_insert_after, params.left, + params.top, params.width, params.height, params.flags); + + if (params.set_parent) { + if (IsWindow(params.parent_window)) { + if (!SetParent(params.window, params.parent_window)) + DLOG(WARNING) << "SetParent failed. Error 0x%x" << GetLastError(); + } + } +} + +void AutomationProvider::OnForwardContextMenuCommandToChrome(int tab_handle, + int command) { + if (tab_tracker_->ContainsHandle(tab_handle)) { + NavigationController* tab = tab_tracker_->GetResource(tab_handle); + if (!tab) { + NOTREACHED(); + return; + } + + TabContents* tab_contents = tab->tab_contents(); + if (!tab_contents || !tab_contents->delegate()) { + NOTREACHED(); + return; + } + + tab_contents->delegate()->ExecuteContextMenuCommand(command); + } +} + +void AutomationProvider::ConnectExternalTab( + uint64 cookie, + bool allow, + gfx::NativeWindow parent_window, + gfx::NativeWindow* tab_container_window, + gfx::NativeWindow* tab_window, + int* tab_handle) { + *tab_handle = 0; + *tab_container_window = NULL; + *tab_window = NULL; + + scoped_refptr<ExternalTabContainer> external_tab_container = + ExternalTabContainer::RemovePendingTab(static_cast<uintptr_t>(cookie)); + if (!external_tab_container.get()) { + NOTREACHED(); + return; + } + + if (allow && AddExternalTab(external_tab_container)) { + external_tab_container->Reinitialize(this, + automation_resource_message_filter_, + parent_window); + TabContents* tab_contents = external_tab_container->tab_contents(); + *tab_handle = external_tab_container->tab_handle(); + *tab_container_window = external_tab_container->GetNativeView(); + *tab_window = tab_contents->GetNativeView(); + } else { + external_tab_container->Uninitialize(); + } +} + +void AutomationProvider::TerminateSession(int handle, bool* success) { + *success = false; + + if (browser_tracker_->ContainsHandle(handle)) { + Browser* browser = browser_tracker_->GetResource(handle); + HWND window = browser->window()->GetNativeHandle(); + *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."; + } +} + +void AutomationProvider::OnBrowserMoved(int tab_handle) { + ExternalTabContainer* external_tab = GetExternalTabForHandle(tab_handle); + if (external_tab) { + external_tab->WindowMoved(); + } else { + DLOG(WARNING) << + "AutomationProvider::OnBrowserMoved called with invalid tab handle."; + } +} + +void AutomationProvider::GetWindowTitle(int handle, string16* text) { + gfx::NativeWindow window = window_tracker_->GetResource(handle); + std::wstring result; + int length = ::GetWindowTextLength(window) + 1; + ::GetWindowText(window, WriteInto(&result, length), length); + text->assign(WideToUTF16(result)); +} + +void AutomationProvider::OnMessageFromExternalHost(int handle, + const std::string& message, + const std::string& origin, + const std::string& target) { + RenderViewHost* view_host = GetViewForTab(handle); + if (!view_host) + return; + + if (AutomationExtensionFunction::InterceptMessageFromExternalHost( + view_host, message, origin, target)) { + // Message was diverted. + return; + } + + if (ExtensionPortContainer::InterceptMessageFromExternalHost( + message, origin, target, this, view_host, handle)) { + // Message was diverted. + return; + } + + if (InterceptBrowserEventMessageFromExternalHost(message, origin, target)) { + // Message was diverted. + return; + } + + view_host->ForwardMessageFromExternalHost(message, origin, target); +} + +bool AutomationProvider::InterceptBrowserEventMessageFromExternalHost( + const std::string& message, const std::string& origin, + const std::string& target) { + if (target != + extension_automation_constants::kAutomationBrowserEventRequestTarget) + return false; + + if (origin != extension_automation_constants::kAutomationOrigin) { + LOG(WARNING) << "Wrong origin on automation browser event " << origin; + return false; + } + + // The message is a JSON-encoded array with two elements, both strings. The + // first is the name of the event to dispatch. The second is a JSON-encoding + // of the arguments specific to that event. + scoped_ptr<Value> message_value(base::JSONReader::Read(message, false)); + if (!message_value.get() || !message_value->IsType(Value::TYPE_LIST)) { + LOG(WARNING) << "Invalid browser event specified through automation"; + return false; + } + + const ListValue* args = static_cast<const ListValue*>(message_value.get()); + + std::string event_name; + if (!args->GetString(0, &event_name)) { + LOG(WARNING) << "No browser event name specified through automation"; + return false; + } + + std::string json_args; + if (!args->GetString(1, &json_args)) { + LOG(WARNING) << "No browser event args specified through automation"; + return false; + } + + if (profile()->GetExtensionMessageService()) { + profile()->GetExtensionMessageService()->DispatchEventToRenderers( + event_name, json_args, profile()->IsOffTheRecord(), GURL()); + } + + return true; +} + +void AutomationProvider::NavigateInExternalTab( + int handle, const GURL& url, const GURL& referrer, + AutomationMsg_NavigationResponseValues* status) { + *status = AUTOMATION_MSG_NAVIGATION_ERROR; + + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + tab->LoadURL(url, referrer, PageTransition::TYPED); + *status = AUTOMATION_MSG_NAVIGATION_SUCCESS; + } +} + +void AutomationProvider::NavigateExternalTabAtIndex( + int handle, int navigation_index, + AutomationMsg_NavigationResponseValues* status) { + *status = AUTOMATION_MSG_NAVIGATION_ERROR; + + if (tab_tracker_->ContainsHandle(handle)) { + NavigationController* tab = tab_tracker_->GetResource(handle); + tab->GoToIndex(navigation_index); + *status = AUTOMATION_MSG_NAVIGATION_SUCCESS; + } +} + +void AutomationProvider::OnRunUnloadHandlers( + int handle, gfx::NativeWindow notification_window, + int notification_message) { + ExternalTabContainer* external_tab = GetExternalTabForHandle(handle); + if (external_tab) { + external_tab->RunUnloadHandlers(notification_window, notification_message); + } +} + diff --git a/chrome/browser/automation/automation_resource_message_filter.cc b/chrome/browser/automation/automation_resource_message_filter.cc new file mode 100644 index 0000000..68fc346 --- /dev/null +++ b/chrome/browser/automation/automation_resource_message_filter.cc @@ -0,0 +1,389 @@ +// Copyright (c) 2006-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/browser/automation/automation_resource_message_filter.h" + +#include "base/histogram.h" +#include "base/path_service.h" +#include "base/stl_util-inl.h" +#include "chrome/browser/automation/url_request_automation_job.h" +#include "chrome/browser/net/url_request_failed_dns_job.h" +#include "chrome/browser/net/url_request_mock_http_job.h" +#include "chrome/browser/net/url_request_mock_util.h" +#include "chrome/browser/net/url_request_slow_download_job.h" +#include "chrome/browser/net/url_request_slow_http_job.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/test/automation/automation_messages.h" +#include "googleurl/src/gurl.h" +#include "net/base/cookie_store.h" +#include "net/base/net_errors.h" +#include "net/url_request/url_request_filter.h" + +AutomationResourceMessageFilter::RenderViewMap + AutomationResourceMessageFilter::filtered_render_views_; + +int AutomationResourceMessageFilter::unique_request_id_ = 1; + +AutomationResourceMessageFilter::AutomationResourceMessageFilter() + : channel_(NULL) { + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableFunction( + URLRequestAutomationJob::EnsureProtocolFactoryRegistered)); +} + +AutomationResourceMessageFilter::~AutomationResourceMessageFilter() { +} + +// Called on the IPC thread: +void AutomationResourceMessageFilter::OnFilterAdded(IPC::Channel* channel) { + DCHECK(channel_ == NULL); + channel_ = channel; +} + +void AutomationResourceMessageFilter::OnFilterRemoved() { + channel_ = NULL; +} + +// Called on the IPC thread: +void AutomationResourceMessageFilter::OnChannelConnected(int32 peer_pid) { +} + +// Called on the IPC thread: +void AutomationResourceMessageFilter::OnChannelClosing() { + channel_ = NULL; + request_map_.clear(); + + // Only erase RenderViews which are associated with this + // AutomationResourceMessageFilter instance. + RenderViewMap::iterator index = filtered_render_views_.begin(); + while (index != filtered_render_views_.end()) { + const AutomationDetails& details = (*index).second; + if (details.filter.get() == this) { + filtered_render_views_.erase(index++); + } else { + index++; + } + } +} + +// Called on the IPC thread: +bool AutomationResourceMessageFilter::OnMessageReceived( + const IPC::Message& message) { + int request_id; + if (URLRequestAutomationJob::MayFilterMessage(message, &request_id)) { + RequestMap::iterator it = request_map_.find(request_id); + if (it != request_map_.end()) { + URLRequestAutomationJob* job = it->second; + DCHECK(job); + if (job) { + job->OnMessage(message); + return true; + } + } + } + + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(AutomationResourceMessageFilter, message) + IPC_MESSAGE_HANDLER(AutomationMsg_SetFilteredInet, + OnSetFilteredInet) + IPC_MESSAGE_HANDLER(AutomationMsg_GetFilteredInetHitCount, + OnGetFilteredInetHitCount) + IPC_MESSAGE_HANDLER(AutomationMsg_RecordHistograms, + OnRecordHistograms) + IPC_MESSAGE_HANDLER(AutomationMsg_GetCookiesHostResponse, + OnGetCookiesHostResponse) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + return handled; +} + +// Called on the IPC thread: +bool AutomationResourceMessageFilter::Send(IPC::Message* message) { + // This has to be called on the IO thread. + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + if (!channel_) { + delete message; + return false; + } + + return channel_->Send(message); +} + +bool AutomationResourceMessageFilter::RegisterRequest( + URLRequestAutomationJob* job) { + if (!job) { + NOTREACHED(); + return false; + } + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + + // Register pending jobs in the pending request map for servicing later. + if (job->is_pending()) { + DCHECK(!ContainsKey(pending_request_map_, job->id())); + DCHECK(!ContainsKey(request_map_, job->id())); + pending_request_map_[job->id()] = job; + } else { + DCHECK(!ContainsKey(request_map_, job->id())); + DCHECK(!ContainsKey(pending_request_map_, job->id())); + request_map_[job->id()] = job; + } + + return true; +} + +void AutomationResourceMessageFilter::UnRegisterRequest( + URLRequestAutomationJob* job) { + if (!job) { + NOTREACHED(); + return; + } + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + + if (job->is_pending()) { + DCHECK(ContainsKey(pending_request_map_, job->id())); + pending_request_map_.erase(job->id()); + } else { + DCHECK(ContainsKey(request_map_, job->id())); + request_map_.erase(job->id()); + } +} + +bool AutomationResourceMessageFilter::RegisterRenderView( + int renderer_pid, int renderer_id, int tab_handle, + AutomationResourceMessageFilter* filter, + bool pending_view) { + if (!renderer_pid || !renderer_id || !tab_handle) { + NOTREACHED(); + return false; + } + + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableFunction( + AutomationResourceMessageFilter::RegisterRenderViewInIOThread, + renderer_pid, renderer_id, tab_handle, filter, pending_view)); + return true; +} + +void AutomationResourceMessageFilter::UnRegisterRenderView( + int renderer_pid, int renderer_id) { + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableFunction( + AutomationResourceMessageFilter::UnRegisterRenderViewInIOThread, + renderer_pid, renderer_id)); +} + +bool AutomationResourceMessageFilter::ResumePendingRenderView( + int renderer_pid, int renderer_id, int tab_handle, + AutomationResourceMessageFilter* filter) { + if (!renderer_pid || !renderer_id || !tab_handle) { + NOTREACHED(); + return false; + } + + ChromeThread::PostTask( + ChromeThread::IO, FROM_HERE, + NewRunnableFunction( + AutomationResourceMessageFilter::ResumePendingRenderViewInIOThread, + renderer_pid, renderer_id, tab_handle, filter)); + return true; +} + +void AutomationResourceMessageFilter::RegisterRenderViewInIOThread( + int renderer_pid, int renderer_id, + int tab_handle, AutomationResourceMessageFilter* filter, + bool pending_view) { + RenderViewMap::iterator automation_details_iter( + filtered_render_views_.find(RendererId(renderer_pid, renderer_id))); + if (automation_details_iter != filtered_render_views_.end()) { + DCHECK(automation_details_iter->second.ref_count > 0); + automation_details_iter->second.ref_count++; + } else { + filtered_render_views_[RendererId(renderer_pid, renderer_id)] = + AutomationDetails(tab_handle, filter, pending_view); + } +} + +// static +void AutomationResourceMessageFilter::UnRegisterRenderViewInIOThread( + int renderer_pid, int renderer_id) { + RenderViewMap::iterator automation_details_iter( + filtered_render_views_.find(RendererId(renderer_pid, renderer_id))); + + if (automation_details_iter == filtered_render_views_.end()) { + LOG(INFO) << "UnRegisterRenderViewInIOThread: already unregistered"; + return; + } + + automation_details_iter->second.ref_count--; + + if (automation_details_iter->second.ref_count <= 0) { + filtered_render_views_.erase(RendererId(renderer_pid, renderer_id)); + } +} + +// static +bool AutomationResourceMessageFilter::ResumePendingRenderViewInIOThread( + int renderer_pid, int renderer_id, int tab_handle, + AutomationResourceMessageFilter* filter) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + + RenderViewMap::iterator automation_details_iter( + filtered_render_views_.find(RendererId(renderer_pid, renderer_id))); + + if (automation_details_iter == filtered_render_views_.end()) { + NOTREACHED() << "Failed to find pending view for renderer pid:" + << renderer_pid + << ", render view id:" + << renderer_id; + return false; + } + + DCHECK(automation_details_iter->second.is_pending_render_view); + + AutomationResourceMessageFilter* old_filter = + automation_details_iter->second.filter; + DCHECK(old_filter != NULL); + + filtered_render_views_[RendererId(renderer_pid, renderer_id)] = + AutomationDetails(tab_handle, filter, false); + + ResumeJobsForPendingView(tab_handle, old_filter, filter); + return true; +} + +bool AutomationResourceMessageFilter::LookupRegisteredRenderView( + int renderer_pid, int renderer_id, AutomationDetails* details) { + bool found = false; + RenderViewMap::iterator it = filtered_render_views_.find(RendererId( + renderer_pid, renderer_id)); + if (it != filtered_render_views_.end()) { + found = true; + if (details) + *details = it->second; + } + + return found; +} + +bool AutomationResourceMessageFilter::GetAutomationRequestId( + int request_id, int* automation_request_id) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + + RequestMap::iterator it = request_map_.begin(); + while (it != request_map_.end()) { + URLRequestAutomationJob* job = it->second; + DCHECK(job); + if (job && job->request_id() == request_id) { + *automation_request_id = job->id(); + return true; + } + it++; + } + + return false; +} + +bool AutomationResourceMessageFilter::SendDownloadRequestToHost( + int routing_id, int tab_handle, int request_id) { + int automation_request_id = 0; + bool valid_id = GetAutomationRequestId(request_id, &automation_request_id); + if (!valid_id) { + NOTREACHED() << "Invalid request id: " << request_id; + return false; + } + + return Send(new AutomationMsg_DownloadRequestInHost(0, tab_handle, + automation_request_id)); +} + +void AutomationResourceMessageFilter::OnSetFilteredInet(bool enable) { + chrome_browser_net::SetUrlRequestMocksEnabled(enable); +} + +void AutomationResourceMessageFilter::OnGetFilteredInetHitCount( + int* hit_count) { + *hit_count = URLRequestFilter::GetInstance()->hit_count(); +} + +void AutomationResourceMessageFilter::OnRecordHistograms( + const std::vector<std::string>& histogram_list) { + for (size_t index = 0; index < histogram_list.size(); ++index) { + Histogram::DeserializeHistogramInfo(histogram_list[index]); + } +} + +void AutomationResourceMessageFilter::GetCookiesForUrl( + int tab_handle, const GURL& url, net::CompletionCallback* callback, + net::CookieStore* cookie_store) { + DCHECK(callback != NULL); + DCHECK(cookie_store != NULL); + + int completion_callback_id = GetNextCompletionCallbackId(); + DCHECK(!ContainsKey(completion_callback_map_, completion_callback_id)); + + CookieCompletionInfo cookie_info; + cookie_info.completion_callback = callback; + cookie_info.cookie_store = cookie_store; + + completion_callback_map_[completion_callback_id] = cookie_info; + + Send(new AutomationMsg_GetCookiesFromHost(0, tab_handle, url, + completion_callback_id)); +} + +void AutomationResourceMessageFilter::OnGetCookiesHostResponse( + int tab_handle, bool success, const GURL& url, const std::string& cookies, + int cookie_id) { + CompletionCallbackMap::iterator index = + completion_callback_map_.find(cookie_id); + if (index != completion_callback_map_.end()) { + net::CompletionCallback* callback = index->second.completion_callback; + scoped_refptr<net::CookieStore> cookie_store = index->second.cookie_store; + + DCHECK(callback != NULL); + DCHECK(cookie_store.get() != NULL); + + completion_callback_map_.erase(index); + + // Set the cookie in the cookie store so that the callback can read it. + cookie_store->SetCookieWithOptions(url, cookies, net::CookieOptions()); + + Tuple1<int> params; + params.a = success ? net::OK : net::ERR_ACCESS_DENIED; + callback->RunWithParams(params); + + // The cookie for this URL is only valid until it is read by the callback. + cookie_store->SetCookieWithOptions(url, "", net::CookieOptions()); + } else { + NOTREACHED() << "Received invalid completion callback id:" + << cookie_id; + } +} + +// static +void AutomationResourceMessageFilter::ResumeJobsForPendingView( + int tab_handle, + AutomationResourceMessageFilter* old_filter, + AutomationResourceMessageFilter* new_filter) { + DCHECK(old_filter != NULL); + DCHECK(new_filter != NULL); + + RequestMap pending_requests = old_filter->pending_request_map_; + + for (RequestMap::iterator index = old_filter->pending_request_map_.begin(); + index != old_filter->pending_request_map_.end(); index++) { + scoped_refptr<URLRequestAutomationJob> job = (*index).second; + DCHECK_EQ(job->message_filter(), old_filter); + DCHECK(job->is_pending()); + // StartPendingJob will register the job with the new filter. + job->StartPendingJob(tab_handle, new_filter); + } + + old_filter->pending_request_map_.clear(); +} diff --git a/chrome/browser/automation/automation_resource_message_filter.h b/chrome/browser/automation/automation_resource_message_filter.h new file mode 100644 index 0000000..55b9bf2 --- /dev/null +++ b/chrome/browser/automation/automation_resource_message_filter.h @@ -0,0 +1,202 @@ +// Copyright (c) 2006-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. + +#ifndef CHROME_BROWSER_AUTOMATION_AUTOMATION_RESOURCE_MESSAGE_FILTER_H_ +#define CHROME_BROWSER_AUTOMATION_AUTOMATION_RESOURCE_MESSAGE_FILTER_H_ + +#include <map> + +#include "base/atomicops.h" +#include "base/lock.h" +#include "base/platform_thread.h" +#include "ipc/ipc_channel_proxy.h" +#include "net/base/completion_callback.h" + +class URLRequestAutomationJob; +class GURL; + +namespace net { +class CookieStore; +} // namespace net + +// This class filters out incoming automation IPC messages for network +// requests and processes them on the IPC thread. As a result, network +// requests are not delayed by costly UI processing that may be occurring +// on the main thread of the browser. It also means that any hangs in +// starting a network request will not interfere with browser UI. +class AutomationResourceMessageFilter + : public IPC::ChannelProxy::MessageFilter, + public IPC::Message::Sender { + public: + // Information needed to send IPCs through automation. + struct AutomationDetails { + AutomationDetails() : tab_handle(0), ref_count(1), + is_pending_render_view(false) {} + AutomationDetails(int tab, AutomationResourceMessageFilter* flt, + bool pending_view) + : tab_handle(tab), ref_count(1), filter(flt), + is_pending_render_view(pending_view) { + } + + int tab_handle; + int ref_count; + scoped_refptr<AutomationResourceMessageFilter> filter; + // Indicates whether network requests issued by this render view need to + // be executed later. + bool is_pending_render_view; + }; + + // Create the filter. + AutomationResourceMessageFilter(); + virtual ~AutomationResourceMessageFilter(); + + // Returns a new automation request id. This is unique across all instances + // of AutomationResourceMessageFilter. + int NewAutomationRequestId() { + return base::subtle::Barrier_AtomicIncrement(&unique_request_id_, 1); + } + + // IPC::ChannelProxy::MessageFilter methods: + virtual void OnFilterAdded(IPC::Channel* channel); + virtual void OnFilterRemoved(); + + virtual void OnChannelConnected(int32 peer_pid); + virtual void OnChannelClosing(); + virtual bool OnMessageReceived(const IPC::Message& message); + + // ResourceDispatcherHost::Receiver methods: + virtual bool Send(IPC::Message* message); + + // Add request to the list of outstanding requests. + virtual bool RegisterRequest(URLRequestAutomationJob* job); + + // Remove request from the list of outstanding requests. + virtual void UnRegisterRequest(URLRequestAutomationJob* job); + + // Can be called from the UI thread. + // The pending_view parameter should be true if network requests initiated by + // this render view need to be paused waiting for an acknowledgement from + // the external host. + static bool RegisterRenderView(int renderer_pid, int renderer_id, + int tab_handle, AutomationResourceMessageFilter* filter, + bool pending_view); + static void UnRegisterRenderView(int renderer_pid, int renderer_id); + + // Can be called from the UI thread. + // Resumes pending render views, i.e. network requests issued by this view + // can now be serviced. + static bool ResumePendingRenderView(int renderer_pid, int renderer_id, + int tab_handle, AutomationResourceMessageFilter* filter); + + // Called only on the IO thread. + static bool LookupRegisteredRenderView( + int renderer_pid, int renderer_id, AutomationDetails* details); + + // Sends the download request to the automation host. + bool SendDownloadRequestToHost(int routing_id, int tab_handle, + int request_id); + + // Retrieves cookies for the url passed in from the external host. The + // callback passed in is notified on success or failure asynchronously. + void GetCookiesForUrl(int tab_handle, const GURL& url, + net::CompletionCallback* callback, + net::CookieStore* cookie_store); + + // This function gets invoked when we receive a response from the external + // host for the cookie request sent in GetCookiesForUrl above. It sets the + // cookie temporarily on the cookie store and executes the completion + // callback which reads the cookie from the store. The cookie value is reset + // after the callback finishes executing. + void OnGetCookiesHostResponse(int tab_handle, bool success, const GURL& url, + const std::string& cookies, int cookie_id); + + protected: + // Retrieves the automation request id for the passed in chrome request + // id and returns it in the automation_request_id parameter. + // Returns true on success. + bool GetAutomationRequestId(int request_id, int* automation_request_id); + + static void RegisterRenderViewInIOThread(int renderer_pid, int renderer_id, + int tab_handle, AutomationResourceMessageFilter* filter, + bool pending_view); + static void UnRegisterRenderViewInIOThread(int renderer_pid, int renderer_id); + + static bool ResumePendingRenderViewInIOThread( + int renderer_pid, int renderer_id, int tab_handle, + AutomationResourceMessageFilter* filter); + + private: + void OnSetFilteredInet(bool enable); + void OnGetFilteredInetHitCount(int* hit_count); + void OnRecordHistograms(const std::vector<std::string>& histogram_list); + + // Resumes pending jobs from the old AutomationResourceMessageFilter instance + // passed in. + static void ResumeJobsForPendingView( + int tab_handle, + AutomationResourceMessageFilter* old_filter, + AutomationResourceMessageFilter* new_filter); + + int GetNextCompletionCallbackId() { + return ++next_completion_callback_id_; + } + + // A unique renderer id is a combination of renderer process id and + // it's routing id. + struct RendererId { + int pid_; + int id_; + + RendererId() : pid_(0), id_(0) {} + RendererId(int pid, int id) : pid_(pid), id_(id) {} + + bool operator < (const RendererId& rhs) const { + return ((pid_ == rhs.pid_) ? (id_ < rhs.id_) : (pid_ < rhs.pid_)); + } + }; + + typedef std::map<RendererId, AutomationDetails> RenderViewMap; + typedef std::map<int, scoped_refptr<URLRequestAutomationJob> > RequestMap; + + // The channel associated with the automation connection. This pointer is not + // owned by this class. + IPC::Channel* channel_; + + // A unique request id per process. + static int unique_request_id_; + + // Map of outstanding requests. + RequestMap request_map_; + + // Map of pending requests, i.e. requests which were waiting for the external + // host to connect back. + RequestMap pending_request_map_; + + // Map of render views interested in diverting url requests over automation. + static RenderViewMap filtered_render_views_; + + // Contains information used for completing the request to read cookies from + // the host coming in from the renderer. + struct CookieCompletionInfo { + net::CompletionCallback* completion_callback; + scoped_refptr<net::CookieStore> cookie_store; + }; + + // Map of completion callback id to CookieCompletionInfo, which contains the + // actual callback which is invoked on successful retrieval of cookies from + // host. The mapping is setup when GetCookiesForUrl is invoked to retrieve + // cookies from the host and is removed when we receive a response from the + // host. Please see the OnGetCookiesHostResponse function. + typedef std::map<int, CookieCompletionInfo> CompletionCallbackMap; + CompletionCallbackMap completion_callback_map_; + + // Contains the id of the next completion callback. This is passed to the the + // external host as a cookie referring to the completion callback. + int next_completion_callback_id_; + + DISALLOW_COPY_AND_ASSIGN(AutomationResourceMessageFilter); +}; + +#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_RESOURCE_MESSAGE_FILTER_H_ + diff --git a/chrome/browser/automation/automation_resource_routing_delegate.cc b/chrome/browser/automation/automation_resource_routing_delegate.cc new file mode 100644 index 0000000..954b05e --- /dev/null +++ b/chrome/browser/automation/automation_resource_routing_delegate.cc @@ -0,0 +1,19 @@ +// 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_resource_routing_delegate.h" + +void AutomationResourceRoutingDelegate::RegisterRenderViewHost( + RenderViewHost* render_view_host) { +} + +void AutomationResourceRoutingDelegate::UnregisterRenderViewHost( + RenderViewHost* render_view_host) { +} + +AutomationResourceRoutingDelegate::AutomationResourceRoutingDelegate() { +} + +AutomationResourceRoutingDelegate::~AutomationResourceRoutingDelegate() { +} diff --git a/chrome/browser/automation/automation_resource_routing_delegate.h b/chrome/browser/automation/automation_resource_routing_delegate.h new file mode 100644 index 0000000..2438e4d --- /dev/null +++ b/chrome/browser/automation/automation_resource_routing_delegate.h @@ -0,0 +1,31 @@ +// 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_RESOURCE_ROUTING_DELEGATE_H_ +#define CHROME_BROWSER_AUTOMATION_AUTOMATION_RESOURCE_ROUTING_DELEGATE_H_ + +#include "base/basictypes.h" + +class RenderViewHost; + +// Interface for registering RenderViewHost instances for resource routing +// automation. +class AutomationResourceRoutingDelegate { + public: + // Call to register |render_view_host| for resource routing automation + // by the delegate. + virtual void RegisterRenderViewHost(RenderViewHost* render_view_host); + + // Call to unregister |render_view_host| from resource routing automation. + virtual void UnregisterRenderViewHost(RenderViewHost* render_view_host); + + protected: + AutomationResourceRoutingDelegate(); + virtual ~AutomationResourceRoutingDelegate(); + + private: + DISALLOW_COPY_AND_ASSIGN(AutomationResourceRoutingDelegate); +}; + +#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_RESOURCE_ROUTING_DELEGATE_H_ diff --git a/chrome/browser/automation/automation_resource_tracker.cc b/chrome/browser/automation/automation_resource_tracker.cc new file mode 100644 index 0000000..de20ba1 --- /dev/null +++ b/chrome/browser/automation/automation_resource_tracker.cc @@ -0,0 +1,76 @@ +// 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. + +#include "chrome/browser/automation/automation_resource_tracker.h" + +#include "chrome/common/notification_service.h" +#include "chrome/test/automation/automation_messages.h" + +int AutomationResourceTrackerImpl::AddImpl(void* resource) { + if (ContainsResourceImpl(resource)) + return resource_to_handle_[resource]; + + int handle = GenerateHandle(); + DCHECK(!ContainsHandleImpl(handle)); + + resource_to_handle_[resource] = handle; + handle_to_resource_[handle] = resource; + + AddObserverTypeProxy(resource); + + return handle; +} + +void AutomationResourceTrackerImpl::RemoveImpl(void* resource) { + if (!ContainsResourceImpl(resource)) + return; + + int handle = resource_to_handle_[resource]; + DCHECK(handle_to_resource_[handle] == resource); + + RemoveObserverTypeProxy(resource); + + resource_to_handle_.erase(resource); + handle_to_resource_.erase(handle); +} + +int AutomationResourceTrackerImpl::GenerateHandle() { + static int handle = 0; + return ++handle; +} + +bool AutomationResourceTrackerImpl::ContainsResourceImpl(void* resource) { + return resource_to_handle_.find(resource) != resource_to_handle_.end(); +} + +bool AutomationResourceTrackerImpl::ContainsHandleImpl(int handle) { + return handle_to_resource_.find(handle) != handle_to_resource_.end(); +} + +void* AutomationResourceTrackerImpl::GetResourceImpl(int handle) { + HandleToResourceMap::const_iterator iter = handle_to_resource_.find(handle); + if (iter == handle_to_resource_.end()) + return NULL; + + return iter->second; +} + +int AutomationResourceTrackerImpl::GetHandleImpl(void* resource) { + ResourceToHandleMap::const_iterator iter = + resource_to_handle_.find(resource); + if (iter == resource_to_handle_.end()) + return 0; + + return iter->second; +} + +void AutomationResourceTrackerImpl::HandleCloseNotification(void* resource) { + if (!ContainsResourceImpl(resource)) + return; + + sender_->Send( + new AutomationMsg_InvalidateHandle(0, resource_to_handle_[resource])); + + RemoveImpl(resource); +} diff --git a/chrome/browser/automation/automation_resource_tracker.h b/chrome/browser/automation/automation_resource_tracker.h new file mode 100644 index 0000000..58703e7 --- /dev/null +++ b/chrome/browser/automation/automation_resource_tracker.h @@ -0,0 +1,158 @@ +// 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_RESOURCE_TRACKER_H__ +#define CHROME_BROWSER_AUTOMATION_AUTOMATION_RESOURCE_TRACKER_H__ + +#include <map> + +#include "base/basictypes.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_type.h" +#include "ipc/ipc_message.h" + +template <class T> class Source; + +// Template trick so that AutomationResourceTracker can be used with non-pointer +// types. +template <class T> +struct AutomationResourceTraits { + typedef T ValueType; +}; + +template <class T> +struct AutomationResourceTraits<T*> { + typedef T ValueType; +}; + +// This class exists for the sole purpose of allowing some of the implementation +// of AutomationResourceTracker to live in a .cc file. +class AutomationResourceTrackerImpl { + public: + explicit AutomationResourceTrackerImpl(IPC::Message::Sender* sender) + : sender_(sender) {} + + virtual ~AutomationResourceTrackerImpl() {} + + // These need to be implemented in AutomationResourceTracker, + // since it needs to call the subclass's type-specific notification + // registration functions. + virtual void AddObserverTypeProxy(void* resource) = 0; + virtual void RemoveObserverTypeProxy(void* resource) = 0; + + int AddImpl(void* resource); + void RemoveImpl(void* resource); + int GenerateHandle(); + bool ContainsResourceImpl(void* resource); + bool ContainsHandleImpl(int handle); + void* GetResourceImpl(int handle); + int GetHandleImpl(void* resource); + void HandleCloseNotification(void* resource); + + protected: + typedef std::map<void*, int> ResourceToHandleMap; + typedef std::map<int, void*> HandleToResourceMap; + ResourceToHandleMap resource_to_handle_; + HandleToResourceMap handle_to_resource_; + + private: + DISALLOW_COPY_AND_ASSIGN(AutomationResourceTrackerImpl); + + IPC::Message::Sender* sender_; +}; + +// This template defines a superclass for an object that wants to track +// a particular kind of application resource (like windows or tabs) for +// automation purposes. The only things that a subclass should need to +// define are AddObserver and RemoveObserver for the given resource's +// close notifications. +template <class T> +class AutomationResourceTracker : public NotificationObserver, + private AutomationResourceTrackerImpl { + public: + explicit AutomationResourceTracker(IPC::Message::Sender* automation) + : AutomationResourceTrackerImpl(automation) {} + + virtual ~AutomationResourceTracker() { + } + + // The implementations for these should call the NotificationService + // to add and remove this object as an observer for the appropriate + // resource closing notification. + virtual void AddObserver(T resource) = 0; + virtual void RemoveObserver(T resource) = 0; + + // Adds the given resource to this tracker, and returns a handle that + // can be used to refer to that resource. If the resource is already + // being tracked, the handle may be the same as one returned previously. + int Add(T resource) { + return AddImpl(resource); + } + + // Removes the given resource from this tracker. If the resource is not + // currently present in the tracker, this is a no-op. + void Remove(T resource) { + RemoveImpl(resource); + } + + // Returns true if this tracker currently tracks the resource pointed to + // by the parameter. + bool ContainsResource(T resource) { + return ContainsResourceImpl(resource); + } + + // Returns true if this tracker currently tracks the given handle. + bool ContainsHandle(int handle) { + return ContainsHandleImpl(handle); + } + + // Returns the resource pointer associated with a given handle, or NULL + // if that handle is not present in the mapping. + T GetResource(int handle) { + return static_cast<T>(GetResourceImpl(handle)); + } + + // Returns the handle associated with a given resource pointer, or 0 if + // the resource is not currently in the mapping. + int GetHandle(T resource) { + return GetHandleImpl(resource); + } + + // NotificationObserver implementation--the only thing that this tracker + // does in response to notifications is to tell the AutomationProxy + // that the associated handle is now invalid. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + T resource = + Source<typename AutomationResourceTraits<T>::ValueType>(source).ptr(); + + 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: + // These proxy calls from the base Impl class to the template's subclss. + virtual void AddObserverTypeProxy(void* resource) { + AddObserver(static_cast<T>(resource)); + } + virtual void RemoveObserverTypeProxy(void* resource) { + RemoveObserver(static_cast<T>(resource)); + } + + DISALLOW_COPY_AND_ASSIGN(AutomationResourceTracker); +}; + +#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_RESOURCE_TRACKER_H__ diff --git a/chrome/browser/automation/automation_tab_tracker.h b/chrome/browser/automation/automation_tab_tracker.h new file mode 100644 index 0000000..02b40be --- /dev/null +++ b/chrome/browser/automation/automation_tab_tracker.h @@ -0,0 +1,92 @@ +// 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_BROWSER_AUTOMATION_AUTOMATION_TAB_TRACKER_H_ +#define CHROME_BROWSER_AUTOMATION_AUTOMATION_TAB_TRACKER_H_ + +#include <map> + +#include "base/time.h" +#include "chrome/browser/automation/automation_resource_tracker.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_type.h" + +class AutomationTabTracker + : public AutomationResourceTracker<NavigationController*> { + public: + explicit AutomationTabTracker(IPC::Message::Sender* automation) + : AutomationResourceTracker<NavigationController*>(automation) {} + + virtual ~AutomationTabTracker() { + } + + virtual void AddObserver(NavigationController* resource) { + // This tab could either be a regular tab or an external tab + // Register for both notifications. + registrar_.Add(this, NotificationType::TAB_CLOSING, + Source<NavigationController>(resource)); + registrar_.Add(this, NotificationType::EXTERNAL_TAB_CLOSED, + Source<NavigationController>(resource)); + // We also want to know about navigations so we can keep track of the last + // navigation time. + registrar_.Add(this, NotificationType::LOAD_STOP, + Source<NavigationController>(resource)); + } + + virtual void RemoveObserver(NavigationController* resource) { + registrar_.Remove(this, NotificationType::TAB_CLOSING, + Source<NavigationController>(resource)); + registrar_.Remove(this, NotificationType::EXTERNAL_TAB_CLOSED, + Source<NavigationController>(resource)); + registrar_.Remove(this, NotificationType::LOAD_STOP, + Source<NavigationController>(resource)); + } + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type.value) { + case NotificationType::LOAD_STOP: + last_navigation_times_[Source<NavigationController>(source).ptr()] = + base::Time::Now(); + return; + case NotificationType::EXTERNAL_TAB_CLOSED: + case NotificationType::TAB_CLOSING: + { + std::map<NavigationController*, base::Time>::iterator iter = + last_navigation_times_.find( + Source<NavigationController>(source).ptr()); + if (iter != last_navigation_times_.end()) + last_navigation_times_.erase(iter); + } + break; + default: + NOTREACHED(); + } + AutomationResourceTracker<NavigationController*>::Observe(type, source, + details); + } + + base::Time GetLastNavigationTime(int handle) { + if (ContainsHandle(handle)) { + NavigationController* controller = GetResource(handle); + if (controller) { + std::map<NavigationController*, base::Time>::const_iterator iter = + last_navigation_times_.find(controller); + if (iter != last_navigation_times_.end()) + return iter->second; + } + } + return base::Time(); + } + + private: + // Last time a navigation occurred. + std::map<NavigationController*, base::Time> last_navigation_times_; + + DISALLOW_COPY_AND_ASSIGN(AutomationTabTracker); +}; + +#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_TAB_TRACKER_H_ diff --git a/chrome/browser/automation/automation_window_tracker.h b/chrome/browser/automation/automation_window_tracker.h new file mode 100644 index 0000000..02cfeea --- /dev/null +++ b/chrome/browser/automation/automation_window_tracker.h @@ -0,0 +1,32 @@ +// 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_BROWSER_AUTOMATION_AUTOMATION_WINDOW_TRACKER_H_ +#define CHROME_BROWSER_AUTOMATION_AUTOMATION_WINDOW_TRACKER_H_ + +#include "build/build_config.h" +#include "chrome/browser/automation/automation_resource_tracker.h" +#include "chrome/common/native_window_notification_source.h" +#include "gfx/native_widget_types.h" + +class AutomationWindowTracker + : public AutomationResourceTracker<gfx::NativeWindow> { + public: + explicit AutomationWindowTracker(IPC::Message::Sender* automation) + : AutomationResourceTracker<gfx::NativeWindow>(automation) { } + virtual ~AutomationWindowTracker() { + } + + virtual void AddObserver(gfx::NativeWindow resource) { + registrar_.Add(this, NotificationType::WINDOW_CLOSED, + Source<gfx::NativeWindow>(resource)); + } + + virtual void RemoveObserver(gfx::NativeWindow resource) { + registrar_.Remove(this, NotificationType::WINDOW_CLOSED, + Source<gfx::NativeWindow>(resource)); + } +}; + +#endif // CHROME_BROWSER_AUTOMATION_AUTOMATION_WINDOW_TRACKER_H_ diff --git a/chrome/browser/automation/chrome_frame_automation_provider.cc b/chrome/browser/automation/chrome_frame_automation_provider.cc new file mode 100644 index 0000000..52f8ded --- /dev/null +++ b/chrome/browser/automation/chrome_frame_automation_provider.cc @@ -0,0 +1,79 @@ +// 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/chrome_frame_automation_provider.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/profile_manager.h" +#include "chrome/test/automation/automation_messages.h" +#include "ipc/ipc_message.h" +#include "ipc/ipc_channel.h" + +ChromeFrameAutomationProvider::ChromeFrameAutomationProvider(Profile* profile) + : AutomationProvider(profile) {} + +void ChromeFrameAutomationProvider::OnMessageReceived( + const IPC::Message& message) { + if (IsValidMessage(message.type())) { + AutomationProvider::OnMessageReceived(message); + } else { + OnUnhandledMessage(message); + } +} + +void ChromeFrameAutomationProvider::OnUnhandledMessage( + const IPC::Message& message) { + NOTREACHED() << __FUNCTION__ + << " Unhandled message type: " + << message.type(); +} + +bool ChromeFrameAutomationProvider::IsValidMessage(uint32 type) { + bool is_valid_message = false; + + switch (type) { + case AutomationMsg_CreateExternalTab::ID: + case AutomationMsg_ConnectExternalTab::ID: +#if defined(OS_WIN) + case AutomationMsg_BrowserMove::ID: + case AutomationMsg_ProcessUnhandledAccelerator::ID: + case AutomationMsg_TabReposition::ID: + case AutomationMsg_ForwardContextMenuCommandToChrome::ID: +#endif // defined(OS_WIN) + case AutomationMsg_NavigateInExternalTab::ID: + case AutomationMsg_NavigateExternalTabAtIndex::ID: + case AutomationMsg_Find::ID: + case AutomationMsg_InstallExtension::ID: + case AutomationMsg_LoadExpandedExtension::ID: + case AutomationMsg_GetEnabledExtensions::ID: + case AutomationMsg_SetEnableExtensionAutomation::ID: + case AutomationMsg_SetInitialFocus::ID: + case AutomationMsg_SetPageFontSize::ID: + case AutomationMsg_SetProxyConfig::ID: + case AutomationMsg_Cut::ID: + case AutomationMsg_Copy::ID: + case AutomationMsg_Paste::ID: + case AutomationMsg_SelectAll::ID: + case AutomationMsg_ReloadAsync::ID: + case AutomationMsg_StopAsync::ID: + case AutomationMsg_PrintAsync::ID: + case AutomationMsg_HandleUnused::ID: + case AutomationMsg_HandleMessageFromExternalHost::ID: + case AutomationMsg_RequestStarted::ID: + case AutomationMsg_RequestData::ID: + case AutomationMsg_RequestEnd::ID: + case AutomationMsg_SaveAsAsync::ID: + case AutomationMsg_RemoveBrowsingData::ID: + case AutomationMsg_OverrideEncoding::ID: + case AutomationMsg_RunUnloadHandlers::ID: { + is_valid_message = true; + break; + } + + default: + break; + } + + return is_valid_message; +} + diff --git a/chrome/browser/automation/chrome_frame_automation_provider.h b/chrome/browser/automation/chrome_frame_automation_provider.h new file mode 100644 index 0000000..0ab6a8f --- /dev/null +++ b/chrome/browser/automation/chrome_frame_automation_provider.h @@ -0,0 +1,41 @@ +// Copyright (c) 2006-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. + +// This implements a browser-side endpoint for ChromeFrame UI automation +// activity. The client-side endpoint is implemented by +// ChromeFrameAutomationClient. +// The entire lifetime of this object should be contained within that of +// the BrowserProcess + +#ifndef CHROME_BROWSER_AUTOMATION_CHROME_FRAME_AUTOMATION_PROVIDER_H_ +#define CHROME_BROWSER_AUTOMATION_CHROME_FRAME_AUTOMATION_PROVIDER_H_ + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/automation/automation_provider.h" + +class Profile; + +// This class services automation IPC requests coming in from ChromeFrame +// instances. +class ChromeFrameAutomationProvider : public AutomationProvider { + public: + explicit ChromeFrameAutomationProvider(Profile* profile); + + // IPC::Channel::Listener overrides. + virtual void OnMessageReceived(const IPC::Message& message); + + protected: + // This function is called when we receive an invalid message type. + virtual void OnUnhandledMessage(const IPC::Message& message); + + // Returns true if the message received is a valid chrome frame message. + bool IsValidMessage(uint32 type); + + private: + DISALLOW_COPY_AND_ASSIGN(ChromeFrameAutomationProvider); +}; + +#endif // CHROME_BROWSER_AUTOMATION_CHROME_FRAME_AUTOMATION_PROVIDER_H_ + diff --git a/chrome/browser/automation/extension_automation_constants.cc b/chrome/browser/automation/extension_automation_constants.cc new file mode 100644 index 0000000..c513945 --- /dev/null +++ b/chrome/browser/automation/extension_automation_constants.cc @@ -0,0 +1,32 @@ +// 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/browser/automation/extension_automation_constants.h" + +namespace extension_automation_constants { + +const char kAutomationOrigin[] = "__priv_xtapi"; +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 kAutomationRequestTarget[] = "__priv_xtreq"; +const char kAutomationResponseTarget[] = "__priv_xtres"; + +const wchar_t kAutomationConnectionIdKey[] = L"connid"; +const wchar_t kAutomationMessageDataKey[] = L"data"; +const wchar_t kAutomationExtensionIdKey[] = L"extid"; +const wchar_t kAutomationPortIdKey[] = L"portid"; +const wchar_t kAutomationChannelNameKey[] = L"chname"; +const wchar_t kAutomationTabJsonKey[] = L"tab"; + +const char kAutomationPortRequestTarget[] = "__priv_prtreq"; +const char kAutomationPortResponseTarget[] = "__priv_prtres"; + +const char kAutomationBrowserEventRequestTarget[] = "__priv_evtreq"; + +} // namespace extension_automation_constants diff --git a/chrome/browser/automation/extension_automation_constants.h b/chrome/browser/automation/extension_automation_constants.h new file mode 100644 index 0000000..8c29293 --- /dev/null +++ b/chrome/browser/automation/extension_automation_constants.h @@ -0,0 +1,54 @@ +// 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_BROWSER_AUTOMATION_EXTENSION_AUTOMATION_CONSTANTS_H_ +#define CHROME_BROWSER_AUTOMATION_EXTENSION_AUTOMATION_CONSTANTS_H_ + +namespace extension_automation_constants { + +// All extension automation related messages will have this origin. +extern const char kAutomationOrigin[]; +// Key used for all extension automation request types. +extern const wchar_t kAutomationRequestIdKey[]; + +// Keys used for API communications +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[]; +// All external API requests have this target. +extern const char kAutomationRequestTarget[]; +// All API responses should have this target. +extern const char kAutomationResponseTarget[]; + +// Keys used for port communications +extern const wchar_t kAutomationConnectionIdKey[]; +extern const wchar_t kAutomationMessageDataKey[]; +extern const wchar_t kAutomationExtensionIdKey[]; +extern const wchar_t kAutomationPortIdKey[]; +extern const wchar_t kAutomationChannelNameKey[]; +extern const wchar_t kAutomationTabJsonKey[]; + +// All external port message requests should have this target. +extern const char kAutomationPortRequestTarget[]; +// All external port message responses have this target. +extern const char kAutomationPortResponseTarget[]; + +// All external browser events have this target. +extern const char kAutomationBrowserEventRequestTarget[]; + +// The command codes for our private port protocol. +enum PrivatePortCommand { + OPEN_CHANNEL = 0, + CHANNEL_OPENED = 1, + POST_MESSAGE = 2, + CHANNEL_CLOSED = 3, +}; + +}; // namespace automation_extension_constants + +#endif // CHROME_BROWSER_AUTOMATION_EXTENSION_AUTOMATION_CONSTANTS_H_ diff --git a/chrome/browser/automation/extension_port_container.cc b/chrome/browser/automation/extension_port_container.cc new file mode 100644 index 0000000..e93f42c --- /dev/null +++ b/chrome/browser/automation/extension_port_container.cc @@ -0,0 +1,266 @@ +// 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/extension_port_container.h" + +#include "base/logging.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/values.h" +#include "chrome/browser/automation/automation_provider.h" +#include "chrome/browser/automation/extension_automation_constants.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/extensions/extension_message_service.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/renderer_host/render_process_host.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/render_messages.h" +#include "chrome/test/automation/automation_messages.h" + +// TODO(siggi): Find a more structured way to read and write JSON messages. + +namespace ext = extension_automation_constants; + +ExtensionPortContainer::ExtensionPortContainer(AutomationProvider* automation, + int tab_handle) : + automation_(automation), service_(NULL), port_id_(-1), + tab_handle_(tab_handle) { + service_ = automation_->profile()->GetExtensionMessageService(); + DCHECK(service_); +} + +ExtensionPortContainer::~ExtensionPortContainer() { + DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); + + if (port_id_ != -1) + service_->CloseChannel(port_id_); + + NotificationService::current()->Notify( + NotificationType::EXTENSION_PORT_DELETED_DEBUG, + Source<IPC::Message::Sender>(this), + NotificationService::NoDetails()); +} + +bool ExtensionPortContainer::PostResponseToExternalPort( + const std::string& message) { + return automation_->Send( + new AutomationMsg_ForwardMessageToExternalHost( + 0, tab_handle_, message, ext::kAutomationOrigin, + ext::kAutomationPortResponseTarget)); +} + +bool ExtensionPortContainer::PostMessageToExternalPort( + const std::string& message) { + return automation_->Send( + new AutomationMsg_ForwardMessageToExternalHost( + 0, tab_handle_, message, + ext::kAutomationOrigin, + ext::kAutomationPortRequestTarget)); +} + +void ExtensionPortContainer::PostMessageFromExternalPort( + const std::string &message) { + service_->PostMessageFromRenderer(port_id_, message); +} + +bool ExtensionPortContainer::Connect(const std::string &extension_id, + int process_id, + int routing_id, + int connection_id, + const std::string& channel_name, + const std::string& tab_json) { + DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); + + port_id_ = service_->OpenSpecialChannelToExtension( + extension_id, channel_name, tab_json, this); + if (port_id_ == -1) { + // In this case a disconnect message has been dispatched. + return false; + } + + SendConnectionResponse(connection_id, port_id_); + return true; +} + +void ExtensionPortContainer::SendConnectionResponse(int connection_id, + int port_id) { + // Compose the reply message. + scoped_ptr<DictionaryValue> msg_dict(new DictionaryValue()); + msg_dict->SetInteger(ext::kAutomationRequestIdKey, ext::CHANNEL_OPENED); + msg_dict->SetInteger(ext::kAutomationConnectionIdKey, connection_id); + msg_dict->SetInteger(ext::kAutomationPortIdKey, port_id); + + std::string msg_json; + base::JSONWriter::Write(msg_dict.get(), false, &msg_json); + + PostResponseToExternalPort(msg_json); +} + +bool ExtensionPortContainer::Send(IPC::Message *message) { + DCHECK_EQ(MessageLoop::current()->type(), MessageLoop::TYPE_UI); + + IPC_BEGIN_MESSAGE_MAP(ExtensionPortContainer, *message) + IPC_MESSAGE_HANDLER(ViewMsg_ExtensionMessageInvoke, + OnExtensionMessageInvoke) + IPC_MESSAGE_UNHANDLED_ERROR() + IPC_END_MESSAGE_MAP() + + delete message; + return true; +} + +void ExtensionPortContainer::OnExtensionMessageInvoke( + const std::string& function_name, + const ListValue& args, + bool requires_incognito_access, + const GURL& event_url) { + if (function_name == ExtensionMessageService::kDispatchOnMessage) { + DCHECK_EQ(args.GetSize(), 2u); + + std::string message; + int source_port_id; + if (args.GetString(0, &message) && args.GetInteger(1, &source_port_id)) + OnExtensionHandleMessage(message, source_port_id); + } else if (function_name == ExtensionMessageService::kDispatchOnDisconnect) { + DCHECK_EQ(args.GetSize(), 1u); + int port_id; + if (args.GetInteger(0, &port_id)) + OnExtensionPortDisconnected(port_id); + } else if (function_name == ExtensionMessageService::kDispatchOnConnect) { + // Do nothing. + // TODO(siggi): implement + } else { + NOTREACHED() << function_name << " shouldn't be called."; + } +} + +void ExtensionPortContainer::OnExtensionHandleMessage( + const std::string& message, int source_port_id) { + // Compose the reply message and fire it away. + DictionaryValue msg_dict; + msg_dict.SetInteger(ext::kAutomationRequestIdKey, ext::POST_MESSAGE); + msg_dict.SetInteger(ext::kAutomationPortIdKey, port_id_); + msg_dict.SetString(ext::kAutomationMessageDataKey, message); + + std::string msg_json; + base::JSONWriter::Write(&msg_dict, false, &msg_json); + + PostMessageToExternalPort(msg_json); +} + +void ExtensionPortContainer::OnExtensionPortDisconnected(int source_port_id) { + // Compose the disconnect message and fire it away. + DictionaryValue msg_dict; + msg_dict.SetInteger(ext::kAutomationRequestIdKey, ext::CHANNEL_CLOSED); + msg_dict.SetInteger(ext::kAutomationPortIdKey, port_id_); + + std::string msg_json; + base::JSONWriter::Write(&msg_dict, false, &msg_json); + + PostMessageToExternalPort(msg_json); +} + +bool ExtensionPortContainer::InterceptMessageFromExternalHost( + const std::string& message, const std::string& origin, + const std::string& target, AutomationProvider* automation, + RenderViewHost *view_host, int tab_handle) { + if (target != ext::kAutomationPortRequestTarget) + return false; + + if (origin != ext::kAutomationOrigin) { + // TODO(siggi): Should we block the message on wrong origin? + LOG(WARNING) << "Wrong origin on automation port message " << origin; + } + + scoped_ptr<Value> message_value(base::JSONReader::Read(message, false)); + DCHECK(message_value->IsType(Value::TYPE_DICTIONARY)); + if (!message_value->IsType(Value::TYPE_DICTIONARY)) + return true; + + DictionaryValue* message_dict = + reinterpret_cast<DictionaryValue*>(message_value.get()); + + int command = -1; + bool got_value = message_dict->GetInteger(ext::kAutomationRequestIdKey, + &command); + DCHECK(got_value); + if (!got_value) + return true; + + if (command == ext::OPEN_CHANNEL) { + // Extract the "extension_id" and "connection_id" parameters. + std::string extension_id; + got_value = message_dict->GetString(ext::kAutomationExtensionIdKey, + &extension_id); + DCHECK(got_value); + if (!got_value) + return true; + + int connection_id; + got_value = message_dict->GetInteger(ext::kAutomationConnectionIdKey, + &connection_id); + DCHECK(got_value); + if (!got_value) + return true; + + std::string channel_name; + // Channel name is optional. + message_dict->GetString(ext::kAutomationChannelNameKey, &channel_name); + + // Tab information is optional, try to retrieve it + // and re-flatten it to a string. + std::string tab_json("null"); + DictionaryValue* tab = NULL; + if (message_dict->GetDictionary(ext::kAutomationTabJsonKey, &tab)) + base::JSONWriter::Write(tab, false, &tab_json); + + int routing_id = view_host->routing_id(); + // Create the extension port and connect it. + scoped_ptr<ExtensionPortContainer> port( + new ExtensionPortContainer(automation, tab_handle)); + + int process_id = view_host->process()->id(); + if (port->Connect(extension_id, process_id, routing_id, connection_id, + channel_name, tab_json)) { + // We have a successful connection. + automation->AddPortContainer(port.release()); + } + } else if (command == ext::POST_MESSAGE) { + int port_id = -1; + got_value = message_dict->GetInteger(ext::kAutomationPortIdKey, &port_id); + DCHECK(got_value); + if (!got_value) + return true; + + std::string data; + got_value = message_dict->GetString(ext::kAutomationMessageDataKey, &data); + DCHECK(got_value); + if (!got_value) + return true; + + ExtensionPortContainer* port = automation->GetPortContainer(port_id); + DCHECK(port); + if (port) + port->PostMessageFromExternalPort(data); + } else if (command == ext::CHANNEL_CLOSED) { + int port_id = -1; + got_value = message_dict->GetInteger(ext::kAutomationPortIdKey, &port_id); + DCHECK(got_value); + if (!got_value) + return true; + + ExtensionPortContainer* port = automation->GetPortContainer(port_id); + DCHECK(port); + if (port) { + // This will delete the port and notify the other end of the disconnect. + automation->RemovePortContainer(port); + } + } else { + // We don't expect other messages here. + NOTREACHED(); + } + + return true; +} diff --git a/chrome/browser/automation/extension_port_container.h b/chrome/browser/automation/extension_port_container.h new file mode 100644 index 0000000..8fd8299 --- /dev/null +++ b/chrome/browser/automation/extension_port_container.h @@ -0,0 +1,87 @@ +// 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_EXTENSION_PORT_CONTAINER_H_ +#define CHROME_BROWSER_AUTOMATION_EXTENSION_PORT_CONTAINER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "ipc/ipc_message.h" + +class AutomationProvider; +class ExtensionMessageService; +class GURL; +class ListValue; +class MessageLoop; +class RenderViewHost; + +// This class represents an external port to an extension, opened +// through the automation interface. +class ExtensionPortContainer : public IPC::Message::Sender { + public: + + // Intercepts and processes a message posted through the automation interface. + // Returns true if the message was intercepted. + static bool InterceptMessageFromExternalHost(const std::string& message, + const std::string& origin, + const std::string& target, + AutomationProvider* automation, + RenderViewHost *view_host, + int tab_handle); + + ExtensionPortContainer(AutomationProvider* automation, int tab_handle); + ~ExtensionPortContainer(); + + int port_id() const { return port_id_; } + void set_port_id(int port_id) { port_id_ = port_id; } + + // IPC implementation. + virtual bool Send(IPC::Message* msg); + + private: + // Posts a message to the external host. + bool PostMessageToExternalPort(const std::string& message); + // Posts a request response message to the external host. + bool PostResponseToExternalPort(const std::string& message); + + // Forwards a message from the external port. + void PostMessageFromExternalPort(const std::string& message); + + // Attempts to connect this instance to the extension id, sends + // a response to the connecting party. + // Returns true if the connection was successful. + bool Connect(const std::string &extension_id, + int process_id, + int routing_id, + int connection_id, + const std::string& channel_name, + const std::string& tab_json); + + // Sends a connect response to the external port. + void SendConnectionResponse(int connection_id, int port_id); + + void OnExtensionMessageInvoke(const std::string& function_name, + const ListValue& args, + bool requires_incognito_access, + const GURL& event_url); + void OnExtensionHandleMessage(const std::string& message, int source_port_id); + void OnExtensionPortDisconnected(int source_port_id); + + // Our automation provider. + AutomationProvider* automation_; + + // The extension message service. + scoped_refptr<ExtensionMessageService> service_; + + // Our assigned port id. + int port_id_; + // Handle to our associated tab. + int tab_handle_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionPortContainer); +}; + +#endif // CHROME_BROWSER_AUTOMATION_EXTENSION_PORT_CONTAINER_H_ diff --git a/chrome/browser/automation/ui_controls.h b/chrome/browser/automation/ui_controls.h new file mode 100644 index 0000000..b0a2675 --- /dev/null +++ b/chrome/browser/automation/ui_controls.h @@ -0,0 +1,100 @@ +// 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. + +#ifndef CHROME_BROWSER_AUTOMATION_UI_CONTROLS_H_ +#define CHROME_BROWSER_AUTOMATION_UI_CONTROLS_H_ + +#include "build/build_config.h" + +#include <string> + +#if defined(OS_WIN) +#include <wtypes.h> +#endif + +#include "gfx/native_widget_types.h" +#include "gfx/point.h" +#include "base/keyboard_codes.h" + +#if defined(TOOLKIT_VIEWS) +namespace views { +class View; +} +#endif + +class Task; + +namespace ui_controls { + +// Many of the functions in this class include a variant that takes a Task. +// The version that takes a Task waits until the generated event is processed. +// Once the generated event is processed the Task is Run (and deleted). Note +// that this is a somewhat fragile process in that any event of the correct +// type (key down, mouse click, etc.) will trigger the Task to be run. Hence +// a usage such as +// +// SendKeyPress(...); +// SendKeyPressNotifyWhenDone(..., task); +// +// might trigger |task| early. +// +// Note: Windows does not currently do anything with the |window| argument for +// these functions, so passing NULL is ok. + +// Send a key press with/without modifier keys. +bool SendKeyPress(gfx::NativeWindow window, + base::KeyboardCode key, + bool control, + bool shift, + bool alt, + bool command); +bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, + base::KeyboardCode key, + bool control, + bool shift, + bool alt, + bool command, + Task* task); + +// Simulate a mouse move. (x,y) are absolute screen coordinates. +bool SendMouseMove(long x, long y); +bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task); + +enum MouseButton { + LEFT = 0, + MIDDLE, + RIGHT, +}; + +// Used to indicate the state of the button when generating events. +enum MouseButtonState { + UP = 1, + DOWN = 2 +}; + +// Sends a mouse down and/or up message. The click will be sent to wherever +// the cursor currently is, so be sure to move the cursor before calling this +// (and be sure the cursor has arrived!). +bool SendMouseEvents(MouseButton type, int state); +bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task); +// Same as SendMouseEvents with UP | DOWN. +bool SendMouseClick(MouseButton type); + +// A combination of SendMouseMove to the middle of the view followed by +// SendMouseEvents. +void MoveMouseToCenterAndPress( +#if defined(TOOLKIT_VIEWS) + views::View* view, +#elif defined(TOOLKIT_GTK) + GtkWidget* widget, +#elif defined(OS_MACOSX) + NSWindow* window, +#endif + MouseButton button, + int state, + Task* task); + +} // ui_controls + +#endif // CHROME_BROWSER_AUTOMATION_UI_CONTROLS_H_ diff --git a/chrome/browser/automation/ui_controls_linux.cc b/chrome/browser/automation/ui_controls_linux.cc new file mode 100644 index 0000000..c9dca78 --- /dev/null +++ b/chrome/browser/automation/ui_controls_linux.cc @@ -0,0 +1,289 @@ +// 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/browser/automation/ui_controls.h" + +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#include "gfx/rect.h" +#include "base/event_synthesis_gtk.h" +#include "base/keyboard_code_conversion_gtk.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "chrome/browser/gtk/gtk_util.h" +#include "chrome/test/automation/automation_constants.h" + +#if defined(TOOLKIT_VIEWS) +#include "views/view.h" +#include "views/widget/widget.h" +#endif + +namespace { + +class EventWaiter : public MessageLoopForUI::Observer { + public: + EventWaiter(Task* task, GdkEventType type, int count) + : task_(task), + type_(type), + count_(count) { + MessageLoopForUI::current()->AddObserver(this); + } + + virtual ~EventWaiter() { + MessageLoopForUI::current()->RemoveObserver(this); + } + + // MessageLoop::Observer implementation: + virtual void WillProcessEvent(GdkEvent* event) { + if ((event->type == type_) && (--count_ == 0)) { + // At the time we're invoked the event has not actually been processed. + // Use PostTask to make sure the event has been processed before + // notifying. + // NOTE: if processing a message results in running a nested message + // loop, then DidProcessEvent isn't immediately sent. As such, we do + // the processing in WillProcessEvent rather than DidProcessEvent. + MessageLoop::current()->PostTask(FROM_HERE, task_); + delete this; + } + } + + virtual void DidProcessEvent(GdkEvent* event) { + // No-op. + } + + private: + // We pass ownership of task_ to MessageLoop when the current event is + // received. + Task* task_; + GdkEventType type_; + // The number of events of this type to wait for. + int count_; +}; + +class ClickTask : public Task { + public: + ClickTask(ui_controls::MouseButton button, int state, Task* followup) + : button_(button), state_(state), followup_(followup) { + } + + virtual ~ClickTask() {} + + virtual void Run() { + if (followup_) + ui_controls::SendMouseEventsNotifyWhenDone(button_, state_, followup_); + else + ui_controls::SendMouseEvents(button_, state_); + } + + private: + ui_controls::MouseButton button_; + int state_; + Task* followup_; +}; + +void FakeAMouseMotionEvent(gint x, gint y) { + GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY); + + event->motion.send_event = false; + event->motion.time = gtk_util::XTimeNow(); + + GtkWidget* grab_widget = gtk_grab_get_current(); + if (grab_widget) { + // If there is a grab, we need to target all events at it regardless of + // what widget the mouse is over. + event->motion.window = grab_widget->window; + } else { + event->motion.window = gdk_window_at_pointer(&x, &y); + } + g_object_ref(event->motion.window); + event->motion.x = x; + event->motion.y = y; + gint origin_x, origin_y; + gdk_window_get_origin(event->motion.window, &origin_x, &origin_y); + event->motion.x_root = x + origin_x; + event->motion.y_root = y + origin_y; + + event->motion.device = gdk_device_get_core_pointer(); + event->type = GDK_MOTION_NOTIFY; + + gdk_event_put(event); + gdk_event_free(event); +} + +} // namespace + +namespace ui_controls { + +bool SendKeyPress(gfx::NativeWindow window, + base::KeyboardCode key, + bool control, bool shift, bool alt, bool command) { + DCHECK(command == false); // No command key on Linux + GdkWindow* event_window = NULL; + GtkWidget* grab_widget = gtk_grab_get_current(); + if (grab_widget) { + // If there is a grab, send all events to the grabbed widget. + event_window = grab_widget->window; + } else if (window) { + event_window = GTK_WIDGET(window)->window; + } else { + // No target was specified. Send the events to the active toplevel. + GList* windows = gtk_window_list_toplevels(); + for (GList* element = windows; element; element = g_list_next(element)) { + GtkWindow* this_window = GTK_WINDOW(element->data); + if (gtk_window_is_active(this_window)) { + event_window = GTK_WIDGET(this_window)->window; + break; + } + } + g_list_free(windows); + } + if (!event_window) { + NOTREACHED() << "Window not specified and none is active"; + return false; + } + + std::vector<GdkEvent*> events; + base::SynthesizeKeyPressEvents(event_window, key, control, shift, alt, + &events); + for (std::vector<GdkEvent*>::iterator iter = events.begin(); + iter != events.end(); ++iter) { + gdk_event_put(*iter); + // gdk_event_put appends a copy of the event. + gdk_event_free(*iter); + } + + return true; +} + +bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, + base::KeyboardCode key, + bool control, bool shift, + bool alt, bool command, + Task* task) { + DCHECK(command == false); // No command key on Linux + int release_count = 1; + if (control) + release_count++; + if (shift) + release_count++; + if (alt) + release_count++; + // This object will delete itself after running |task|. + new EventWaiter(task, GDK_KEY_RELEASE, release_count); + return SendKeyPress(window, key, control, shift, alt, command); +} + +bool SendMouseMove(long x, long y) { + gdk_display_warp_pointer(gdk_display_get_default(), gdk_screen_get_default(), + x, y); + // Sometimes gdk_display_warp_pointer fails to send back any indication of + // the move, even though it succesfully moves the server cursor. We fake it in + // order to get drags to work. + FakeAMouseMotionEvent(x, y); + + return true; +} + +bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { + bool rv = SendMouseMove(x, y); + // We can't rely on any particular event signalling the completion of the + // mouse move. Posting the task to the message loop hopefully guarantees + // the pointer has moved before task is run (although it may not run it as + // soon as it could). + MessageLoop::current()->PostTask(FROM_HERE, task); + return rv; +} + +bool SendMouseEvents(MouseButton type, int state) { + GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS); + + event->button.send_event = false; + event->button.time = gtk_util::XTimeNow(); + + gint x, y; + GtkWidget* grab_widget = gtk_grab_get_current(); + if (grab_widget) { + // If there is a grab, we need to target all events at it regardless of + // what widget the mouse is over. + event->button.window = grab_widget->window; + gdk_window_get_pointer(event->button.window, &x, &y, NULL); + } else { + event->button.window = gdk_window_at_pointer(&x, &y); + } + + g_object_ref(event->button.window); + event->button.x = x; + event->button.y = y; + gint origin_x, origin_y; + gdk_window_get_origin(event->button.window, &origin_x, &origin_y); + event->button.x_root = x + origin_x; + event->button.y_root = y + origin_y; + + event->button.axes = NULL; + GdkModifierType modifier; + gdk_window_get_pointer(event->button.window, NULL, NULL, &modifier); + event->button.state = modifier; + event->button.button = type == LEFT ? 1 : (type == MIDDLE ? 2 : 3); + event->button.device = gdk_device_get_core_pointer(); + + event->button.type = GDK_BUTTON_PRESS; + if (state & DOWN) + gdk_event_put(event); + + // Also send a release event. + GdkEvent* release_event = gdk_event_copy(event); + release_event->button.type = GDK_BUTTON_RELEASE; + release_event->button.time++; + if (state & UP) + gdk_event_put(release_event); + + gdk_event_free(event); + gdk_event_free(release_event); + + return false; +} + +bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { + bool rv = SendMouseEvents(type, state); + GdkEventType wait_type; + if (state & UP) { + wait_type = GDK_BUTTON_RELEASE; + } else { + if (type == LEFT) + wait_type = GDK_BUTTON_PRESS; + else if (type == MIDDLE) + wait_type = GDK_2BUTTON_PRESS; + else + wait_type = GDK_3BUTTON_PRESS; + } + new EventWaiter(task, wait_type, 1); + return rv; +} + +bool SendMouseClick(MouseButton type) { + return SendMouseEvents(type, UP | DOWN); +} + +#if defined(TOOLKIT_VIEWS) +void MoveMouseToCenterAndPress(views::View* view, MouseButton button, + int state, Task* task) { + gfx::Point view_center(view->width() / 2, view->height() / 2); + views::View::ConvertPointToScreen(view, &view_center); + SendMouseMoveNotifyWhenDone(view_center.x(), view_center.y(), + new ClickTask(button, state, task)); +} +#else +void MoveMouseToCenterAndPress(GtkWidget* widget, + MouseButton button, + int state, + Task* task) { + gfx::Rect bounds = gtk_util::GetWidgetScreenBounds(widget); + SendMouseMoveNotifyWhenDone(bounds.x() + bounds.width() / 2, + bounds.y() + bounds.height() / 2, + new ClickTask(button, state, task)); +} +#endif + +} // namespace ui_controls diff --git a/chrome/browser/automation/ui_controls_mac.mm b/chrome/browser/automation/ui_controls_mac.mm new file mode 100644 index 0000000..715135c --- /dev/null +++ b/chrome/browser/automation/ui_controls_mac.mm @@ -0,0 +1,242 @@ +// 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/ui_controls.h" + +#import <Cocoa/Cocoa.h> +#include <mach/mach_time.h> + +#include "base/message_loop.h" +#include "chrome/browser/chrome_thread.h" + +// Implementation details: We use [NSApplication sendEvent:] instead +// of [NSApplication postEvent:atStart:] so that the event gets sent +// immediately. This lets us run the post-event task right +// immediately as well. Unfortunately I cannot subclass NSEvent (it's +// probably a class cluster) to allow other easy answers. For +// example, if I could subclass NSEvent, I could run the Task in it's +// dealloc routine (which necessarily happens after the event is +// dispatched). Unlike Linux, Mac does not have message loop +// observer/notification. Unlike windows, I cannot post non-events +// into the event queue. (I can post other kinds of tasks but can't +// guarantee their order with regards to events). + +namespace { + +// From +// http://stackoverflow.com/questions/1597383/cgeventtimestamp-to-nsdate +// Which credits Apple sample code for this routine. +uint64_t UpTimeInNanoseconds(void) { + uint64_t time; + uint64_t timeNano; + static mach_timebase_info_data_t sTimebaseInfo; + + time = mach_absolute_time(); + + // Convert to nanoseconds. + + // If this is the first time we've run, get the timebase. + // We can use denom == 0 to indicate that sTimebaseInfo is + // uninitialised because it makes no sense to have a zero + // denominator is a fraction. + if (sTimebaseInfo.denom == 0) { + (void) mach_timebase_info(&sTimebaseInfo); + } + + // This could overflow; for testing needs we probably don't care. + timeNano = time * sTimebaseInfo.numer / sTimebaseInfo.denom; + return timeNano; +} + +NSTimeInterval TimeIntervalSinceSystemStartup() { + return UpTimeInNanoseconds() / 1000000000.0; +} + +} // anonymous namespace + + +namespace ui_controls { + +bool SendKeyPress(gfx::NativeWindow window, + base::KeyboardCode key, + bool control, + bool shift, + bool alt, + bool command) { + return SendKeyPressNotifyWhenDone(window, key, + control, shift, alt, command, + NULL); +} + +// Win and Linux implement a SendKeyPress() this as a +// SendKeyPressAndRelease(), so we should as well (despite the name). +// +// TODO(jrg): handle "characters" better (e.g. apply shift?) +bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, + base::KeyboardCode key, + bool control, + bool shift, + bool alt, + bool command, + Task* task) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + NSUInteger flags = 0; + if (control) + flags |= NSControlKeyMask; + if (shift) + flags |= NSShiftKeyMask; + if (alt) + flags |= NSAlternateKeyMask; + if (command) + flags |= NSCommandKeyMask; + unsigned char keycode = key; + NSString* charactersIgnoringModifiers = [[[NSString alloc] + initWithBytes:&keycode + length:1 + encoding:NSUTF8StringEncoding] + autorelease]; + NSString* characters = charactersIgnoringModifiers; + + // For events other than mouse moved, [event locationInWindow] is + // UNDEFINED if the event is not NSMouseMoved. Thus, the (0,0) + // locaiton should be fine. + // First a key down... + NSEvent* event = + [NSEvent keyEventWithType:NSKeyDown + location:NSMakePoint(0,0) + modifierFlags:flags + timestamp:TimeIntervalSinceSystemStartup() + windowNumber:[window windowNumber] + context:nil + characters:characters + charactersIgnoringModifiers:charactersIgnoringModifiers + isARepeat:NO + keyCode:key]; + [[NSApplication sharedApplication] sendEvent:event]; + // Then a key up. + event = + [NSEvent keyEventWithType:NSKeyUp + location:NSMakePoint(0,0) + modifierFlags:flags + timestamp:TimeIntervalSinceSystemStartup() + windowNumber:[window windowNumber] + context:nil + characters:characters + charactersIgnoringModifiers:charactersIgnoringModifiers + isARepeat:NO + keyCode:key]; + [[NSApplication sharedApplication] sendEvent:event]; + + if (task) + MessageLoop::current()->PostTask(FROM_HERE, task); + return true; +} + +bool SendMouseMove(long x, long y) { + return SendMouseMoveNotifyWhenDone(x, y, NULL); +} + +// Input position is in screen coordinates. However, NSMouseMoved +// events require them window-relative, so we adjust. We *DO* flip +// the coordinate space, so input events can be the same for all +// platforms. E.g. (0,0) is upper-left. +bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { + NSWindow* window = [[NSApplication sharedApplication] keyWindow]; + CGFloat screenHeight = [[NSScreen mainScreen] frame].size.height; + NSPoint pointInWindow = NSMakePoint(x, screenHeight - y); // flip! + if (window) + pointInWindow = [window convertScreenToBase:pointInWindow]; + NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); + + NSEvent* event = + [NSEvent mouseEventWithType:NSMouseMoved + location:pointInWindow + modifierFlags:0 + timestamp:timestamp + windowNumber:[window windowNumber] + context:nil + eventNumber:0 + clickCount:0 + pressure:0.0]; + [[NSApplication sharedApplication] postEvent:event atStart:NO]; + if (task) + MessageLoop::current()->PostTask(FROM_HERE, task); + return true; +} + +bool SendMouseEvents(MouseButton type, int state) { + return SendMouseEventsNotifyWhenDone(type, state, NULL); +} + +bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { + // On windows it appears state can be (UP|DOWN). It is unclear if + // that'll happen here but prepare for it just in case. + if (state == (UP|DOWN)) { + return (SendMouseEventsNotifyWhenDone(type, DOWN, NULL) && + SendMouseEventsNotifyWhenDone(type, UP, task)); + } + + NSEventType etype = 0; + if (type == LEFT) { + if (state == UP) { + etype = NSLeftMouseUp; + } else { + etype = NSLeftMouseDown; + } + } else if (type == MIDDLE) { + if (state == UP) { + etype = NSOtherMouseUp; + } else { + etype = NSOtherMouseDown; + } + } else if (type == RIGHT) { + if (state == UP) { + etype = NSRightMouseUp; + } else { + etype = NSRightMouseDown; + } + } else { + return false; + } + NSWindow* window = [[NSApplication sharedApplication] keyWindow]; + NSPoint location = [NSEvent mouseLocation]; + NSPoint pointInWindow = location; + if (window) + pointInWindow = [window convertScreenToBase:pointInWindow]; + + NSEvent* event = + [NSEvent mouseEventWithType:etype + location:pointInWindow + modifierFlags:0 + timestamp:TimeIntervalSinceSystemStartup() + windowNumber:[window windowNumber] + context:nil + eventNumber:0 + clickCount:0 + pressure:0.0]; + [[NSApplication sharedApplication] sendEvent:event]; + if (task) + MessageLoop::current()->PostTask(FROM_HERE, task); + return true; +} + +bool SendMouseClick(MouseButton type) { + return SendMouseEventsNotifyWhenDone(type, UP|DOWN, NULL); +} + +// This appears to only be used by a function in test/ui_test_utils.h: +// ui_test_utils::ClickOnView(). That is not implemented on Mac, so +// we don't need to implement MoveMouseToCenterAndPress(). I've +// suggested an implementation of ClickOnView() which would call Cocoa +// directly and not need this indirection, so this may not be needed, +// ever. +void MoveMouseToCenterAndPress( + NSWindow* window, + MouseButton button, + int state, + Task* task) { + NOTIMPLEMENTED(); +} + +} // ui_controls diff --git a/chrome/browser/automation/ui_controls_win.cc b/chrome/browser/automation/ui_controls_win.cc new file mode 100644 index 0000000..50bb88f --- /dev/null +++ b/chrome/browser/automation/ui_controls_win.cc @@ -0,0 +1,370 @@ +// 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/ui_controls.h" + +#include "base/keyboard_codes.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/win_util.h" +#include "base/ref_counted.h" +#include "base/task.h" +#include "views/view.h" + +namespace ui_controls { + +namespace { + +// InputDispatcher ------------------------------------------------------------ + +// InputDispatcher is used to listen for a mouse/keyboard event. When the +// appropriate event is received the task is notified. +class InputDispatcher : public base::RefCounted<InputDispatcher> { + public: + InputDispatcher(Task* task, WPARAM message_waiting_for); + + // Invoked from the hook. If mouse_message matches message_waiting_for_ + // MatchingMessageFound is invoked. + void DispatchedMessage(WPARAM mouse_message); + + // Invoked when a matching event is found. Uninstalls the hook and schedules + // an event that notifies the task. + void MatchingMessageFound(); + + private: + friend class base::RefCounted<InputDispatcher>; + + ~InputDispatcher(); + + // Notifies the task and release this (which should delete it). + void NotifyTask(); + + // The task we notify. + scoped_ptr<Task> task_; + + // Message we're waiting for. Not used for keyboard events. + const WPARAM message_waiting_for_; + + DISALLOW_COPY_AND_ASSIGN(InputDispatcher); +}; + +// Have we installed the hook? +bool installed_hook_ = false; + +// Return value from SetWindowsHookEx. +HHOOK next_hook_ = NULL; + +// If a hook is installed, this is the dispatcher. +InputDispatcher* current_dispatcher_ = NULL; + +// Callback from hook when a mouse message is received. +LRESULT CALLBACK MouseHook(int n_code, WPARAM w_param, LPARAM l_param) { + HHOOK next_hook = next_hook_; + if (n_code == HC_ACTION) { + DCHECK(current_dispatcher_); + current_dispatcher_->DispatchedMessage(w_param); + } + return CallNextHookEx(next_hook, n_code, w_param, l_param); +} + +// Callback from hook when a key message is received. +LRESULT CALLBACK KeyHook(int n_code, WPARAM w_param, LPARAM l_param) { + HHOOK next_hook = next_hook_; + if (n_code == HC_ACTION) { + DCHECK(current_dispatcher_); + if (l_param & (1 << 30)) // Only send on key up. + current_dispatcher_->MatchingMessageFound(); + } + return CallNextHookEx(next_hook, n_code, w_param, l_param); +} + +// Installs dispatcher as the current hook. +void InstallHook(InputDispatcher* dispatcher, bool key_hook) { + DCHECK(!installed_hook_); + current_dispatcher_ = dispatcher; + installed_hook_ = true; + if (key_hook) { + next_hook_ = SetWindowsHookEx(WH_KEYBOARD, &KeyHook, NULL, + GetCurrentThreadId()); + } else { + // NOTE: I originally tried WH_CALLWNDPROCRET, but for some reason I + // didn't get a mouse message like I do with MouseHook. + next_hook_ = SetWindowsHookEx(WH_MOUSE, &MouseHook, NULL, + GetCurrentThreadId()); + } + DCHECK(next_hook_); +} + +// Uninstalls the hook set in InstallHook. +void UninstallHook(InputDispatcher* dispatcher) { + if (current_dispatcher_ == dispatcher) { + installed_hook_ = false; + current_dispatcher_ = NULL; + UnhookWindowsHookEx(next_hook_); + } +} + +InputDispatcher::InputDispatcher(Task* task, UINT message_waiting_for) + : task_(task), message_waiting_for_(message_waiting_for) { + InstallHook(this, message_waiting_for == WM_KEYUP); +} + +InputDispatcher::~InputDispatcher() { + // Make sure the hook isn't installed. + UninstallHook(this); +} + +void InputDispatcher::DispatchedMessage(WPARAM message) { + if (message == message_waiting_for_) + MatchingMessageFound(); +} + +void InputDispatcher::MatchingMessageFound() { + UninstallHook(this); + // At the time we're invoked the event has not actually been processed. + // Use PostTask to make sure the event has been processed before notifying. + MessageLoop::current()->PostDelayedTask( + FROM_HERE, NewRunnableMethod(this, &InputDispatcher::NotifyTask), 0); +} + +void InputDispatcher::NotifyTask() { + task_->Run(); + Release(); +} + +// Private functions ---------------------------------------------------------- + +// Populate the INPUT structure with the appropriate keyboard event +// parameters required by SendInput +bool FillKeyboardInput(base::KeyboardCode key, INPUT* input, bool key_up) { + memset(input, 0, sizeof(INPUT)); + input->type = INPUT_KEYBOARD; + input->ki.wVk = win_util::KeyboardCodeToWin(key); + input->ki.dwFlags = key_up ? KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP : + KEYEVENTF_EXTENDEDKEY; + + return true; +} + +// Send a key event (up/down) +bool SendKeyEvent(base::KeyboardCode key, bool up) { + INPUT input = { 0 }; + + if (!FillKeyboardInput(key, &input, up)) + return false; + + if (!::SendInput(1, &input, sizeof(INPUT))) + return false; + + return true; +} + +bool SendKeyPressImpl(base::KeyboardCode key, + bool control, bool shift, bool alt, + Task* task) { + scoped_refptr<InputDispatcher> dispatcher( + task ? new InputDispatcher(task, WM_KEYUP) : NULL); + + // If a pop-up menu is open, it won't receive events sent using SendInput. + // Check for a pop-up menu using its window class (#32768) and if one + // exists, send the key event directly there. + HWND popup_menu = ::FindWindow(L"#32768", 0); + if (popup_menu != NULL && popup_menu == ::GetTopWindow(NULL)) { + WPARAM w_param = win_util::KeyboardCodeToWin(key); + LPARAM l_param = 0; + ::SendMessage(popup_menu, WM_KEYDOWN, w_param, l_param); + ::SendMessage(popup_menu, WM_KEYUP, w_param, l_param); + + if (dispatcher.get()) + dispatcher->AddRef(); + return true; + } + + INPUT input[8] = { 0 }; // 8, assuming all the modifiers are activated + + UINT i = 0; + if (control) { + if (!FillKeyboardInput(base::VKEY_CONTROL, &input[i], false)) + return false; + i++; + } + + if (shift) { + if (!FillKeyboardInput(base::VKEY_SHIFT, &input[i], false)) + return false; + i++; + } + + if (alt) { + if (!FillKeyboardInput(base::VKEY_MENU, &input[i], false)) + return false; + i++; + } + + if (!FillKeyboardInput(key, &input[i], false)) + return false; + i++; + + if (!FillKeyboardInput(key, &input[i], true)) + return false; + i++; + + if (alt) { + if (!FillKeyboardInput(base::VKEY_MENU, &input[i], true)) + return false; + i++; + } + + if (shift) { + if (!FillKeyboardInput(base::VKEY_SHIFT, &input[i], true)) + return false; + i++; + } + + if (control) { + if (!FillKeyboardInput(base::VKEY_CONTROL, &input[i], true)) + return false; + i++; + } + + if (::SendInput(i, input, sizeof(INPUT)) != i) + return false; + + if (dispatcher.get()) + dispatcher->AddRef(); + return true; +} + +bool SendMouseMoveImpl(long x, long y, Task* task) { + // First check if the mouse is already there. + POINT current_pos; + ::GetCursorPos(¤t_pos); + if (x == current_pos.x && y == current_pos.y) { + if (task) + MessageLoop::current()->PostTask(FROM_HERE, task); + return true; + } + + INPUT input = { 0 }; + + int screen_width = ::GetSystemMetrics(SM_CXSCREEN) - 1; + int screen_height = ::GetSystemMetrics(SM_CYSCREEN) - 1; + LONG pixel_x = static_cast<LONG>(x * (65535.0f / screen_width)); + LONG pixel_y = static_cast<LONG>(y * (65535.0f / screen_height)); + + input.type = INPUT_MOUSE; + input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + input.mi.dx = pixel_x; + input.mi.dy = pixel_y; + + scoped_refptr<InputDispatcher> dispatcher( + task ? new InputDispatcher(task, WM_MOUSEMOVE) : NULL); + + if (!::SendInput(1, &input, sizeof(INPUT))) + return false; + + if (dispatcher.get()) + dispatcher->AddRef(); + + return true; +} + +bool SendMouseEventsImpl(MouseButton type, int state, Task* task) { + DWORD down_flags = MOUSEEVENTF_ABSOLUTE; + DWORD up_flags = MOUSEEVENTF_ABSOLUTE; + UINT last_event; + + switch (type) { + case LEFT: + down_flags |= MOUSEEVENTF_LEFTDOWN; + up_flags |= MOUSEEVENTF_LEFTUP; + last_event = (state & UP) ? WM_LBUTTONUP : WM_LBUTTONDOWN; + break; + + case MIDDLE: + down_flags |= MOUSEEVENTF_MIDDLEDOWN; + up_flags |= MOUSEEVENTF_MIDDLEUP; + last_event = (state & UP) ? WM_MBUTTONUP : WM_MBUTTONDOWN; + break; + + case RIGHT: + down_flags |= MOUSEEVENTF_RIGHTDOWN; + up_flags |= MOUSEEVENTF_RIGHTUP; + last_event = (state & UP) ? WM_RBUTTONUP : WM_RBUTTONDOWN; + break; + + default: + NOTREACHED(); + return false; + } + + scoped_refptr<InputDispatcher> dispatcher( + task ? new InputDispatcher(task, last_event) : NULL); + + INPUT input = { 0 }; + input.type = INPUT_MOUSE; + input.mi.dwFlags = down_flags; + if ((state & DOWN) && !::SendInput(1, &input, sizeof(INPUT))) + return false; + + input.mi.dwFlags = up_flags; + if ((state & UP) && !::SendInput(1, &input, sizeof(INPUT))) + return false; + + if (dispatcher.get()) + dispatcher->AddRef(); + + return true; +} + +} // namespace + +// public functions ----------------------------------------------------------- + +bool SendKeyPress(gfx::NativeWindow window, base::KeyboardCode key, + bool control, bool shift, bool alt, bool command) { + DCHECK(command == false); // No command key on Windows + return SendKeyPressImpl(key, control, shift, alt, NULL); +} + +bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, + base::KeyboardCode key, + bool control, bool shift, bool alt, + bool command, + Task* task) { + DCHECK(command == false); // No command key on Windows + return SendKeyPressImpl(key, control, shift, alt, task); +} + +bool SendMouseMove(long x, long y) { + return SendMouseMoveImpl(x, y, NULL); +} + +bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { + return SendMouseMoveImpl(x, y, task); +} + +bool SendMouseEvents(MouseButton type, int state) { + return SendMouseEventsImpl(type, state, NULL); +} + +bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { + return SendMouseEventsImpl(type, state, task); +} + +bool SendMouseClick(MouseButton type) { + return SendMouseEventsImpl(type, UP | DOWN, NULL); +} + +void MoveMouseToCenterAndPress(views::View* view, MouseButton button, + int state, Task* task) { + DCHECK(view); + DCHECK(view->GetWidget()); + gfx::Point view_center(view->width() / 2, view->height() / 2); + views::View::ConvertPointToScreen(view, &view_center); + SendMouseMove(view_center.x(), view_center.y()); + SendMouseEventsNotifyWhenDone(button, state, task); +} + +} // ui_controls diff --git a/chrome/browser/automation/url_request_automation_job.cc b/chrome/browser/automation/url_request_automation_job.cc new file mode 100644 index 0000000..6cdfdbf --- /dev/null +++ b/chrome/browser/automation/url_request_automation_job.cc @@ -0,0 +1,460 @@ +// 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/url_request_automation_job.h" + +#include "base/message_loop.h" +#include "base/time.h" +#include "chrome/browser/automation/automation_resource_message_filter.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.h" +#include "chrome/test/automation/automation_messages.h" +#include "net/base/cookie_monster.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_util.h" +#include "net/url_request/url_request_context.h" + +using base::Time; +using base::TimeDelta; + +// The list of filtered headers that are removed from requests sent via +// StartAsync(). These must be lower case. +static const char* const kFilteredHeaderStrings[] = { + "accept", + "cache-control", + "connection", + "cookie", + "expect", + "max-forwards", + "proxy-authorization", + "te", + "upgrade", + "via" +}; + +int URLRequestAutomationJob::instance_count_ = 0; +bool URLRequestAutomationJob::is_protocol_factory_registered_ = false; + +URLRequest::ProtocolFactory* URLRequestAutomationJob::old_http_factory_ + = NULL; +URLRequest::ProtocolFactory* URLRequestAutomationJob::old_https_factory_ + = NULL; + +URLRequestAutomationJob::URLRequestAutomationJob(URLRequest* request, int tab, + int request_id, AutomationResourceMessageFilter* filter, bool is_pending) + : URLRequestJob(request), + tab_(tab), + message_filter_(filter), + pending_buf_size_(0), + redirect_status_(0), + request_id_(request_id), + is_pending_(is_pending) { + DLOG(INFO) << "URLRequestAutomationJob create. Count: " << ++instance_count_; + DCHECK(message_filter_ != NULL); + + if (message_filter_) { + id_ = message_filter_->NewAutomationRequestId(); + DCHECK_NE(id_, 0); + } +} + +URLRequestAutomationJob::~URLRequestAutomationJob() { + DLOG(INFO) << "URLRequestAutomationJob delete. Count: " << --instance_count_; + Cleanup(); +} + +bool URLRequestAutomationJob::EnsureProtocolFactoryRegistered() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); + + if (!is_protocol_factory_registered_) { + old_http_factory_ = + URLRequest::RegisterProtocolFactory("http", + &URLRequestAutomationJob::Factory); + old_https_factory_ = + URLRequest::RegisterProtocolFactory("https", + &URLRequestAutomationJob::Factory); + is_protocol_factory_registered_ = true; + } + + return true; +} + +URLRequestJob* URLRequestAutomationJob::Factory(URLRequest* request, + const std::string& scheme) { + bool scheme_is_http = request->url().SchemeIs("http"); + bool scheme_is_https = request->url().SchemeIs("https"); + + // Returning null here just means that the built-in handler will be used. + if (scheme_is_http || scheme_is_https) { + ResourceDispatcherHostRequestInfo* request_info = + ResourceDispatcherHost::InfoForRequest(request); + if (request_info) { + int child_id = request_info->child_id(); + int route_id = request_info->route_id(); + + if (request_info->process_type() == ChildProcessInfo::PLUGIN_PROCESS) { + child_id = request_info->host_renderer_id(); + route_id = request_info->host_render_view_id(); + } + + AutomationResourceMessageFilter::AutomationDetails details; + if (AutomationResourceMessageFilter::LookupRegisteredRenderView( + child_id, route_id, &details)) { + URLRequestAutomationJob* job = new URLRequestAutomationJob(request, + details.tab_handle, request_info->request_id(), details.filter, + details.is_pending_render_view); + return job; + } + } + + if (scheme_is_http && old_http_factory_) + return old_http_factory_(request, scheme); + else if (scheme_is_https && old_https_factory_) + return old_https_factory_(request, scheme); + } + return NULL; +} + +// URLRequestJob Implementation. +void URLRequestAutomationJob::Start() { + if (!is_pending()) { + // Start reading asynchronously so that all error reporting and data + // callbacks happen as they would for network requests. + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + this, &URLRequestAutomationJob::StartAsync)); + } else { + // If this is a pending job, then register it immediately with the message + // filter so it can be serviced later when we receive a request from the + // external host to connect to the corresponding external tab. + message_filter_->RegisterRequest(this); + } +} + +void URLRequestAutomationJob::Kill() { + if (message_filter_.get()) { + if (!is_pending()) { + message_filter_->Send(new AutomationMsg_RequestEnd(0, tab_, id_, + URLRequestStatus(URLRequestStatus::CANCELED, net::ERR_ABORTED))); + } + } + DisconnectFromMessageFilter(); + URLRequestJob::Kill(); +} + +bool URLRequestAutomationJob::ReadRawData( + net::IOBuffer* buf, int buf_size, int* bytes_read) { + DLOG(INFO) << "URLRequestAutomationJob: " << + request_->url().spec() << " - read pending: " << buf_size; + + // We should not receive a read request for a pending job. + DCHECK(!is_pending()); + + pending_buf_ = buf; + pending_buf_size_ = buf_size; + + if (message_filter_) { + message_filter_->Send(new AutomationMsg_RequestRead(0, tab_, id_, + buf_size)); + SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); + } else { + ChromeThread::PostTask(ChromeThread::IO, FROM_HERE, + NewRunnableMethod(this, + &URLRequestAutomationJob::NotifyJobCompletionTask)); + } + return false; +} + +bool URLRequestAutomationJob::GetMimeType(std::string* mime_type) const { + if (!mime_type_.empty()) { + *mime_type = mime_type_; + } else if (headers_) { + headers_->GetMimeType(mime_type); + } + + return (!mime_type->empty()); +} + +bool URLRequestAutomationJob::GetCharset(std::string* charset) { + if (headers_) + return headers_->GetCharset(charset); + return false; +} + +void URLRequestAutomationJob::GetResponseInfo(net::HttpResponseInfo* info) { + if (headers_) + info->headers = headers_; + if (request_->url().SchemeIsSecure()) { + // Make up a fake certificate for this response since we don't have + // access to the real SSL info. + const char* kCertIssuer = "Chrome Internal"; + const int kLifetimeDays = 100; + + info->ssl_info.cert = + new net::X509Certificate(request_->url().GetWithEmptyPath().spec(), + kCertIssuer, + Time::Now(), + Time::Now() + + TimeDelta::FromDays(kLifetimeDays)); + info->ssl_info.cert_status = 0; + info->ssl_info.security_bits = 0; + } +} + +int URLRequestAutomationJob::GetResponseCode() const { + if (headers_) + return headers_->response_code(); + + static const int kDefaultResponseCode = 200; + return kDefaultResponseCode; +} + +bool URLRequestAutomationJob::IsRedirectResponse( + GURL* location, int* http_status_code) { + if (!net::HttpResponseHeaders::IsRedirectResponseCode(redirect_status_)) + return false; + + *http_status_code = redirect_status_; + *location = GURL(redirect_url_); + return true; +} + +bool URLRequestAutomationJob::MayFilterMessage(const IPC::Message& message, + int* request_id) { + switch (message.type()) { + case AutomationMsg_RequestStarted::ID: + case AutomationMsg_RequestData::ID: + case AutomationMsg_RequestEnd::ID: { + void* iter = NULL; + int tab = 0; + if (message.ReadInt(&iter, &tab) && + message.ReadInt(&iter, request_id)) { + return true; + } + break; + } + } + + return false; +} + +void URLRequestAutomationJob::OnMessage(const IPC::Message& message) { + if (!request_) { + NOTREACHED() << __FUNCTION__ + << ": Unexpected request received for job:" + << id(); + return; + } + + IPC_BEGIN_MESSAGE_MAP(URLRequestAutomationJob, message) + IPC_MESSAGE_HANDLER(AutomationMsg_RequestStarted, OnRequestStarted) + IPC_MESSAGE_HANDLER(AutomationMsg_RequestData, OnDataAvailable) + IPC_MESSAGE_HANDLER(AutomationMsg_RequestEnd, OnRequestEnd) + IPC_END_MESSAGE_MAP() +} + +void URLRequestAutomationJob::OnRequestStarted(int tab, int id, + const IPC::AutomationURLResponse& response) { + DLOG(INFO) << "URLRequestAutomationJob: " << + request_->url().spec() << " - response started."; + set_expected_content_size(response.content_length); + mime_type_ = response.mime_type; + + redirect_url_ = response.redirect_url; + redirect_status_ = response.redirect_status; + DCHECK(redirect_status_ == 0 || redirect_status_ == 200 || + (redirect_status_ >= 300 && redirect_status_ < 400)); + + if (!response.headers.empty()) { + headers_ = new net::HttpResponseHeaders( + net::HttpUtil::AssembleRawHeaders(response.headers.data(), + response.headers.size())); + } + NotifyHeadersComplete(); +} + +void URLRequestAutomationJob::OnDataAvailable( + int tab, int id, const std::string& bytes) { + DLOG(INFO) << "URLRequestAutomationJob: " << + request_->url().spec() << " - data available, Size: " << bytes.size(); + DCHECK(!bytes.empty()); + + // The request completed, and we have all the data. + // Clear any IO pending status. + SetStatus(URLRequestStatus()); + + if (pending_buf_ && pending_buf_->data()) { + DCHECK_GE(pending_buf_size_, bytes.size()); + const int bytes_to_copy = std::min(bytes.size(), pending_buf_size_); + memcpy(pending_buf_->data(), &bytes[0], bytes_to_copy); + + pending_buf_ = NULL; + pending_buf_size_ = 0; + + NotifyReadComplete(bytes_to_copy); + } else { + NOTREACHED() << "Received unexpected data of length:" << bytes.size(); + } +} + +void URLRequestAutomationJob::OnRequestEnd( + int tab, int id, const URLRequestStatus& status) { +#ifndef NDEBUG + std::string url; + if (request_) + url = request_->url().spec(); + DLOG(INFO) << "URLRequestAutomationJob: " + << url << " - request end. Status: " << status.status(); +#endif + + // TODO(tommi): When we hit certificate errors, notify the delegate via + // OnSSLCertificateError(). Right now we don't have the certificate + // so we don't. We could possibly call OnSSLCertificateError with a NULL + // certificate, but I'm not sure if all implementations expect it. + // if (status.status() == URLRequestStatus::FAILED && + // net::IsCertificateError(status.os_error()) && request_->delegate()) { + // request_->delegate()->OnSSLCertificateError(request_, status.os_error()); + // } + + DisconnectFromMessageFilter(); + // NotifyDone may have been called on the job if the original request was + // redirected. + if (!is_done()) { + // We can complete the job if we have a valid response or a pending read. + // An end request can be received in the following cases + // 1. We failed to connect to the server, in which case we did not receive + // a valid response. + // 2. In response to a read request. + if (!has_response_started() || pending_buf_) { + NotifyDone(status); + } else { + // Wait for the http stack to issue a Read request where we will notify + // that the job has completed. + request_status_ = status; + return; + } + } + + // Reset any pending reads. + if (pending_buf_) { + pending_buf_ = NULL; + pending_buf_size_ = 0; + NotifyReadComplete(0); + } +} + +void URLRequestAutomationJob::Cleanup() { + headers_ = NULL; + mime_type_.erase(); + + id_ = 0; + tab_ = 0; + + DCHECK(message_filter_ == NULL); + DisconnectFromMessageFilter(); + + pending_buf_ = NULL; + pending_buf_size_ = 0; +} + +void URLRequestAutomationJob::StartAsync() { + DLOG(INFO) << "URLRequestAutomationJob: start request: " << + (request_ ? request_->url().spec() : "NULL request"); + + // If the job is cancelled before we got a chance to start it + // we have nothing much to do here. + if (is_done()) + return; + + // We should not receive a Start request for a pending job. + DCHECK(!is_pending()); + + if (!request_) { + NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, + net::ERR_FAILED)); + return; + } + + // Register this request with automation message filter. + message_filter_->RegisterRequest(this); + + // Strip unwanted headers. + net::HttpRequestHeaders new_request_headers; + new_request_headers.MergeFrom(request_->extra_request_headers()); + for (size_t i = 0; i < arraysize(kFilteredHeaderStrings); ++i) + new_request_headers.RemoveHeader(kFilteredHeaderStrings[i]); + + if (request_->context()) { + // Only add default Accept-Language and Accept-Charset if the request + // didn't have them specified. + if (!new_request_headers.HasHeader( + net::HttpRequestHeaders::kAcceptLanguage) && + !request_->context()->accept_language().empty()) { + new_request_headers.SetHeader(net::HttpRequestHeaders::kAcceptLanguage, + request_->context()->accept_language()); + } + if (!new_request_headers.HasHeader( + net::HttpRequestHeaders::kAcceptCharset) && + !request_->context()->accept_charset().empty()) { + new_request_headers.SetHeader(net::HttpRequestHeaders::kAcceptCharset, + request_->context()->accept_charset()); + } + } + + // Ensure that we do not send username and password fields in the referrer. + GURL referrer(request_->GetSanitizedReferrer()); + + // The referrer header must be suppressed if the preceding URL was + // a secure one and the new one is not. + if (referrer.SchemeIsSecure() && !request_->url().SchemeIsSecure()) { + DLOG(INFO) << + "Suppressing referrer header since going from secure to non-secure"; + referrer = GURL(); + } + + // Ask automation to start this request. + IPC::AutomationURLRequest automation_request = { + request_->url().spec(), + request_->method(), + referrer.spec(), + new_request_headers.ToString(), + request_->get_upload() + }; + + DCHECK(message_filter_); + message_filter_->Send(new AutomationMsg_RequestStart(0, tab_, id_, + automation_request)); +} + +void URLRequestAutomationJob::DisconnectFromMessageFilter() { + if (message_filter_) { + message_filter_->UnRegisterRequest(this); + message_filter_ = NULL; + } +} + +void URLRequestAutomationJob::StartPendingJob( + int new_tab_handle, + AutomationResourceMessageFilter* new_filter) { + DCHECK(new_filter != NULL); + tab_ = new_tab_handle; + message_filter_ = new_filter; + is_pending_ = false; + Start(); +} + +void URLRequestAutomationJob::NotifyJobCompletionTask() { + if (!is_done()) { + NotifyDone(request_status_); + } + // Reset any pending reads. + if (pending_buf_) { + pending_buf_ = NULL; + pending_buf_size_ = 0; + NotifyReadComplete(0); + } +} diff --git a/chrome/browser/automation/url_request_automation_job.h b/chrome/browser/automation/url_request_automation_job.h new file mode 100644 index 0000000..b8c7442 --- /dev/null +++ b/chrome/browser/automation/url_request_automation_job.h @@ -0,0 +1,127 @@ +// Copyright (c) 2006-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. +// This class simulates what wininet does when a dns lookup fails. + +#ifndef CHROME_BROWSER_AUTOMATION_URL_REQUEST_AUTOMATION_JOB_H_ +#define CHROME_BROWSER_AUTOMATION_URL_REQUEST_AUTOMATION_JOB_H_ + +#include <vector> + +#include "chrome/browser/automation/automation_resource_message_filter.h" +#include "chrome/common/ref_counted_util.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_job.h" + +class AutomationResourceMessageFilter; + +namespace IPC { +class Message; +struct AutomationURLResponse; +}; + +// URLRequestJob implementation that loads the resources using +// automation. +class URLRequestAutomationJob : public URLRequestJob { + public: + URLRequestAutomationJob(URLRequest* request, int tab, int request_id, + AutomationResourceMessageFilter* filter, + bool is_pending); + + // Register our factory for HTTP/HTTPs requests. + static bool EnsureProtocolFactoryRegistered(); + + static URLRequest::ProtocolFactory Factory; + + // URLRequestJob methods. + virtual void Start(); + virtual void Kill(); + virtual bool GetMimeType(std::string* mime_type) const; + virtual bool GetCharset(std::string* charset); + virtual void GetResponseInfo(net::HttpResponseInfo* info); + virtual int GetResponseCode() const; + virtual bool IsRedirectResponse(GURL* location, int* http_status_code); + + // Peek and process automation messages for URL requests. + static bool MayFilterMessage(const IPC::Message& message, int* request_id); + void OnMessage(const IPC::Message& message); + + int id() const { + return id_; + } + + int request_id() const { + return request_id_; + } + + bool is_pending() const { + return is_pending_; + } + + AutomationResourceMessageFilter* message_filter() const { + return message_filter_; + } + + // Resumes a job, which was waiting for the external host to connect to the + // automation channel. This is to ensure that this request gets routed to the + // external host. + void StartPendingJob(int new_tab_handle, + AutomationResourceMessageFilter* new_filter); + + protected: + // Protected URLRequestJob override. + virtual bool ReadRawData(net::IOBuffer* buf, int buf_size, int* bytes_read); + + void StartAsync(); + void Cleanup(); + void DisconnectFromMessageFilter(); + + // IPC message handlers. + void OnRequestStarted(int tab, int id, + const IPC::AutomationURLResponse& response); + void OnDataAvailable(int tab, int id, const std::string& bytes); + void OnRequestEnd(int tab, int id, const URLRequestStatus& status); + + private: + virtual ~URLRequestAutomationJob(); + + // Task which is scheduled in the URLRequestAutomationJob::ReadRawData + // function, which completes the job. + void NotifyJobCompletionTask(); + + int id_; + int tab_; + scoped_refptr<AutomationResourceMessageFilter> message_filter_; + + scoped_refptr<net::IOBuffer> pending_buf_; + size_t pending_buf_size_; + + std::string mime_type_; + scoped_refptr<net::HttpResponseHeaders> headers_; + std::string redirect_url_; + int redirect_status_; + int request_id_; + + static int instance_count_; + + static bool is_protocol_factory_registered_; + // The previous HTTP/HTTPs protocol factories. We pass unhandled + // requests off to these factories + static URLRequest::ProtocolFactory* old_http_factory_; + static URLRequest::ProtocolFactory* old_https_factory_; + + // Set to true if the job is waiting for the external host to connect to the + // automation channel, which will be used for routing the network requests to + // the host. + bool is_pending_; + + // Contains the request status code, which is eventually passed to the http + // stack when we receive a Read request for a completed job. + URLRequestStatus request_status_; + + DISALLOW_COPY_AND_ASSIGN(URLRequestAutomationJob); +}; + +#endif // CHROME_BROWSER_AUTOMATION_URL_REQUEST_AUTOMATION_JOB_H_ + |