diff --git a/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in b/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in index 834e1538d68..53b2e28c9e0 100644 --- a/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in +++ b/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in @@ -177,6 +177,9 @@ var gCheckCompatibility = true; var gCheckUpdateSecurity = true; var gLocale = "en-US"; var gFirstRun = false; +var gAllowFlush = true; +var gDSNeedsFlush = false; +var gManifestNeedsFlush = false; /** * Valid GUIDs fit this pattern. @@ -2661,6 +2664,25 @@ ExtensionManager.prototype = { * Clean up on application shutdown to avoid leaks. */ _shutdown: function() { + if (!gAllowFlush) { + // Something went wrong and there are potentially flushes pending. + ERROR("Reached _shutdown and without clearing any pending flushes"); + try { + gAllowFlush = true; + if (gManifestNeedsFlush) { + gManifestNeedsFlush = false; + this._updateManifests(false); + } + if (gDSNeedsFlush) { + gDSNeedsFlush = false; + this.datasource.Flush(); + } + } + catch (e) { + ERROR("Error flushing caches: " + e); + } + } + gOS.removeObserver(this, "xpcom-shutdown"); // Release strongly held services. @@ -2733,6 +2755,9 @@ ExtensionManager.prototype = { if (this._ensureDatasetIntegrity()) isDirty = true; + // Block attempts to flush for the entire startup + gAllowFlush = false; + // Configure any items that are being installed, uninstalled or upgraded // by being added, removed or modified by another process. We must do this // on every startup since there is no way we can tell if this has happened @@ -2745,20 +2770,37 @@ ExtensionManager.prototype = { if (PendingOperations.size != 0) isDirty = true; + var needsRestart = false; // Extension Changes if (isDirty) { - var needsRestart = this._finishOperations(); + needsRestart = this._finishOperations(); if (forceAutoReg) { this._extensionListChanged = true; needsRestart = true; } - return needsRestart; } - this._startTimers(); + // Resume flushing and perform a flush for anything that was deferred + try { + gAllowFlush = true; + if (gManifestNeedsFlush) { + gManifestNeedsFlush = false; + this._updateManifests(false); + } + if (gDSNeedsFlush) { + gDSNeedsFlush = false; + this.datasource.Flush(); + } + } + catch (e) { + ERROR("Error flushing caches: " + e); + } - return false; + if (!needsRestart) + this._startTimers(); + + return needsRestart; }, /** @@ -3568,6 +3610,9 @@ ExtensionManager.prototype = { return false; } + // Block attempts to flush for the entire startup + gAllowFlush = false; + // Version mismatch, we have to load the extensions datasource and do // version checking. Time hit here doesn't matter since this doesn't happen // all that often. @@ -3630,6 +3675,11 @@ ExtensionManager.prototype = { else if (allAppManaged) allAppManaged = false; + var properties = { + availableUpdateURL: null, + availableUpdateVersion: null + }; + if (ds.getItemProperty(id, "providesUpdatesSecurely") == "false") { /* It's possible the previous version did not understand updateKeys so * check if we can import one for this addon from it's manifest. */ @@ -3646,13 +3696,13 @@ ExtensionManager.prototype = { // updates, satisfying its dependencies, and not being blocklisted if (this._isUsableItem(id)) { if (ds.getItemProperty(id, "appDisabled")) - ds.setItemProperty(id, EM_R("appDisabled"), null); + properties.appDisabled = null; + } + else if (!ds.getItemProperty(id, "appDisabled")) { + properties.appDisabled = EM_L("true"); } - else if (!ds.getItemProperty(id, "appDisabled")) - ds.setItemProperty(id, EM_R("appDisabled"), EM_L("true")); - ds.setItemProperty(id, EM_R("availableUpdateURL"), null); - ds.setItemProperty(id, EM_R("availableUpdateVersion"), null); + ds.setItemProperties(id, properties); } // Must clean up outside of the loop. Modifying the container while @@ -3702,6 +3752,23 @@ ExtensionManager.prototype = { // Prevent extension update dialog from showing gPref.setBoolPref(PREF_UPDATE_NOTIFYUSER, false); + + // Re-enable flushing and flush anything that was deferred + try { + gAllowFlush = true; + if (gManifestNeedsFlush) { + gManifestNeedsFlush = false; + this._updateManifests(false); + } + if (gDSNeedsFlush) { + gDSNeedsFlush = false; + this.datasource.Flush(); + } + } + catch (e) { + ERROR("Error flushing caches: " + e); + } + return true; }, @@ -3914,10 +3981,20 @@ ExtensionManager.prototype = { * true if the application needs to restart again, false otherwise. */ _updateManifests: function(needsRestart) { - // Write the Startup Cache (All Items, visible or not) - StartupCache.write(); - // Write the Extensions Locations Manifest (Visible, enabled items) - this._updateExtensionsManifest(needsRestart); + // During startup we block flushing until the startup operations are all + // complete to reduce file accesses that can trigger bug 431065 + if (gAllowFlush) { + // Write the Startup Cache (All Items, visible or not) + StartupCache.write(); + // Write the Extensions Locations Manifest (Visible, enabled items) + this._updateExtensionsManifest(); + } + else { + gManifestNeedsFlush = true; + } + + // Notify nsAppRunner to update the compatibility manifest on next startup + this._extensionListChanged = needsRestart; }, /** @@ -3959,10 +4036,8 @@ ExtensionManager.prototype = { /** * Write the Extensions List - * @param needsRestart - * true if the application needs to restart again, false otherwise. */ - _updateExtensionsManifest: function(needsRestart) { + _updateExtensionsManifest: function() { // When an operation is performed that requires a component re-registration // (extension enabled/disabled, installed, uninstalled), we must write the // set of paths where extensions live so that the startup system can determine @@ -4004,9 +4079,6 @@ ExtensionManager.prototype = { // Cache the enabled list for annotating the crash report subsequently gPref.setCharPref(PREF_EM_ENABLED_ITEMS, enabledItems.join(",")); - - // Now refresh the compatibility manifest. - this._extensionListChanged = needsRestart; }, /** @@ -4765,8 +4837,7 @@ ExtensionManager.prototype = { availableUpdateHash : null, availableUpdateVersion: null, availableUpdateInfo : null }; - for (var p in props) - ds.setItemProperty(id, EM_R(p), props[p]); + ds.setItemProperties(id, props); ds.updateProperty(id, "availableUpdateURL"); this._setOp(id, OP_NEEDS_INSTALL); @@ -4806,8 +4877,7 @@ ExtensionManager.prototype = { availableUpdateHash : null, availableUpdateVersion : null, availableUpdateInfo : null }; - for (var p in props) - ds.setItemProperty(id, EM_R(p), props[p]); + ds.setItemProperties(id, props); ds.updateProperty(id, "availableUpdateURL"); this._setOp(id, OP_NEEDS_UPGRADE); @@ -5670,10 +5740,12 @@ ExtensionManager.prototype = { // being updated during an install. if (!manager) { var id = currItem.id - ds.setItemProperty(id, EM_R("availableUpdateURL"), null); - ds.setItemProperty(id, EM_R("availableUpdateHash"), null); - ds.setItemProperty(id, EM_R("availableUpdateVersion"), null); - ds.setItemProperty(id, EM_R("availableUpdateInfo"), null); + ds.setItemProperties(id, { + availableUpdateURL: null, + availableUpdateHash: null, + availableUpdateVersion: null, + availableUpdateInfo: null + }); ds.updateProperty(id, "availableUpdateURL"); ds.updateProperty(id, "updateable"); } @@ -7547,6 +7619,20 @@ ExtensionsDataSource.prototype = { this.Flush(); }, + /** + * Sets one or more properties for an item. + * @param id + * The ID of the item + * @param properties + * A JS object which maps properties to values. + */ + setItemProperties: function (id, properties) { + var item = getResourceForID(id); + for (var key in properties) + this._setProperty(this._inner, item, EM_R(key), properties[key]); + this.Flush(); + }, + /** * Inserts the RDF resource for an item into a container. * @param id @@ -8143,9 +8229,11 @@ ExtensionsDataSource.prototype = { hash = EM_L(addon.xpiHash); version = EM_L(addon.version); } - this.setItemProperty(addon.id, EM_R("availableUpdateURL"), url); - this.setItemProperty(addon.id, EM_R("availableUpdateHash"), hash); - this.setItemProperty(addon.id, EM_R("availableUpdateVersion"), version); + this.setItemProperties(addon.id, { + availableUpdateURL: url, + availableUpdateHash: hash, + availableUpdateVersion: version + }); this.updateProperty(addon.id, "availableUpdateURL"); }, @@ -8710,6 +8798,12 @@ ExtensionsDataSource.prototype = { }, Flush: function() { + // For some operations we block repeated flushing until all operations + // are complete to reduce file accesses that can trigger bug 431065 + if (!gAllowFlush) { + gDSNeedsFlush = true; + return; + } if (this._inner instanceof Ci.nsIRDFRemoteDataSource) this._inner.Flush(); },