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. 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 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 104 OpenWithDialogController::Context::~Context() { 105 DCHECK(thread_checker_.CalledOnValidThread()); 106 } 107 108 base::WeakPtr<OpenWithDialogController::Context> 109 OpenWithDialogController::Context::AsWeakPtr() { 110 DCHECK(thread_checker_.CalledOnValidThread()); 111 return weak_ptr_factory_.GetWeakPtr(); 112 } 113 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 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 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 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 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 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 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 236 OpenWithDialogController::OpenWithDialogController() {} 237 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 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 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