0

Give an app the file entries it had back on restart.

BUG=165832


Review URL: https://chromiumcodereview.appspot.com/12391006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@187846 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
koz@chromium.org
2013-03-13 13:14:57 +00:00
parent b790fd02a2
commit fc2a40fab7
13 changed files with 318 additions and 12 deletions

@ -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

@ -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

@ -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()->

@ -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

@ -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
}

@ -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_;

@ -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

@ -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_

@ -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) {

@ -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 {

@ -0,0 +1,11 @@
{
"name": "Files Preserved Test",
"version": "1",
"manifest_version": 2,
"app": {
"background": {
"scripts": ["test.js"]
}
},
"permissions": ["fileSystem", "fileSystem.write", "storage"]
}

@ -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);
});
});
});