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