Make Ctrl + T from apps focus the new tab's omnibox on Linux and ChromeOS.
[chromium-blink-merge.git] / win8 / test / open_with_dialog_controller.cc
blobe980197f15ccd4a0171c606772535b9ea7774ee9
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"
7 #include <shlobj.h>
9 #include "base/bind.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"
21 namespace win8 {
23 namespace {
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,
33 HRESULT* result_out,
34 std::vector<base::string16>* choices_out,
35 HRESULT hr,
36 std::vector<base::string16> choices) {
37 *result_out = hr;
38 *choices_out = choices;
39 closure.Run();
42 } // namespace
44 // Lives on the main thread and is owned by a controller. May outlive the
45 // controller (see Orphan).
46 class OpenWithDialogController::Context {
47 public:
48 Context();
49 ~Context();
51 base::WeakPtr<Context> AsWeakPtr();
53 void Orphan();
55 void Begin(HWND parent_window,
56 const base::string16& url_protocol,
57 const base::string16& program_name,
58 const OpenWithDialogController::SetDefaultCallback& callback);
60 private:
61 enum State {
62 // The Context has been constructed.
63 CONTEXT_INITIALIZED,
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,
70 CONTEXT_FINISHED,
73 // Invokes the client's callback and destroys this instance.
74 void NotifyClientAndDie();
76 void OnTimeout();
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_;
82 State state_;
83 internal::UIAutomationClient automation_client_;
84 HWND parent_window_;
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),
98 parent_window_(),
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.";
122 delete this;
125 void OpenWithDialogController::Context::Begin(
126 HWND parent_window,
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 |
136 OAIF_REGISTER_EXT);
137 callback_ = callback;
139 // Post a delayed callback to abort the operation if it takes too long.
140 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
141 FROM_HERE,
142 base::Bind(&OpenWithDialogController::Context::OnTimeout, AsWeakPtr()),
143 base::TimeDelta::FromSeconds(kControllerTimeoutSeconds));
145 automation_client_.Begin(
146 kShellFlyoutClassName,
147 program_name,
148 base::Bind(&OpenWithDialogController::Context::OnInitialized,
149 AsWeakPtr()),
150 base::Bind(&OpenWithDialogController::Context::OnAutomationResult,
151 AsWeakPtr()));
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_);
163 delete this;
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();
183 return;
185 state_ = CONTEXT_AUTOMATION_READY;
186 OpenWithDialogAsync(
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(
193 HRESULT result,
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;
200 switch (state_) {
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;
205 break;
206 case CONTEXT_WAITING_FOR_RESULTS:
207 state_ = CONTEXT_FINISHED;
208 NotifyClientAndDie();
209 break;
210 default:
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;
220 switch (state_) {
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;
225 break;
226 case CONTEXT_WAITING_FOR_DIALOG:
227 // All results are in. Invoke the caller's callback.
228 state_ = CONTEXT_FINISHED;
229 NotifyClientAndDie();
230 break;
231 default:
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.
241 if (context_)
242 context_->Orphan();
245 void OpenWithDialogController::Begin(
246 HWND parent_window,
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(
256 FROM_HERE,
257 base::Bind(callback, E_FAIL, std::vector<base::string16>()));
258 return;
261 context_ = (new Context())->AsWeakPtr();
262 context_->Begin(parent_window, url_protocol, program, callback);
265 HRESULT OpenWithDialogController::RunSynchronously(
266 HWND parent_window,
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.";
274 return E_FAIL;
277 HRESULT result = S_OK;
278 base::MessageLoop message_loop;
279 base::RunLoop run_loop;
281 message_loop.PostTask(
282 FROM_HERE,
283 base::Bind(&OpenWithDialogController::Begin, base::Unretained(this),
284 parent_window, protocol, program,
285 Bind(&OnMakeDefaultComplete, run_loop.QuitClosure(),
286 &result, choices)));
288 run_loop.Run();
289 return result;
292 } // namespace win8