Bug 481603: Flush caches for restartless add-on changes. r=robstrong

This commit is contained in:
Dave Townsend 2011-06-15 11:12:53 -07:00
Родитель cf0726ba75
Коммит c140ed162a
10 изменённых файлов: 177 добавлений и 158 удалений

Просмотреть файл

@ -906,6 +906,33 @@ function loadManifestFromFile(aFile) {
return loadManifestFromDir(aFile);
}
/**
* Gets an nsIURI for a file within another file, either a directory or an XPI
* file. If aFile is a directory then this will return a file: URI, if it is an
* XPI file then it will return a jar: URI.
*
* @param aFile
* The file containing the resources, must be either a directory or an
* XPI file
* @param aPath
* The path to find the resource at, "/" separated. If aPath is empty
* then the uri to the root of the contained files will be returned
* @return an nsIURI pointing at the resource
*/
function getURIForResourceInFile(aFile, aPath) {
if (aFile.isDirectory()) {
let resource = aFile.clone();
if (aPath) {
aPath.split("/").forEach(function(aPart) {
resource.append(aPart);
});
}
return NetUtil.newURI(resource);
}
return buildJarURI(aFile, aPath);
}
/**
* Creates a jar: URI for a file inside a ZIP file.
*
@ -933,6 +960,12 @@ function flushJarCache(aJarFile) {
.sendAsyncMessage(MSG_JAR_FLUSH, aJarFile.path);
}
function flushStartupCache() {
// Init this, so it will get the notification.
Cc["@mozilla.org/xul/xul-prototype-cache;1"].getService(Ci.nsISupports);
Services.obs.notifyObservers(null, "startupcache-invalidate", null);
}
/**
* Creates and returns a new unique temporary file. The caller should delete
* the file when it is no longer needed.
@ -1565,9 +1598,7 @@ var XPIProvider = {
}
if (flushCaches) {
// Init this, so it will get the notification.
let xulPrototypeCache = Cc["@mozilla.org/xul/xul-prototype-cache;1"].getService(Ci.nsISupports);
Services.obs.notifyObservers(null, "startupcache-invalidate", null);
flushStartupCache();
// UI displayed early in startup (like the compatibility UI) may have
// caused us to cache parts of the skin or locale in memory. These must
@ -1989,6 +2020,7 @@ var XPIProvider = {
this.callBootstrapMethod(existingAddonID, oldBootstrap.version,
existingAddon, "uninstall", uninstallReason);
this.unloadBootstrapScope(existingAddonID);
flushStartupCache();
}
}
catch (e) {
@ -2259,6 +2291,9 @@ var XPIProvider = {
// If the new add-on is bootstrapped and active then call its install method
if (newAddon.active && newAddon.bootstrap) {
// Startup cache must be flushed before calling the bootstrap script
flushStartupCache();
let installReason = Services.vc.compare(aOldAddon.version, newAddon.version) < 0 ?
BOOTSTRAP_REASONS.ADDON_UPGRADE :
BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
@ -2270,7 +2305,6 @@ var XPIProvider = {
return false;
}
// Otherwise the caches will need to be invalidated
return true;
}
@ -2445,9 +2479,7 @@ var XPIProvider = {
if (aOldAddon.type == "theme")
XPIProvider.enableDefaultTheme();
// If this was not a bootstrapped add-on then we must force a restart.
if (!aOldAddon.bootstrap)
return true;
return true;
}
return false;
@ -2586,10 +2618,16 @@ var XPIProvider = {
let oldAddonFile = Cc["@mozilla.org/file/local;1"].
createInstance(Ci.nsILocalFile);
oldAddonFile.persistentDescriptor = oldBootstrap.descriptor;
XPIProvider.callBootstrapMethod(newAddon.id, oldBootstrap.version,
oldAddonFile, "uninstall",
installReason);
XPIProvider.unloadBootstrapScope(newAddon.id);
// If the new add-on is bootstrapped then we must flush the caches
// before calling the new bootstrap script
if (newAddon.bootstrap)
flushStartupCache();
}
if (!newAddon.bootstrap)
@ -3355,47 +3393,32 @@ var XPIProvider = {
createInstance(Ci.nsIPrincipal);
this.bootstrapScopes[aId] = new Components.utils.Sandbox(principal);
let bootstrap = aFile.clone();
let name = aFile.leafName;
let spec;
if (!bootstrap.exists()) {
if (!aFile.exists()) {
ERROR("Attempted to load bootstrap scope from missing directory " + bootstrap.path);
return;
}
if (bootstrap.isDirectory()) {
bootstrap.append("bootstrap.js");
let uri = Services.io.newFileURI(bootstrap);
spec = uri.spec;
} else {
spec = buildJarURI(bootstrap, "bootstrap.js").spec;
}
if (bootstrap.exists()) {
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
createInstance(Ci.mozIJSSubScriptLoader);
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
createInstance(Ci.mozIJSSubScriptLoader);
try {
// As we don't want our caller to control the JS version used for the
// bootstrap file, we run loadSubScript within the context of the
// sandbox with the latest JS version set explicitly.
this.bootstrapScopes[aId].__SCRIPT_URI_SPEC__ = spec;
Components.utils.evalInSandbox(
"Components.classes['@mozilla.org/moz/jssubscript-loader;1'] \
.createInstance(Components.interfaces.mozIJSSubScriptLoader) \
.loadSubScript(__SCRIPT_URI_SPEC__);", this.bootstrapScopes[aId], "ECMAv5");
}
catch (e) {
WARN("Error loading bootstrap.js for " + aId, e);
}
try {
// As we don't want our caller to control the JS version used for the
// bootstrap file, we run loadSubScript within the context of the
// sandbox with the latest JS version set explicitly.
this.bootstrapScopes[aId].__SCRIPT_URI_SPEC__ =
getURIForResourceInFile(aFile, "bootstrap.js").spec;
Components.utils.evalInSandbox(
"Components.classes['@mozilla.org/moz/jssubscript-loader;1'] \
.createInstance(Components.interfaces.mozIJSSubScriptLoader) \
.loadSubScript(__SCRIPT_URI_SPEC__);", this.bootstrapScopes[aId], "ECMAv5");
}
catch (e) {
WARN("Error loading bootstrap.js for " + aId, e);
}
// Copy the reason values from the global object into the bootstrap scope.
for (let name in BOOTSTRAP_REASONS)
this.bootstrapScopes[aId][name] = BOOTSTRAP_REASONS[name];
}
else {
WARN("Bootstrap missing for " + aId);
}
// Copy the reason values from the global object into the bootstrap scope.
for (let name in BOOTSTRAP_REASONS)
this.bootstrapScopes[aId][name] = BOOTSTRAP_REASONS[name];
},
/**
@ -3443,7 +3466,8 @@ var XPIProvider = {
let params = {
id: aId,
version: aVersion,
installPath: aFile.clone()
installPath: aFile.clone(),
resourceURI: getURIForResourceInFile(aFile, "")
};
LOG("Calling bootstrap method " + aMethod + " on " + aId + " version " +
@ -3623,9 +3647,11 @@ var XPIProvider = {
this.callBootstrapMethod(aAddon.id, aAddon.version, file, "shutdown",
BOOTSTRAP_REASONS.ADDON_UNINSTALL);
}
this.callBootstrapMethod(aAddon.id, aAddon.version, file, "uninstall",
BOOTSTRAP_REASONS.ADDON_UNINSTALL);
this.unloadBootstrapScope(aAddon.id);
flushStartupCache();
}
aAddon._installLocation.uninstallAddon(aAddon.id);
XPIDatabase.removeAddonMetadata(aAddon);
@ -6216,10 +6242,12 @@ AddonInstall.prototype = {
this.existingAddon.version,
file, "shutdown", reason);
}
XPIProvider.callBootstrapMethod(this.existingAddon.id,
this.existingAddon.version,
file, "uninstall", reason);
XPIProvider.unloadBootstrapScope(this.existingAddon.id);
flushStartupCache();
}
if (!isUpgrade && this.existingAddon.active) {
@ -7210,21 +7238,22 @@ function AddonWrapper(aAddon) {
return result;
},
/**
* Returns a URI to the selected resource or to the add-on bundle if aPath
* is null. URIs to the bundle will always be file: URIs. URIs to resources
* will be file: URIs if the add-on is unpacked or jar: URIs if the add-on is
* still an XPI file.
*
* @param aPath
* The path in the add-on to get the URI for or null to get a URI to
* the file or directory the add-on is installed as.
* @return an nsIURI
*/
this.getResourceURI = function(aPath) {
let bundle = aAddon._sourceBundle.clone();
if (bundle.isDirectory()) {
if (aPath) {
aPath.split("/").forEach(function(aPart) {
bundle.append(aPart);
});
}
return Services.io.newFileURI(bundle);
}
if (!aPath)
return Services.io.newFileURI(bundle);
return buildJarURI(bundle, aPath);
return NetUtil.newURI(aAddon._sourceBundle);
return getURIForResourceInFile(aAddon._sourceBundle, aPath);
}
}

