diff options
13 files changed, 318 insertions, 12 deletions
diff --git a/apps/app_restore_service.cc b/apps/app_restore_service.cc index f1b3fd3..683718a 100644 --- a/apps/app_restore_service.cc +++ b/apps/app_restore_service.cc @@ -10,6 +10,7 @@ #include "chrome/browser/extensions/extension_host.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/extensions/platform_app_launcher.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_set.h" @@ -103,8 +104,9 @@ void AppRestoreService::RecordAppStop(const std::string& extension_id) { void AppRestoreService::RestoreApp( const Extension* extension, const std::vector<SavedFileEntry>& file_entries) { - // TODO(koz): Make |file_entries| available to the newly restarted app. - AppEventRouter::DispatchOnRestartedEvent(profile_, extension); + extensions::RestartPlatformAppWithFileEntries(profile_, + extension, + file_entries); } } // namespace apps diff --git a/apps/app_restore_service_browsertest.cc b/apps/app_restore_service_browsertest.cc index 3eaf3ff..f17cc64 100644 --- a/apps/app_restore_service_browsertest.cc +++ b/apps/app_restore_service_browsertest.cc @@ -98,4 +98,52 @@ IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, FileAccessIsSavedToPrefs) { ASSERT_TRUE(file_entries.empty()); } +IN_PROC_BROWSER_TEST_F(PlatformAppBrowserTest, FileAccessIsRestored) { + content::WindowedNotificationObserver extension_suspended( + chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED, + content::NotificationService::AllSources()); + + base::ScopedTempDir temp_directory; + ASSERT_TRUE(temp_directory.CreateUniqueTempDir()); + base::FilePath temp_file; + ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_directory.path(), + &temp_file)); + + FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest( + &temp_file); + + ExtensionTestMessageListener file_written_listener("fileWritten", false); + ExtensionTestMessageListener access_ok_listener( + "restartedFileAccessOK", false); + + const Extension* extension = + LoadAndLaunchPlatformApp("file_access_restored_test"); + ASSERT_TRUE(extension); + file_written_listener.WaitUntilSatisfied(); + + ExtensionService* extension_service = + ExtensionSystem::Get(browser()->profile())->extension_service(); + ExtensionPrefs* extension_prefs = extension_service->extension_prefs(); + + // Record the file entries in prefs because when the app gets suspended it + // will have them all cleared. + std::vector<SavedFileEntry> file_entries; + extension_prefs->GetSavedFileEntries(extension->id(), &file_entries); + extension_suspended.Wait(); + + // Simulate a restart by populating the preferences as if the browser didn't + // get time to clean itself up. + extension_prefs->SetExtensionRunning(extension->id(), true); + for (std::vector<SavedFileEntry>::const_iterator it = file_entries.begin(); + it != file_entries.end(); ++it) { + extension_prefs->AddSavedFileEntry( + extension->id(), it->id, it->path, it->writable); + } + + apps::AppRestoreServiceFactory::GetForProfile(browser()->profile())-> + HandleStartup(true); + + access_ok_listener.WaitUntilSatisfied(); +} + } // namespace apps diff --git a/chrome/browser/extensions/api/app_runtime/app_runtime_api.cc b/chrome/browser/extensions/api/app_runtime/app_runtime_api.cc index ff70376..10e29f57 100644 --- a/chrome/browser/extensions/api/app_runtime/app_runtime_api.cc +++ b/chrome/browser/extensions/api/app_runtime/app_runtime_api.cc @@ -9,6 +9,7 @@ #include "base/strings/string_number_conversions.h" #include "base/utf_string_conversions.h" #include "base/values.h" +#include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h" #include "chrome/browser/extensions/event_router.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/profiles/profile.h" @@ -51,10 +52,28 @@ void AppEventRouter::DispatchOnLaunchedEvent( DispatchOnLaunchedEventImpl(extension->id(), arguments.Pass(), profile); } +DictionaryValue* DictionaryFromSavedFileEntry( + const app_file_handler_util::GrantedFileEntry& file_entry) { + DictionaryValue* result = new DictionaryValue(); + result->SetString("id", file_entry.id); + result->SetString("fileSystemId", file_entry.filesystem_id); + result->SetString("baseName", file_entry.registered_name); + return result; +} + // static. void AppEventRouter::DispatchOnRestartedEvent( - Profile* profile, const Extension* extension) { + Profile* profile, + const Extension* extension, + const std::vector<app_file_handler_util::GrantedFileEntry>& file_entries) { + ListValue* file_entries_list = new ListValue(); + for (std::vector<extensions::app_file_handler_util::GrantedFileEntry> + ::const_iterator it = file_entries.begin(); it != file_entries.end(); + ++it) { + file_entries_list->Append(DictionaryFromSavedFileEntry(*it)); + } scoped_ptr<ListValue> arguments(new ListValue()); + arguments->Append(file_entries_list); scoped_ptr<Event> event(new Event(kOnRestartedEvent, arguments.Pass())); event->restrict_to_profile = profile; extensions::ExtensionSystem::Get(profile)->event_router()-> diff --git a/chrome/browser/extensions/api/app_runtime/app_runtime_api.h b/chrome/browser/extensions/api/app_runtime/app_runtime_api.h index 84ade63..f9075b4 100644 --- a/chrome/browser/extensions/api/app_runtime/app_runtime_api.h +++ b/chrome/browser/extensions/api/app_runtime/app_runtime_api.h @@ -18,14 +18,20 @@ namespace extensions { class Extension; +namespace app_file_handler_util { +struct GrantedFileEntry; +} + class AppEventRouter { public: // Dispatches the onLaunched event to the given app, providing no launch // data. static void DispatchOnLaunchedEvent(Profile* profile, const Extension* extension); - static void DispatchOnRestartedEvent(Profile* profile, - const Extension* extension); + static void DispatchOnRestartedEvent( + Profile* profile, + const Extension* extension, + const std::vector<app_file_handler_util::GrantedFileEntry>& file_entries); // TODO(benwells): Update this comment, it is out of date. // Dispatches the onLaunched event to the given app, providing launch data of diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc index 38df2d4..832218d 100644 --- a/chrome/browser/extensions/extension_service.cc +++ b/chrome/browser/extensions/extension_service.cc @@ -32,6 +32,7 @@ #include "chrome/browser/extensions/api/app_runtime/app_runtime_api.h" #include "chrome/browser/extensions/api/declarative/rules_registry_service.h" #include "chrome/browser/extensions/api/extension_action/extension_action_api.h" +#include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h" #include "chrome/browser/extensions/api/profile_keyed_api_factory.h" #include "chrome/browser/extensions/api/runtime/runtime_api.h" #include "chrome/browser/extensions/api/storage/settings_frontend.h" @@ -743,6 +744,11 @@ void ExtensionService::ReloadExtensionWithEvents( } on_load_events_[extension_id] = events; + if (events & EVENT_RESTARTED) { + extension_prefs_->GetSavedFileEntries( + extension_id, &on_restart_file_entries_[extension_id]); + } + if (delayed_updates_for_idle_.Contains(extension_id)) { FinishDelayedInstallation(extension_id); @@ -2910,9 +2916,16 @@ void ExtensionService::DoPostLoadTasks(const Extension* extension) { if (events_to_fire & EVENT_LAUNCHED) queue->AddPendingTask(profile(), extension->id(), base::Bind(&ExtensionService::LaunchApplication)); - if (events_to_fire & EVENT_RESTARTED) + if (events_to_fire & EVENT_RESTARTED) { + SavedFileEntryMap::iterator it = + on_restart_file_entries_.find(extension->id()); + if (it == on_restart_file_entries_.end()) + NOTREACHED(); queue->AddPendingTask(profile(), extension->id(), - base::Bind(&ExtensionService::RestartApplication)); + base::Bind(&ExtensionService::RestartApplication, + it->second)); + on_restart_file_entries_.erase(it); + } } on_load_events_.erase(it); @@ -2933,13 +2946,14 @@ void ExtensionService::LaunchApplication( // static void ExtensionService::RestartApplication( + std::vector<extensions::app_file_handler_util::SavedFileEntry> file_entries, extensions::ExtensionHost* extension_host) { if (!extension_host) return; #if !defined(OS_ANDROID) - extensions::AppEventRouter::DispatchOnRestartedEvent( - extension_host->profile(), extension_host->extension()); + extensions::RestartPlatformAppWithFileEntries( + extension_host->profile(), extension_host->extension(), file_entries); #endif } diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h index 7fb4a13..7a4eaea 100644 --- a/chrome/browser/extensions/extension_service.h +++ b/chrome/browser/extensions/extension_service.h @@ -796,7 +796,10 @@ class ExtensionService // Dispatches a restart event to the platform app associated with // |extension_host|. - static void RestartApplication(extensions::ExtensionHost* extension_host); + static void RestartApplication( + std::vector<extensions::app_file_handler_util::SavedFileEntry> + file_entries, + extensions::ExtensionHost* extension_host); // Helper to inspect an ExtensionHost after it has been loaded. void InspectExtensionHost(extensions::ExtensionHost* host); @@ -924,6 +927,13 @@ class ExtensionService // dispatched to the extension when it is loaded. std::map<std::string, int> on_load_events_; + // Maps extension ids to vectors of saved file entries that the extension + // should be given access to on restart. + typedef std::map<std::string, + std::vector<extensions::app_file_handler_util::SavedFileEntry> > + SavedFileEntryMap; + SavedFileEntryMap on_restart_file_entries_; + content::NotificationRegistrar registrar_; PrefChangeRegistrar pref_change_registrar_; diff --git a/chrome/browser/extensions/platform_app_launcher.cc b/chrome/browser/extensions/platform_app_launcher.cc index 78548d8..0d37609 100644 --- a/chrome/browser/extensions/platform_app_launcher.cc +++ b/chrome/browser/extensions/platform_app_launcher.cc @@ -13,8 +13,11 @@ #include "base/utf_string_conversions.h" #include "chrome/browser/extensions/api/app_runtime/app_runtime_api.h" #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h" +#include "chrome/browser/extensions/api/file_system/file_system_api.h" #include "chrome/browser/extensions/extension_host.h" +#include "chrome/browser/extensions/extension_prefs.h" #include "chrome/browser/extensions/extension_process_manager.h" +#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/extensions/lazy_background_task_queue.h" #include "chrome/browser/profiles/profile.h" @@ -33,6 +36,9 @@ using content::BrowserThread; using extensions::app_file_handler_util::FileHandlerForId; using extensions::app_file_handler_util::FileHandlerCanHandleFileWithMimeType; using extensions::app_file_handler_util::FirstFileHandlerForMimeType; +using extensions::app_file_handler_util::CreateFileEntry; +using extensions::app_file_handler_util::GrantedFileEntry; +using extensions::app_file_handler_util::SavedFileEntry; namespace extensions { @@ -239,6 +245,70 @@ class PlatformAppPathLauncher DISALLOW_COPY_AND_ASSIGN(PlatformAppPathLauncher); }; +class SavedFileEntryLauncher + : public base::RefCountedThreadSafe<SavedFileEntryLauncher> { + public: + SavedFileEntryLauncher( + Profile* profile, + const Extension* extension, + const std::vector<SavedFileEntry>& file_entries) + : profile_(profile), + extension_(extension), + file_entries_(file_entries) {} + + void Launch() { + // Access needs to be granted to the file or filesystem for the process + // associated with the extension. To do this the ExtensionHost is needed. + // This might not be available, or it might be in the process of being + // unloaded, in which case the lazy background task queue is used to load + // he extension and then call back to us. + extensions::LazyBackgroundTaskQueue* queue = + ExtensionSystem::Get(profile_)->lazy_background_task_queue(); + if (queue->ShouldEnqueueTask(profile_, extension_)) { + queue->AddPendingTask(profile_, extension_->id(), base::Bind( + &SavedFileEntryLauncher::GrantAccessToFilesAndLaunch, + this)); + return; + } + ExtensionProcessManager* process_manager = + ExtensionSystem::Get(profile_)->process_manager(); + extensions::ExtensionHost* host = + process_manager->GetBackgroundHostForExtension(extension_->id()); + DCHECK(host); + GrantAccessToFilesAndLaunch(host); + } + + private: + friend class base::RefCountedThreadSafe<SavedFileEntryLauncher>; + ~SavedFileEntryLauncher() {} + void GrantAccessToFilesAndLaunch(ExtensionHost* host) { + int renderer_id = host->render_process_host()->GetID(); + std::vector<GrantedFileEntry> granted_file_entries; + for (std::vector<SavedFileEntry>::const_iterator it = + file_entries_.begin(); it != file_entries_.end(); ++it) { + GrantedFileEntry file_entry = CreateFileEntry( + profile_, extension_->id(), renderer_id, it->path, it->writable); + file_entry.id = it->id; + granted_file_entries.push_back(file_entry); + + // Record that we have granted this file permission. + ExtensionPrefs* extension_prefs = ExtensionSystem::Get(profile_)-> + extension_service()->extension_prefs(); + extension_prefs->AddSavedFileEntry( + host->extension()->id(), it->id, it->path, it->writable); + } + extensions::AppEventRouter::DispatchOnRestartedEvent( + profile_, extension_, granted_file_entries); + } + + // The profile the app should be run in. + Profile* profile_; + // The extension providing the app. + const Extension* extension_; + + std::vector<SavedFileEntry> file_entries_; +}; + } // namespace void LaunchPlatformApp(Profile* profile, @@ -275,4 +345,13 @@ void LaunchPlatformAppWithFileHandler(Profile* profile, launcher->LaunchWithHandler(handler_id); } +void RestartPlatformAppWithFileEntries( + Profile* profile, + const Extension* extension, + const std::vector<SavedFileEntry>& file_entries) { + scoped_refptr<SavedFileEntryLauncher> launcher = new SavedFileEntryLauncher( + profile, extension, file_entries); + launcher->Launch(); +} + } // namespace extensions diff --git a/chrome/browser/extensions/platform_app_launcher.h b/chrome/browser/extensions/platform_app_launcher.h index c301c45..fc7965a 100644 --- a/chrome/browser/extensions/platform_app_launcher.h +++ b/chrome/browser/extensions/platform_app_launcher.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_EXTENSIONS_PLATFORM_APP_LAUNCHER_H_ #include <string> +#include <vector> class CommandLine; class Profile; @@ -22,6 +23,10 @@ namespace extensions { class Extension; +namespace app_file_handler_util { +struct SavedFileEntry; +} + // Launches the platform app |extension|. Creates appropriate launch data for // the |command_line| fields present. |extension| and |profile| must not be // NULL. A NULL |command_line| means there is no launch data. If non-empty, @@ -44,6 +49,12 @@ void LaunchPlatformAppWithFileHandler(Profile* profile, const std::string& handler_id, const base::FilePath& file_path); +void RestartPlatformAppWithFileEntries( + Profile* profile, + const Extension* extension, + const std::vector<app_file_handler_util::SavedFileEntry>& + saved_file_entries); + } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_PLATFORM_APP_LAUNCHER_H_ diff --git a/chrome/renderer/resources/extensions/app_runtime_custom_bindings.js b/chrome/renderer/resources/extensions/app_runtime_custom_bindings.js index d17d2e5..0f3a0f7 100644 --- a/chrome/renderer/resources/extensions/app_runtime_custom_bindings.js +++ b/chrome/renderer/resources/extensions/app_runtime_custom_bindings.js @@ -15,6 +15,38 @@ var appNatives = requireNative('app_runtime'); var DeserializeString = appNatives.DeserializeString; var SerializeToString = appNatives.SerializeToString; var CreateBlob = appNatives.CreateBlob; +var entryIdManager = require('entryIdManager'); + +chromeHidden.Event.registerArgumentMassager('app.runtime.onRestarted', + function(args, dispatch) { + // These file entries don't get dispatched, we just use this hook to register + // them all with entryIdManager. + var fileEntries = args[0]; + + var pendingCallbacks = fileEntries.length; + + var dispatchIfNoPendingCallbacks = function() { + if (pendingCallbacks == 0) + dispatch([]); + }; + + for (var i = 0; i < fileEntries.length; i++) { + var fe = fileEntries[i]; + var fs = GetIsolatedFileSystem(fe.fileSystemId); + (function(fe, fs) { + fs.root.getFile(fe.baseName, {}, function(fileEntry) { + entryIdManager.registerEntry(fe.id, fileEntry); + pendingCallbacks--; + dispatchIfNoPendingCallbacks(); + }, function(err) { + console.error('Error getting fileEntry, code: ' + err.code); + pendingCallbacks--; + dispatchIfNoPendingCallbacks(); + }); + })(fe, fs); + } + dispatchIfNoPendingCallbacks(); +}); chromeHidden.Event.registerArgumentMassager('app.runtime.onLaunched', function(args, dispatch) { diff --git a/chrome/renderer/resources/extensions/file_system_custom_bindings.js b/chrome/renderer/resources/extensions/file_system_custom_bindings.js index 123eabe..acf163a 100644 --- a/chrome/renderer/resources/extensions/file_system_custom_bindings.js +++ b/chrome/renderer/resources/extensions/file_system_custom_bindings.js @@ -36,8 +36,7 @@ binding.registerCustomHook(function(bindingsAPI) { var fileSystemId = response.fileSystemId; var baseName = response.baseName; - // TODO(koz): Generate a persistent id in the browser and use it here. - var id = fileSystemId + ":" + baseName; + var id = response.id; var fs = GetIsolatedFileSystem(fileSystemId); try { diff --git a/chrome/test/data/extensions/platform_apps/file_access_restored_test/index.html b/chrome/test/data/extensions/platform_apps/file_access_restored_test/index.html new file mode 100644 index 0000000..45b983b --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/file_access_restored_test/index.html @@ -0,0 +1 @@ +hi diff --git a/chrome/test/data/extensions/platform_apps/file_access_restored_test/manifest.json b/chrome/test/data/extensions/platform_apps/file_access_restored_test/manifest.json new file mode 100644 index 0000000..4f6d3e3 --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/file_access_restored_test/manifest.json @@ -0,0 +1,11 @@ +{ + "name": "Files Preserved Test", + "version": "1", + "manifest_version": 2, + "app": { + "background": { + "scripts": ["test.js"] + } + }, + "permissions": ["fileSystem", "fileSystem.write", "storage"] +} diff --git a/chrome/test/data/extensions/platform_apps/file_access_restored_test/test.js b/chrome/test/data/extensions/platform_apps/file_access_restored_test/test.js new file mode 100644 index 0000000..07ebb0b --- /dev/null +++ b/chrome/test/data/extensions/platform_apps/file_access_restored_test/test.js @@ -0,0 +1,74 @@ +// Copyright (c) 2012 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. + +var expectedText = 'def'; + +function truncateAndWriteToFile(writableEntry, callback) { + writableEntry.createWriter(function(fileWriter) { + fileWriter.onerror = function(e) { + console.error("Couldn't write file: " + e.toString()); + }; + fileWriter.onwriteend = function(e) { + fileWriter.onwriteend = function(e) { + callback(); + }; + var blob = new Blob([expectedText], {type: 'text/plain'}); + fileWriter.write(blob); + }; + fileWriter.truncate(0); + }); +} + +chrome.app.runtime.onLaunched.addListener(function() { + chrome.app.window.create('index.html', {width: 100, height: 100}, + function(win) { + var fs = win.contentWindow.chrome.fileSystem; + fs.chooseEntry({type: 'openFile'}, function(entry) { + fs.getWritableEntry(entry, function(writableEntry) { + var id = fs.getEntryId(entry); + chrome.storage.local.set({id:id}, function() { + truncateAndWriteToFile(writableEntry, function() { + chrome.test.sendMessage('fileWritten'); + win.close(); + }); + }); + }); + }); + }); +}); + +chrome.app.runtime.onRestarted.addListener(function() { + chrome.storage.local.get(null, function(data) { + var entry = chrome.fileSystem.getEntryById(data.id); + if (!entry) { + console.error("couldn't get file entry " + data.id); + return; + } + entry.file(function(file) { + var fr = new FileReader(); + fr.onload = function(e) { + if (e.target.result != expectedText) { + console.error( + "expected '" + expectedText + "', got '" + e.target.result + "'"); + return; + } + entry.createWriter(function(fileWriter) { + fileWriter.onwriteend = function(e) { + chrome.test.sendMessage('restartedFileAccessOK'); + win.close(); + }; + fileWriter.onerror = function(e) { + console.error('Write failed: ' + e.toString()); + }; + var blob = new Blob(["doesn't matter"], {type: 'text/plain'}); + fileWriter.write(blob); + }); + }; + fr.onerror = function(e) { + chrome.test.fail("error reading file"); + }; + fr.readAsText(file); + }); + }); +}); |