Home | History | Annotate | Download | only in browser
      1 // Copyright 2014 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 "chrome/browser/chrome_select_file_dialog_factory_win.h"
      6 
      7 #include <Windows.h>
      8 #include <commdlg.h>
      9 
     10 #include "base/bind.h"
     11 #include "base/bind_helpers.h"
     12 #include "base/callback.h"
     13 #include "base/location.h"
     14 #include "base/logging.h"
     15 #include "base/metrics/field_trial.h"
     16 #include "base/strings/string16.h"
     17 #include "base/synchronization/waitable_event.h"
     18 #include "base/win/metro.h"
     19 #include "chrome/common/chrome_utility_messages.h"
     20 #include "content/public/browser/utility_process_host.h"
     21 #include "content/public/browser/utility_process_host_client.h"
     22 #include "ipc/ipc_message_macros.h"
     23 #include "ui/base/win/open_file_name_win.h"
     24 #include "ui/shell_dialogs/select_file_dialog_win.h"
     25 
     26 namespace {
     27 
     28 bool CallMetroOPENFILENAMEMethod(const char* method_name, OPENFILENAME* ofn) {
     29   typedef BOOL (*MetroOPENFILENAMEMethod)(OPENFILENAME*);
     30   MetroOPENFILENAMEMethod metro_method = NULL;
     31   HMODULE metro_module = base::win::GetMetroModule();
     32 
     33   if (metro_module != NULL) {
     34     metro_method = reinterpret_cast<MetroOPENFILENAMEMethod>(
     35         ::GetProcAddress(metro_module, method_name));
     36   }
     37 
     38   if (metro_method != NULL)
     39     return metro_method(ofn) == TRUE;
     40 
     41   NOTREACHED();
     42 
     43   return false;
     44 }
     45 
     46 bool ShouldIsolateShellOperations() {
     47   return base::FieldTrialList::FindFullName("IsolateShellOperations") ==
     48          "Enabled";
     49 }
     50 
     51 // Receives the GetOpenFileName result from the utility process.
     52 class GetOpenFileNameClient : public content::UtilityProcessHostClient {
     53  public:
     54   GetOpenFileNameClient();
     55 
     56   // Blocks until the GetOpenFileName result is received (including failure to
     57   // launch or a crash of the utility process).
     58   void WaitForCompletion();
     59 
     60   // Returns the selected directory.
     61   const base::FilePath& directory() const { return directory_; }
     62 
     63   // Returns the list of selected filenames. Each should be interpreted as a
     64   // child of directory().
     65   const std::vector<base::FilePath>& filenames() const { return filenames_; }
     66 
     67   // UtilityProcessHostClient implementation
     68   virtual void OnProcessCrashed(int exit_code) OVERRIDE;
     69   virtual void OnProcessLaunchFailed() OVERRIDE;
     70   virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
     71 
     72  protected:
     73   virtual ~GetOpenFileNameClient();
     74 
     75  private:
     76   void OnResult(const base::FilePath& directory,
     77                 const std::vector<base::FilePath>& filenames);
     78   void OnFailure();
     79 
     80   base::FilePath directory_;
     81   std::vector<base::FilePath> filenames_;
     82   base::WaitableEvent event_;
     83 
     84   DISALLOW_COPY_AND_ASSIGN(GetOpenFileNameClient);
     85 };
     86 
     87 GetOpenFileNameClient::GetOpenFileNameClient() : event_(true, false) {
     88 }
     89 
     90 void GetOpenFileNameClient::WaitForCompletion() {
     91   event_.Wait();
     92 }
     93 
     94 void GetOpenFileNameClient::OnProcessCrashed(int exit_code) {
     95   event_.Signal();
     96 }
     97 
     98 void GetOpenFileNameClient::OnProcessLaunchFailed() {
     99   event_.Signal();
    100 }
    101 
    102 bool GetOpenFileNameClient::OnMessageReceived(const IPC::Message& message) {
    103   bool handled = true;
    104   IPC_BEGIN_MESSAGE_MAP(GetOpenFileNameClient, message)
    105     IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_GetOpenFileName_Failed,
    106                         OnFailure)
    107     IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_GetOpenFileName_Result,
    108                         OnResult)
    109     IPC_MESSAGE_UNHANDLED(handled = false)
    110   IPC_END_MESSAGE_MAP()
    111   return handled;
    112 }
    113 
    114 GetOpenFileNameClient::~GetOpenFileNameClient() {}
    115 
    116 void GetOpenFileNameClient::OnResult(
    117     const base::FilePath& directory,
    118     const std::vector<base::FilePath>& filenames) {
    119   directory_ = directory;
    120   filenames_ = filenames;
    121   event_.Signal();
    122 }
    123 
    124 void GetOpenFileNameClient::OnFailure() {
    125   event_.Signal();
    126 }
    127 
    128 // Initiates IPC with a new utility process using |client|. Instructs the
    129 // utility process to call GetOpenFileName with |ofn|. |current_task_runner|
    130 // must be the currently executing task runner.
    131 void DoInvokeGetOpenFileName(
    132     OPENFILENAME* ofn,
    133     scoped_refptr<GetOpenFileNameClient> client,
    134     const scoped_refptr<base::SequencedTaskRunner>& current_task_runner) {
    135   DCHECK(current_task_runner->RunsTasksOnCurrentThread());
    136 
    137   base::WeakPtr<content::UtilityProcessHost> utility_process_host(
    138       content::UtilityProcessHost::Create(client, current_task_runner)
    139       ->AsWeakPtr());
    140   utility_process_host->DisableSandbox();
    141   utility_process_host->Send(new ChromeUtilityMsg_GetOpenFileName(
    142       ofn->hwndOwner,
    143       ofn->Flags & ~OFN_ENABLEHOOK,  // We can't send a hook function over IPC.
    144       ui::win::OpenFileName::GetFilters(ofn),
    145       base::FilePath(ofn->lpstrInitialDir ? ofn->lpstrInitialDir
    146                                           : base::string16()),
    147       base::FilePath(ofn->lpstrFile)));
    148 }
    149 
    150 // Invokes GetOpenFileName in a utility process. Blocks until the result is
    151 // received. Uses |blocking_task_runner| for IPC.
    152 bool GetOpenFileNameInUtilityProcess(
    153     const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner,
    154     OPENFILENAME* ofn) {
    155   scoped_refptr<GetOpenFileNameClient> client(new GetOpenFileNameClient);
    156   blocking_task_runner->PostTask(
    157       FROM_HERE,
    158       base::Bind(&DoInvokeGetOpenFileName,
    159                  base::Unretained(ofn), client, blocking_task_runner));
    160   client->WaitForCompletion();
    161 
    162   if (!client->filenames().size())
    163     return false;
    164 
    165   ui::win::OpenFileName::SetResult(
    166       client->directory(), client->filenames(), ofn);
    167   return true;
    168 }
    169 
    170 // Implements GetOpenFileName for CreateWinSelectFileDialog by delegating either
    171 // to Metro or a utility process.
    172 bool GetOpenFileNameImpl(
    173     const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner,
    174     OPENFILENAME* ofn) {
    175   if (base::win::IsMetroProcess())
    176     return CallMetroOPENFILENAMEMethod("MetroGetOpenFileName", ofn);
    177 
    178   if (ShouldIsolateShellOperations())
    179     return GetOpenFileNameInUtilityProcess(blocking_task_runner, ofn);
    180 
    181   return ::GetOpenFileName(ofn) == TRUE;
    182 }
    183 
    184 class GetSaveFileNameClient : public content::UtilityProcessHostClient {
    185  public:
    186   GetSaveFileNameClient();
    187 
    188   // Blocks until the GetSaveFileName result is received (including failure to
    189   // launch or a crash of the utility process).
    190   void WaitForCompletion();
    191 
    192   // Returns the selected path.
    193   const base::FilePath& path() const { return path_; }
    194 
    195   // Returns the index of the user-selected filter.
    196   int one_based_filter_index() const { return one_based_filter_index_; }
    197 
    198   // UtilityProcessHostClient implementation
    199   virtual void OnProcessCrashed(int exit_code) OVERRIDE;
    200   virtual void OnProcessLaunchFailed() OVERRIDE;
    201   virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
    202 
    203  protected:
    204   virtual ~GetSaveFileNameClient();
    205 
    206  private:
    207   void OnResult(const base::FilePath& path, int one_based_filter_index);
    208   void OnFailure();
    209 
    210   base::FilePath path_;
    211   int one_based_filter_index_;
    212   base::WaitableEvent event_;
    213 
    214   DISALLOW_COPY_AND_ASSIGN(GetSaveFileNameClient);
    215 };
    216 
    217 GetSaveFileNameClient::GetSaveFileNameClient()
    218     : event_(true, false), one_based_filter_index_(0) {
    219 }
    220 
    221 void GetSaveFileNameClient::WaitForCompletion() {
    222   event_.Wait();
    223 }
    224 
    225 void GetSaveFileNameClient::OnProcessCrashed(int exit_code) {
    226   event_.Signal();
    227 }
    228 
    229 void GetSaveFileNameClient::OnProcessLaunchFailed() {
    230   event_.Signal();
    231 }
    232 
    233 bool GetSaveFileNameClient::OnMessageReceived(const IPC::Message& message) {
    234   bool handled = true;
    235   IPC_BEGIN_MESSAGE_MAP(GetSaveFileNameClient, message)
    236     IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_GetSaveFileName_Failed,
    237                         OnFailure)
    238     IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_GetSaveFileName_Result,
    239                         OnResult)
    240     IPC_MESSAGE_UNHANDLED(handled = false)
    241   IPC_END_MESSAGE_MAP()
    242   return handled;
    243 }
    244 
    245 GetSaveFileNameClient::~GetSaveFileNameClient() {}
    246 
    247 void GetSaveFileNameClient::OnResult(const base::FilePath& path,
    248                                      int one_based_filter_index) {
    249   path_ = path;
    250   one_based_filter_index_ = one_based_filter_index;
    251   event_.Signal();
    252 }
    253 
    254 void GetSaveFileNameClient::OnFailure() {
    255   event_.Signal();
    256 }
    257 
    258 // Initiates IPC with a new utility process using |client|. Instructs the
    259 // utility process to call GetSaveFileName with |ofn|. |current_task_runner|
    260 // must be the currently executing task runner.
    261 void DoInvokeGetSaveFileName(
    262     OPENFILENAME* ofn,
    263     scoped_refptr<GetSaveFileNameClient> client,
    264     const scoped_refptr<base::SequencedTaskRunner>& current_task_runner) {
    265   DCHECK(current_task_runner->RunsTasksOnCurrentThread());
    266 
    267   base::WeakPtr<content::UtilityProcessHost> utility_process_host(
    268       content::UtilityProcessHost::Create(client, current_task_runner)
    269       ->AsWeakPtr());
    270   utility_process_host->DisableSandbox();
    271   ChromeUtilityMsg_GetSaveFileName_Params params;
    272   params.owner = ofn->hwndOwner;
    273   // We can't pass the hook function over IPC.
    274   params.flags = ofn->Flags & ~OFN_ENABLEHOOK;
    275   params.filters = ui::win::OpenFileName::GetFilters(ofn);
    276   params.one_based_filter_index = ofn->nFilterIndex;
    277   params.suggested_filename = base::FilePath(ofn->lpstrFile);
    278   params.initial_directory = base::FilePath(
    279       ofn->lpstrInitialDir ? ofn->lpstrInitialDir : base::string16());
    280   params.default_extension =
    281       ofn->lpstrDefExt ? base::string16(ofn->lpstrDefExt) : base::string16();
    282 
    283   utility_process_host->Send(new ChromeUtilityMsg_GetSaveFileName(params));
    284 }
    285 
    286 // Invokes GetSaveFileName in a utility process. Blocks until the result is
    287 // received. Uses |blocking_task_runner| for IPC.
    288 bool GetSaveFileNameInUtilityProcess(
    289     const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner,
    290     OPENFILENAME* ofn) {
    291   scoped_refptr<GetSaveFileNameClient> client(new GetSaveFileNameClient);
    292   blocking_task_runner->PostTask(
    293       FROM_HERE,
    294       base::Bind(&DoInvokeGetSaveFileName,
    295                  base::Unretained(ofn), client, blocking_task_runner));
    296   client->WaitForCompletion();
    297 
    298   if (client->path().empty())
    299     return false;
    300 
    301   base::wcslcpy(ofn->lpstrFile, client->path().value().c_str(), ofn->nMaxFile);
    302   ofn->nFilterIndex = client->one_based_filter_index();
    303 
    304   return true;
    305 }
    306 
    307 // Implements GetSaveFileName for CreateWinSelectFileDialog by delegating either
    308 // to Metro or a utility process.
    309 bool GetSaveFileNameImpl(
    310     const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner,
    311     OPENFILENAME* ofn) {
    312   if (base::win::IsMetroProcess())
    313     return CallMetroOPENFILENAMEMethod("MetroGetSaveFileName", ofn);
    314 
    315   if (ShouldIsolateShellOperations())
    316     return GetSaveFileNameInUtilityProcess(blocking_task_runner, ofn);
    317 
    318   return ::GetSaveFileName(ofn) == TRUE;
    319 }
    320 
    321 }  // namespace
    322 
    323 ChromeSelectFileDialogFactory::ChromeSelectFileDialogFactory(
    324     const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner)
    325     : blocking_task_runner_(blocking_task_runner) {
    326 }
    327 
    328 ChromeSelectFileDialogFactory::~ChromeSelectFileDialogFactory() {}
    329 
    330 ui::SelectFileDialog* ChromeSelectFileDialogFactory::Create(
    331     ui::SelectFileDialog::Listener* listener,
    332     ui::SelectFilePolicy* policy) {
    333   return ui::CreateWinSelectFileDialog(
    334       listener,
    335       policy,
    336       base::Bind(GetOpenFileNameImpl, blocking_task_runner_),
    337       base::Bind(GetSaveFileNameImpl, blocking_task_runner_));
    338 }
    339