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 26namespace { 27 28bool 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 46bool ShouldIsolateShellOperations() { 47 return base::FieldTrialList::FindFullName("IsolateShellOperations") == 48 "Enabled"; 49} 50 51// Receives the GetOpenFileName result from the utility process. 52class 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 87GetOpenFileNameClient::GetOpenFileNameClient() : event_(true, false) { 88} 89 90void GetOpenFileNameClient::WaitForCompletion() { 91 event_.Wait(); 92} 93 94void GetOpenFileNameClient::OnProcessCrashed(int exit_code) { 95 event_.Signal(); 96} 97 98void GetOpenFileNameClient::OnProcessLaunchFailed() { 99 event_.Signal(); 100} 101 102bool 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 114GetOpenFileNameClient::~GetOpenFileNameClient() {} 115 116void 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 124void 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. 131void 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. 152bool 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. 172bool 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 184class 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 217GetSaveFileNameClient::GetSaveFileNameClient() 218 : event_(true, false), one_based_filter_index_(0) { 219} 220 221void GetSaveFileNameClient::WaitForCompletion() { 222 event_.Wait(); 223} 224 225void GetSaveFileNameClient::OnProcessCrashed(int exit_code) { 226 event_.Signal(); 227} 228 229void GetSaveFileNameClient::OnProcessLaunchFailed() { 230 event_.Signal(); 231} 232 233bool 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 245GetSaveFileNameClient::~GetSaveFileNameClient() {} 246 247void 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 254void 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. 261void 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. 288bool 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. 309bool 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 323ChromeSelectFileDialogFactory::ChromeSelectFileDialogFactory( 324 const scoped_refptr<base::SequencedTaskRunner>& blocking_task_runner) 325 : blocking_task_runner_(blocking_task_runner) { 326} 327 328ChromeSelectFileDialogFactory::~ChromeSelectFileDialogFactory() {} 329 330ui::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