summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/app_restore_service_browsertest.cc3
-rw-r--r--apps/launcher.cc9
-rw-r--r--apps/saved_files_service.cc26
-rw-r--r--apps/saved_files_service.h7
-rw-r--r--apps/saved_files_service_unittest.cc15
-rw-r--r--chrome/app/generated_resources.grd3
-rw-r--r--chrome/browser/extensions/api/file_handlers/app_file_handler_util.cc26
-rw-r--r--chrome/browser/extensions/api/file_handlers/app_file_handler_util.h6
-rw-r--r--chrome/browser/extensions/api/file_system/file_system_api.cc116
-rw-r--r--chrome/browser/extensions/api/file_system/file_system_api.h22
-rw-r--r--chrome/browser/extensions/api/file_system/file_system_apitest.cc103
-rw-r--r--chrome/common/extensions/api/_permission_features.json4
-rw-r--r--chrome/common/extensions/api/file_system.idl45
-rw-r--r--chrome/common/extensions/permissions/api_permission.h1
-rw-r--r--chrome/common/extensions/permissions/chrome_api_permissions.cc13
-rw-r--r--chrome/common/extensions/permissions/permission_message.h1
-rw-r--r--chrome/renderer/resources/extensions/file_system_custom_bindings.js36
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory/background.js7
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory/manifest.json15
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory/test.html4
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory/test.js98
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory/test_util.js62
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/background.js7
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/manifest.json13
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/test.html3
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/test.js12
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory_with_write/background.js7
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory_with_write/manifest.json15
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory_with_write/test.html4
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory_with_write/test.js99
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory_with_write/test_util.js75
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/background.js7
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/manifest.json13
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/test.html3
-rw-r--r--chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/test.js12
-rw-r--r--chrome/test/data/extensions/api_test/file_system/restore_directory/background.js7
-rw-r--r--chrome/test/data/extensions/api_test/file_system/restore_directory/manifest.json11
-rw-r--r--chrome/test/data/extensions/api_test/file_system/restore_directory/test.html4
-rw-r--r--chrome/test/data/extensions/api_test/file_system/restore_directory/test.js25
-rw-r--r--chrome/test/data/extensions/api_test/file_system/restore_directory/test_util.js62
-rw-r--r--chrome/test/data/extensions/api_test/file_system/retain_directory/background.js7
-rw-r--r--chrome/test/data/extensions/api_test/file_system/retain_directory/manifest.json11
-rw-r--r--chrome/test/data/extensions/api_test/file_system/retain_directory/test.html4
-rw-r--r--chrome/test/data/extensions/api_test/file_system/retain_directory/test.js36
-rw-r--r--chrome/test/data/extensions/api_test/file_system/retain_directory/test_other_window.html3
-rw-r--r--chrome/test/data/extensions/api_test/file_system/retain_directory/test_other_window.js18
-rw-r--r--chrome/test/data/extensions/api_test/file_system/retain_directory/test_util.js62
47 files changed, 1046 insertions, 96 deletions
diff --git a/apps/app_restore_service_browsertest.cc b/apps/app_restore_service_browsertest.cc
index cdde267..d6cc728 100644
--- a/apps/app_restore_service_browsertest.cc
+++ b/apps/app_restore_service_browsertest.cc
@@ -136,7 +136,8 @@ IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, MAYBE_FileAccessIsRestored) {
extension_prefs->SetExtensionRunning(extension->id(), true);
for (std::vector<SavedFileEntry>::const_iterator it = file_entries.begin();
it != file_entries.end(); ++it) {
- saved_files_service->RegisterFileEntry(extension->id(), it->id, it->path);
+ saved_files_service->RegisterFileEntry(
+ extension->id(), it->id, it->path, it->is_directory);
}
apps::AppRestoreServiceFactory::GetForProfile(browser()->profile())->
diff --git a/apps/launcher.cc b/apps/launcher.cc
index d3bc43c..6843790 100644
--- a/apps/launcher.cc
+++ b/apps/launcher.cc
@@ -133,6 +133,7 @@ class PlatformAppPathLauncher
CheckWritableFiles(
paths,
profile_,
+ false,
base::Bind(&PlatformAppPathLauncher::OnFileValid, this),
base::Bind(&PlatformAppPathLauncher::OnFileInvalid, this));
return;
@@ -283,8 +284,12 @@ class PlatformAppPathLauncher
return;
}
- GrantedFileEntry file_entry = CreateFileEntry(
- profile_, extension_, host->render_process_host()->GetID(), file_path_);
+ GrantedFileEntry file_entry =
+ CreateFileEntry(profile_,
+ extension_,
+ host->render_process_host()->GetID(),
+ file_path_,
+ false);
extensions::AppEventRouter::DispatchOnLaunchedEventWithFileEntry(
profile_, extension_, handler_id_, mime_type, file_entry);
}
diff --git a/apps/saved_files_service.cc b/apps/saved_files_service.cc
index aa64af9..f832ffe 100644
--- a/apps/saved_files_service.cc
+++ b/apps/saved_files_service.cc
@@ -36,6 +36,9 @@ const char kFileEntries[] = "file_entries";
// The path to a file entry that the app had permission to access.
const char kFileEntryPath[] = "path";
+// Whether or not the the entry refers to a directory.
+const char kFileEntryIsDirectory[] = "is_directory";
+
// The sequence number in the LRU of the file entry.
const char kFileEntrySequenceNumber[] = "sequence_number";
@@ -59,6 +62,7 @@ void AddSavedFileEntry(ExtensionPrefs* prefs,
DictionaryValue* file_entry_dict = new DictionaryValue();
file_entry_dict->Set(kFileEntryPath, CreateFilePathValue(file_entry.path));
+ file_entry_dict->SetBoolean(kFileEntryIsDirectory, file_entry.is_directory);
file_entry_dict->SetInteger(kFileEntrySequenceNumber,
file_entry.sequence_number);
file_entries->SetWithoutPathExpansion(file_entry.id, file_entry_dict);
@@ -118,25 +122,30 @@ std::vector<SavedFileEntry> GetSavedFileEntries(
base::FilePath file_path;
if (!GetValueAsFilePath(*path_value, &file_path))
continue;
+ bool is_directory = false;
+ file_entry->GetBoolean(kFileEntryIsDirectory, &is_directory);
int sequence_number = 0;
if (!file_entry->GetInteger(kFileEntrySequenceNumber, &sequence_number))
continue;
if (!sequence_number)
continue;
- result.push_back(SavedFileEntry(it.key(), file_path, sequence_number));
+ result.push_back(
+ SavedFileEntry(it.key(), file_path, is_directory, sequence_number));
}
return result;
}
} // namespace
-SavedFileEntry::SavedFileEntry() : sequence_number(0) {}
+SavedFileEntry::SavedFileEntry() : is_directory(false), sequence_number(0) {}
SavedFileEntry::SavedFileEntry(const std::string& id,
const base::FilePath& path,
+ bool is_directory,
int sequence_number)
: id(id),
path(path),
+ is_directory(is_directory),
sequence_number(sequence_number) {}
class SavedFilesService::SavedFiles {
@@ -145,7 +154,8 @@ class SavedFilesService::SavedFiles {
~SavedFiles();
void RegisterFileEntry(const std::string& id,
- const base::FilePath& file_path);
+ const base::FilePath& file_path,
+ bool is_directory);
void EnqueueFileEntry(const std::string& id);
bool IsRegistered(const std::string& id) const;
const SavedFileEntry* GetFileEntry(const std::string& id) const;
@@ -219,8 +229,9 @@ void SavedFilesService::Observe(int type,
void SavedFilesService::RegisterFileEntry(const std::string& extension_id,
const std::string& id,
- const base::FilePath& file_path) {
- GetOrInsert(extension_id)->RegisterFileEntry(id, file_path);
+ const base::FilePath& file_path,
+ bool is_directory) {
+ GetOrInsert(extension_id)->RegisterFileEntry(id, file_path, is_directory);
}
void SavedFilesService::EnqueueFileEntry(const std::string& extension_id,
@@ -303,12 +314,13 @@ SavedFilesService::SavedFiles::~SavedFiles() {}
void SavedFilesService::SavedFiles::RegisterFileEntry(
const std::string& id,
- const base::FilePath& file_path) {
+ const base::FilePath& file_path,
+ bool is_directory) {
if (ContainsKey(registered_file_entries_, id))
return;
registered_file_entries_.insert(
- std::make_pair(id, new SavedFileEntry(id, file_path, 0)));
+ std::make_pair(id, new SavedFileEntry(id, file_path, is_directory, 0)));
}
void SavedFilesService::SavedFiles::EnqueueFileEntry(const std::string& id) {
diff --git a/apps/saved_files_service.h b/apps/saved_files_service.h
index 25b96d7..4c86a0c 100644
--- a/apps/saved_files_service.h
+++ b/apps/saved_files_service.h
@@ -37,6 +37,7 @@ struct SavedFileEntry {
SavedFileEntry(const std::string& id,
const base::FilePath& path,
+ bool is_directory,
int sequence_number);
// The opaque id of this file entry.
@@ -45,6 +46,9 @@ struct SavedFileEntry {
// The path to a file entry that the app had permission to access.
base::FilePath path;
+ // Whether or not the entry refers to a directory.
+ bool is_directory;
+
// The sequence number in the LRU of the file entry. The value 0 indicates
// that the entry is not in the LRU.
int sequence_number;
@@ -65,7 +69,8 @@ class SavedFilesService : public BrowserContextKeyedService,
// object construction are automatically registered.
void RegisterFileEntry(const std::string& extension_id,
const std::string& id,
- const base::FilePath& file_path);
+ const base::FilePath& file_path,
+ bool is_directory);
// If the file with |id| is not in the queue of files to be retained
// permanently, adds the file to the back of the queue, evicting the least
diff --git a/apps/saved_files_service_unittest.cc b/apps/saved_files_service_unittest.cc
index 336049b..313c97c 100644
--- a/apps/saved_files_service_unittest.cc
+++ b/apps/saved_files_service_unittest.cc
@@ -71,6 +71,7 @@ class SavedFilesServiceUnitTest : public testing::Test {
ASSERT_TRUE(entry);
EXPECT_EQ(id_string, entry->id);
EXPECT_EQ(path_, entry->path);
+ EXPECT_TRUE(entry->is_directory);
EXPECT_EQ(sequence_number, entry->sequence_number);
}
@@ -89,9 +90,9 @@ class SavedFilesServiceUnitTest : public testing::Test {
};
TEST_F(SavedFilesServiceUnitTest, RetainTwoFilesTest) {
- service_->RegisterFileEntry(extension_->id(), GenerateId(1), path_);
- service_->RegisterFileEntry(extension_->id(), GenerateId(2), path_);
- service_->RegisterFileEntry(extension_->id(), GenerateId(3), path_);
+ service_->RegisterFileEntry(extension_->id(), GenerateId(1), path_, true);
+ service_->RegisterFileEntry(extension_->id(), GenerateId(2), path_, true);
+ service_->RegisterFileEntry(extension_->id(), GenerateId(3), path_, true);
// Test that no entry has a sequence number.
TRACE_CALL(CheckEntrySequenceNumber(1, 0));
@@ -151,7 +152,7 @@ TEST_F(SavedFilesServiceUnitTest, NoRetainEntriesPermissionTest) {
extension_ = env_.MakeExtension(*base::test::ParseJson(
"{\"app\": {\"background\": {\"scripts\": [\"background.js\"]}},"
"\"permissions\": [\"fileSystem\"]}"));
- service_->RegisterFileEntry(extension_->id(), GenerateId(1), path_);
+ service_->RegisterFileEntry(extension_->id(), GenerateId(1), path_, true);
TRACE_CALL(CheckEntrySequenceNumber(1, 0));
SavedFileEntry entry;
service_->EnqueueFileEntry(extension_->id(), GenerateId(1));
@@ -170,10 +171,10 @@ TEST_F(SavedFilesServiceUnitTest, NoRetainEntriesPermissionTest) {
TEST_F(SavedFilesServiceUnitTest, EvictionTest) {
SavedFilesService::SetLruSizeForTest(10);
for (int i = 0; i < 10; i++) {
- service_->RegisterFileEntry(extension_->id(), GenerateId(i), path_);
+ service_->RegisterFileEntry(extension_->id(), GenerateId(i), path_, true);
service_->EnqueueFileEntry(extension_->id(), GenerateId(i));
}
- service_->RegisterFileEntry(extension_->id(), GenerateId(10), path_);
+ service_->RegisterFileEntry(extension_->id(), GenerateId(10), path_, true);
// Expect that entries 0 to 9 are in the queue, but 10 is not.
TRACE_CALL(CheckRangeEnqueuedInOrder(0, 10));
@@ -208,7 +209,7 @@ TEST_F(SavedFilesServiceUnitTest, SequenceNumberCompactionTest) {
SavedFilesService::SetMaxSequenceNumberForTest(8);
SavedFilesService::SetLruSizeForTest(8);
for (int i = 0; i < 4; i++) {
- service_->RegisterFileEntry(extension_->id(), GenerateId(i), path_);
+ service_->RegisterFileEntry(extension_->id(), GenerateId(i), path_, true);
service_->EnqueueFileEntry(extension_->id(), GenerateId(i));
}
service_->EnqueueFileEntry(extension_->id(), GenerateId(2));
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 8c5c691..a456dc2 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -4150,6 +4150,9 @@ Make sure you do not expose any sensitive information.
<message name="IDS_EXTENSION_PROMPT_WARNING_DOWNLOADS_OPEN" desc="Permission string for access to downloads.">
Open downloaded files
</message>
+ <message name="IDS_EXTENSION_PROMPT_WARNING_FILE_SYSTEM_DIRECTORY" desc="Permission string for access to directories and their contents.">
+ Access folders that you open, and all files and folders those folders contain.
+ </message>
<message name="IDS_EXTENSION_PROMPT_WARNING_FILE_SYSTEM_WRITE" desc="Permission string for write access to the file system.">
Write to files that you have opened in the application
</message>
diff --git a/chrome/browser/extensions/api/file_handlers/app_file_handler_util.cc b/chrome/browser/extensions/api/file_handlers/app_file_handler_util.cc
index 64c8172..81648f0 100644
--- a/chrome/browser/extensions/api/file_handlers/app_file_handler_util.cc
+++ b/chrome/browser/extensions/api/file_handlers/app_file_handler_util.cc
@@ -59,11 +59,14 @@ bool FileHandlerCanHandleFileWithMimeType(
return false;
}
-bool DoCheckWritableFile(const base::FilePath& path) {
+bool DoCheckWritableFile(const base::FilePath& path, bool is_directory) {
// Don't allow links.
if (base::PathExists(path) && file_util::IsLink(path))
return false;
+ if (is_directory)
+ return base::DirectoryExists(path);
+
// Create the file if it doesn't already exist.
base::PlatformFileError error = base::PLATFORM_FILE_OK;
int creation_flags = base::PLATFORM_FILE_CREATE |
@@ -92,6 +95,7 @@ class WritableFileChecker
WritableFileChecker(
const std::vector<base::FilePath>& paths,
Profile* profile,
+ bool is_directory,
const base::Closure& on_success,
const base::Callback<void(const base::FilePath&)>& on_failure);
@@ -119,6 +123,7 @@ class WritableFileChecker
const std::vector<base::FilePath> paths_;
Profile* profile_;
+ const bool is_directory_;
int outstanding_tasks_;
base::FilePath error_path_;
base::Closure on_success_;
@@ -128,10 +133,12 @@ class WritableFileChecker
WritableFileChecker::WritableFileChecker(
const std::vector<base::FilePath>& paths,
Profile* profile,
+ bool is_directory,
const base::Closure& on_success,
const base::Callback<void(const base::FilePath&)>& on_failure)
: paths_(paths),
profile_(profile),
+ is_directory_(is_directory),
outstanding_tasks_(1),
on_success_(on_success),
on_failure_(on_failure) {}
@@ -184,7 +191,7 @@ void WritableFileChecker::CheckLocalWritableFiles() {
for (std::vector<base::FilePath>::const_iterator it = paths_.begin();
it != paths_.end();
++it) {
- if (!DoCheckWritableFile(*it)) {
+ if (!DoCheckWritableFile(*it, is_directory_)) {
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
@@ -290,7 +297,8 @@ GrantedFileEntry CreateFileEntry(
Profile* profile,
const Extension* extension,
int renderer_id,
- const base::FilePath& path) {
+ const base::FilePath& path,
+ bool is_directory) {
GrantedFileEntry result;
fileapi::IsolatedContext* isolated_context =
fileapi::IsolatedContext::GetInstance();
@@ -303,8 +311,11 @@ GrantedFileEntry CreateFileEntry(
content::ChildProcessSecurityPolicy* policy =
content::ChildProcessSecurityPolicy::GetInstance();
policy->GrantReadFileSystem(renderer_id, result.filesystem_id);
- if (HasFileSystemWritePermission(extension))
+ if (HasFileSystemWritePermission(extension)) {
policy->GrantWriteFileSystem(renderer_id, result.filesystem_id);
+ if (is_directory)
+ policy->GrantCreateFileForFileSystem(renderer_id, result.filesystem_id);
+ }
result.id = result.filesystem_id + ":" + result.registered_name;
return result;
@@ -313,13 +324,16 @@ GrantedFileEntry CreateFileEntry(
void CheckWritableFiles(
const std::vector<base::FilePath>& paths,
Profile* profile,
+ bool is_directory,
const base::Closure& on_success,
const base::Callback<void(const base::FilePath&)>& on_failure) {
- scoped_refptr<WritableFileChecker> checker(
- new WritableFileChecker(paths, profile, on_success, on_failure));
+ scoped_refptr<WritableFileChecker> checker(new WritableFileChecker(
+ paths, profile, is_directory, on_success, on_failure));
checker->Check();
}
+GrantedFileEntry::GrantedFileEntry() {}
+
bool HasFileSystemWritePermission(const Extension* extension) {
return extension->HasAPIPermission(APIPermission::kFileSystemWrite);
}
diff --git a/chrome/browser/extensions/api/file_handlers/app_file_handler_util.h b/chrome/browser/extensions/api/file_handlers/app_file_handler_util.h
index a006e84..a2ab82b 100644
--- a/chrome/browser/extensions/api/file_handlers/app_file_handler_util.h
+++ b/chrome/browser/extensions/api/file_handlers/app_file_handler_util.h
@@ -49,6 +49,8 @@ bool FileHandlerCanHandleFile(
// Refers to a file entry that a renderer has been given access to.
struct GrantedFileEntry {
+ GrantedFileEntry();
+
std::string id;
std::string filesystem_id;
std::string registered_name;
@@ -60,11 +62,13 @@ GrantedFileEntry CreateFileEntry(
Profile* profile,
const Extension* extension,
int renderer_id,
- const base::FilePath& path);
+ const base::FilePath& path,
+ bool is_directory);
void CheckWritableFiles(
const std::vector<base::FilePath>& paths,
Profile* profile,
+ bool is_directory,
const base::Closure& on_success,
const base::Callback<void(const base::FilePath&)>& on_failure);
diff --git a/chrome/browser/extensions/api/file_system/file_system_api.cc b/chrome/browser/extensions/api/file_system/file_system_api.cc
index 43c69a1..4e3de96 100644
--- a/chrome/browser/extensions/api/file_system/file_system_api.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_api.cc
@@ -60,6 +60,8 @@ const char kUserCancelled[] = "User cancelled";
const char kWritableFileErrorFormat[] = "Error opening %s";
const char kRequiresFileSystemWriteError[] =
"Operation requires fileSystem.write permission";
+const char kRequiresFileSystemDirectoryError[] =
+ "Operation requires fileSystem.directory permission";
const char kMultipleUnsupportedError[] =
"acceptsMultiple: true is not supported for 'saveFile'";
const char kUnknownIdError[] = "Unknown id";
@@ -304,6 +306,7 @@ bool FileSystemGetDisplayPathFunction::RunImpl() {
FileSystemEntryFunction::FileSystemEntryFunction()
: multiple_(false),
+ is_directory_(false),
response_(NULL) {}
void FileSystemEntryFunction::CheckWritableFiles(
@@ -312,6 +315,7 @@ void FileSystemEntryFunction::CheckWritableFiles(
app_file_handler_util::CheckWritableFiles(
paths,
profile_,
+ is_directory_,
base::Bind(&FileSystemEntryFunction::RegisterFileSystemsAndSendResponse,
this,
paths),
@@ -348,7 +352,8 @@ void FileSystemEntryFunction::AddEntryToResponse(
profile(),
GetExtension(),
render_view_host_->GetProcess()->GetID(),
- path);
+ path,
+ is_directory_);
base::ListValue* entries;
bool success = response_->GetList("entries", &entries);
DCHECK(success);
@@ -360,6 +365,7 @@ void FileSystemEntryFunction::AddEntryToResponse(
entry->SetString("id", file_entry.id);
else
entry->SetString("id", id_override);
+ entry->SetBoolean("isDirectory", is_directory_);
entries->Append(entry);
}
@@ -382,15 +388,39 @@ bool FileSystemGetWritableEntryFunction::RunImpl() {
return false;
}
- base::FilePath path;
if (!ValidateFileEntryAndGetPath(filesystem_name, filesystem_path,
- render_view_host_, &path, &error_))
+ render_view_host_, &path_, &error_))
return false;
+ content::BrowserThread::PostTaskAndReply(
+ content::BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(
+ &FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread,
+ this),
+ base::Bind(
+ &FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse,
+ this));
+ return true;
+}
+
+void FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse() {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (is_directory_ &&
+ !extension_->HasAPIPermission(APIPermission::kFileSystemDirectory)) {
+ error_ = kRequiresFileSystemDirectoryError;
+ SendResponse(false);
+ }
std::vector<base::FilePath> paths;
- paths.push_back(path);
+ paths.push_back(path_);
CheckWritableFiles(paths);
- return true;
+}
+
+void FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread() {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
+ if (base::DirectoryExists(path_)) {
+ is_directory_ = true;
+ }
}
bool FileSystemIsWritableEntryFunction::RunImpl() {
@@ -627,8 +657,15 @@ void FileSystemChooseEntryFunction::SetInitialPathOnFileThread(
void FileSystemChooseEntryFunction::FilesSelected(
const std::vector<base::FilePath>& paths) {
DCHECK(!paths.empty());
- file_system_api::SetLastChooseEntryDirectory(
- ExtensionPrefs::Get(profile()), GetExtension()->id(), paths[0].DirName());
+ base::FilePath last_choose_directory;
+ if (is_directory_) {
+ last_choose_directory = paths[0];
+ } else {
+ last_choose_directory = paths[0].DirName();
+ }
+ file_system_api::SetLastChooseEntryDirectory(ExtensionPrefs::Get(profile()),
+ GetExtension()->id(),
+ last_choose_directory);
if (app_file_handler_util::HasFileSystemWritePermission(extension_)) {
CheckWritableFiles(paths);
return;
@@ -732,6 +769,17 @@ bool FileSystemChooseEntryFunction::RunImpl() {
return false;
}
picker_type = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
+ } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENDIRECTORY) {
+ is_directory_ = true;
+ if (!extension_->HasAPIPermission(APIPermission::kFileSystemDirectory)) {
+ error_ = kRequiresFileSystemDirectoryError;
+ return false;
+ }
+ if (multiple_) {
+ error_ = kMultipleUnsupportedError;
+ return false;
+ }
+ picker_type = ui::SelectFileDialog::SELECT_FOLDER;
}
base::FilePath::StringType suggested_extension;
@@ -767,32 +815,47 @@ bool FileSystemRetainEntryFunction::RunImpl() {
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
SavedFilesService* saved_files_service = SavedFilesService::Get(profile());
// Add the file to the retain list if it is not already on there.
- if (!saved_files_service->IsRegistered(extension_->id(), entry_id) &&
- !RetainFileEntry(entry_id)) {
- return false;
+ if (!saved_files_service->IsRegistered(extension_->id(), entry_id)) {
+ std::string filesystem_name;
+ std::string filesystem_path;
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_name));
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &filesystem_path));
+ if (!ValidateFileEntryAndGetPath(filesystem_name,
+ filesystem_path,
+ render_view_host_,
+ &path_,
+ &error_)) {
+ return false;
+ }
+
+ content::BrowserThread::PostTaskAndReply(
+ content::BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&FileSystemRetainEntryFunction::SetIsDirectoryOnFileThread,
+ this),
+ base::Bind(
+ &FileSystemRetainEntryFunction::RetainFileEntry, this, entry_id));
+ return true;
}
+
saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
+ SendResponse(true);
return true;
}
-bool FileSystemRetainEntryFunction::RetainFileEntry(
+void FileSystemRetainEntryFunction::RetainFileEntry(
const std::string& entry_id) {
- std::string filesystem_name;
- std::string filesystem_path;
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_name));
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &filesystem_path));
- base::FilePath path;
- if (!ValidateFileEntryAndGetPath(filesystem_name,
- filesystem_path,
- render_view_host_,
- &path,
- &error_)) {
- return false;
- }
+ SavedFilesService* saved_files_service = SavedFilesService::Get(profile());
+ saved_files_service->RegisterFileEntry(
+ extension_->id(), entry_id, path_, is_directory_);
+ saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
+ SendResponse(true);
+}
- SavedFilesService::Get(profile())->RegisterFileEntry(
- extension_->id(), entry_id, path);
- return true;
+void FileSystemRetainEntryFunction::SetIsDirectoryOnFileThread() {
+ if (base::DirectoryExists(path_)) {
+ is_directory_ = true;
+ }
}
bool FileSystemIsRestorableFunction::RunImpl() {
@@ -822,6 +885,7 @@ bool FileSystemRestoreEntryFunction::RunImpl() {
// |needs_new_entry| will be false if the renderer already has an Entry for
// |entry_id|.
if (needs_new_entry) {
+ is_directory_ = file_entry->is_directory;
CreateResponse();
AddEntryToResponse(file_entry->path, file_entry->id);
}
diff --git a/chrome/browser/extensions/api/file_system/file_system_api.h b/chrome/browser/extensions/api/file_system/file_system_api.h
index f44d8c6..2a65fa5 100644
--- a/chrome/browser/extensions/api/file_system/file_system_api.h
+++ b/chrome/browser/extensions/api/file_system/file_system_api.h
@@ -74,6 +74,9 @@ class FileSystemEntryFunction : public AsyncExtensionFunction {
// Whether multiple entries have been requested.
bool multiple_;
+ // Whether a directory has been requested.
+ bool is_directory_;
+
// The dictionary to send as the response.
base::DictionaryValue* response_;
};
@@ -86,6 +89,13 @@ class FileSystemGetWritableEntryFunction : public FileSystemEntryFunction {
protected:
virtual ~FileSystemGetWritableEntryFunction() {}
virtual bool RunImpl() OVERRIDE;
+
+ private:
+ void CheckPermissionAndSendResponse();
+ void SetIsDirectoryOnFileThread();
+
+ // The path to the file for which a writable entry has been requested.
+ base::FilePath path_;
};
class FileSystemIsWritableEntryFunction : public SyncExtensionFunction {
@@ -146,7 +156,7 @@ class FileSystemChooseEntryFunction : public FileSystemEntryFunction {
base::FilePath initial_path_;
};
-class FileSystemRetainEntryFunction : public SyncExtensionFunction {
+class FileSystemRetainEntryFunction : public AsyncExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("fileSystem.retainEntry", FILESYSTEM_RETAINENTRY)
@@ -157,7 +167,15 @@ class FileSystemRetainEntryFunction : public SyncExtensionFunction {
private:
// Retains the file entry referenced by |entry_id| in apps::SavedFilesService.
// |entry_id| must refer to an entry in an isolated file system.
- bool RetainFileEntry(const std::string& entry_id);
+ void RetainFileEntry(const std::string& entry_id);
+
+ void SetIsDirectoryOnFileThread();
+
+ // Whether the file being retained is a directory.
+ bool is_directory_;
+
+ // The path to the file to retain.
+ base::FilePath path_;
};
class FileSystemIsRestorableFunction : public SyncExtensionFunction {
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest.cc b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
index 08570df..b26f530 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
@@ -11,6 +11,7 @@
#include "chrome/browser/extensions/api/file_system/file_system_api.h"
#include "chrome/browser/extensions/extension_prefs.h"
#include "chrome/common/chrome_paths.h"
+#include "chrome/common/extensions/features/feature_channel.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_service.h"
@@ -56,9 +57,11 @@ void SetLastChooseEntryDirectoryToAppDirectory(
}
void AddSavedEntry(const base::FilePath& path_to_save,
+ bool is_directory,
apps::SavedFilesService* service,
const Extension* extension) {
- service->RegisterFileEntry(extension->id(), "magic id", path_to_save);
+ service->RegisterFileEntry(
+ extension->id(), "magic id", path_to_save, is_directory);
}
} // namespace
@@ -274,6 +277,58 @@ IN_PROC_BROWSER_TEST_F(FileSystemApiTest,
<< message_;
}
+IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiOpenDirectoryTest) {
+ ScopedCurrentChannel channel(chrome::VersionInfo::CHANNEL_UNKNOWN);
+ base::FilePath test_file = TempFilePath("open_existing.txt", true);
+ ASSERT_FALSE(test_file.empty());
+ base::FilePath test_directory = test_file.DirName();
+ FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
+ &test_directory);
+ ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/open_directory"))
+ << message_;
+ CheckStoredDirectoryMatches(test_file);
+}
+
+IN_PROC_BROWSER_TEST_F(FileSystemApiTest,
+ FileSystemApiOpenDirectoryWithWriteTest) {
+ ScopedCurrentChannel channel(chrome::VersionInfo::CHANNEL_UNKNOWN);
+ base::FilePath test_file = TempFilePath("open_existing.txt", true);
+ ASSERT_FALSE(test_file.empty());
+ base::FilePath test_directory = test_file.DirName();
+ FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
+ &test_directory);
+ ASSERT_TRUE(
+ RunPlatformAppTest("api_test/file_system/open_directory_with_write"))
+ << message_;
+ CheckStoredDirectoryMatches(test_file);
+}
+
+IN_PROC_BROWSER_TEST_F(FileSystemApiTest,
+ FileSystemApiOpenDirectoryWithoutPermissionTest) {
+ base::FilePath test_file = TempFilePath("open_existing.txt", true);
+ ASSERT_FALSE(test_file.empty());
+ base::FilePath test_directory = test_file.DirName();
+ FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
+ &test_directory);
+ ASSERT_TRUE(RunPlatformAppTest(
+ "api_test/file_system/open_directory_without_permission"))
+ << message_;
+ CheckStoredDirectoryMatches(base::FilePath());
+}
+
+IN_PROC_BROWSER_TEST_F(FileSystemApiTest,
+ FileSystemApiOpenDirectoryWithOnlyWritePermissionTest) {
+ base::FilePath test_file = TempFilePath("open_existing.txt", true);
+ ASSERT_FALSE(test_file.empty());
+ base::FilePath test_directory = test_file.DirName();
+ FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
+ &test_directory);
+ ASSERT_TRUE(RunPlatformAppTest(
+ "api_test/file_system/open_directory_with_only_write"))
+ << message_;
+ CheckStoredDirectoryMatches(base::FilePath());
+}
+
IN_PROC_BROWSER_TEST_F(FileSystemApiTest,
FileSystemApiInvalidChooseEntryTypeTest) {
base::FilePath test_file = TempFilePath("open_existing.txt", true);
@@ -468,17 +523,51 @@ IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiRetainEntry) {
EXPECT_EQ(1, file_entries[0].sequence_number);
}
+IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiRetainDirectoryEntry) {
+ ScopedCurrentChannel channel(chrome::VersionInfo::CHANNEL_UNKNOWN);
+ base::FilePath test_file = TempFilePath("open_existing.txt", true);
+ ASSERT_FALSE(test_file.empty());
+ base::FilePath test_directory = test_file.DirName();
+ FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
+ &test_directory);
+ ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/retain_directory"))
+ << message_;
+ std::vector<apps::SavedFileEntry> file_entries = apps::SavedFilesService::Get(
+ profile())->GetAllFileEntries(GetSingleLoadedExtension()->id());
+ ASSERT_EQ(1u, file_entries.size());
+ EXPECT_EQ(test_directory, file_entries[0].path);
+ EXPECT_EQ(1, file_entries[0].sequence_number);
+ EXPECT_TRUE(file_entries[0].is_directory);
+}
+
IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiRestoreEntry) {
base::FilePath test_file = TempFilePath("writable.txt", true);
ASSERT_FALSE(test_file.empty());
FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
&test_file);
- {
- AppInstallObserver observer(base::Bind(
- AddSavedEntry, test_file, apps::SavedFilesService::Get(profile())));
- ASSERT_TRUE(RunPlatformAppTest(
- "api_test/file_system/restore_entry")) << message_;
- }
+ AppInstallObserver observer(
+ base::Bind(AddSavedEntry,
+ test_file,
+ false,
+ apps::SavedFilesService::Get(profile())));
+ ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/restore_entry"))
+ << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiRestoreDirectoryEntry) {
+ ScopedCurrentChannel channel(chrome::VersionInfo::CHANNEL_UNKNOWN);
+ base::FilePath test_file = TempFilePath("writable.txt", true);
+ ASSERT_FALSE(test_file.empty());
+ base::FilePath test_directory = test_file.DirName();
+ FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
+ &test_file);
+ AppInstallObserver observer(
+ base::Bind(AddSavedEntry,
+ test_directory,
+ true,
+ apps::SavedFilesService::Get(profile())));
+ ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/restore_directory"))
+ << message_;
}
} // namespace extensions
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json
index ed458ad..af16181 100644
--- a/chrome/common/extensions/api/_permission_features.json
+++ b/chrome/common/extensions/api/_permission_features.json
@@ -261,6 +261,10 @@
"extension_types": ["extension"],
"whitelist": [ "2FC374607C2DF285634B67C64A2E356C607091C3" ]
}],
+ "fileSystem.directory": [{
+ "channel": "trunk",
+ "extension_types": ["platform_app"]
+ }],
"fileSystem.retainEntries": [{
"channel": "dev",
"extension_types": ["platform_app"]
diff --git a/chrome/common/extensions/api/file_system.idl b/chrome/common/extensions/api/file_system.idl
index 3ddec88..7e3d6d6 100644
--- a/chrome/common/extensions/api/file_system.idl
+++ b/chrome/common/extensions/api/file_system.idl
@@ -38,7 +38,14 @@ namespace fileSystem {
// Prompts the user to open an existing file or a new file and returns a
// writable FileEntry on success. Calls using this type will fail unless the
// application has the 'write' permission under 'fileSystem'.
- saveFile
+ saveFile,
+
+ // Prompts the user to open a directory and returns a DirectoryEntry on
+ // success. Calls using this type will fail unless the application has the
+ // 'directory' permission under 'fileSystem'. If the application has the
+ // 'write' permission under 'fileSystem', the returned DirectoryEntry will
+ // be writable; otherwise it will be read-only. New in Chrome 31.
+ openDirectory
};
dictionary ChooseEntryOptions {
@@ -68,36 +75,38 @@ namespace fileSystem {
boolean? acceptsMultiple;
};
callback GetDisplayPathCallback = void (DOMString displayPath);
- callback FileEntryCallback = void ([instanceOf=FileEntry] object fileEntry);
- callback FileEntriesCallback = void (
- [instanceOf=FileEntry] optional object fileEntry,
+ callback EntryCallback = void ([instanceOf=Entry] object entry);
+ callback EntriesCallback = void (
+ [instanceOf=Entry] optional object entry,
[instanceOf=FileEntry] optional object[] fileEntries);
callback IsWritableCallback = void (boolean isWritable);
callback IsRestorableCallback = void (boolean isRestorable);
interface Functions {
- // Get the display path of a FileEntry object. The display path is based on
- // the full path of the file on the local file system, but may be made more
- // readable for display purposes.
- static void getDisplayPath([instanceOf=FileEntry] object fileEntry,
+ // Get the display path of an Entry object. The display path is based on
+ // the full path of the file or directory on the local file system, but may
+ // be made more readable for display purposes.
+ static void getDisplayPath([instanceOf=Entry] object entry,
GetDisplayPathCallback callback);
- // Get a writable FileEntry from another FileEntry. This call will fail if
- // the application does not have the 'write' permission under 'fileSystem'.
- static void getWritableEntry([instanceOf=FileEntry] object fileEntry,
- FileEntryCallback callback);
+ // Get a writable Entry from another Entry. This call will fail if the
+ // application does not have the 'write' permission under 'fileSystem'. If
+ // entry is a DirectoryEntry, this call will fail if the application does
+ // not have the 'directory' permission under 'fileSystem'.
+ static void getWritableEntry([instanceOf=Entry] object entry,
+ EntryCallback callback);
- // Gets whether this FileEntry is writable or not.
- static void isWritableEntry([instanceOf=FileEntry] object fileEntry,
+ // Gets whether this Entry is writable or not.
+ static void isWritableEntry([instanceOf=Entry] object entry,
IsWritableCallback callback);
- // Ask the user to choose a file.
+ // Ask the user to choose a file or directory.
static void chooseEntry(optional ChooseEntryOptions options,
- FileEntriesCallback callback);
+ EntriesCallback callback);
// Returns the file entry with the given id if it can be restored. This call
// will fail otherwise. This method is new in Chrome 30.
- static void restoreEntry(DOMString id, FileEntryCallback callback);
+ static void restoreEntry(DOMString id, EntryCallback callback);
// Returns whether a file entry for the given id can be restored, i.e.
// whether restoreEntry would succeed with this id now. This method is new
@@ -111,6 +120,6 @@ namespace fileSystem {
// to dev channel), entries are retained indefinitely. Otherwise, entries
// are retained only while the app is running and across restarts. This
// method is new in Chrome 30.
- static DOMString retainEntry([instanceOf=FileEntry] object fileEntry);
+ static DOMString retainEntry([instanceOf=Entry] object entry);
};
};
diff --git a/chrome/common/extensions/permissions/api_permission.h b/chrome/common/extensions/permissions/api_permission.h
index a92cab9..3bae6c8 100644
--- a/chrome/common/extensions/permissions/api_permission.h
+++ b/chrome/common/extensions/permissions/api_permission.h
@@ -79,6 +79,7 @@ class APIPermission {
kFileBrowserHandlerInternal,
kFileBrowserPrivate,
kFileSystem,
+ kFileSystemDirectory,
kFileSystemRetainEntries,
kFileSystemWrite,
kFontSettings,
diff --git a/chrome/common/extensions/permissions/chrome_api_permissions.cc b/chrome/common/extensions/permissions/chrome_api_permissions.cc
index 53cf6b2..9b9347e 100644
--- a/chrome/common/extensions/permissions/chrome_api_permissions.cc
+++ b/chrome/common/extensions/permissions/chrome_api_permissions.cc
@@ -268,11 +268,16 @@ std::vector<APIPermissionInfo*> ChromeAPIPermissions::GetAllPermissions()
APIPermissionInfo::kFlagNone,
IDS_EXTENSION_PROMPT_WARNING_VIDEO_CAPTURE,
PermissionMessage::kVideoCapture },
- // The permission string for "fileSystem" is only shown when "write" is
- // present. Read-only access is only granted after the user has been shown
- // a file chooser dialog and selected a file. Selecting the file is
- // considered consent to read it.
+ // The permission string for "fileSystem" is only shown when "write" or
+ // "directory" is present. Read-only access is only granted after the user
+ // has been shown a file or directory chooser dialog and selected a file or
+ // directory . Selecting the file or directory is considered consent to
+ // read it.
{ APIPermission::kFileSystem, "fileSystem" },
+ { APIPermission::kFileSystemDirectory, "fileSystem.directory",
+ APIPermissionInfo::kFlagNone,
+ IDS_EXTENSION_PROMPT_WARNING_FILE_SYSTEM_DIRECTORY,
+ PermissionMessage::kFileSystemDirectory },
{ APIPermission::kFileSystemRetainEntries, "fileSystem.retainEntries" },
{ APIPermission::kFileSystemWrite, "fileSystem.write",
APIPermissionInfo::kFlagNone,
diff --git a/chrome/common/extensions/permissions/permission_message.h b/chrome/common/extensions/permissions/permission_message.h
index dd352863..5118315 100644
--- a/chrome/common/extensions/permissions/permission_message.h
+++ b/chrome/common/extensions/permissions/permission_message.h
@@ -70,6 +70,7 @@ class PermissionMessage {
kDownloadsOpen,
kNetworkingPrivate,
kDeclarativeWebRequest,
+ kFileSystemDirectory,
kEnumBoundary,
};
COMPILE_ASSERT(PermissionMessage::kNone > PermissionMessage::kUnknown,
diff --git a/chrome/renderer/resources/extensions/file_system_custom_bindings.js b/chrome/renderer/resources/extensions/file_system_custom_bindings.js
index 921ff23..6fdf89d 100644
--- a/chrome/renderer/resources/extensions/file_system_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/file_system_custom_bindings.js
@@ -33,6 +33,17 @@ if (window == backgroundPage) {
var entries = [];
var hasError = false;
+ var getEntryError = function(fileError) {
+ if (!hasError) {
+ hasError = true;
+ lastError.run(
+ 'fileSystem.' + functionName,
+ 'Error getting fileEntry, code: ' + fileError.code,
+ request.stack,
+ callback);
+ }
+ }
+
// Loop through the response entries and asynchronously get the
// FileEntry for each. We use hasError to ensure that only the first
// error is reported. Note that an error can occur either during the
@@ -46,10 +57,7 @@ if (window == backgroundPage) {
var fs = GetIsolatedFileSystem(fileSystemId);
try {
- // TODO(koz): fs.root.getFile() makes a trip to the browser process,
- // but it might be possible avoid that by calling
- // WebFrame::createFileEntry().
- fs.root.getFile(baseName, {}, function(fileEntry) {
+ var getEntryCallback = function(fileEntry) {
if (hasError)
return;
entryIdManager.registerEntry(id, fileEntry);
@@ -64,16 +72,16 @@ if (window == backgroundPage) {
callback(entries[0]);
}
}
- }, function(fileError) {
- if (!hasError) {
- hasError = true;
- lastError.run(
- 'fileSystem.' + functionName,
- 'Error getting fileEntry, code: ' + fileError.code,
- request.stack,
- callback);
- }
- });
+ }
+ // TODO(koz): fs.root.getFile() makes a trip to the browser process,
+ // but it might be possible avoid that by calling
+ // WebFrame::createFileEntry().
+ if (entry.isDirectory) {
+ fs.root.getDirectory(baseName, {}, getEntryCallback,
+ getEntryError);
+ } else {
+ fs.root.getFile(baseName, {}, getEntryCallback, getEntryError);
+ }
} catch (e) {
if (!hasError) {
hasError = true;
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory/background.js b/chrome/test/data/extensions/api_test/file_system/open_directory/background.js
new file mode 100644
index 0000000..e63978c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory/background.js
@@ -0,0 +1,7 @@
+// Copyright 2013 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.
+
+chrome.app.runtime.onLaunched.addListener(function () {
+ chrome.app.window.create('test.html');
+});
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory/manifest.json b/chrome/test/data/extensions/api_test/file_system/open_directory/manifest.json
new file mode 100644
index 0000000..00d7e5a
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory/manifest.json
@@ -0,0 +1,15 @@
+{
+ "name": "chrome.fileSystem open existing directory",
+ "version": "0.1",
+ "description": "Test for chrome.fileSystem.chooseEntry opening existing directory.",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "permissions": [
+ {
+ "fileSystem": ["directory"]
+ }
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory/test.html b/chrome/test/data/extensions/api_test/file_system/open_directory/test.html
new file mode 100644
index 0000000..d89c528
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory/test.html
@@ -0,0 +1,4 @@
+<html>
+<script src="test_util.js"></script>
+<script src="test.js"></script>
+</html>
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory/test.js b/chrome/test/data/extensions/api_test/file_system/open_directory/test.js
new file mode 100644
index 0000000..db4a01e
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory/test.js
@@ -0,0 +1,98 @@
+// Copyright 2013 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.
+
+// Testing with directory access, but no write permission.
+chrome.test.runTests([
+ function openFile() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory'},
+ chrome.test.callbackPass(function(directoryEntry) {
+ directoryEntry.getFile(
+ 'open_existing.txt', {},
+ chrome.test.callback(function(entry) {
+ checkEntry(entry, 'open_existing.txt', false, false);
+ }));
+ }));
+ },
+ function readDirectory() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory'},
+ chrome.test.callbackPass(function(directoryEntry) {
+ var reader = directoryEntry.createReader();
+ reader.readEntries(chrome.test.callback(function(entries) {
+ chrome.test.assertEq(entries.length, 1);
+ var entry = entries[0];
+ checkEntry(entry, 'open_existing.txt', false, false);
+ }));
+ }));
+ },
+ function removeFile() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory'},
+ chrome.test.callbackPass(function(directoryEntry) {
+ directoryEntry.getFile(
+ 'open_existing.txt', {}, chrome.test.callback(function(entry) {
+ entry.remove(chrome.test.callback(function() {
+ chrome.test.fail('Could delete a file without permission');
+ }), chrome.test.callback(function() {
+ chrome.test.succeed();
+ }));
+ }));
+ }));
+ },
+ function copyFile() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory'},
+ chrome.test.callbackPass(function(directoryEntry) {
+ directoryEntry.getFile(
+ 'open_existing.txt', {}, chrome.test.callback(function(entry) {
+ entry.copyTo(
+ directoryEntry, 'copy.txt', chrome.test.callback(function() {
+ chrome.test.fail('Could copy a file without permission.');
+ }), chrome.test.callback(function() {
+ chrome.test.succeed();
+ }));
+ }));
+ }));
+ },
+ function moveFile() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory'},
+ chrome.test.callbackPass(function(directoryEntry) {
+ directoryEntry.getFile(
+ 'open_existing.txt', {}, chrome.test.callback(function(entry) {
+ entry.moveTo(
+ directoryEntry, 'moved.txt', chrome.test.callback(function() {
+ chrome.test.fail('Could move a file without permission.');
+ }), chrome.test.callback(function() {
+ chrome.test.succeed();
+ }));
+ }));
+ }));
+ },
+ function createFile() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory'},
+ chrome.test.callbackPass(function(directoryEntry) {
+ directoryEntry.getFile(
+ 'new.txt', {create: true}, chrome.test.callback(function() {
+ chrome.test.fail('Could create a file without permission.');
+ }), chrome.test.callback(function() {
+ chrome.test.succeed();
+ }));
+ }));
+ },
+ function createDirectory() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory'},
+ chrome.test.callbackPass(function(directoryEntry) {
+ directoryEntry.getDirectory(
+ 'new', {create: true}, chrome.test.callback(function() {
+ chrome.test.fail('Could create a directory without permission.');
+ }), chrome.test.callback(function() {
+ chrome.test.succeed();
+ }));
+ }));
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory/test_util.js b/chrome/test/data/extensions/api_test/file_system/open_directory/test_util.js
new file mode 100644
index 0000000..a31ee51
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory/test_util.js
@@ -0,0 +1,62 @@
+// Copyright 2013 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.
+
+// This is a duplicate of the file test_util in
+// chrome/test/data/extensions/api_test/file_system
+
+function checkEntry(entry, expectedName, isNew, shouldBeWritable) {
+ chrome.test.assertEq(expectedName, entry.name);
+ // Test that the file can be read.
+ entry.file(chrome.test.callback(function(file) {
+ var reader = new FileReader();
+ reader.onloadend = chrome.test.callbackPass(function(e) {
+ if (isNew)
+ chrome.test.assertEq(reader.result, "");
+ else
+ chrome.test.assertEq(reader.result.indexOf("Can you see me?"), 0);
+ // Test that we can write to the file, or not, depending on
+ // |shouldBeWritable|.
+ entry.createWriter(function(fileWriter) {
+ fileWriter.onwriteend = chrome.test.callback(function(e) {
+ if (fileWriter.error) {
+ if (shouldBeWritable) {
+ chrome.test.fail("Error writing to file: " +
+ fileWriter.error.toString());
+ } else {
+ chrome.test.succeed();
+ }
+ } else {
+ if (shouldBeWritable) {
+ // Get a new entry and check the data got to disk.
+ chrome.fileSystem.chooseEntry(chrome.test.callbackPass(
+ function(readEntry) {
+ readEntry.file(chrome.test.callback(function(readFile) {
+ var readReader = new FileReader();
+ readReader.onloadend = function(e) {
+ chrome.test.assertEq(readReader.result.indexOf("HoHoHo!"),
+ 0);
+ chrome.test.succeed();
+ };
+ readReader.onerror = function(e) {
+ chrome.test.fail("Failed to read file after write.");
+ };
+ readReader.readAsText(readFile);
+ }));
+ }));
+ } else {
+ chrome.test.fail(
+ "'Could write to file that should not be writable.");
+ }
+ }
+ });
+ var blob = new Blob(["HoHoHo!"], {type: "text/plain"});
+ fileWriter.write(blob);
+ });
+ });
+ reader.onerror = chrome.test.callback(function(e) {
+ chrome.test.fail("Error reading file contents.");
+ });
+ reader.readAsText(file);
+ }));
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/background.js b/chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/background.js
new file mode 100644
index 0000000..e63978c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/background.js
@@ -0,0 +1,7 @@
+// Copyright 2013 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.
+
+chrome.app.runtime.onLaunched.addListener(function () {
+ chrome.app.window.create('test.html');
+});
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/manifest.json b/chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/manifest.json
new file mode 100644
index 0000000..4fb4662
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/manifest.json
@@ -0,0 +1,13 @@
+{
+ "name": "chrome.fileSystem open directory",
+ "version": "0.1",
+ "description": "Test for chrome.fileSystem.chooseFile opening a directory.",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "permissions": [
+ {"fileSystem": ["write"]}
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/test.html b/chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/test.html
new file mode 100644
index 0000000..8d7d1db
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/test.html
@@ -0,0 +1,3 @@
+<html>
+<script src="test.js"></script>
+</html>
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/test.js b/chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/test.js
new file mode 100644
index 0000000..d3982b3
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory_with_only_write/test.js
@@ -0,0 +1,12 @@
+// Copyright 2013 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.
+
+chrome.test.runTests([
+ function openFile() {
+ chrome.fileSystem.chooseEntry({type: 'openDirectory'},
+ chrome.test.callbackFail(
+ 'Operation requires fileSystem.directory permission',
+ function(entry) {}));
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/background.js b/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/background.js
new file mode 100644
index 0000000..e63978c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/background.js
@@ -0,0 +1,7 @@
+// Copyright 2013 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.
+
+chrome.app.runtime.onLaunched.addListener(function () {
+ chrome.app.window.create('test.html');
+});
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/manifest.json b/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/manifest.json
new file mode 100644
index 0000000..f634f5e
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/manifest.json
@@ -0,0 +1,15 @@
+{
+ "name": "chrome.fileSystem open existing directory",
+ "version": "0.1",
+ "description": "Test for chrome.fileSystem.chooseEntry opening existing directory.",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "permissions": [
+ {
+ "fileSystem": ["directory", "write"]
+ }
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/test.html b/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/test.html
new file mode 100644
index 0000000..d89c528
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/test.html
@@ -0,0 +1,4 @@
+<html>
+<script src="test_util.js"></script>
+<script src="test.js"></script>
+</html>
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/test.js b/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/test.js
new file mode 100644
index 0000000..5375a80
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/test.js
@@ -0,0 +1,99 @@
+// Copyright 2013 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.
+
+// Testing with directory access and write permission.
+chrome.test.runTests([
+ function moveFile() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory'},
+ chrome.test.callbackPass(function(directoryEntry) {
+ directoryEntry.getFile(
+ 'open_existing.txt', {}, chrome.test.callback(function(entry) {
+ entry.copyTo(
+ directoryEntry, 'to_move.txt', chrome.test.callback(function(copy) {
+ copy.moveTo(
+ directoryEntry, 'moved.txt', chrome.test.callback(function(move) {
+ directoryEntry.getFile(
+ 'to_move.txt', {}, chrome.test.callback(function(entry) {
+ chrome.test.fail('Could open move source');
+ }), chrome.test.callback(function() {
+ checkEntry(move, 'moved.txt', false, true);
+ }));
+ }), chrome.test.callback(function() {
+ chrome.test.fail('Failed to move file');
+ }));
+ }), chrome.test.callback(function() {
+ chrome.test.fail('Failed to copy file');
+ }));
+ }), chrome.test.callback(function() {
+ chrome.test.fail('Failed to open file');
+ }));
+ }))
+ },
+ function copyFile() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory'},
+ chrome.test.callbackPass(function(directoryEntry) {
+ directoryEntry.getFile(
+ 'open_existing.txt', {}, chrome.test.callback(function(entry) {
+ entry.copyTo(
+ directoryEntry, 'copy.txt', chrome.test.callback(function(copy) {
+ checkEntry(copy, 'copy.txt', false, true);
+ }), chrome.test.callback(function() {
+ chrome.test.fail('Failed to copy file');
+ }));
+ }), chrome.test.callback(function() {
+ chrome.test.fail('Failed to open file');
+ }));
+ }));
+ },
+ function createFile() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory'},
+ chrome.test.callbackPass(function(directoryEntry) {
+ directoryEntry.getFile(
+ 'new.txt', {create: true}, chrome.test.callback(function(entry) {
+ checkEntry(entry, 'new.txt', true, true);
+ }), chrome.test.callback(function(err) {
+ chrome.test.fail('Failed to create file');
+ }));
+ }));
+ },
+ function createDirectory() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory'},
+ chrome.test.callbackPass(function(directoryEntry) {
+ directoryEntry.getDirectory(
+ 'new', {create: true}, chrome.test.callback(function(entry) {
+ chrome.test.assertEq(entry.name, 'new');
+ chrome.test.succeed();
+ }), chrome.test.callback(function(err) {
+ chrome.test.fail('Failed to create directory');
+ }));
+ }));
+ },
+ function removeFile() {
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory'},
+ chrome.test.callbackPass(function(directoryEntry) {
+ directoryEntry.getFile(
+ 'remove.txt', {create: true},
+ chrome.test.callback(function(entry) {
+ entry.remove(chrome.test.callback(function() {
+ directoryEntry.getFile(
+ 'remove.txt', {},
+ chrome.test.callback(function() {
+ chrome.test.fail('Could open deleted file');
+ }), chrome.test.callback(function() {
+ chrome.test.succeed();
+ }))
+ }), chrome.test.callback(function() {
+ chrome.test.fail('Failed to delete file');
+ }));
+ }), chrome.test.callback(function(err) {
+ chrome.test.fail('Failed to create file');
+ }));
+ }));
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/test_util.js b/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/test_util.js
new file mode 100644
index 0000000..b66226d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory_with_write/test_util.js
@@ -0,0 +1,75 @@
+// Copyright 2013 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.
+
+// This is a modified duplicate of the file test_util in
+// chrome/test/data/extensions/api_test/file_system
+
+// TODO(sammc): Merge the changes back to the original test_util.js. See
+// http://crbug.com/280942.
+function checkEntry(entry, expectedName, isNew, shouldBeWritable) {
+ chrome.test.assertEq(expectedName, entry.name);
+
+ // Test that we are writable (or not), as expected.
+ chrome.fileSystem.isWritableEntry(entry, chrome.test.callbackPass(
+ function(isWritable) {
+ chrome.test.assertEq(isWritable, shouldBeWritable);
+ }));
+
+ // Test that the file can be read.
+ entry.file(chrome.test.callback(function(file) {
+ var reader = new FileReader();
+ reader.onloadend = chrome.test.callbackPass(function(e) {
+ if (isNew)
+ chrome.test.assertEq(reader.result, "");
+ else
+ chrome.test.assertEq(reader.result.indexOf("Can you see me?"), 0);
+ // Test that we can write to the file, or not, depending on
+ // |shouldBeWritable|.
+ entry.createWriter(function(fileWriter) {
+ fileWriter.onwriteend = chrome.test.callback(function(e) {
+ if (fileWriter.error) {
+ if (shouldBeWritable) {
+ chrome.test.fail("Error writing to file: " +
+ fileWriter.error.toString());
+ } else {
+ chrome.test.succeed();
+ }
+ } else {
+ if (shouldBeWritable) {
+ // Get a new entry and check the data got to disk.
+ chrome.fileSystem.chooseEntry(
+ {type: 'openDirectory'}, chrome.test.callbackPass(
+ function(directoryEntry) {
+ directoryEntry.getFile(
+ entry.name, {}, chrome.test.callback(function(readEntry) {
+ readEntry.file(chrome.test.callback(function(readFile) {
+ var readReader = new FileReader();
+ readReader.onloadend = function(e) {
+ chrome.test.assertEq(readReader.result.indexOf("HoHoHo!"),
+ 0);
+ chrome.test.succeed();
+ };
+ readReader.onerror = function(e) {
+ chrome.test.fail("Failed to read file after write.");
+ };
+ readReader.readAsText(readFile);
+ }));
+ }));
+ }));
+ } else {
+ chrome.test.fail(
+ "'Could write to file that should not be writable.");
+ }
+ }
+ });
+ var blob = new Blob(["HoHoHo!"], {type: "text/plain"});
+ fileWriter.write(blob);
+ });
+ });
+ reader.onerror = chrome.test.callback(function(e) {
+ chrome.test.fail("Error reading file contents.");
+ });
+ reader.readAsText(file);
+ }));
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/background.js b/chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/background.js
new file mode 100644
index 0000000..e63978c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/background.js
@@ -0,0 +1,7 @@
+// Copyright 2013 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.
+
+chrome.app.runtime.onLaunched.addListener(function () {
+ chrome.app.window.create('test.html');
+});
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/manifest.json b/chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/manifest.json
new file mode 100644
index 0000000..b8758b5
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/manifest.json
@@ -0,0 +1,13 @@
+{
+ "name": "chrome.fileSystem open directory",
+ "version": "0.1",
+ "description": "Test for chrome.fileSystem.chooseFile opening a directory.",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "permissions": [
+ {"fileSystem": []}
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/test.html b/chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/test.html
new file mode 100644
index 0000000..8d7d1db
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/test.html
@@ -0,0 +1,3 @@
+<html>
+<script src="test.js"></script>
+</html>
diff --git a/chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/test.js b/chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/test.js
new file mode 100644
index 0000000..d3982b3
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/open_directory_without_permission/test.js
@@ -0,0 +1,12 @@
+// Copyright 2013 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.
+
+chrome.test.runTests([
+ function openFile() {
+ chrome.fileSystem.chooseEntry({type: 'openDirectory'},
+ chrome.test.callbackFail(
+ 'Operation requires fileSystem.directory permission',
+ function(entry) {}));
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/file_system/restore_directory/background.js b/chrome/test/data/extensions/api_test/file_system/restore_directory/background.js
new file mode 100644
index 0000000..e63978c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/restore_directory/background.js
@@ -0,0 +1,7 @@
+// Copyright 2013 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.
+
+chrome.app.runtime.onLaunched.addListener(function () {
+ chrome.app.window.create('test.html');
+});
diff --git a/chrome/test/data/extensions/api_test/file_system/restore_directory/manifest.json b/chrome/test/data/extensions/api_test/file_system/restore_directory/manifest.json
new file mode 100644
index 0000000..afa32cd
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/restore_directory/manifest.json
@@ -0,0 +1,11 @@
+{
+ "name": "chrome.fileSystem.restoreEntry test",
+ "version": "0.1",
+ "description": "Test for chrome.fileSystem.restoreEntry",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "permissions": [{"fileSystem": ["write", "directory"]}]
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/restore_directory/test.html b/chrome/test/data/extensions/api_test/file_system/restore_directory/test.html
new file mode 100644
index 0000000..d89c528
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/restore_directory/test.html
@@ -0,0 +1,4 @@
+<html>
+<script src="test_util.js"></script>
+<script src="test.js"></script>
+</html>
diff --git a/chrome/test/data/extensions/api_test/file_system/restore_directory/test.js b/chrome/test/data/extensions/api_test/file_system/restore_directory/test.js
new file mode 100644
index 0000000..4084332
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/restore_directory/test.js
@@ -0,0 +1,25 @@
+// Copyright 2013 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.
+
+chrome.test.runTests([
+ function restoreEntryWorks() {
+ var id = 'magic id';
+ chrome.fileSystem.isRestorable(id, chrome.test.callbackPass(
+ function(isRestorable) {
+ chrome.test.assertTrue(isRestorable);
+ }));
+ chrome.fileSystem.restoreEntry(id, chrome.test.callbackPass(
+ function(restoredEntry) {
+ chrome.test.assertTrue(restoredEntry != null);
+ chrome.test.assertTrue(restoredEntry.isDirectory);
+ chrome.test.assertEq(
+ chrome.fileSystem.retainEntry(restoredEntry), id);
+ restoredEntry.getFile(
+ 'writable.txt', {}, chrome.test.callback(function(entry) {
+ checkEntry(entry, 'writable.txt', false /* isNew */,
+ true /*shouldBeWritable */);
+ }));
+ }));
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/file_system/restore_directory/test_util.js b/chrome/test/data/extensions/api_test/file_system/restore_directory/test_util.js
new file mode 100644
index 0000000..a31ee51
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/restore_directory/test_util.js
@@ -0,0 +1,62 @@
+// Copyright 2013 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.
+
+// This is a duplicate of the file test_util in
+// chrome/test/data/extensions/api_test/file_system
+
+function checkEntry(entry, expectedName, isNew, shouldBeWritable) {
+ chrome.test.assertEq(expectedName, entry.name);
+ // Test that the file can be read.
+ entry.file(chrome.test.callback(function(file) {
+ var reader = new FileReader();
+ reader.onloadend = chrome.test.callbackPass(function(e) {
+ if (isNew)
+ chrome.test.assertEq(reader.result, "");
+ else
+ chrome.test.assertEq(reader.result.indexOf("Can you see me?"), 0);
+ // Test that we can write to the file, or not, depending on
+ // |shouldBeWritable|.
+ entry.createWriter(function(fileWriter) {
+ fileWriter.onwriteend = chrome.test.callback(function(e) {
+ if (fileWriter.error) {
+ if (shouldBeWritable) {
+ chrome.test.fail("Error writing to file: " +
+ fileWriter.error.toString());
+ } else {
+ chrome.test.succeed();
+ }
+ } else {
+ if (shouldBeWritable) {
+ // Get a new entry and check the data got to disk.
+ chrome.fileSystem.chooseEntry(chrome.test.callbackPass(
+ function(readEntry) {
+ readEntry.file(chrome.test.callback(function(readFile) {
+ var readReader = new FileReader();
+ readReader.onloadend = function(e) {
+ chrome.test.assertEq(readReader.result.indexOf("HoHoHo!"),
+ 0);
+ chrome.test.succeed();
+ };
+ readReader.onerror = function(e) {
+ chrome.test.fail("Failed to read file after write.");
+ };
+ readReader.readAsText(readFile);
+ }));
+ }));
+ } else {
+ chrome.test.fail(
+ "'Could write to file that should not be writable.");
+ }
+ }
+ });
+ var blob = new Blob(["HoHoHo!"], {type: "text/plain"});
+ fileWriter.write(blob);
+ });
+ });
+ reader.onerror = chrome.test.callback(function(e) {
+ chrome.test.fail("Error reading file contents.");
+ });
+ reader.readAsText(file);
+ }));
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/retain_directory/background.js b/chrome/test/data/extensions/api_test/file_system/retain_directory/background.js
new file mode 100644
index 0000000..e63978c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/retain_directory/background.js
@@ -0,0 +1,7 @@
+// Copyright 2013 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.
+
+chrome.app.runtime.onLaunched.addListener(function () {
+ chrome.app.window.create('test.html');
+});
diff --git a/chrome/test/data/extensions/api_test/file_system/retain_directory/manifest.json b/chrome/test/data/extensions/api_test/file_system/retain_directory/manifest.json
new file mode 100644
index 0000000..c424ddb
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/retain_directory/manifest.json
@@ -0,0 +1,11 @@
+{
+ "name": "chrome.fileSystem.retainEntry test",
+ "version": "0.1",
+ "description": "Test for chrome.fileSystem.retainEntry",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "permissions": [{"fileSystem": ["directory"]}]
+}
diff --git a/chrome/test/data/extensions/api_test/file_system/retain_directory/test.html b/chrome/test/data/extensions/api_test/file_system/retain_directory/test.html
new file mode 100644
index 0000000..d89c528
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/retain_directory/test.html
@@ -0,0 +1,4 @@
+<html>
+<script src="test_util.js"></script>
+<script src="test.js"></script>
+</html>
diff --git a/chrome/test/data/extensions/api_test/file_system/retain_directory/test.js b/chrome/test/data/extensions/api_test/file_system/retain_directory/test.js
new file mode 100644
index 0000000..03f03ac
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/retain_directory/test.js
@@ -0,0 +1,36 @@
+// Copyright 2013 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.
+
+chrome.test.runTests([
+ function retainEntryWorks() {
+ chrome.app.window.create('test_other_window.html', chrome.test.callbackPass(
+ function(otherWindow) {
+ otherWindow.contentWindow.callback = chrome.test.callbackPass(
+ function(id, entry) {
+ otherWindow.close();
+ chrome.fileSystem.isRestorable(id, chrome.test.callbackPass(
+ function(isRestorable) {
+ chrome.test.assertTrue(isRestorable);
+ }));
+ chrome.test.assertEq(chrome.fileSystem.retainEntry(entry), id);
+ chrome.fileSystem.restoreEntry(id, chrome.test.callbackPass(
+ function(restoredEntry) {
+ chrome.test.assertEq(restoredEntry, entry);
+ chrome.test.assertEq(
+ chrome.fileSystem.retainEntry(restoredEntry), id);
+ var reader = entry.createReader();
+ reader.readEntries(chrome.test.callback(function(entries) {
+ chrome.test.assertEq(entries.length, 1);
+ checkEntry(entries[0], 'open_existing.txt', false, false);
+ }));
+ entry.getFile(
+ 'open_existing.txt', {},
+ chrome.test.callbackPass(function(entry) {
+ checkEntry(entry, 'open_existing.txt', false, false);
+ }));
+ }));
+ });
+ }));
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/file_system/retain_directory/test_other_window.html b/chrome/test/data/extensions/api_test/file_system/retain_directory/test_other_window.html
new file mode 100644
index 0000000..a85a652
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/retain_directory/test_other_window.html
@@ -0,0 +1,3 @@
+<html>
+<script src="test_other_window.js"></script>
+</html>
diff --git a/chrome/test/data/extensions/api_test/file_system/retain_directory/test_other_window.js b/chrome/test/data/extensions/api_test/file_system/retain_directory/test_other_window.js
new file mode 100644
index 0000000..8475c1e8
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/retain_directory/test_other_window.js
@@ -0,0 +1,18 @@
+// Copyright 2013 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.
+
+chrome.fileSystem.chooseEntry({type: 'openDirectory'},
+ chrome.test.callbackPass(function(entry) {
+ var id = chrome.fileSystem.retainEntry(entry);
+ chrome.test.assertTrue(id != null);
+ chrome.fileSystem.isRestorable(id, chrome.test.callbackPass(
+ function(isRestorable) {
+ chrome.test.assertTrue(isRestorable);
+ }));
+ chrome.fileSystem.restoreEntry(id, chrome.test.callbackPass(
+ function(restoredEntry) {
+ chrome.test.assertEq(restoredEntry, entry);
+ }));
+ callback(id, entry);
+}));
diff --git a/chrome/test/data/extensions/api_test/file_system/retain_directory/test_util.js b/chrome/test/data/extensions/api_test/file_system/retain_directory/test_util.js
new file mode 100644
index 0000000..a31ee51
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/file_system/retain_directory/test_util.js
@@ -0,0 +1,62 @@
+// Copyright 2013 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.
+
+// This is a duplicate of the file test_util in
+// chrome/test/data/extensions/api_test/file_system
+
+function checkEntry(entry, expectedName, isNew, shouldBeWritable) {
+ chrome.test.assertEq(expectedName, entry.name);
+ // Test that the file can be read.
+ entry.file(chrome.test.callback(function(file) {
+ var reader = new FileReader();
+ reader.onloadend = chrome.test.callbackPass(function(e) {
+ if (isNew)
+ chrome.test.assertEq(reader.result, "");
+ else
+ chrome.test.assertEq(reader.result.indexOf("Can you see me?"), 0);
+ // Test that we can write to the file, or not, depending on
+ // |shouldBeWritable|.
+ entry.createWriter(function(fileWriter) {
+ fileWriter.onwriteend = chrome.test.callback(function(e) {
+ if (fileWriter.error) {
+ if (shouldBeWritable) {
+ chrome.test.fail("Error writing to file: " +
+ fileWriter.error.toString());
+ } else {
+ chrome.test.succeed();
+ }
+ } else {
+ if (shouldBeWritable) {
+ // Get a new entry and check the data got to disk.
+ chrome.fileSystem.chooseEntry(chrome.test.callbackPass(
+ function(readEntry) {
+ readEntry.file(chrome.test.callback(function(readFile) {
+ var readReader = new FileReader();
+ readReader.onloadend = function(e) {
+ chrome.test.assertEq(readReader.result.indexOf("HoHoHo!"),
+ 0);
+ chrome.test.succeed();
+ };
+ readReader.onerror = function(e) {
+ chrome.test.fail("Failed to read file after write.");
+ };
+ readReader.readAsText(readFile);
+ }));
+ }));
+ } else {
+ chrome.test.fail(
+ "'Could write to file that should not be writable.");
+ }
+ }
+ });
+ var blob = new Blob(["HoHoHo!"], {type: "text/plain"});
+ fileWriter.write(blob);
+ });
+ });
+ reader.onerror = chrome.test.callback(function(e) {
+ chrome.test.fail("Error reading file contents.");
+ });
+ reader.readAsText(file);
+ }));
+}