普通文本  |  339行  |  10.99 KB

// 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 <Windows.h>
#include <commdlg.h>

#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<MetroOPENFILENAMEMethod>(
        ::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<base::FilePath>& 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<base::FilePath>& filenames);
  void OnFailure();

  base::FilePath directory_;
  std::vector<base::FilePath> 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<base::FilePath>& 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<GetOpenFileNameClient> client,
    const scoped_refptr<base::SequencedTaskRunner>& current_task_runner) {
  DCHECK(current_task_runner->RunsTasksOnCurrentThread());

  base::WeakPtr<content::UtilityProcessHost> 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<base::SequencedTaskRunner>& blocking_task_runner,
    OPENFILENAME* ofn) {
  scoped_refptr<GetOpenFileNameClient> 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<base::SequencedTaskRunner>& 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<GetSaveFileNameClient> client,
    const scoped_refptr<base::SequencedTaskRunner>& current_task_runner) {
  DCHECK(current_task_runner->RunsTasksOnCurrentThread());

  base::WeakPtr<content::UtilityProcessHost> 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<base::SequencedTaskRunner>& blocking_task_runner,
    OPENFILENAME* ofn) {
  scoped_refptr<GetSaveFileNameClient> 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<base::SequencedTaskRunner>& 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<base::SequencedTaskRunner>& 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_));
}