diff options
author | sammc@chromium.org <sammc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-29 14:16:24 +0000 |
---|---|---|
committer | sammc@chromium.org <sammc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-29 14:16:24 +0000 |
commit | 6b7ecdd48738d917c870a18e1ba3bb7707a4894e (patch) | |
tree | a1fd71148f5c1f89c166ac35cce308c29ddcb5a1 | |
parent | f5f2b724da0926b45c9a6bd34df0e974206819c5 (diff) | |
download | chromium_src-6b7ecdd48738d917c870a18e1ba3bb7707a4894e.zip chromium_src-6b7ecdd48738d917c870a18e1ba3bb7707a4894e.tar.gz chromium_src-6b7ecdd48738d917c870a18e1ba3bb7707a4894e.tar.bz2 |
Add support for directory access to the file system API.
This adds 'openDirectory' as a type option for
chrome.fileSystem.chooseEntry to allow access to directories. This is
restricted to apps with the fileSystem.directory permission.
BUG=148486
Review URL: https://chromiumcodereview.appspot.com/23146016
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@220287 0039d316-1c4b-4281-b951-d872f2087c98
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); + })); +} |