summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorhirono@chromium.org <hirono@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-04 09:40:29 +0000
committerhirono@chromium.org <hirono@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-06-04 09:40:29 +0000
commit3113a232cc1ca764298b3eca4e062d10982c2672 (patch)
treee2ff0c2f53e6b28e977fe74f753016fd7c22b6b9
parentc2b43dbac2d7feff547cf7e1caf0689b713d6195 (diff)
downloadchromium_src-3113a232cc1ca764298b3eca4e062d10982c2672.zip
chromium_src-3113a232cc1ca764298b3eca4e062d10982c2672.tar.gz
chromium_src-3113a232cc1ca764298b3eca4e062d10982c2672.tar.bz2
Files.app: Let Files.app pass mutliple files to file handlers.
Previously if multiple files are selected for launching a file handler, Files.app sends multiple launch events for each file. But according to the specification of the launch event, we can put multiple entries in a single launch event. This CL lets Files.app follow the specificaiton. BUG=358694 TEST=manually R=benwells@chromium.org, kinaba@chromium.org Review URL: https://codereview.chromium.org/300063006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@274755 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--apps/browser/api/app_runtime/app_runtime_api.cc21
-rw-r--r--apps/browser/api/app_runtime/app_runtime_api.h9
-rw-r--r--apps/launcher.cc245
-rw-r--r--apps/launcher.h12
-rw-r--r--chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc84
-rw-r--r--chrome/browser/chromeos/file_manager/file_tasks.cc7
-rw-r--r--chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/manifest.json23
-rw-r--r--chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/test.js183
8 files changed, 480 insertions, 104 deletions
diff --git a/apps/browser/api/app_runtime/app_runtime_api.cc b/apps/browser/api/app_runtime/app_runtime_api.cc
index 65ebf61..68be588 100644
--- a/apps/browser/api/app_runtime/app_runtime_api.cc
+++ b/apps/browser/api/app_runtime/app_runtime_api.cc
@@ -65,23 +65,26 @@ void AppEventRouter::DispatchOnRestartedEvent(BrowserContext* context,
}
// static.
-void AppEventRouter::DispatchOnLaunchedEventWithFileEntry(
+void AppEventRouter::DispatchOnLaunchedEventWithFileEntries(
BrowserContext* context,
const Extension* extension,
const std::string& handler_id,
- const std::string& mime_type,
- const file_handler_util::GrantedFileEntry& file_entry) {
+ const std::vector<std::string>& mime_types,
+ const std::vector<file_handler_util::GrantedFileEntry>& file_entries) {
// TODO(sergeygs): Use the same way of creating an event (using the generated
// boilerplate) as below in DispatchOnLaunchedEventWithUrl.
scoped_ptr<base::DictionaryValue> launch_data(new base::DictionaryValue);
launch_data->SetString("id", handler_id);
- scoped_ptr<base::DictionaryValue> launch_item(new base::DictionaryValue);
- launch_item->SetString("fileSystemId", file_entry.filesystem_id);
- launch_item->SetString("baseName", file_entry.registered_name);
- launch_item->SetString("mimeType", mime_type);
- launch_item->SetString("entryId", file_entry.id);
scoped_ptr<base::ListValue> items(new base::ListValue);
- items->Append(launch_item.release());
+ DCHECK(file_entries.size() == mime_types.size());
+ for (size_t i = 0; i < file_entries.size(); ++i) {
+ scoped_ptr<base::DictionaryValue> launch_item(new base::DictionaryValue);
+ launch_item->SetString("fileSystemId", file_entries[i].filesystem_id);
+ launch_item->SetString("baseName", file_entries[i].registered_name);
+ launch_item->SetString("mimeType", mime_types[i]);
+ launch_item->SetString("entryId", file_entries[i].id);
+ items->Append(launch_item.release());
+ }
launch_data->Set("items", items.release());
DispatchOnLaunchedEventImpl(extension->id(), launch_data.Pass(), context);
}
diff --git a/apps/browser/api/app_runtime/app_runtime_api.h b/apps/browser/api/app_runtime/app_runtime_api.h
index 829becf..6853696 100644
--- a/apps/browser/api/app_runtime/app_runtime_api.h
+++ b/apps/browser/api/app_runtime/app_runtime_api.h
@@ -6,6 +6,7 @@
#define APPS_BROWSER_API_APP_RUNTIME_APP_RUNTIME_API_H_
#include <string>
+#include <vector>
class GURL;
@@ -47,15 +48,15 @@ class AppEventRouter {
// }
// }
- // The FileEntry is created from |file_system_id| and |base_name|.
+ // The FileEntries are created from |file_system_id| and |base_name|.
// |handler_id| corresponds to the id of the file_handlers item in the
// manifest that resulted in a match which triggered this launch.
- static void DispatchOnLaunchedEventWithFileEntry(
+ static void DispatchOnLaunchedEventWithFileEntries(
content::BrowserContext* context,
const extensions::Extension* extension,
const std::string& handler_id,
- const std::string& mime_type,
- const file_handler_util::GrantedFileEntry& file_entry);
+ const std::vector<std::string>& mime_types,
+ const std::vector<file_handler_util::GrantedFileEntry>& file_entries);
// |handler_id| corresponds to the id of the url_handlers item
// in the manifest that resulted in a match which triggered this launch.
diff --git a/apps/launcher.cc b/apps/launcher.cc
index f357881..8cfbee9 100644
--- a/apps/launcher.cc
+++ b/apps/launcher.cc
@@ -89,7 +89,7 @@ void LaunchPlatformAppWithNoData(Profile* profile, const Extension* extension) {
AppEventRouter::DispatchOnLaunchedEvent(profile, extension);
}
-// Class to handle launching of platform apps to open a specific path.
+// Class to handle launching of platform apps to open specific paths.
// An instance of this class is created for each launch. The lifetime of these
// instances is managed by reference counted pointers. As long as an instance
// has outstanding tasks on a message queue it will be retained; once all
@@ -99,23 +99,31 @@ class PlatformAppPathLauncher
public:
PlatformAppPathLauncher(Profile* profile,
const Extension* extension,
+ const std::vector<base::FilePath>& file_paths)
+ : profile_(profile), extension_(extension), file_paths_(file_paths) {}
+
+ PlatformAppPathLauncher(Profile* profile,
+ const Extension* extension,
const base::FilePath& file_path)
- : profile_(profile), extension_(extension), file_path_(file_path) {}
+ : profile_(profile), extension_(extension) {
+ if (!file_path.empty())
+ file_paths_.push_back(file_path);
+ }
void Launch() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
- if (file_path_.empty()) {
+ if (file_paths_.empty()) {
LaunchPlatformAppWithNoData(profile_, extension_);
return;
}
- DCHECK(file_path_.IsAbsolute());
+ for (size_t i = 0; i < file_paths_.size(); ++i) {
+ DCHECK(file_paths_[i].IsAbsolute());
+ }
if (HasFileSystemWritePermission(extension_)) {
- std::vector<base::FilePath> paths;
- paths.push_back(file_path_);
PrepareFilesForWritableApp(
- paths,
+ file_paths_,
profile_,
false,
base::Bind(&PlatformAppPathLauncher::OnFileValid, this),
@@ -148,9 +156,17 @@ class PlatformAppPathLauncher
void MakePathAbsolute(const base::FilePath& current_directory) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
- if (!DoMakePathAbsolute(current_directory, &file_path_)) {
- LOG(WARNING) << "Cannot make absolute path from " << file_path_.value();
- file_path_ = base::FilePath();
+ for (std::vector<base::FilePath>::iterator it = file_paths_.begin();
+ it != file_paths_.end();
+ ++it) {
+ if (!DoMakePathAbsolute(current_directory, &*it)) {
+ LOG(WARNING) << "Cannot make absolute path from " << it->value();
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&PlatformAppPathLauncher::LaunchWithNoLaunchData, this));
+ return;
+ }
}
BrowserThread::PostTask(BrowserThread::UI,
@@ -159,93 +175,149 @@ class PlatformAppPathLauncher
}
void OnFileValid() {
+ mime_types_.resize(file_paths_.size());
#if defined(OS_CHROMEOS)
- if (file_manager::util::IsUnderNonNativeLocalPath(profile_, file_path_)) {
- file_manager::util::GetNonNativeLocalPathMimeType(
- profile_,
- file_path_,
- base::Bind(&PlatformAppPathLauncher::OnGotMimeType, this));
- return;
- }
-#endif
-
+ GetNextNonNativeMimeType();
+#else
BrowserThread::PostTask(
BrowserThread::FILE,
FROM_HERE,
- base::Bind(&PlatformAppPathLauncher::GetMimeTypeAndLaunch, this));
+ base::Bind(&PlatformAppPathLauncher::GetMimeTypesAndLaunch, this));
+#endif
}
void OnFileInvalid(const base::FilePath& /* error_path */) {
LaunchWithNoLaunchData();
}
- void GetMimeTypeAndLaunch() {
- DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+#if defined(OS_CHROMEOS)
+ void GetNextNonNativeMimeType() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
- // If the file doesn't exist, or is a directory, launch with no launch data.
- if (!base::PathExists(file_path_) ||
- base::DirectoryExists(file_path_)) {
- LOG(WARNING) << "No file exists with path " << file_path_.value();
- BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
- &PlatformAppPathLauncher::LaunchWithNoLaunchData, this));
- return;
+ bool any_native_files = false;
+ for (size_t i = 0; i < mime_types_.size(); ++i) {
+ if (!mime_types_[i].empty())
+ continue;
+ const base::FilePath& file_path = file_paths_[i];
+ if (file_manager::util::IsUnderNonNativeLocalPath(profile_, file_path)) {
+ file_manager::util::GetNonNativeLocalPathMimeType(
+ profile_,
+ file_path,
+ base::Bind(&PlatformAppPathLauncher::OnGotMimeType, this, i));
+ return;
+ }
+ any_native_files = true;
}
- std::string mime_type;
- if (!net::GetMimeTypeFromFile(file_path_, &mime_type)) {
- // If MIME type of the file can't be determined by its path,
- // try to sniff it by its content.
- std::vector<char> content(net::kMaxBytesToSniff);
- int bytes_read = base::ReadFile(file_path_, &content[0], content.size());
- if (bytes_read >= 0) {
- net::SniffMimeType(&content[0],
- bytes_read,
- net::FilePathToFileURL(file_path_),
- std::string(), // type_hint (passes no hint)
- &mime_type);
- }
- if (mime_type.empty())
- mime_type = kFallbackMimeType;
+ // If there are any native files, we need to call GetMimeTypesAndLaunch to
+ // obtain mime types for the files.
+ if (any_native_files) {
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&PlatformAppPathLauncher::GetMimeTypesAndLaunch, this));
+ return;
}
- BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
- &PlatformAppPathLauncher::LaunchWithMimeType, this, mime_type));
+ // Otherwise, we can call LaunchWithMimeTypes directly.
+ LaunchWithMimeTypes();
}
-#if defined(OS_CHROMEOS)
- void OnGotMimeType(bool success, const std::string& mime_type) {
+ void OnGotMimeType(size_t index, bool success, const std::string& mime_type) {
if (!success) {
LaunchWithNoLaunchData();
return;
}
- LaunchWithMimeType(mime_type.empty() ? kFallbackMimeType : mime_type);
+ mime_types_[index] = mime_type.empty() ? kFallbackMimeType : mime_type;
+ GetNextNonNativeMimeType();
}
#endif
+ void GetMimeTypesAndLaunch() {
+ DCHECK_CURRENTLY_ON(BrowserThread::FILE);
+
+ for (size_t i = 0; i < mime_types_.size(); ++i) {
+ if (!this->mime_types_[i].empty())
+ continue;
+ const base::FilePath& file_path = file_paths_[i];
+
+ // If the file doesn't exist, or is a directory, launch with no launch
+ // data.
+ if (!base::PathExists(file_path) || base::DirectoryExists(file_path)) {
+ LOG(WARNING) << "No file exists with path " << file_path.value();
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&PlatformAppPathLauncher::LaunchWithNoLaunchData, this));
+ return;
+ }
+
+ std::string mime_type;
+ if (!net::GetMimeTypeFromFile(file_path, &mime_type)) {
+ // If MIME type of the file can't be determined by its path,
+ // try to sniff it by its content.
+ std::vector<char> content(net::kMaxBytesToSniff);
+ int bytes_read = base::ReadFile(file_path, &content[0], content.size());
+ if (bytes_read >= 0) {
+ net::SniffMimeType(&content[0],
+ bytes_read,
+ net::FilePathToFileURL(file_path),
+ std::string(), // type_hint (passes no hint)
+ &mime_type);
+ }
+ if (mime_type.empty())
+ mime_type = kFallbackMimeType;
+ }
+ mime_types_[i] = mime_type;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&PlatformAppPathLauncher::LaunchWithMimeTypes, this));
+ }
+
void LaunchWithNoLaunchData() {
// This method is required as an entry point on the UI thread.
LaunchPlatformAppWithNoData(profile_, extension_);
}
- void LaunchWithMimeType(const std::string& mime_type) {
+ void LaunchWithMimeTypes() {
+ DCHECK(file_paths_.size() == mime_types_.size());
+
// Find file handler from the platform app for the file being opened.
const extensions::FileHandlerInfo* handler = NULL;
- if (!handler_id_.empty())
+ if (!handler_id_.empty()) {
handler = FileHandlerForId(*extension_, handler_id_);
- else
- handler = FirstFileHandlerForFile(*extension_, mime_type, file_path_);
- if (handler && !FileHandlerCanHandleFile(*handler, mime_type, file_path_)) {
- LOG(WARNING) << "Extension does not provide a valid file handler for "
- << file_path_.value();
- LaunchWithNoLaunchData();
- return;
+ if (handler) {
+ for (size_t i = 0; i < file_paths_.size(); ++i) {
+ if (!FileHandlerCanHandleFile(
+ *handler, mime_types_[i], file_paths_[i])) {
+ LOG(WARNING)
+ << "Extension does not provide a valid file handler for "
+ << file_paths_[i].value();
+ handler = NULL;
+ break;
+ }
+ }
+ }
+ } else {
+ std::set<std::pair<base::FilePath, std::string> > path_and_file_type_set;
+ for (size_t i = 0; i < file_paths_.size(); ++i) {
+ path_and_file_type_set.insert(
+ std::make_pair(file_paths_[i], mime_types_[i]));
+ }
+ const std::vector<const extensions::FileHandlerInfo*>& handlers =
+ extensions::app_file_handler_util::FindFileHandlersForFiles(
+ *extension_, path_and_file_type_set);
+ if (!handlers.empty())
+ handler = handlers[0];
}
// If this app doesn't have a file handler that supports the file, launch
// with no launch data.
if (!handler) {
- LOG(WARNING) << "Extension does not provide a valid file handler for "
- << file_path_.value();
+ LOG(WARNING) << "Extension does not provide a valid file handler.";
LaunchWithNoLaunchData();
return;
}
@@ -258,39 +330,44 @@ class PlatformAppPathLauncher
// available, or it might be in the process of being unloaded, in which case
// the lazy background task queue is used to load the extension and then
// call back to us.
- extensions::LazyBackgroundTaskQueue* queue =
+ extensions::LazyBackgroundTaskQueue* const queue =
ExtensionSystem::Get(profile_)->lazy_background_task_queue();
if (queue->ShouldEnqueueTask(profile_, extension_)) {
- queue->AddPendingTask(profile_, extension_->id(), base::Bind(
- &PlatformAppPathLauncher::GrantAccessToFileAndLaunch,
- this, mime_type));
+ queue->AddPendingTask(
+ profile_,
+ extension_->id(),
+ base::Bind(&PlatformAppPathLauncher::GrantAccessToFilesAndLaunch,
+ this));
return;
}
- extensions::ProcessManager* process_manager =
+ extensions::ProcessManager* const process_manager =
ExtensionSystem::Get(profile_)->process_manager();
- ExtensionHost* host =
+ ExtensionHost* const host =
process_manager->GetBackgroundHostForExtension(extension_->id());
DCHECK(host);
- GrantAccessToFileAndLaunch(mime_type, host);
+ GrantAccessToFilesAndLaunch(host);
}
- void GrantAccessToFileAndLaunch(const std::string& mime_type,
- ExtensionHost* host) {
+ void GrantAccessToFilesAndLaunch(ExtensionHost* host) {
// If there was an error loading the app page, |host| will be NULL.
if (!host) {
LOG(ERROR) << "Could not load app page for " << extension_->id();
return;
}
- GrantedFileEntry file_entry =
- CreateFileEntry(profile_,
- extension_,
- host->render_process_host()->GetID(),
- file_path_,
- false);
- AppEventRouter::DispatchOnLaunchedEventWithFileEntry(
- profile_, extension_, handler_id_, mime_type, file_entry);
+ std::vector<GrantedFileEntry> file_entries;
+ for (size_t i = 0; i < file_paths_.size(); ++i) {
+ file_entries.push_back(
+ CreateFileEntry(profile_,
+ extension_,
+ host->render_process_host()->GetID(),
+ file_paths_[i],
+ false));
+ }
+
+ AppEventRouter::DispatchOnLaunchedEventWithFileEntries(
+ profile_, extension_, handler_id_, mime_types_, file_entries);
}
// The profile the app should be run in.
@@ -301,7 +378,8 @@ class PlatformAppPathLauncher
// See http://crbug.com/372270 for details.
const Extension* extension_;
// The path to be passed through to the app.
- base::FilePath file_path_;
+ std::vector<base::FilePath> file_paths_;
+ std::vector<std::string> mime_types_;
// The ID of the file handler used to launch the app.
std::string handler_id_;
@@ -369,12 +447,13 @@ void LaunchPlatformApp(Profile* profile, const Extension* extension) {
base::FilePath());
}
-void LaunchPlatformAppWithFileHandler(Profile* profile,
- const Extension* extension,
- const std::string& handler_id,
- const base::FilePath& file_path) {
+void LaunchPlatformAppWithFileHandler(
+ Profile* profile,
+ const Extension* extension,
+ const std::string& handler_id,
+ const std::vector<base::FilePath>& file_paths) {
scoped_refptr<PlatformAppPathLauncher> launcher =
- new PlatformAppPathLauncher(profile, extension, file_path);
+ new PlatformAppPathLauncher(profile, extension, file_paths);
launcher->LaunchWithHandler(handler_id);
}
diff --git a/apps/launcher.h b/apps/launcher.h
index d41f517..eb2d98f 100644
--- a/apps/launcher.h
+++ b/apps/launcher.h
@@ -6,6 +6,7 @@
#define APPS_LAUNCHER_H_
#include <string>
+#include <vector>
class GURL;
class Profile;
@@ -41,13 +42,14 @@ void LaunchPlatformApp(Profile* profile,
const extensions::Extension* extension);
// Launches the platform app |extension| with |handler_id| and the contents of
-// |file_path| available through the launch data. |handler_id| corresponds to
+// |file_paths| available through the launch data. |handler_id| corresponds to
// the id of the file_handlers item in the manifest that resulted in a match
// that triggered this launch.
-void LaunchPlatformAppWithFileHandler(Profile* profile,
- const extensions::Extension* extension,
- const std::string& handler_id,
- const base::FilePath& file_path);
+void LaunchPlatformAppWithFileHandler(
+ Profile* profile,
+ const extensions::Extension* extension,
+ const std::string& handler_id,
+ const std::vector<base::FilePath>& file_paths);
// Launches the platform app |extension| with |handler_id|, |url| and
// |referrer_url| available through the launch data. |handler_id| corresponds to
diff --git a/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc b/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc
index c4fd4f6..70e67f2a 100644
--- a/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc
+++ b/chrome/browser/chromeos/file_manager/external_filesystem_apitest.cc
@@ -599,6 +599,78 @@ class MultiProfileDriveFileSystemExtensionApiTest :
std::map<std::string, std::string> resource_ids_;
};
+class LocalAndDriveFileSystemExtensionApiTest
+ : public FileSystemExtensionApiTestBase {
+ public:
+ LocalAndDriveFileSystemExtensionApiTest() {}
+ virtual ~LocalAndDriveFileSystemExtensionApiTest() {}
+
+ // FileSystemExtensionApiTestBase OVERRIDE.
+ virtual void InitTestFileSystem() OVERRIDE {
+ ASSERT_TRUE(InitializeLocalFileSystem(
+ kLocalMountPointName, &local_tmp_dir_, &local_mount_point_dir_))
+ << "Failed to initialize file system.";
+
+ // Set up cache root to be used by DriveIntegrationService. This has to be
+ // done before the browser is created because the service instance is
+ // initialized by EventRouter.
+ ASSERT_TRUE(test_cache_root_.CreateUniqueTempDir());
+
+ // This callback will get called during Profile creation.
+ create_drive_integration_service_ = base::Bind(
+ &LocalAndDriveFileSystemExtensionApiTest::CreateDriveIntegrationService,
+ base::Unretained(this));
+ service_factory_for_test_.reset(
+ new DriveIntegrationServiceFactory::ScopedFactoryForTest(
+ &create_drive_integration_service_));
+ }
+
+ // FileSystemExtensionApiTestBase OVERRIDE.
+ virtual void AddTestMountPoint() OVERRIDE {
+ EXPECT_TRUE(content::BrowserContext::GetMountPoints(browser()->profile())
+ ->RegisterFileSystem(kLocalMountPointName,
+ fileapi::kFileSystemTypeNativeLocal,
+ fileapi::FileSystemMountOption(),
+ local_mount_point_dir_));
+ VolumeManager::Get(browser()->profile())
+ ->AddVolumeInfoForTesting(local_mount_point_dir_,
+ VOLUME_TYPE_TESTING,
+ chromeos::DEVICE_TYPE_UNKNOWN);
+ test_util::WaitUntilDriveMountPointIsAdded(browser()->profile());
+ }
+
+ protected:
+ // DriveIntegrationService factory function for this test.
+ drive::DriveIntegrationService* CreateDriveIntegrationService(
+ Profile* profile) {
+ fake_drive_service_ = new drive::FakeDriveService;
+ fake_drive_service_->LoadAppListForDriveApi("drive/applist.json");
+
+ std::map<std::string, std::string> resource_ids;
+ EXPECT_TRUE(InitializeDriveService(fake_drive_service_, &resource_ids));
+
+ return new drive::DriveIntegrationService(profile,
+ NULL,
+ fake_drive_service_,
+ "drive",
+ test_cache_root_.path(),
+ NULL);
+ }
+
+ private:
+ // For local volume.
+ base::ScopedTempDir local_tmp_dir_;
+ base::FilePath local_mount_point_dir_;
+
+ // For drive volume.
+ base::ScopedTempDir test_cache_root_;
+ drive::FakeDriveService* fake_drive_service_;
+ DriveIntegrationServiceFactory::FactoryCallback
+ create_drive_integration_service_;
+ scoped_ptr<DriveIntegrationServiceFactory::ScopedFactoryForTest>
+ service_factory_for_test_;
+};
+
//
// LocalFileSystemExtensionApiTests.
//
@@ -712,5 +784,17 @@ IN_PROC_BROWSER_TEST_F(MultiProfileDriveFileSystemExtensionApiTest,
FLAGS_NONE)) << message_;
}
+//
+// LocalAndDriveFileSystemExtensionApiTests.
+//
+IN_PROC_BROWSER_TEST_F(LocalAndDriveFileSystemExtensionApiTest,
+ AppFileHandlerMulti) {
+ EXPECT_TRUE(
+ RunFileSystemExtensionApiTest("file_browser/app_file_handler_multi",
+ FILE_PATH_LITERAL("manifest.json"),
+ "",
+ FLAGS_NONE))
+ << message_;
+}
} // namespace
} // namespace file_manager
diff --git a/chrome/browser/chromeos/file_manager/file_tasks.cc b/chrome/browser/chromeos/file_manager/file_tasks.cc
index 2cb78d3..c74cd2c 100644
--- a/chrome/browser/chromeos/file_manager/file_tasks.cc
+++ b/chrome/browser/chromeos/file_manager/file_tasks.cc
@@ -311,11 +311,12 @@ bool ExecuteFileTask(Profile* profile,
file_urls,
done);
} else if (task.task_type == TASK_TYPE_FILE_HANDLER) {
+ std::vector<base::FilePath> paths;
for (size_t i = 0; i != file_urls.size(); ++i) {
- apps::LaunchPlatformAppWithFileHandler(
- profile, extension, task.action_id, file_urls[i].path());
+ paths.push_back(file_urls[i].path());
}
-
+ apps::LaunchPlatformAppWithFileHandler(
+ profile, extension, task.action_id, paths);
if (!done.is_null())
done.Run(extensions::api::file_browser_private::TASK_RESULT_MESSAGE_SENT);
return true;
diff --git a/chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/manifest.json b/chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/manifest.json
new file mode 100644
index 0000000..01c3d35
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/manifest.json
@@ -0,0 +1,23 @@
+{
+ "key": "MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQChptAQ0n4R56N03nWQ1ogR7DVRBjGo80Vw6G9KLjzZv44D8rq5Q5IkeQrtKgWyZfXevlsCe3LaLo18rcz8iZx6lK2xhLdUR+ORjsjuBfdEL5a5cWeRTSxf75AcqndQsmpwMBdrMTCZ8jQNusUI+XlrihLNNJuI5TM4vNINI5bYFQIBIw==",
+ "name": "ChromeOS File handler extension",
+ "version": "0.1",
+ "manifest_version": 2,
+ "description": "Tests of the file handler for multiple items",
+ "app": {
+ "background": {
+ "scripts": ["test.js"]
+ }
+ },
+ "file_handlers": {
+ "textAction": {
+ "extensions": ["txt"],
+ "title": "foo action handler"
+ }
+ },
+ "permissions": [
+ "fileBrowserPrivate",
+ "fileBrowserHandler",
+ "unlimitedStorage"
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/test.js b/chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/test.js
new file mode 100644
index 0000000..5af5d73
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_browser/app_file_handler_multi/test.js
@@ -0,0 +1,183 @@
+// Copyright (c) 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.
+
+'use strict';
+
+/**
+ * Promise of the volume list.
+ * @type {Promise}
+ */
+var volumeListPromise = new Promise(function(fulfill, reject) {
+ chrome.fileBrowserPrivate.getVolumeMetadataList(fulfill);
+});
+
+/**
+ * Obtains file system of the volume type.
+ * @param {string} volumeType VolumeType.
+ * @return {Promise} Promise to be fulfilled with a file system.
+ */
+function getFileSystem(volumeType) {
+ return volumeListPromise.then(function(list) {
+ for (var i = 0; i < list.length; i++) {
+ if (list[i].volumeType == volumeType) {
+ return new Promise(function(fulfill) {
+ chrome.fileBrowserPrivate.requestFileSystem(
+ list[i].volumeId, fulfill);
+ });
+ }
+ }
+ throw new Error('The volume is not found: ' + volumeType + '.');
+ });
+}
+
+/**
+ * Prepares a file on the file system.
+ * @param {FileSystem} filesystem File system.
+ * @param {string} name Name of the file.
+ * @param {Blob} contents Contents of the file.
+ * @return {Promise} Promise to be fulfilled with FileEntry of the new file.
+ */
+function prepareFile(filesystem, name, contents) {
+ return new Promise(function(fulfill, reject) {
+ filesystem.root.getFile(name, {create: true}, function(file) {
+ file.createWriter(function(writer) {
+ writer.write(contents);
+ writer.onwrite = fulfill.bind(null, file);
+ writer.onerror = reject;
+ });
+ fulfill(file);
+ }, reject);
+ });
+}
+
+/**
+ * Prepares two test files on the file system.
+ * @param {FileSystem} filesystem File system.
+ * @return {Promise} Promise to be fullfilled with an object {filesystem:
+ * FileSystem, entries: Array.<FileEntry>} that contains the passed file
+ * system and the created entries.
+ */
+function prepareFiles(filesystem) {
+ var testFileA =
+ prepareFile(filesystem, 'test_file_a.txt', TEST_FILE_CONTENTS);
+ var testFileB =
+ prepareFile(filesystem, 'test_file_b.txt', TEST_FILE_CONTENTS);
+ return Promise.all([testFileA, testFileB]).then(function(entries) {
+ return {filesystem: filesystem, entries: entries};
+ });
+}
+
+/**
+ * Contents of the test file.
+ * @type {Blob}
+ * @const
+ */
+var TEST_FILE_CONTENTS = new Blob(['This is a test file.']);
+
+/**
+ * File system of the drive volume.
+ * @type {Promise}
+ */
+var driveFileSystemPromise = getFileSystem('drive').then(prepareFiles);
+
+/**
+ * File system of the local volume.
+ * @type {Promise}
+ */
+var localFileSystemPromise = getFileSystem('testing').then(prepareFiles);
+
+/**
+ * Calls test functions depends on the result of the promise.
+ * @param {Promise} promise Promise to be fulfilled or to be rejected depends on
+ * the test results.
+ */
+function testPromise(promise) {
+ promise.then(
+ chrome.test.callbackPass(),
+ function(error) {
+ chrome.test.fail(error.stack || error);
+ });
+}
+
+/**
+ * Calls the executeTask API with the entries and checks the launch data passed
+ * to onLaunched events.
+ * @param {entries} entries Entries to be tested.
+ * @return {Promise} Promise to be fulfilled on success.
+ */
+function launchWithEntries(entries) {
+ var urls = entries.map(function(entry) { return entry.toURL(); });
+ var mimeTypes = urls.map(function() { return ''; });
+ var tasksPromise = new Promise(function(fulfill) {
+ chrome.fileBrowserPrivate.getFileTasks(urls, mimeTypes, fulfill);
+ }).then(function(tasks) {
+ chrome.test.assertEq(1, tasks.length);
+ chrome.test.assertEq('kidcpjlbjdmcnmccjhjdckhbngnhnepk|app|textAction',
+ tasks[0].taskId);
+ return tasks[0];
+ });
+ var launchDataPromise = new Promise(function(fulfill) {
+ chrome.app.runtime.onLaunched.addListener(function handler(launchData) {
+ chrome.app.runtime.onLaunched.removeListener(handler);
+ fulfill(launchData);
+ });
+ });
+ var taskExecutedPromise = tasksPromise.then(function(task) {
+ return new Promise(function(fulfill, reject) {
+ chrome.fileBrowserPrivate.executeTask(
+ task.taskId,
+ urls,
+ function(result) {
+ if (result)
+ fulfill();
+ else
+ reject();
+ });
+ });
+ });
+ return Promise.all([taskExecutedPromise, launchDataPromise]).then(
+ function(args) {
+ chrome.test.assertEq(entries.length, args[1].items.length);
+ chrome.test.assertEq(
+ entries.map(function(entry) { return entry.name; }),
+ args[1].items.map(function(item) { return item.entry.name; }));
+ });
+}
+
+/**
+ * Tests the file handler feature with entries on the local volume.
+ */
+function testForLocalFiles() {
+ testPromise(localFileSystemPromise.then(function(volume) {
+ return launchWithEntries(volume.entries);
+ }));
+}
+
+/**
+ * Tests the file handler feature with entries on the drive volume.
+ */
+function testForDriveFiles() {
+ testPromise(driveFileSystemPromise.then(function(volume) {
+ return launchWithEntries(volume.entries);
+ }));
+}
+
+/**
+ * Tests the file handler with entries both on the local and on the drive
+ * volumes.
+ */
+function testForMixedFiles() {
+ testPromise(
+ Promise.all([localFileSystemPromise, driveFileSystemPromise]).then(
+ function(args) {
+ return launchWithEntries(args[0].entries.concat(args[1].entries));
+ }));
+}
+
+// Run the tests.
+chrome.test.runTests([
+ testForLocalFiles,
+ testForDriveFiles,
+ testForMixedFiles
+]);