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:
apps
chrome
browser
extensions
renderer
resources
test
data
extensions
platform_apps
file_access_restored_test
@ -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 @@
|
||||
hi
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user