blob: b9a160903f859ef351b14cf7819a558ed7230b50 [file] [log] [blame]
// 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