Skip to content

Commit

Permalink
mac: Zip packages when they are selected by the file opener.
Browse files Browse the repository at this point in the history
When a package is selected in the file opener, it is automatically zipped, and
the zipped file is passed to the render view host.

This CL fixes 2 unrelated bugs in file_select_helper:
- If the render_view_host_ was destroyed before the user finished with the
file_select_helper, then Release() would never be called and the
file_select_helper would leak.

- If the render_view_host_ was destroyed before the user finished with the
file_select_helper, the default directory would be updated if the user was
opening multiple files, but it would not be updated if the user was opening a
single file. Now both update the default directory.

Note: When a large package is being zipped, the UI thread and renderer are
still responsive, but display no indication of the ongoing work.

BUG=33920

Review URL: https://codereview.chromium.org/634833003

Cr-Commit-Position: refs/heads/master@{#299143}
  • Loading branch information
erikchen authored and Commit bot committed Oct 10, 2014
1 parent e3e806f commit 22de64a
Show file tree
Hide file tree
Showing 11 changed files with 851 additions and 41 deletions.
91 changes: 66 additions & 25 deletions chrome/browser/file_select_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ std::vector<ui::SelectedFileInfo> FilePathListToSelectedFileInfoList(
return selected_files;
}

void DeleteFiles(const std::vector<base::FilePath>& paths) {
for (auto& file_path : paths)
base::DeleteFile(file_path, false);
}

} // namespace

