summaryrefslogtreecommitdiffstats
path: root/win8/test
diff options
context:
space:
mode:
authorgrt@chromium.org <grt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-17 16:21:01 +0000
committergrt@chromium.org <grt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-17 16:21:01 +0000
commite1eeaf4fd62e470d1ab9f6b670c3cf74eda8d435 (patch)
tree8cc99739ba4d4f306e10c78bf909214bd0f01b77 /win8/test
parentd28c6b2c72a373702f4604af540b4c7b10fd2c7d (diff)
downloadchromium_src-e1eeaf4fd62e470d1ab9f6b670c3cf74eda8d435.zip
chromium_src-e1eeaf4fd62e470d1ab9f6b670c3cf74eda8d435.tar.gz
chromium_src-e1eeaf4fd62e470d1ab9f6b670c3cf74eda8d435.tar.bz2
A mechanism to set the default handler for a URL protocol on Windows 8.
This change introduces OpenWithDialogController, which provides an asynchronous as well as a synchronous interface to setting a default protocol handler on Windows 8. BUG=154081 Review URL: https://chromiumcodereview.appspot.com/11863016 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@177417 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'win8/test')
-rw-r--r--win8/test/open_with_dialog_async.cc121
-rw-r--r--win8/test/open_with_dialog_async.h35
-rw-r--r--win8/test/open_with_dialog_controller.cc291
-rw-r--r--win8/test/open_with_dialog_controller.h61
-rw-r--r--win8/test/ui_automation_client.cc632
-rw-r--r--win8/test/ui_automation_client.h70
6 files changed, 1210 insertions, 0 deletions
diff --git a/win8/test/open_with_dialog_async.cc b/win8/test/open_with_dialog_async.cc
new file mode 100644
index 0000000..f491149
--- /dev/null
+++ b/win8/test/open_with_dialog_async.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 2013 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.
+
+// Implementation for the asynchronous interface to the Windows shell
+// SHOpenWithDialog function. The call is made on a dedicated UI thread in a
+// single-threaded apartment.
+
+#include "win8/test/open_with_dialog_async.h"
+
+#include <shlobj.h>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
+#include "base/win/windows_version.h"
+
+namespace win8 {
+
+namespace {
+
+struct OpenWithContext {
+ OpenWithContext(
+ HWND parent_window_in,
+ const string16& file_name_in,
+ const string16& file_type_class_in,
+ int open_as_info_flags_in,
+ const scoped_refptr<base::SingleThreadTaskRunner>& client_runner_in,
+ const OpenWithDialogCallback& callback_in);
+ ~OpenWithContext();
+
+ base::Thread thread;
+ HWND parent_window;
+ string16 file_name;
+ string16 file_type_class;
+ int open_as_info_flags;
+ scoped_refptr<base::SingleThreadTaskRunner> client_runner;
+ OpenWithDialogCallback callback;
+};
+
+OpenWithContext::OpenWithContext(
+ HWND parent_window_in,
+ const string16& file_name_in,
+ const string16& file_type_class_in,
+ int open_as_info_flags_in,
+ const scoped_refptr<base::SingleThreadTaskRunner>& client_runner_in,
+ const OpenWithDialogCallback& callback_in)
+ : thread("OpenWithDialog"),
+ parent_window(parent_window_in),
+ file_name(file_name_in),
+ file_type_class(file_type_class_in),
+ open_as_info_flags(open_as_info_flags_in),
+ client_runner(client_runner_in),
+ callback(callback_in) {
+ thread.init_com_with_mta(false);
+ thread.Start();
+}
+
+OpenWithContext::~OpenWithContext() {}
+
+// Runs the caller-provided |callback| with the result of the call to
+// SHOpenWithDialog on the caller's initial thread.
+void OnOpenWithDialogDone(OpenWithContext* context, HRESULT result) {
+ DCHECK(context->client_runner->BelongsToCurrentThread());
+ OpenWithDialogCallback callback(context->callback);
+
+ // Join with the thread.
+ delete context;
+
+ // Run the client's callback.
+ callback.Run(result);
+}
+
+// Calls SHOpenWithDialog (blocking), and returns the result back to the client
+// thread.
+void OpenWithDialogTask(OpenWithContext* context) {
+ DCHECK_EQ(context->thread.thread_id(), base::PlatformThread::CurrentId());
+ OPENASINFO open_as_info = {
+ context->file_name.c_str(),
+ context->file_type_class.c_str(),
+ context->open_as_info_flags
+ };
+
+ HRESULT result = ::SHOpenWithDialog(context->parent_window, &open_as_info);
+
+ // Bounce back to the calling thread to release resources and deliver the
+ // callback.
+ if (!context->client_runner->PostTask(
+ FROM_HERE,
+ base::Bind(&OnOpenWithDialogDone, context, result))) {
+ // The calling thread has gone away. There's nothing to be done but leak.
+ // In practice this is only likely to happen at shutdown, so there isn't
+ // much of a concern that it'll happen in the real world.
+ DLOG(ERROR) << "leaking OpenWith thread; result = " << std::hex << result;
+ }
+}
+
+} // namespace
+
+void OpenWithDialogAsync(
+ HWND parent_window,
+ const string16& file_name,
+ const string16& file_type_class,
+ int open_as_info_flags,
+ const OpenWithDialogCallback& callback) {
+ DCHECK_GE(base::win::GetVersion(), base::win::VERSION_VISTA);
+ OpenWithContext* context =
+ new OpenWithContext(parent_window, file_name, file_type_class,
+ open_as_info_flags,
+ base::ThreadTaskRunnerHandle::Get(), callback);
+ context->thread.message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&OpenWithDialogTask, context));
+}
+
+} // namespace win8
diff --git a/win8/test/open_with_dialog_async.h b/win8/test/open_with_dialog_async.h
new file mode 100644
index 0000000..dadc9ca
--- /dev/null
+++ b/win8/test/open_with_dialog_async.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2013 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 WIN8_TEST_OPEN_WITH_DIALOG_ASYNC_H_
+#define WIN8_TEST_OPEN_WITH_DIALOG_ASYNC_H_
+
+#include <windows.h>
+
+#include "base/callback_forward.h"
+#include "base/string16.h"
+
+namespace win8 {
+
+// Expected HRESULTS:
+// S_OK - A choice was made.
+// HRESULT_FROM_WIN32(ERROR_CANCELLED) - The dialog was dismissed.
+// HRESULT_FROM_WIN32(RPC_S_CALL_FAILED) - OpenWith.exe died.
+typedef base::Callback<void(HRESULT)> OpenWithDialogCallback;
+
+// Calls SHOpenWithDialog on a dedicated thread, returning the result to the
+// caller via |callback| on the current thread. The Windows SHOpenWithDialog
+// function blocks until the user makes a choice or dismisses the dialog (there
+// is no natural timeout nor a means by which it can be cancelled). Note that
+// the dedicated thread will be leaked if the calling thread's message loop goes
+// away before the interaction completes.
+void OpenWithDialogAsync(HWND parent_window,
+ const string16& file_name,
+ const string16& file_type_class,
+ int open_as_info_flags,
+ const OpenWithDialogCallback& callback);
+
+} // namespace win8
+
+#endif // WIN8_TEST_OPEN_WITH_DIALOG_ASYNC_H_
diff --git a/win8/test/open_with_dialog_controller.cc b/win8/test/open_with_dialog_controller.cc
new file mode 100644
index 0000000..7a63dd0
--- /dev/null
+++ b/win8/test/open_with_dialog_controller.cc
@@ -0,0 +1,291 @@
+// Copyright (c) 2013 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 "win8/test/open_with_dialog_controller.h"
+
+#include <shlobj.h>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/run_loop.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/thread_checker.h"
+#include "base/win/windows_version.h"
+#include "win8/test/open_with_dialog_async.h"
+#include "win8/test/ui_automation_client.h"
+
+namespace win8 {
+
+namespace {
+
+const int kControllerTimeoutSeconds = 5;
+const wchar_t kShellFlyoutClassName[] = L"Shell_Flyout";
+
+// A callback invoked with the OpenWithDialogController's results. Said results
+// are copied to |result_out| and |choices_out| and then |closure| is invoked.
+// This function is in support of OpenWithDialogController::RunSynchronously.
+void OnMakeDefaultComplete(
+ const base::Closure& closure,
+ HRESULT* result_out,
+ std::vector<string16>* choices_out,
+ HRESULT hr,
+ std::vector<string16> choices) {
+ *result_out = hr;
+ *choices_out = choices;
+ closure.Run();
+}
+
+} // namespace
+
+// Lives on the main thread and is owned by a controller. May outlive the
+// controller (see Orphan).
+class OpenWithDialogController::Context {
+ public:
+ Context();
+ ~Context();
+
+ base::WeakPtr<Context> AsWeakPtr();
+
+ void Orphan();
+
+ void Begin(HWND parent_window,
+ const string16& url_protocol,
+ const string16& program_name,
+ const OpenWithDialogController::SetDefaultCallback& callback);
+
+ private:
+ enum State {
+ // The Context has been constructed.
+ CONTEXT_INITIALIZED,
+ // The UI automation event handler is ready.
+ CONTEXT_AUTOMATION_READY,
+ // The automation results came back before the call to SHOpenWithDialog.
+ CONTEXT_WAITING_FOR_DIALOG,
+ // The call to SHOpenWithDialog returned before automation results.
+ CONTEXT_WAITING_FOR_RESULTS,
+ CONTEXT_FINISHED,
+ };
+
+ // Invokes the client's callback and destroys this instance.
+ void NotifyClientAndDie();
+
+ void OnTimeout();
+ void OnInitialized(HRESULT result);
+ void OnAutomationResult(HRESULT result, std::vector<string16> choices);
+ void OnOpenWithComplete(HRESULT result);
+
+ base::ThreadChecker thread_checker_;
+ State state_;
+ internal::UIAutomationClient automation_client_;
+ HWND parent_window_;
+ string16 file_name_;
+ string16 file_type_class_;
+ int open_as_info_flags_;
+ OpenWithDialogController::SetDefaultCallback callback_;
+ HRESULT open_with_result_;
+ HRESULT automation_result_;
+ std::vector<string16> automation_choices_;
+ base::WeakPtrFactory<Context> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(OpenWithDialogController::Context);
+};
+
+OpenWithDialogController::Context::Context()
+ : state_(CONTEXT_INITIALIZED),
+ parent_window_(),
+ open_as_info_flags_(),
+ open_with_result_(E_FAIL),
+ automation_result_(E_FAIL),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {}
+
+OpenWithDialogController::Context::~Context() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+base::WeakPtr<OpenWithDialogController::Context>
+ OpenWithDialogController::Context::AsWeakPtr() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+void OpenWithDialogController::Context::Orphan() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // The controller is being destroyed. Its client is no longer interested in
+ // having the interaction continue.
+ DLOG_IF(WARNING, (state_ == CONTEXT_AUTOMATION_READY ||
+ state_ == CONTEXT_WAITING_FOR_DIALOG))
+ << "Abandoning the OpenWithDialog.";
+ delete this;
+}
+
+void OpenWithDialogController::Context::Begin(
+ HWND parent_window,
+ const string16& url_protocol,
+ const string16& program_name,
+ const OpenWithDialogController::SetDefaultCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ parent_window_ = parent_window;
+ file_name_ = url_protocol;
+ file_type_class_.clear();
+ open_as_info_flags_ = (OAIF_URL_PROTOCOL | OAIF_FORCE_REGISTRATION |
+ OAIF_REGISTER_EXT);
+ callback_ = callback;
+
+ // Post a delayed callback to abort the operation if it takes too long.
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&OpenWithDialogController::Context::OnTimeout, AsWeakPtr()),
+ base::TimeDelta::FromSeconds(kControllerTimeoutSeconds));
+
+ automation_client_.Begin(
+ kShellFlyoutClassName,
+ program_name,
+ base::Bind(&OpenWithDialogController::Context::OnInitialized,
+ AsWeakPtr()),
+ base::Bind(&OpenWithDialogController::Context::OnAutomationResult,
+ AsWeakPtr()));
+}
+
+void OpenWithDialogController::Context::NotifyClientAndDie() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(state_, CONTEXT_FINISHED);
+ DLOG_IF(WARNING, SUCCEEDED(automation_result_) && FAILED(open_with_result_))
+ << "Automation succeeded, yet SHOpenWithDialog failed.";
+
+ // Ignore any future callbacks (such as the timeout) or calls to Orphan.
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ callback_.Run(automation_result_, automation_choices_);
+ delete this;
+}
+
+void OpenWithDialogController::Context::OnTimeout() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // This is a LOG rather than a DLOG since it represents something that needs
+ // to be investigated and fixed.
+ LOG(ERROR) << __FUNCTION__ " state: " << state_;
+
+ state_ = CONTEXT_FINISHED;
+ NotifyClientAndDie();
+}
+
+void OpenWithDialogController::Context::OnInitialized(HRESULT result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(state_, CONTEXT_INITIALIZED);
+ if (FAILED(result)) {
+ automation_result_ = result;
+ state_ = CONTEXT_FINISHED;
+ NotifyClientAndDie();
+ return;
+ }
+ state_ = CONTEXT_AUTOMATION_READY;
+ OpenWithDialogAsync(
+ parent_window_, file_name_, file_type_class_, open_as_info_flags_,
+ base::Bind(&OpenWithDialogController::Context::OnOpenWithComplete,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void OpenWithDialogController::Context::OnAutomationResult(
+ HRESULT result,
+ std::vector<string16> choices) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(automation_result_, E_FAIL);
+
+ automation_result_ = result;
+ automation_choices_ = choices;
+ switch (state_) {
+ case CONTEXT_AUTOMATION_READY:
+ // The results of automation are in and we're waiting for
+ // SHOpenWithDialog to return.
+ state_ = CONTEXT_WAITING_FOR_DIALOG;
+ break;
+ case CONTEXT_WAITING_FOR_RESULTS:
+ state_ = CONTEXT_FINISHED;
+ NotifyClientAndDie();
+ break;
+ default:
+ NOTREACHED() << state_;
+ }
+}
+
+void OpenWithDialogController::Context::OnOpenWithComplete(HRESULT result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(open_with_result_, E_FAIL);
+
+ open_with_result_ = result;
+ switch (state_) {
+ case CONTEXT_AUTOMATION_READY:
+ // The interaction completed and we're waiting for the results from the
+ // automation side to come in.
+ state_ = CONTEXT_WAITING_FOR_RESULTS;
+ break;
+ case CONTEXT_WAITING_FOR_DIALOG:
+ // All results are in. Invoke the caller's callback.
+ state_ = CONTEXT_FINISHED;
+ NotifyClientAndDie();
+ break;
+ default:
+ NOTREACHED() << state_;
+ }
+}
+
+OpenWithDialogController::OpenWithDialogController() {}
+
+OpenWithDialogController::~OpenWithDialogController() {
+ // Orphan the context if this instance is being destroyed before the context
+ // finishes its work.
+ if (context_)
+ context_->Orphan();
+}
+
+void OpenWithDialogController::Begin(
+ HWND parent_window,
+ const string16& url_protocol,
+ const string16& program,
+ const SetDefaultCallback& callback) {
+ DCHECK_EQ(context_, static_cast<Context*>(NULL));
+ if (base::win::GetVersion() < base::win::VERSION_WIN8) {
+ NOTREACHED() << "Windows 8 is required.";
+ // The callback may not properly handle being run from Begin, so post a task
+ // to this thread's task runner to call it.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, E_FAIL, std::vector<string16>()));
+ return;
+ }
+
+ context_ = (new Context())->AsWeakPtr();
+ context_->Begin(parent_window, url_protocol, program, callback);
+}
+
+HRESULT OpenWithDialogController::RunSynchronously(
+ HWND parent_window,
+ const string16& protocol,
+ const string16& program,
+ std::vector<string16>* choices) {
+ DCHECK_EQ(MessageLoop::current(), static_cast<MessageLoop*>(NULL));
+ if (base::win::GetVersion() < base::win::VERSION_WIN8) {
+ NOTREACHED() << "Windows 8 is required.";
+ return E_FAIL;
+ }
+
+ HRESULT result = S_OK;
+ MessageLoop message_loop;
+ base::RunLoop run_loop;
+
+ message_loop.PostTask(
+ FROM_HERE,
+ base::Bind(&OpenWithDialogController::Begin, base::Unretained(this),
+ parent_window, protocol, program,
+ Bind(&OnMakeDefaultComplete, run_loop.QuitClosure(),
+ &result, choices)));
+
+ run_loop.Run();
+ return result;
+}
+
+} // namespace win8
diff --git a/win8/test/open_with_dialog_controller.h b/win8/test/open_with_dialog_controller.h
new file mode 100644
index 0000000..8c664fc
--- /dev/null
+++ b/win8/test/open_with_dialog_controller.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2013 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 WIN8_TEST_OPEN_WITH_DIALOG_CONTROLLER_H_
+#define WIN8_TEST_OPEN_WITH_DIALOG_CONTROLLER_H_
+
+#include <windows.h>
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/memory/weak_ptr.h"
+#include "base/string16.h"
+
+namespace win8 {
+
+// Asynchronously drives Windows 8's OpenWithDialog into making a given program
+// the default handler for an URL protocol.
+class OpenWithDialogController {
+ public:
+ // A callback that is invoked upon completion of the OpenWithDialog
+ // interaction. If the HRESULT indicates success, the interaction completed
+ // successfully. Otherwise, the vector of strings may contain the list of
+ // possible choices if the desired program could not be selected.
+ typedef base::Callback<void(HRESULT,
+ std::vector<string16>)> SetDefaultCallback;
+
+ OpenWithDialogController();
+ ~OpenWithDialogController();
+
+ // Starts the process of making |program| the default handler for
+ // |url_protocol|. |parent_window| may be NULL. |callback| will be invoked
+ // upon completion. This instance may be deleted prior to |callback| being
+ // invoked to cancel the operation.
+ void Begin(HWND parent_window,
+ const string16& url_protocol,
+ const string16& program,
+ const SetDefaultCallback& callback);
+
+ // Sychronously drives the dialog by running a message loop. Do not by any
+ // means call this on a thread that already has a message loop. Returns S_OK
+ // on success. Otherwise, |choices| may contain the list of possible choices
+ // if the desired program could not be selected.
+ HRESULT RunSynchronously(HWND parent_window,
+ const string16& url_protocol,
+ const string16& program,
+ std::vector<string16>* choices);
+
+ private:
+ class Context;
+
+ base::WeakPtr<Context> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(OpenWithDialogController);
+};
+
+} // namespace win8
+
+#endif // WIN8_TEST_OPEN_WITH_DIALOG_CONTROLLER_H_
diff --git a/win8/test/ui_automation_client.cc b/win8/test/ui_automation_client.cc
new file mode 100644
index 0000000..42de5d5
--- /dev/null
+++ b/win8/test/ui_automation_client.cc
@@ -0,0 +1,632 @@
+// Copyright (c) 2013 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 "win8/test/ui_automation_client.h"
+
+#include <atlbase.h>
+#include <atlcom.h>
+#include <oleauto.h>
+#include <uiautomation.h>
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/win/scoped_comptr.h"
+#include "base/win/scoped_variant.h"
+
+namespace win8 {
+namespace internal {
+
+// The guts of the UI automation client which runs on a dedicated thread in the
+// multi-threaded COM apartment. An instance may be constructed on any thread,
+// but Initialize() must be invoked on a thread in the MTA.
+class UIAutomationClient::Context {
+ public:
+ // Returns a new instance ready for initialization and use on another thread.
+ static base::WeakPtr<Context> Create();
+
+ // Deletes the instance.
+ void DeleteOnAutomationThread();
+
+ // Initializes the context, invoking |init_callback| via |client_runner| when
+ // done. On success, |result_callback| will eventually be called after the
+ // window has been processed. On failure, this instance self-destructs after
+ // posting |init_callback|.
+ void Initialize(
+ scoped_refptr<base::SingleThreadTaskRunner> client_runner,
+ string16 class_name,
+ string16 item_name,
+ UIAutomationClient::InitializedCallback init_callback,
+ UIAutomationClient::ResultCallback result_callback);
+
+ // Methods invoked by event handlers via weak pointers.
+ void HandleAutomationEvent(
+ base::win::ScopedComPtr<IUIAutomationElement> sender,
+ EVENTID eventId);
+
+ private:
+ class EventHandler;
+
+ // The only and only method that may be called from outside of the automation
+ // thread.
+ Context();
+ ~Context();
+
+ HRESULT InstallWindowObserver();
+ HRESULT RemoveWindowObserver();
+
+ void HandleWindowOpen(
+ const base::win::ScopedComPtr<IUIAutomationElement>& window);
+ void ProcessWindow(
+ const base::win::ScopedComPtr<IUIAutomationElement>& window);
+ HRESULT InvokeDesiredItem(
+ const base::win::ScopedComPtr<IUIAutomationElement>& element);
+ HRESULT GetInvokableItems(
+ const base::win::ScopedComPtr<IUIAutomationElement>& element,
+ std::vector<string16>* choices);
+ void CloseWindow(const base::win::ScopedComPtr<IUIAutomationElement>& window);
+
+ base::ThreadChecker thread_checker_;
+
+ // The loop on which the client itself lives.
+ scoped_refptr<base::SingleThreadTaskRunner> client_runner_;
+
+ // The class name of the window for which the client waits.
+ string16 class_name_;
+
+ // The name of the item to invoke.
+ string16 item_name_;
+
+ // The consumer's result callback.
+ ResultCallback result_callback_;
+
+ // The automation client.
+ base::win::ScopedComPtr<IUIAutomation> automation_;
+
+ // A handler of Window open events.
+ base::win::ScopedComPtr<IUIAutomationEventHandler> event_handler_;
+
+ // Weak pointers to the context are given to event handlers.
+ base::WeakPtrFactory<UIAutomationClient::Context> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(Context);
+};
+
+class UIAutomationClient::Context::EventHandler
+ : public CComObjectRootEx<CComMultiThreadModel>,
+ public IUIAutomationEventHandler {
+ public:
+ BEGIN_COM_MAP(UIAutomationClient::Context::EventHandler)
+ COM_INTERFACE_ENTRY(IUIAutomationEventHandler)
+ END_COM_MAP()
+
+ EventHandler();
+ virtual ~EventHandler();
+
+ // Initializes the object with its parent UI automation client context's
+ // message loop and pointer. Events are dispatched back to the context on
+ // the given loop.
+ void Initialize(
+ const scoped_refptr<base::SingleThreadTaskRunner>& context_runner,
+ const base::WeakPtr<UIAutomationClient::Context>& context);
+
+ // IUIAutomationEventHandler methods.
+ STDMETHOD(HandleAutomationEvent)(IUIAutomationElement* sender,
+ EVENTID eventId);
+
+ private:
+ // The task runner for the UI automation client context.
+ scoped_refptr<base::SingleThreadTaskRunner> context_runner_;
+
+ // The parent UI automation client context.
+ base::WeakPtr<UIAutomationClient::Context> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventHandler);
+};
+
+UIAutomationClient::Context::EventHandler::EventHandler() {}
+
+UIAutomationClient::Context::EventHandler::~EventHandler() {}
+
+void UIAutomationClient::Context::EventHandler::Initialize(
+ const scoped_refptr<base::SingleThreadTaskRunner>& context_runner,
+ const base::WeakPtr<UIAutomationClient::Context>& context) {
+ context_runner_ = context_runner;
+ context_ = context;
+}
+
+HRESULT UIAutomationClient::Context::EventHandler::HandleAutomationEvent(
+ IUIAutomationElement* sender,
+ EVENTID eventId) {
+ // Event handlers are invoked on an arbitrary thread in the MTA. Send the
+ // event back to the main UI automation thread for processing.
+ context_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&UIAutomationClient::Context::HandleAutomationEvent, context_,
+ base::win::ScopedComPtr<IUIAutomationElement>(sender),
+ eventId));
+
+ return S_OK;
+}
+
+base::WeakPtr<UIAutomationClient::Context>
+ UIAutomationClient::Context::Create() {
+ Context* context = new Context();
+ base::WeakPtr<Context> context_ptr(context->weak_ptr_factory_.GetWeakPtr());
+ // Unbind from this thread so that the instance will bind to the automation
+ // thread when Initialize is called.
+ context->weak_ptr_factory_.DetachFromThread();
+ return context_ptr;
+}
+
+void UIAutomationClient::Context::DeleteOnAutomationThread() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ delete this;
+}
+
+UIAutomationClient::Context::Context()
+ : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {}
+
+UIAutomationClient::Context::~Context() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (event_handler_.get()) {
+ event_handler_ = NULL;
+ HRESULT result = automation_->RemoveAllEventHandlers();
+ LOG_IF(ERROR, FAILED(result)) << std::hex << result;
+ }
+}
+
+void UIAutomationClient::Context::Initialize(
+ scoped_refptr<base::SingleThreadTaskRunner> client_runner,
+ string16 class_name,
+ string16 item_name,
+ UIAutomationClient::InitializedCallback init_callback,
+ UIAutomationClient::ResultCallback result_callback) {
+ // This and all other methods must be called on the automation thread.
+ DCHECK(!client_runner->BelongsToCurrentThread());
+ // Bind the checker to this thread.
+ thread_checker_.DetachFromThread();
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ client_runner_ = client_runner;
+ class_name_ = class_name;
+ item_name_ = item_name;
+ result_callback_ = result_callback;
+
+ HRESULT result = automation_.CreateInstance(CLSID_CUIAutomation, NULL,
+ CLSCTX_INPROC_SERVER);
+ if (FAILED(result) || !automation_.get())
+ LOG(ERROR) << std::hex << result;
+ else
+ result = InstallWindowObserver();
+
+ // Tell the client that initialization is complete.
+ client_runner_->PostTask(FROM_HERE, base::Bind(init_callback, result));
+
+ // Self-destruct if the overall operation failed.
+ if (FAILED(result))
+ delete this;
+}
+
+// Installs the window observer.
+HRESULT UIAutomationClient::Context::InstallWindowObserver() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(automation_.get());
+ DCHECK(!event_handler_.get());
+
+ HRESULT result = S_OK;
+ base::win::ScopedComPtr<IUIAutomationElement> root_element;
+ base::win::ScopedComPtr<IUIAutomationCacheRequest> cache_request;
+
+ // Observe the opening of all windows.
+ result = automation_->GetRootElement(root_element.Receive());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ // Cache Window class, HWND, and window pattern for opened windows.
+ result = automation_->CreateCacheRequest(cache_request.Receive());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+ cache_request->AddProperty(UIA_ClassNamePropertyId);
+ cache_request->AddProperty(UIA_NativeWindowHandlePropertyId);
+
+ // Create the observer.
+ CComObject<EventHandler>* event_handler_obj = NULL;
+ result = CComObject<EventHandler>::CreateInstance(&event_handler_obj);
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+ event_handler_obj->Initialize(base::ThreadTaskRunnerHandle::Get(),
+ weak_ptr_factory_.GetWeakPtr());
+ base::win::ScopedComPtr<IUIAutomationEventHandler> event_handler(
+ event_handler_obj);
+
+ result = automation_->AddAutomationEventHandler(
+ UIA_Window_WindowOpenedEventId,
+ root_element,
+ TreeScope_Descendants,
+ cache_request,
+ event_handler);
+
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ event_handler_ = event_handler;
+ return S_OK;
+}
+
+// Removes this instance's window observer.
+HRESULT UIAutomationClient::Context::RemoveWindowObserver() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(automation_.get());
+ DCHECK(event_handler_.get());
+
+ HRESULT result = S_OK;
+ base::win::ScopedComPtr<IUIAutomationElement> root_element;
+
+ // The opening of all windows are observed.
+ result = automation_->GetRootElement(root_element.Receive());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ result = automation_->RemoveAutomationEventHandler(
+ UIA_Window_WindowOpenedEventId,
+ root_element,
+ event_handler_);
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ event_handler_ = NULL;
+ return S_OK;
+}
+
+// Handles an automation event. If the event results in the processing for which
+// this context was created, the context self-destructs after posting the
+// results to the client.
+void UIAutomationClient::Context::HandleAutomationEvent(
+ base::win::ScopedComPtr<IUIAutomationElement> sender,
+ EVENTID eventId) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (eventId == UIA_Window_WindowOpenedEventId)
+ HandleWindowOpen(sender);
+}
+
+// Handles a WindowOpen event. If |window| is the one for which this instance is
+// waiting, it is processed and this instance self-destructs after posting the
+// results to the client.
+void UIAutomationClient::Context::HandleWindowOpen(
+ const base::win::ScopedComPtr<IUIAutomationElement>& window) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ HRESULT hr = S_OK;
+ base::win::ScopedVariant var;
+
+ hr = window->GetCachedPropertyValueEx(UIA_ClassNamePropertyId, TRUE,
+ var.Receive());
+ if (FAILED(hr)) {
+ LOG(ERROR) << std::hex << hr;
+ return;
+ }
+
+ if (V_VT(&var) != VT_BSTR) {
+ LOG(ERROR) << __FUNCTION__ " class name is not a BSTR: " << V_VT(&var);
+ return;
+ }
+
+ string16 class_name(V_BSTR(&var));
+
+ // Window class names are atoms, which are case-insensitive.
+ if (class_name.size() == class_name_.size() &&
+ std::equal(class_name.begin(), class_name.end(), class_name_.begin(),
+ base::CaseInsensitiveCompare<wchar_t>())) {
+ RemoveWindowObserver();
+ ProcessWindow(window);
+ }
+}
+
+// Processes |window| by invoking the desired child item. If the item cannot be
+// found or invoked, an attempt is made to get a list of all invokable children.
+// The results are posted back to the client on |client_runner_|, and this
+// instance self-destructs.
+void UIAutomationClient::Context::ProcessWindow(
+ const base::win::ScopedComPtr<IUIAutomationElement>& window) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ HRESULT result = S_OK;
+ std::vector<string16> choices;
+ result = InvokeDesiredItem(window);
+ if (FAILED(result)) {
+ GetInvokableItems(window, &choices);
+ CloseWindow(window);
+ }
+
+ client_runner_->PostTask(FROM_HERE,
+ base::Bind(result_callback_, result, choices));
+
+ // Self-destruct since there's nothing more to be done here.
+ delete this;
+}
+
+// Invokes the desired child of |element|.
+HRESULT UIAutomationClient::Context::InvokeDesiredItem(
+ const base::win::ScopedComPtr<IUIAutomationElement>& element) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ HRESULT result = S_OK;
+ base::win::ScopedVariant var;
+ base::win::ScopedComPtr<IUIAutomationCondition> invokable_condition;
+ base::win::ScopedComPtr<IUIAutomationCondition> item_name_condition;
+ base::win::ScopedComPtr<IUIAutomationCondition> control_view_condition;
+ base::win::ScopedComPtr<IUIAutomationCondition> condition;
+ base::win::ScopedComPtr<IUIAutomationCacheRequest> cache_request;
+ base::win::ScopedComPtr<IUIAutomationElement> target;
+
+ // Search for an invokable element named item_name.
+ var.Set(true);
+ result = automation_->CreatePropertyCondition(
+ UIA_IsInvokePatternAvailablePropertyId,
+ var,
+ invokable_condition.Receive());
+ var.Reset();
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return false;
+ }
+
+ var.Set(item_name_.c_str());
+ result = automation_->CreatePropertyCondition(UIA_NamePropertyId,
+ var,
+ item_name_condition.Receive());
+ var.Reset();
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ result = automation_->get_ControlViewCondition(
+ control_view_condition.Receive());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ std::vector<IUIAutomationCondition*> conditions;
+ conditions.push_back(invokable_condition.get());
+ conditions.push_back(item_name_condition.get());
+ conditions.push_back(control_view_condition.get());
+ result = automation_->CreateAndConditionFromNativeArray(
+ &conditions[0], conditions.size(), condition.Receive());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ // Cache invokable pattern for the item.
+ result = automation_->CreateCacheRequest(cache_request.Receive());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+ cache_request->AddPattern(UIA_InvokePatternId);
+
+ result = element->FindFirstBuildCache(
+ static_cast<TreeScope>(TreeScope_Children | TreeScope_Descendants),
+ condition,
+ cache_request,
+ target.Receive());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ // If the item was found, invoke it.
+ if (!target.get()) {
+ LOG(ERROR) << "Failed to find desired item to invoke.";
+ return E_FAIL;
+ }
+
+ base::win::ScopedComPtr<IUIAutomationInvokePattern> invoker;
+ result = target->GetCachedPatternAs(UIA_InvokePatternId, invoker.iid(),
+ invoker.ReceiveVoid());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ result = invoker->Invoke();
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ return S_OK;
+}
+
+// Populates |choices| with the names of all invokable children of |element|.
+HRESULT UIAutomationClient::Context::GetInvokableItems(
+ const base::win::ScopedComPtr<IUIAutomationElement>& element,
+ std::vector<string16>* choices) {
+ DCHECK(choices);
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ HRESULT result = S_OK;
+ base::win::ScopedVariant var;
+ base::win::ScopedComPtr<IUIAutomationCondition> invokable_condition;
+ base::win::ScopedComPtr<IUIAutomationCondition> control_view_condition;
+ base::win::ScopedComPtr<IUIAutomationCondition> condition;
+ base::win::ScopedComPtr<IUIAutomationCacheRequest> cache_request;
+ base::win::ScopedComPtr<IUIAutomationElementArray> element_array;
+ base::win::ScopedComPtr<IUIAutomationElement> child_element;
+
+ // Search for all invokable elements.
+ var.Set(true);
+ result = automation_->CreatePropertyCondition(
+ UIA_IsInvokePatternAvailablePropertyId,
+ var,
+ invokable_condition.Receive());
+ var.Reset();
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ result = automation_->get_ControlViewCondition(
+ control_view_condition.Receive());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ result = automation_->CreateAndCondition(
+ invokable_condition, control_view_condition, condition.Receive());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ // Cache item names.
+ result = automation_->CreateCacheRequest(cache_request.Receive());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+ cache_request->AddProperty(UIA_NamePropertyId);
+
+ result = element->FindAllBuildCache(
+ static_cast<TreeScope>(TreeScope_Children | TreeScope_Descendants),
+ condition,
+ cache_request,
+ element_array.Receive());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ if (!element_array.get()) {
+ LOG(ERROR) << "The window may have vanished.";
+ return S_OK;
+ }
+
+ int num_elements = 0;
+ result = element_array->get_Length(&num_elements);
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return result;
+ }
+
+ choices->clear();
+ choices->reserve(num_elements);
+ for (int i = 0; i < num_elements; ++i) {
+ child_element.Release();
+ result = element_array->GetElement(i, child_element.Receive());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ continue;
+ }
+ result = child_element->GetCachedPropertyValueEx(UIA_NamePropertyId, TRUE,
+ var.Receive());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ continue;
+ }
+ if (V_VT(&var) != VT_BSTR) {
+ LOG(ERROR) << __FUNCTION__ " name is not a BSTR: " << V_VT(&var);
+ continue;
+ }
+ choices->push_back(string16(V_BSTR(&var)));
+ var.Reset();
+ }
+
+ return result;
+}
+
+// Closes the element |window| by sending it an escape key.
+void UIAutomationClient::Context::CloseWindow(
+ const base::win::ScopedComPtr<IUIAutomationElement>& window) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // It's tempting to get the Window pattern from |window| and invoke its Close
+ // method. Unfortunately, this doesn't work. Sending an escape key does the
+ // trick, though.
+ HRESULT result = S_OK;
+ base::win::ScopedVariant var;
+
+ result = window->GetCachedPropertyValueEx(
+ UIA_NativeWindowHandlePropertyId,
+ TRUE,
+ var.Receive());
+ if (FAILED(result)) {
+ LOG(ERROR) << std::hex << result;
+ return;
+ }
+
+ if (V_VT(&var) != VT_I4) {
+ LOG(ERROR) << __FUNCTION__ " window handle is not an int: " << V_VT(&var);
+ return;
+ }
+
+ HWND handle = reinterpret_cast<HWND>(V_I4(&var));
+
+ uint32 scan_code = MapVirtualKey(VK_ESCAPE, MAPVK_VK_TO_VSC);
+ PostMessage(handle, WM_KEYDOWN, VK_ESCAPE,
+ MAKELPARAM(1, scan_code));
+ PostMessage(handle, WM_KEYUP, VK_ESCAPE,
+ MAKELPARAM(1, scan_code | KF_REPEAT | KF_UP));
+}
+
+UIAutomationClient::UIAutomationClient()
+ : automation_thread_("UIAutomation") {}
+
+UIAutomationClient::~UIAutomationClient() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // context_ is still valid when the caller destroys the instance before the
+ // callback(s) have fired. In this case, delete the context on the automation
+ // thread before joining with it.
+ automation_thread_.message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&UIAutomationClient::Context::DeleteOnAutomationThread,
+ context_));
+}
+
+void UIAutomationClient::Begin(const wchar_t* class_name,
+ const string16& item_name,
+ const InitializedCallback& init_callback,
+ const ResultCallback& result_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(context_, static_cast<Context*>(NULL));
+
+ // Start the automation thread and initialize our automation client on it.
+ context_ = Context::Create();
+ automation_thread_.init_com_with_mta(true);
+ automation_thread_.Start();
+ automation_thread_.message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&UIAutomationClient::Context::Initialize,
+ context_,
+ base::ThreadTaskRunnerHandle::Get(),
+ string16(class_name),
+ item_name,
+ init_callback,
+ result_callback));
+}
+
+} // namespace internal
+} // namespace win8
diff --git a/win8/test/ui_automation_client.h b/win8/test/ui_automation_client.h
new file mode 100644
index 0000000..c285c7b
--- /dev/null
+++ b/win8/test/ui_automation_client.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2013 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 WIN8_TEST_UI_AUTOMATION_CLIENT_H_
+#define WIN8_TEST_UI_AUTOMATION_CLIENT_H_
+
+// This file contains UI automation implementation details for the
+// OpenWithDialogController. See that class for consumable entrypoints.
+
+#include <windows.h>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/memory/weak_ptr.h"
+#include "base/string16.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_checker.h"
+
+namespace win8 {
+namespace internal {
+
+// An asynchronous UI automation client that waits for the appearance of a
+// Window of a particular class and then invokes one of its children identified
+// by name. An instance may be destroyed at any time after Begin() is invoked.
+// Any pending operation is cancelled.
+class UIAutomationClient {
+ public:
+ // If the HRESULT argument indicates success, the automation client has been
+ // initialized, has installed its Window observer, and is ready to process
+ // the named window. Otherwise, initialization has failed and ResultCallback
+ // will not be invoked.
+ typedef base::Callback<void(HRESULT)> InitializedCallback;
+
+ // If the HRESULT argument indicates success, the desired item in the window
+ // was invoked. Otherwise, the string vector (if not empty) contains the list
+ // of possible items in the window.
+ typedef base::Callback<void(HRESULT, std::vector<string16>)> ResultCallback;
+
+ UIAutomationClient();
+ ~UIAutomationClient();
+
+ // Starts the client. Invokes |callback| once the client is ready or upon
+ // failure to start, in which case |result_callback| will never be called.
+ // Otherwise, |result_callback| will be invoked once |item_name| has been
+ // invoked.
+ void Begin(const wchar_t* class_name,
+ const string16& item_name,
+ const InitializedCallback& init_callback,
+ const ResultCallback& result_callback);
+
+ private:
+ class Context;
+
+ base::ThreadChecker thread_checker_;
+
+ // A thread in the COM MTA in which automation calls are made.
+ base::Thread automation_thread_;
+
+ // A pointer to the context object that lives on the automation thread.
+ base::WeakPtr<Context> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(UIAutomationClient);
+};
+
+} // namespace internal
+} // namespace win8
+
+#endif // WIN8_TEST_UI_AUTOMATION_CLIENT_H_