• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "win8/test/open_with_dialog_controller.h"
6 
7 #include <shlobj.h>
8 
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"
20 
21 namespace win8 {
22 
23 namespace {
24 
25 const int kControllerTimeoutSeconds = 5;
26 const wchar_t kShellFlyoutClassName[] = L"Shell_Flyout";
27 
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.
OnMakeDefaultComplete(const base::Closure & closure,HRESULT * result_out,std::vector<string16> * choices_out,HRESULT hr,std::vector<string16> choices)31 void OnMakeDefaultComplete(
32     const base::Closure& closure,
33     HRESULT* result_out,
34     std::vector<string16>* choices_out,
35     HRESULT hr,
36     std::vector<string16> choices) {
37   *result_out = hr;
38   *choices_out = choices;
39   closure.Run();
40 }
41 
42 }  // namespace
43 
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();
50 
51   base::WeakPtr<Context> AsWeakPtr();
52 
53   void Orphan();
54 
55   void Begin(HWND parent_window,
56              const string16& url_protocol,
57              const string16& program_name,
58              const OpenWithDialogController::SetDefaultCallback& callback);
59 
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,
71   };
72 
73   // Invokes the client's callback and destroys this instance.
74   void NotifyClientAndDie();
75 
76   void OnTimeout();
77   void OnInitialized(HRESULT result);
78   void OnAutomationResult(HRESULT result, std::vector<string16> choices);
79   void OnOpenWithComplete(HRESULT result);
80 
81   base::ThreadChecker thread_checker_;
82   State state_;
83   internal::UIAutomationClient automation_client_;
84   HWND parent_window_;
85   string16 file_name_;
86   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<string16> automation_choices_;
92   base::WeakPtrFactory<Context> weak_ptr_factory_;
93   DISALLOW_COPY_AND_ASSIGN(OpenWithDialogController::Context);
94 };
95 
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) {}
103 
~Context()104 OpenWithDialogController::Context::~Context() {
105   DCHECK(thread_checker_.CalledOnValidThread());
106 }
107 
108 base::WeakPtr<OpenWithDialogController::Context>
AsWeakPtr()109     OpenWithDialogController::Context::AsWeakPtr() {
110   DCHECK(thread_checker_.CalledOnValidThread());
111   return weak_ptr_factory_.GetWeakPtr();
112 }
113 
Orphan()114 void OpenWithDialogController::Context::Orphan() {
115   DCHECK(thread_checker_.CalledOnValidThread());
116 
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;
123 }
124 
Begin(HWND parent_window,const string16 & url_protocol,const string16 & program_name,const OpenWithDialogController::SetDefaultCallback & callback)125 void OpenWithDialogController::Context::Begin(
126     HWND parent_window,
127     const string16& url_protocol,
128     const string16& program_name,
129     const OpenWithDialogController::SetDefaultCallback& callback) {
130   DCHECK(thread_checker_.CalledOnValidThread());
131 
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;
138 
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));
144 
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()));
152 }
153 
NotifyClientAndDie()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.";
159 
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;
164 }
165 
OnTimeout()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_;
171 
172   state_ = CONTEXT_FINISHED;
173   NotifyClientAndDie();
174 }
175 
OnInitialized(HRESULT result)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;
184   }
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()));
190 }
191 
OnAutomationResult(HRESULT result,std::vector<string16> choices)192 void OpenWithDialogController::Context::OnAutomationResult(
193     HRESULT result,
194     std::vector<string16> choices) {
195   DCHECK(thread_checker_.CalledOnValidThread());
196   DCHECK_EQ(automation_result_, E_FAIL);
197 
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_;
212   }
213 }
214 
OnOpenWithComplete(HRESULT result)215 void OpenWithDialogController::Context::OnOpenWithComplete(HRESULT result) {
216   DCHECK(thread_checker_.CalledOnValidThread());
217   DCHECK_EQ(open_with_result_, E_FAIL);
218 
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_;
233   }
234 }
235 
OpenWithDialogController()236 OpenWithDialogController::OpenWithDialogController() {}
237 
~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();
243 }
244 
Begin(HWND parent_window,const string16 & url_protocol,const string16 & program,const SetDefaultCallback & callback)245 void OpenWithDialogController::Begin(
246     HWND parent_window,
247     const string16& url_protocol,
248     const 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<string16>()));
258     return;
259   }
260 
261   context_ = (new Context())->AsWeakPtr();
262   context_->Begin(parent_window, url_protocol, program, callback);
263 }
264 
RunSynchronously(HWND parent_window,const string16 & protocol,const string16 & program,std::vector<string16> * choices)265 HRESULT OpenWithDialogController::RunSynchronously(
266     HWND parent_window,
267     const string16& protocol,
268     const string16& program,
269     std::vector<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;
275   }
276 
277   HRESULT result = S_OK;
278   base::MessageLoop message_loop;
279   base::RunLoop run_loop;
280 
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)));
287 
288   run_loop.Run();
289   return result;
290 }
291 
292 }  // namespace win8
293