struct FileSelectHelper::ActiveDirectoryEnumeration {
Expand Down Expand Up @@ -126,11 +131,13 @@ void FileSelectHelper::FileSelectedWithExtraInfo(
const ui::SelectedFileInfo& file,
int index,
void* params) {
if (!render_view_host_)
return;

profile_->set_last_selected_directory(file.file_path.DirName());

if (!render_view_host_) {
RunFileChooserEnd();
return;
}

const base::FilePath& path = file.local_path;
if (dialog_type_ == ui::SelectFileDialog::SELECT_UPLOAD_FOLDER) {
StartNewEnumeration(path, kFileSelectEnumerationId, render_view_host_);
Expand All @@ -139,10 +146,15 @@ void FileSelectHelper::FileSelectedWithExtraInfo(

std::vector<ui::SelectedFileInfo> files;
files.push_back(file);
NotifyRenderViewHost(render_view_host_, files, dialog_mode_);

// No members should be accessed from here on.
RunFileChooserEnd();
#if defined(OS_MACOSX) && !defined(OS_IOS)
content::BrowserThread::PostTask(
content::BrowserThread::FILE_USER_BLOCKING,
FROM_HERE,
base::Bind(&FileSelectHelper::ProcessSelectedFilesMac, this, files));
#else
NotifyRenderViewHostAndEnd(files);
#endif // defined(OS_MACOSX) && !defined(OS_IOS)
}

void FileSelectHelper::MultiFilesSelected(
Expand All @@ -159,27 +171,19 @@ void FileSelectHelper::MultiFilesSelectedWithExtraInfo(
void* params) {
if (!files.empty())
profile_->set_last_selected_directory(files[0].file_path.DirName());
if (!render_view_host_)
return;

NotifyRenderViewHost(render_view_host_, files, dialog_mode_);

// No members should be accessed from here on.
RunFileChooserEnd();
#if defined(OS_MACOSX) && !defined(OS_IOS)
content::BrowserThread::PostTask(
content::BrowserThread::FILE_USER_BLOCKING,
FROM_HERE,
base::Bind(&FileSelectHelper::ProcessSelectedFilesMac, this, files));
#else
NotifyRenderViewHostAndEnd(files);
#endif // defined(OS_MACOSX) && !defined(OS_IOS)
}

void FileSelectHelper::FileSelectionCanceled(void* params) {
if (!render_view_host_)
return;

// If the user cancels choosing a file to upload we pass back an
// empty vector.
NotifyRenderViewHost(
render_view_host_, std::vector<ui::SelectedFileInfo>(),
dialog_mode_);

// No members should be accessed from here on.
RunFileChooserEnd();
NotifyRenderViewHostAndEnd(std::vector<ui::SelectedFileInfo>());
}

void FileSelectHelper::StartNewEnumeration(const base::FilePath& path,
Expand Down Expand Up @@ -237,6 +241,22 @@ void FileSelectHelper::OnListDone(int id, int error) {
EnumerateDirectoryEnd();
}

void FileSelectHelper::NotifyRenderViewHostAndEnd(
const std::vector<ui::SelectedFileInfo>& files) {
if (render_view_host_)
NotifyRenderViewHost(render_view_host_, files, dialog_mode_);

// No members should be accessed from here on.
RunFileChooserEnd();
}

void FileSelectHelper::DeleteTemporaryFiles() {
BrowserThread::PostTask(BrowserThread::FILE,
FROM_HERE,
base::Bind(&DeleteFiles, temporary_files_));
temporary_files_.clear();
}

scoped_ptr<ui::SelectFileDialog::FileTypeInfo>
FileSelectHelper::GetFileTypesFromAcceptType(
const std::vector<base::string16>& accept_types) {
Expand Down Expand Up @@ -335,8 +355,12 @@ void FileSelectHelper::RunFileChooser(RenderViewHost* render_view_host,
render_view_host_ = render_view_host;
web_contents_ = web_contents;
notification_registrar_.RemoveAll();
notification_registrar_.Add(this,
content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED,
content::Source<WebContents>(web_contents_));
notification_registrar_.Add(
this, content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
this,
content::NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
content::Source<RenderWidgetHost>(render_view_host_));
notification_registrar_.Add(
this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
Expand Down Expand Up @@ -433,6 +457,12 @@ void FileSelectHelper::RunFileChooserOnUIThread(
// chooser dialog. Perform any cleanup and release the reference we added
// in RunFileChooser().
void FileSelectHelper::RunFileChooserEnd() {
// If there are temporary files, then this instance needs to stick around
// until web_contents_ is destroyed, so that this instance can delete the
// temporary files.
if (!temporary_files_.empty())
return;

render_view_host_ = NULL;
web_contents_ = NULL;
Release();
Expand Down Expand Up @@ -472,9 +502,20 @@ void FileSelectHelper::Observe(int type,
case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
DCHECK(content::Source<WebContents>(source).ptr() == web_contents_);
web_contents_ = NULL;
break;
}

// Intentional fall through.
case content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED:
if (!temporary_files_.empty()) {
DeleteTemporaryFiles();

// Now that the temporary files have been scheduled for deletion, there
// is no longer any reason to keep this instance around.
Release();
}

break;

default:
NOTREACHED();
}
Expand Down
32 changes: 32 additions & 0 deletions chrome/browser/file_select_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class FileSelectHelper
private:
friend class base::RefCountedThreadSafe<FileSelectHelper>;
FRIEND_TEST_ALL_PREFIXES(FileSelectHelperTest, IsAcceptTypeValid);
FRIEND_TEST_ALL_PREFIXES(FileSelectHelperTest, ZipPackage);
explicit FileSelectHelper(Profile* profile);
virtual ~FileSelectHelper();

Expand Down Expand Up @@ -121,6 +122,33 @@ class FileSelectHelper
// callback is received from the enumeration code.
void EnumerateDirectoryEnd();

#if defined(OS_MACOSX) && !defined(OS_IOS)
// Must be called on the FILE_USER_BLOCKING thread. Each selected file that is
// a package will be zipped, and the zip will be passed to the render view
// host in place of the package.
void ProcessSelectedFilesMac(const std::vector<ui::SelectedFileInfo>& files);

// Saves the paths of |zipped_files| for later deletion. Passes |files| to the
// render view host.
void ProcessSelectedFilesMacOnUIThread(
const std::vector<ui::SelectedFileInfo>& files,
const std::vector<base::FilePath>& zipped_files);

// Zips the package at |path| into a temporary destination. Returns the
// temporary destination, if the zip was successful. Otherwise returns an
// empty path.
static base::FilePath ZipPackage(const base::FilePath& path);
#endif // defined(OS_MACOSX) && !defined(OS_IOS)

// Utility method that passes |files| to the render view host, and ends the
// file chooser.
void NotifyRenderViewHostAndEnd(
const std::vector<ui::SelectedFileInfo>& files);

// Schedules the deletion of the files in |temporary_files_| and clears the
// vector.
void DeleteTemporaryFiles();

// Helper method to get allowed extensions for select file dialog from
// the specified accept types as defined in the spec:
// http://whatwg.org/html/number-state.html#attr-input-accept
Expand Down Expand Up @@ -161,6 +189,10 @@ class FileSelectHelper
// Registrar for notifications regarding our RenderViewHost.
content::NotificationRegistrar notification_registrar_;

// Temporary files only used on OSX. This class is responsible for deleting
// these files when they are no longer needed.
std::vector<base::FilePath> temporary_files_;

DISALLOW_COPY_AND_ASSIGN(FileSelectHelper);
};

Expand Down
142 changes: 142 additions & 0 deletions chrome/browser/file_select_helper_mac.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// 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/file_select_helper.h"

#include <Cocoa/Cocoa.h>
#include <sys/stat.h>

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/mac/foundation_util.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/zlib/google/zip.h"
#include "ui/shell_dialogs/selected_file_info.h"

namespace {

// Given the |path| of a package, returns the destination that the package
// should be zipped to. Returns an empty path on any errors.
base::FilePath ZipDestination(const base::FilePath& path) {
NSMutableString* dest =
[NSMutableString stringWithString:NSTemporaryDirectory()];

// Couldn't get the temporary directory.
if (!dest)
return base::FilePath();

[dest appendFormat:@"%@/zip_cache/%@",
[[NSBundle mainBundle] bundleIdentifier],
[[NSProcessInfo processInfo] globallyUniqueString]];

return base::mac::NSStringToFilePath(dest);
}

// Returns the path of the package and its components relative to the package's
// parent directory.
std::vector<base::FilePath> RelativePathsForPackage(
const base::FilePath& package) {
// Get the base directory.
base::FilePath base_dir = package.DirName();

// Add the package as the first relative path.
std::vector<base::FilePath> relative_paths;
relative_paths.push_back(package.BaseName());

// Add the components of the package as relative paths.
base::FileEnumerator file_enumerator(
package,
true /* recursive */,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
for (base::FilePath path = file_enumerator.Next(); !path.empty();
path = file_enumerator.Next()) {
base::FilePath relative_path;
bool success = base_dir.AppendRelativePath(path, &relative_path);
if (success)
relative_paths.push_back(relative_path);
}

return relative_paths;
}

} // namespace

base::FilePath FileSelectHelper::ZipPackage(const base::FilePath& path) {
base::FilePath dest(ZipDestination(path));
if (dest.empty())
return dest;

if (!base::CreateDirectory(dest.DirName()))
return base::FilePath();

base::File file(dest, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
if (!file.IsValid())
return base::FilePath();

std::vector<base::FilePath> files_to_zip(RelativePathsForPackage(path));
base::FilePath base_dir = path.DirName();
bool success = zip::ZipFiles(base_dir, files_to_zip, file.GetPlatformFile());

int result = -1;
if (success)
result = fchmod(file.GetPlatformFile(), S_IRUSR);

return result >= 0 ? dest : base::FilePath();
}

void FileSelectHelper::ProcessSelectedFilesMac(
const std::vector<ui::SelectedFileInfo>& files) {
DCHECK_CURRENTLY_ON(content::BrowserThread::FILE_USER_BLOCKING);

// Make a mutable copy of the input files.
std::vector<ui::SelectedFileInfo> files_out(files);
std::vector<base::FilePath> temporary_files;

for (auto& file_info : files_out) {
NSString* filename = base::mac::FilePathToNSString(file_info.local_path);
BOOL isPackage =
[[NSWorkspace sharedWorkspace] isFilePackageAtPath:filename];
if (isPackage && base::DirectoryExists(file_info.local_path)) {
base::FilePath result = ZipPackage(file_info.local_path);

if (!result.empty()) {
temporary_files.push_back(result);
file_info.local_path = result;
file_info.file_path = result;
file_info.display_name.append(".zip");
}
}
}

content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(&FileSelectHelper::ProcessSelectedFilesMacOnUIThread,
base::Unretained(this),
files_out,
temporary_files));
}

void FileSelectHelper::ProcessSelectedFilesMacOnUIThread(
const std::vector<ui::SelectedFileInfo>& files,
const std::vector<base::FilePath>& temporary_files) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

if (!temporary_files.empty()) {
temporary_files_.insert(
temporary_files_.end(), temporary_files.begin(), temporary_files.end());

// Typically, |temporary_files| are deleted after |web_contents_| is
// destroyed. If |web_contents_| is already NULL, then the temporary files
// need to be deleted now.
if (!web_contents_) {
DeleteTemporaryFiles();
RunFileChooserEnd();
return;
}
}

NotifyRenderViewHostAndEnd(files);
}
Loading

0 comments on commit 22de64a

Please sign in to comment.