// Copyright 2014 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 "chrome/browser/chrome_select_file_dialog_factory_win.h" #include #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" #include "base/location.h" #include "base/logging.h" #include "base/metrics/field_trial.h" #include "base/strings/string16.h" #include "base/synchronization/waitable_event.h" #include "base/win/metro.h" #include "chrome/common/chrome_utility_messages.h" #include "content/public/browser/utility_process_host.h" #include "content/public/browser/utility_process_host_client.h" #include "ipc/ipc_message_macros.h" #include "ui/base/win/open_file_name_win.h" #include "ui/shell_dialogs/select_file_dialog_win.h" namespace { bool CallMetroOPENFILENAMEMethod(const char* method_name, OPENFILENAME* ofn) { typedef BOOL (*MetroOPENFILENAMEMethod)(OPENFILENAME*); MetroOPENFILENAMEMethod metro_method = NULL; HMODULE metro_module = base::win::GetMetroModule(); if (metro_module != NULL) { metro_method = reinterpret_cast( ::GetProcAddress(metro_module, method_name)); } if (metro_method != NULL) return metro_method(ofn) == TRUE; NOTREACHED(); return false; } bool ShouldIsolateShellOperations() { return base::FieldTrialList::FindFullName("IsolateShellOperations") == "Enabled"; } // Receives the GetOpenFileName result from the utility process. class GetOpenFileNameClient : public content::UtilityProcessHostClient { public: GetOpenFileNameClient(); // Blocks until the GetOpenFileName result is received (including failure to // launch or a crash of the utility process). void WaitForCompletion(); // Returns the selected directory. const base::FilePath& directory() const { return directory_; } // Returns the list of selected filenames. Each should be interpreted as a // child of directory(). const std::vector& filenames() const { return filenames_; } // UtilityProcessHostClient implementation virtual void OnProcessCrashed(int exit_code) override; virtual void OnProcessLaunchFailed() override; virtual bool OnMessageReceived(const IPC::Message& message) override; protected: virtual ~GetOpenFileNameClient(); private: void OnResult(const base::FilePath& directory, const std::vector& filenames); void OnFailure(); base::FilePath directory_; std::vector filenames_; base::WaitableEvent event_; DISALLOW_COPY_AND_ASSIGN(GetOpenFileNameClient); }; GetOpenFileNameClient::GetOpenFileNameClient() : event_(true, false) { } void GetOpenFileNameClient::WaitForCompletion() { event_.Wait(); } void GetOpenFileNameClient::OnProcessCrashed(int exit_code) { event_.Signal(); } void GetOpenFileNameClient::OnProcessLaunchFailed() { event_.Signal(); } bool GetOpenFileNameClient::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(GetOpenFileNameClient, message) IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_GetOpenFileName_Failed, OnFailure) IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_GetOpenFileName_Result, OnResult) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } GetOpenFileNameClient::~GetOpenFileNameClient() {} void GetOpenFileNameClient::OnResult( const base::FilePath& directory, const std::vector& filenames) { directory_ = directory; filenames_ = filenames; event_.Signal(); } void GetOpenFileNameClient::OnFailure() { event_.Signal(); } // Initiates IPC with a new utility process using |client|. Instructs the // utility process to call GetOpenFileName with |ofn|. |current_task_runner| // must be the currently executing task runner. void DoInvokeGetOpenFileName( OPENFILENAME* ofn, scoped_refptr client, const scoped_refptr& current_task_runner) { DCHECK(current_task_runner->RunsTasksOnCurrentThread()); base::WeakPtr utility_process_host( content::UtilityProcessHost::Create(client, current_task_runner) ->AsWeakPtr()); utility_process_host->DisableSandbox(); utility_process_host->Send(new ChromeUtilityMsg_GetOpenFileName( ofn->hwndOwner, ofn->Flags & ~OFN_ENABLEHOOK, // We can't send a hook function over IPC. ui::win::OpenFileName::GetFilters(ofn), base::FilePath(ofn->lpstrInitialDir ? ofn->lpstrInitialDir : base::string16()), base::FilePath(ofn->lpstrFile))); } // Invokes GetOpenFileName in a utility process. Blocks until the result is // received. Uses |blocking_task_runner| for IPC. bool GetOpenFileNameInUtilityProcess( const scoped_refptr& blocking_task_runner, OPENFILENAME* ofn) { scoped_refptr client(new GetOpenFileNameClient); blocking_task_runner->PostTask( FROM_HERE, base::Bind(&DoInvokeGetOpenFileName, base::Unretained(ofn), client, blocking_task_runner)); client->WaitForCompletion(); if (!client->filenames().size()) return false; ui::win::OpenFileName::SetResult( client->directory(), client->filenames(), ofn); return true; } // Implements GetOpenFileName for CreateWinSelectFileDialog by delegating either // to Metro or a utility process. bool GetOpenFileNameImpl( const scoped_refptr& blocking_task_runner, OPENFILENAME* ofn) { if (base::win::IsMetroProcess()) return CallMetroOPENFILENAMEMethod("MetroGetOpenFileName", ofn); if (ShouldIsolateShellOperations()) return GetOpenFileNameInUtilityProcess(blocking_task_runner, ofn); return ::GetOpenFileName(ofn) == TRUE; } class GetSaveFileNameClient : public content::UtilityProcessHostClient { public: GetSaveFileNameClient(); // Blocks until the GetSaveFileName result is received (including failure to // launch or a crash of the utility process). void WaitForCompletion(); // Returns the selected path. const base::FilePath& path() const { return path_; } // Returns the index of the user-selected filter. int one_based_filter_index() const { return one_based_filter_index_; } // UtilityProcessHostClient implementation virtual void OnProcessCrashed(int exit_code) override; virtual void OnProcessLaunchFailed() override; virtual bool OnMessageReceived(const IPC::Message& message) override; protected: virtual ~GetSaveFileNameClient(); private: void OnResult(const base::FilePath& path, int one_based_filter_index); void OnFailure(); base::FilePath path_; int one_based_filter_index_; base::WaitableEvent event_; DISALLOW_COPY_AND_ASSIGN(GetSaveFileNameClient); }; GetSaveFileNameClient::GetSaveFileNameClient() : event_(true, false), one_based_filter_index_(0) { } void GetSaveFileNameClient::WaitForCompletion() { event_.Wait(); } void GetSaveFileNameClient::OnProcessCrashed(int exit_code) { event_.Signal(); } void GetSaveFileNameClient::OnProcessLaunchFailed() { event_.Signal(); } bool GetSaveFileNameClient::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(GetSaveFileNameClient, message) IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_GetSaveFileName_Failed, OnFailure) IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_GetSaveFileName_Result, OnResult) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } GetSaveFileNameClient::~GetSaveFileNameClient() {} void GetSaveFileNameClient::OnResult(const base::FilePath& path, int one_based_filter_index) { path_ = path; one_based_filter_index_ = one_based_filter_index; event_.Signal(); } void GetSaveFileNameClient::OnFailure() { event_.Signal(); } // Initiates IPC with a new utility process using |client|. Instructs the // utility process to call GetSaveFileName with |ofn|. |current_task_runner| // must be the currently executing task runner. void DoInvokeGetSaveFileName( OPENFILENAME* ofn, scoped_refptr client, const scoped_refptr& current_task_runner) { DCHECK(current_task_runner->RunsTasksOnCurrentThread()); base::WeakPtr utility_process_host( content::UtilityProcessHost::Create(client, current_task_runner) ->AsWeakPtr()); utility_process_host->DisableSandbox(); ChromeUtilityMsg_GetSaveFileName_Params params; params.owner = ofn->hwndOwner; // We can't pass the hook function over IPC. params.flags = ofn->Flags & ~OFN_ENABLEHOOK; params.filters = ui::win::OpenFileName::GetFilters(ofn); params.one_based_filter_index = ofn->nFilterIndex; params.suggested_filename = base::FilePath(ofn->lpstrFile); params.initial_directory = base::FilePath( ofn->lpstrInitialDir ? ofn->lpstrInitialDir : base::string16()); params.default_extension = ofn->lpstrDefExt ? base::string16(ofn->lpstrDefExt) : base::string16(); utility_process_host->Send(new ChromeUtilityMsg_GetSaveFileName(params)); } // Invokes GetSaveFileName in a utility process. Blocks until the result is // received. Uses |blocking_task_runner| for IPC. bool GetSaveFileNameInUtilityProcess( const scoped_refptr& blocking_task_runner, OPENFILENAME* ofn) { scoped_refptr client(new GetSaveFileNameClient); blocking_task_runner->PostTask( FROM_HERE, base::Bind(&DoInvokeGetSaveFileName, base::Unretained(ofn), client, blocking_task_runner)); client->WaitForCompletion(); if (client->path().empty()) return false; base::wcslcpy(ofn->lpstrFile, client->path().value().c_str(), ofn->nMaxFile); ofn->nFilterIndex = client->one_based_filter_index(); return true; } // Implements GetSaveFileName for CreateWinSelectFileDialog by delegating either // to Metro or a utility process. bool GetSaveFileNameImpl( const scoped_refptr& blocking_task_runner, OPENFILENAME* ofn) { if (base::win::IsMetroProcess()) return CallMetroOPENFILENAMEMethod("MetroGetSaveFileName", ofn); if (ShouldIsolateShellOperations()) return GetSaveFileNameInUtilityProcess(blocking_task_runner, ofn); return ::GetSaveFileName(ofn) == TRUE; } } // namespace ChromeSelectFileDialogFactory::ChromeSelectFileDialogFactory( const scoped_refptr& blocking_task_runner) : blocking_task_runner_(blocking_task_runner) { } ChromeSelectFileDialogFactory::~ChromeSelectFileDialogFactory() {} ui::SelectFileDialog* ChromeSelectFileDialogFactory::Create( ui::SelectFileDialog::Listener* listener, ui::SelectFilePolicy* policy) { return ui::CreateWinSelectFileDialog( listener, policy, base::Bind(GetOpenFileNameImpl, blocking_task_runner_), base::Bind(GetSaveFileNameImpl, blocking_task_runner_)); }