1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "win8/test/open_with_dialog_controller.h"
10 #include "base/callback.h"
11 #include "base/logging.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/run_loop.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "base/threading/thread_checker.h"
17 #include "base/win/windows_version.h"
18 #include "win8/test/open_with_dialog_async.h"
19 #include "win8/test/ui_automation_client.h"
25 const int kControllerTimeoutSeconds
= 5;
26 const wchar_t kShellFlyoutClassName
[] = L
"Shell_Flyout";
28 // A callback invoked with the OpenWithDialogController's results. Said results
29 // are copied to |result_out| and |choices_out| and then |closure| is invoked.
30 // This function is in support of OpenWithDialogController::RunSynchronously.
31 void OnMakeDefaultComplete(
32 const base::Closure
& closure
,
34 std::vector
<base::string16
>* choices_out
,
36 std::vector
<base::string16
> choices
) {
38 *choices_out
= choices
;
44 // Lives on the main thread and is owned by a controller. May outlive the
45 // controller (see Orphan).
46 class OpenWithDialogController::Context
{
51 base::WeakPtr
<Context
> AsWeakPtr();
55 void Begin(HWND parent_window
,
56 const base::string16
& url_protocol
,
57 const base::string16
& program_name
,
58 const OpenWithDialogController::SetDefaultCallback
& callback
);
62 // The Context has been constructed.
64 // The UI automation event handler is ready.
65 CONTEXT_AUTOMATION_READY
,
66 // The automation results came back before the call to SHOpenWithDialog.
67 CONTEXT_WAITING_FOR_DIALOG
,
68 // The call to SHOpenWithDialog returned before automation results.
69 CONTEXT_WAITING_FOR_RESULTS
,
73 // Invokes the client's callback and destroys this instance.
74 void NotifyClientAndDie();
77 void OnInitialized(HRESULT result
);
78 void OnAutomationResult(HRESULT result
, std::vector
<base::string16
> choices
);
79 void OnOpenWithComplete(HRESULT result
);
81 base::ThreadChecker thread_checker_
;
83 internal::UIAutomationClient automation_client_
;
85 base::string16 file_name_
;
86 base::string16 file_type_class_
;
87 int open_as_info_flags_
;
88 OpenWithDialogController::SetDefaultCallback callback_
;
89 HRESULT open_with_result_
;
90 HRESULT automation_result_
;
91 std::vector
<base::string16
> automation_choices_
;
92 base::WeakPtrFactory
<Context
> weak_ptr_factory_
;
93 DISALLOW_COPY_AND_ASSIGN(Context
);
96 OpenWithDialogController::Context::Context()
97 : state_(CONTEXT_INITIALIZED
),
99 open_as_info_flags_(),
100 open_with_result_(E_FAIL
),
101 automation_result_(E_FAIL
),
102 weak_ptr_factory_(this) {}
104 OpenWithDialogController::Context::~Context() {
105 DCHECK(thread_checker_
.CalledOnValidThread());
108 base::WeakPtr
<OpenWithDialogController::Context
>
109 OpenWithDialogController::Context::AsWeakPtr() {
110 DCHECK(thread_checker_
.CalledOnValidThread());
111 return weak_ptr_factory_
.GetWeakPtr();
114 void OpenWithDialogController::Context::Orphan() {
115 DCHECK(thread_checker_
.CalledOnValidThread());
117 // The controller is being destroyed. Its client is no longer interested in
118 // having the interaction continue.
119 DLOG_IF(WARNING
, (state_
== CONTEXT_AUTOMATION_READY
||
120 state_
== CONTEXT_WAITING_FOR_DIALOG
))
121 << "Abandoning the OpenWithDialog.";
125 void OpenWithDialogController::Context::Begin(
127 const base::string16
& url_protocol
,
128 const base::string16
& program_name
,
129 const OpenWithDialogController::SetDefaultCallback
& callback
) {
130 DCHECK(thread_checker_
.CalledOnValidThread());
132 parent_window_
= parent_window
;
133 file_name_
= url_protocol
;
134 file_type_class_
.clear();
135 open_as_info_flags_
= (OAIF_URL_PROTOCOL
| OAIF_FORCE_REGISTRATION
|
137 callback_
= callback
;
139 // Post a delayed callback to abort the operation if it takes too long.
140 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
142 base::Bind(&OpenWithDialogController::Context::OnTimeout
, AsWeakPtr()),
143 base::TimeDelta::FromSeconds(kControllerTimeoutSeconds
));
145 automation_client_
.Begin(
146 kShellFlyoutClassName
,
148 base::Bind(&OpenWithDialogController::Context::OnInitialized
,
150 base::Bind(&OpenWithDialogController::Context::OnAutomationResult
,
154 void OpenWithDialogController::Context::NotifyClientAndDie() {
155 DCHECK(thread_checker_
.CalledOnValidThread());
156 DCHECK_EQ(state_
, CONTEXT_FINISHED
);
157 DLOG_IF(WARNING
, SUCCEEDED(automation_result_
) && FAILED(open_with_result_
))
158 << "Automation succeeded, yet SHOpenWithDialog failed.";
160 // Ignore any future callbacks (such as the timeout) or calls to Orphan.
161 weak_ptr_factory_
.InvalidateWeakPtrs();
162 callback_
.Run(automation_result_
, automation_choices_
);
166 void OpenWithDialogController::Context::OnTimeout() {
167 DCHECK(thread_checker_
.CalledOnValidThread());
168 // This is a LOG rather than a DLOG since it represents something that needs
169 // to be investigated and fixed.
170 LOG(ERROR
) << __FUNCTION__
" state: " << state_
;
172 state_
= CONTEXT_FINISHED
;
173 NotifyClientAndDie();
176 void OpenWithDialogController::Context::OnInitialized(HRESULT result
) {
177 DCHECK(thread_checker_
.CalledOnValidThread());
178 DCHECK_EQ(state_
, CONTEXT_INITIALIZED
);
179 if (FAILED(result
)) {
180 automation_result_
= result
;
181 state_
= CONTEXT_FINISHED
;
182 NotifyClientAndDie();
185 state_
= CONTEXT_AUTOMATION_READY
;
187 parent_window_
, file_name_
, file_type_class_
, open_as_info_flags_
,
188 base::Bind(&OpenWithDialogController::Context::OnOpenWithComplete
,
189 weak_ptr_factory_
.GetWeakPtr()));
192 void OpenWithDialogController::Context::OnAutomationResult(
194 std::vector
<base::string16
> choices
) {
195 DCHECK(thread_checker_
.CalledOnValidThread());
196 DCHECK_EQ(automation_result_
, E_FAIL
);
198 automation_result_
= result
;
199 automation_choices_
= choices
;
201 case CONTEXT_AUTOMATION_READY
:
202 // The results of automation are in and we're waiting for
203 // SHOpenWithDialog to return.
204 state_
= CONTEXT_WAITING_FOR_DIALOG
;
206 case CONTEXT_WAITING_FOR_RESULTS
:
207 state_
= CONTEXT_FINISHED
;
208 NotifyClientAndDie();
211 NOTREACHED() << state_
;
215 void OpenWithDialogController::Context::OnOpenWithComplete(HRESULT result
) {
216 DCHECK(thread_checker_
.CalledOnValidThread());
217 DCHECK_EQ(open_with_result_
, E_FAIL
);
219 open_with_result_
= result
;
221 case CONTEXT_AUTOMATION_READY
:
222 // The interaction completed and we're waiting for the results from the
223 // automation side to come in.
224 state_
= CONTEXT_WAITING_FOR_RESULTS
;
226 case CONTEXT_WAITING_FOR_DIALOG
:
227 // All results are in. Invoke the caller's callback.
228 state_
= CONTEXT_FINISHED
;
229 NotifyClientAndDie();
232 NOTREACHED() << state_
;
236 OpenWithDialogController::OpenWithDialogController() {}
238 OpenWithDialogController::~OpenWithDialogController() {
239 // Orphan the context if this instance is being destroyed before the context
240 // finishes its work.
245 void OpenWithDialogController::Begin(
247 const base::string16
& url_protocol
,
248 const base::string16
& program
,
249 const SetDefaultCallback
& callback
) {
250 DCHECK_EQ(context_
.get(), static_cast<Context
*>(NULL
));
251 if (base::win::GetVersion() < base::win::VERSION_WIN8
) {
252 NOTREACHED() << "Windows 8 is required.";
253 // The callback may not properly handle being run from Begin, so post a task
254 // to this thread's task runner to call it.
255 base::ThreadTaskRunnerHandle::Get()->PostTask(
257 base::Bind(callback
, E_FAIL
, std::vector
<base::string16
>()));
261 context_
= (new Context())->AsWeakPtr();
262 context_
->Begin(parent_window
, url_protocol
, program
, callback
);
265 HRESULT
OpenWithDialogController::RunSynchronously(
267 const base::string16
& protocol
,
268 const base::string16
& program
,
269 std::vector
<base::string16
>* choices
) {
270 DCHECK_EQ(base::MessageLoop::current(),
271 static_cast<base::MessageLoop
*>(NULL
));
272 if (base::win::GetVersion() < base::win::VERSION_WIN8
) {
273 NOTREACHED() << "Windows 8 is required.";
277 HRESULT result
= S_OK
;
278 base::MessageLoop message_loop
;
279 base::RunLoop run_loop
;
281 message_loop
.PostTask(
283 base::Bind(&OpenWithDialogController::Begin
, base::Unretained(this),
284 parent_window
, protocol
, program
,
285 Bind(&OnMakeDefaultComplete
, run_loop
.QuitClosure(),