Home | History | Annotate | Download | only in test
      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<base::string16>* choices_out,
     35     HRESULT hr,
     36     std::vector<base::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 base::string16& url_protocol,
     57              const base::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<base::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   base::string16 file_name_;
     86   base::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<base::string16> automation_choices_;
     92   base::WeakPtrFactory<Context> weak_ptr_factory_;
     93   DISALLOW_COPY_AND_ASSIGN(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 base::string16& url_protocol,
    128     const base::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<base::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 base::string16& url_protocol,
    248     const base::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<base::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 base::string16& protocol,
    268     const base::string16& program,
    269     std::vector<base::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