| // 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/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<base::string16>* choices_out, |
| HRESULT hr, |
| std::vector<base::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 base::string16& url_protocol, |
| const base::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<base::string16> choices); |
| void OnOpenWithComplete(HRESULT result); |
| |
| base::ThreadChecker thread_checker_; |
| State state_; |
| internal::UIAutomationClient automation_client_; |
| HWND parent_window_; |
| base::string16 file_name_; |
| base::string16 file_type_class_; |
| int open_as_info_flags_; |
| OpenWithDialogController::SetDefaultCallback callback_; |
| HRESULT open_with_result_; |
| HRESULT automation_result_; |
| std::vector<base::string16> automation_choices_; |
| base::WeakPtrFactory<Context> weak_ptr_factory_; |
| DISALLOW_COPY_AND_ASSIGN(Context); |
| }; |
| |
| OpenWithDialogController::Context::Context() |
| : state_(CONTEXT_INITIALIZED), |
| parent_window_(), |
| open_as_info_flags_(), |
| open_with_result_(E_FAIL), |
| automation_result_(E_FAIL), |
| 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 base::string16& url_protocol, |
| const base::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<base::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 base::string16& url_protocol, |
| const base::string16& program, |
| const SetDefaultCallback& callback) { |
| DCHECK_EQ(context_.get(), 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<base::string16>())); |
| return; |
| } |
| |
| context_ = (new Context())->AsWeakPtr(); |
| context_->Begin(parent_window, url_protocol, program, callback); |
| } |
| |
| HRESULT OpenWithDialogController::RunSynchronously( |
| HWND parent_window, |
| const base::string16& protocol, |
| const base::string16& program, |
| std::vector<base::string16>* choices) { |
| DCHECK_EQ(base::MessageLoop::current(), |
| static_cast<base::MessageLoop*>(NULL)); |
| if (base::win::GetVersion() < base::win::VERSION_WIN8) { |
| NOTREACHED() << "Windows 8 is required."; |
| return E_FAIL; |
| } |
| |
| HRESULT result = S_OK; |
| base::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 |