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