// 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 #include "base/bind.h" #include "base/callback.h" #include "base/logging.h" #include "base/macros.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* choices_out, HRESULT hr, std::vector 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 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 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 automation_choices_; base::WeakPtrFactory 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::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 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(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())); 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* choices) { DCHECK_EQ(base::MessageLoop::current(), static_cast(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