Просмотреть файл

@ -1,13 +1,17 @@
Components.utils.import("resource://gre/modules/Services.jsm");
function install(data, reason) {
Services.prefs.setIntPref("bootstraptest.installed_version", 1);
Components.utils.import(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.installed_version", VERSION);
Services.prefs.setIntPref("bootstraptest.install_reason", reason);
Components.utils.unload(data.resourceURI.spec + "version.jsm");
}
function startup(data, reason) {
Services.prefs.setIntPref("bootstraptest.active_version", 1);
Components.utils.import(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.active_version", VERSION);
Services.prefs.setIntPref("bootstraptest.startup_reason", reason);
Components.utils.unload(data.resourceURI.spec + "version.jsm");
}
function shutdown(data, reason) {

Просмотреть файл

@ -0,0 +1,3 @@
var EXPORTED_SYMBOLS = ["VERSION"];
var VERSION = 1;

Просмотреть файл

@ -1,13 +1,17 @@
Components.utils.import("resource://gre/modules/Services.jsm");
function install(data, reason) {
Services.prefs.setIntPref("bootstraptest.installed_version", 2);
Components.utils.import(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.installed_version", VERSION);
Services.prefs.setIntPref("bootstraptest.install_reason", reason);
Components.utils.unload(data.resourceURI.spec + "version.jsm");
}
function startup(data, reason) {
Services.prefs.setIntPref("bootstraptest.active_version", 2);
Components.utils.import(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.active_version", VERSION);
Services.prefs.setIntPref("bootstraptest.startup_reason", reason);
Components.utils.unload(data.resourceURI.spec + "version.jsm");
}
function shutdown(data, reason) {

Просмотреть файл

@ -0,0 +1,3 @@
var EXPORTED_SYMBOLS = ["VERSION"];
var VERSION = 2;

Просмотреть файл

@ -1,13 +1,17 @@
Components.utils.import("resource://gre/modules/Services.jsm");
function install(data, reason) {
Services.prefs.setIntPref("bootstraptest.installed_version", 3);
Components.utils.import(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.installed_version", VERSION);
Services.prefs.setIntPref("bootstraptest.install_reason", reason);
Components.utils.unload(data.resourceURI.spec + "version.jsm");
}
function startup(data, reason) {
Services.prefs.setIntPref("bootstraptest.active_version", 3);
Components.utils.import(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.active_version", VERSION);
Services.prefs.setIntPref("bootstraptest.startup_reason", reason);
Components.utils.unload(data.resourceURI.spec + "version.jsm");
}
function shutdown(data, reason) {

Просмотреть файл

@ -0,0 +1,3 @@
var EXPORTED_SYMBOLS = ["VERSION"];
var VERSION = 3;

Просмотреть файл

@ -1 +1,2 @@
Services.prefs.setBoolPref("extensions.alwaysUnpack", true);
TEST_UNPACKED = true;

Просмотреть файл

@ -19,6 +19,8 @@ var gInternalManager = null;
var gAppInfo = null;
var gAddonsList;
var TEST_UNPACKED = false;
function createAppInfo(id, name, version, platformVersion) {
gAppInfo = {
// nsIXULAppInfo
@ -161,7 +163,7 @@ function do_get_addon_root_uri(aProfileDir, aId) {
}
function do_get_expected_addon_name(aId) {
if (Services.prefs.getBoolPref("extensions.alwaysUnpack"))
if (TEST_UNPACKED)
return aId;
return aId + ".xpi";
}
@ -569,7 +571,7 @@ function writeInstallRDFForExtension(aData, aDir, aId, aExtraFile) {
var dir = aDir.clone();
if (Services.prefs.getBoolPref("extensions.alwaysUnpack")) {
if (TEST_UNPACKED) {
dir.append(id);
writeInstallRDFToDir(aData, dir, aExtraFile);
return dir;

Просмотреть файл

@ -73,6 +73,46 @@ function getUninstallReason() {
return Services.prefs.getIntPref("bootstraptest.uninstall_reason");
}
function manuallyInstall(aXPIFile, aInstallLocation, aID) {
if (TEST_UNPACKED) {
let dir = aInstallLocation.clone();
dir.append(aID);
dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
let zip = AM_Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(AM_Ci.nsIZipReader);
zip.open(aXPIFile);
let entries = zip.findEntries(null);
while (entries.hasMore()) {
let entry = entries.getNext();
let target = dir.clone();
entry.split("/").forEach(function(aPart) {
target.append(aPart);
});
zip.extract(entry, target);
}
zip.close();
return dir;
}
else {
let target = aInstallLocation.clone();
target.append(aID + ".xpi");
aXPIFile.copyTo(target.parent, target.leafName);
return target;
}
}
function manuallyUninstall(aInstallLocation, aID) {
let file = getFileForAddon(aInstallLocation, aID);
// In reality because the app is restarted a flush isn't necessary for XPIs
// removed outside the app, but for testing we must flush manually.
if (file.isFile())
Services.obs.notifyObservers(file, "flush-cache-entry", null);
file.remove(true);
}
function run_test() {
do_test_pending();
@ -369,18 +409,8 @@ function check_test_7() {
function run_test_8() {
shutdownManager();
let dir = profileDir.clone();
dir.append("bootstrap1@tests.mozilla.org");
dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
let zip = AM_Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(AM_Ci.nsIZipReader);
zip.open(do_get_addon("test_bootstrap1_1"));
dir.append("install.rdf");
zip.extract("install.rdf", dir);
dir = dir.parent;
dir.append("bootstrap.js");
zip.extract("bootstrap.js", dir);
zip.close();
manuallyInstall(do_get_addon("test_bootstrap1_1"), profileDir,
"bootstrap1@tests.mozilla.org");
startupManager(false);
@ -403,9 +433,8 @@ function run_test_8() {
function run_test_9() {
shutdownManager();
let dir = profileDir.clone();
dir.append("bootstrap1@tests.mozilla.org");
dir.remove(true);
manuallyUninstall(profileDir, "bootstrap1@tests.mozilla.org");
startupManager(false);
AddonManager.getAddonByID("bootstrap1@tests.mozilla.org", function(b1) {
@ -548,18 +577,8 @@ function check_test_11() {
function run_test_12() {
shutdownManager();
let dir = profileDir.clone();
dir.append("bootstrap1@tests.mozilla.org");
dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
let zip = AM_Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(AM_Ci.nsIZipReader);
zip.open(do_get_addon("test_bootstrap1_1"));
dir.append("install.rdf");
zip.extract("install.rdf", dir);
dir = dir.parent;
dir.append("bootstrap.js");
zip.extract("bootstrap.js", dir);
zip.close();
manuallyInstall(do_get_addon("test_bootstrap1_1"), profileDir,
"bootstrap1@tests.mozilla.org");
startupManager(true);
@ -654,18 +673,8 @@ function check_test_13() {
function run_test_14() {
shutdownManager();
let dir = profileDir.clone();
dir.append("bootstrap1@tests.mozilla.org");
dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
let zip = AM_Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(AM_Ci.nsIZipReader);
zip.open(do_get_addon("test_bootstrap1_3"));
dir.append("install.rdf");
zip.extract("install.rdf", dir);
dir = dir.parent;
dir.append("bootstrap.js");
zip.extract("bootstrap.js", dir);
zip.close();
manuallyInstall(do_get_addon("test_bootstrap1_3"), profileDir,
"bootstrap1@tests.mozilla.org");
startupManager(false);
@ -808,18 +817,8 @@ function run_test_16() {
function run_test_17() {
shutdownManager();
let dir = userExtDir.clone();
dir.append("bootstrap1@tests.mozilla.org");
dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
let zip = AM_Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(AM_Ci.nsIZipReader);
zip.open(do_get_addon("test_bootstrap1_1"));
dir.append("install.rdf");
zip.extract("install.rdf", dir);
dir = dir.parent;
dir.append("bootstrap.js");
zip.extract("bootstrap.js", dir);
zip.close();
manuallyInstall(do_get_addon("test_bootstrap1_1"), userExtDir,
"bootstrap1@tests.mozilla.org");
resetPrefs();
startupManager();
@ -902,18 +901,8 @@ function run_test_20() {
resetPrefs();
shutdownManager();
let dir = profileDir.clone();
dir.append("bootstrap1@tests.mozilla.org");
dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
let zip = AM_Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(AM_Ci.nsIZipReader);
zip.open(do_get_addon("test_bootstrap1_2"));
dir.append("install.rdf");
zip.extract("install.rdf", dir);
dir = dir.parent;
dir.append("bootstrap.js");
zip.extract("bootstrap.js", dir);
zip.close();
manuallyInstall(do_get_addon("test_bootstrap1_2"), profileDir,
"bootstrap1@tests.mozilla.org");
startupManager();
@ -939,9 +928,7 @@ function run_test_21() {
resetPrefs();
shutdownManager();
let dir = profileDir.clone();
dir.append("bootstrap1@tests.mozilla.org");
dir.remove(true);
manuallyUninstall(profileDir, "bootstrap1@tests.mozilla.org");
startupManager();
@ -964,9 +951,7 @@ function run_test_21() {
do_check_eq(getStartupReason(), APP_STARTUP);
dir = userExtDir.clone();
dir.append("bootstrap1@tests.mozilla.org");
dir.remove(true);
manuallyUninstall(userExtDir, "bootstrap1@tests.mozilla.org");
restartManager();
@ -978,21 +963,11 @@ function run_test_21() {
function run_test_22() {
shutdownManager();
let dir = profileDir.clone();
dir.append("bootstrap1@tests.mozilla.org");
dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
let zip = AM_Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(AM_Ci.nsIZipReader);
zip.open(do_get_addon("test_bootstrap1_1"));
dir.append("install.rdf");
zip.extract("install.rdf", dir);
dir = dir.parent;
dir.append("bootstrap.js");
zip.extract("bootstrap.js", dir);
zip.close();
let file = manuallyInstall(do_get_addon("test_bootstrap1_1"), profileDir,
"bootstrap1@tests.mozilla.org");
// Make it look old so changes are detected
setExtensionModifiedTime(dir.parent, dir.parent.lastModifiedTime - 5000);
setExtensionModifiedTime(file, file.lastModifiedTime - 5000);
startupManager();
@ -1007,18 +982,9 @@ function run_test_22() {
resetPrefs();
shutdownManager();
dir = dir.parent;
dir.remove(true);
dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, 0755);
let zip = AM_Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(AM_Ci.nsIZipReader);
zip.open(do_get_addon("test_bootstrap1_2"));
dir.append("install.rdf");
zip.extract("install.rdf", dir);
dir = dir.parent;
dir.append("bootstrap.js");
zip.extract("bootstrap.js", dir);
zip.close();
manuallyUninstall(profileDir, "bootstrap1@tests.mozilla.org");
manuallyInstall(do_get_addon("test_bootstrap1_2"), profileDir,
"bootstrap1@tests.mozilla.org");
startupManager();