Bug 1364768: Part 2 - Add AsyncShutdown finalizer support to DeferredSave. r?rhelmer
MozReview-Commit-ID: LAvOErU1YB8
--- a/toolkit/mozapps/extensions/DeferredSave.jsm
+++ b/toolkit/mozapps/extensions/DeferredSave.jsm
@@ -38,16 +38,21 @@ parentLogger.addAppender(new Log.DumpApp
// messages at runtime.
// If the "extensions.logging.enabled" preference is
// missing or 'false', messages at the WARNING and higher
// severity should be logged to the JS console and standard error.
// If "extensions.logging.enabled" is set to 'true', messages
// at DEBUG and higher should go to JS console and standard error.
Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
+ "resource://gre/modules/AsyncShutdown.jsm");
+
const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
/**
* Preference listener which listens for a change in the
* "extensions.logging.enabled" preference and changes the logging level of the
* parent 'addons' level logger accordingly.
*/
@@ -78,31 +83,40 @@ PrefObserver.init();
/**
* A module to manage deferred, asynchronous writing of data files
* to disk. Writing is deferred by waiting for a specified delay after
* a request to save the data, before beginning to write. If more than
* one save request is received during the delay, all requests are
* fulfilled by a single write.
*
* @constructor
- * @param aPath
+ * @param {string} aPath
* String representing the full path of the file where the data
* is to be written.
- * @param aDataProvider
+ * @param {function} aDataProvider
* Callback function that takes no argument and returns the data to
* be written. If aDataProvider returns an ArrayBufferView, the
* bytes it contains are written to the file as is.
* If aDataProvider returns a String the data are UTF-8 encoded
* and then written to the file.
- * @param [optional] aDelay
+ * @param {object | integer} [aOptions]
* The delay in milliseconds between the first saveChanges() call
* that marks the data as needing to be saved, and when the DeferredSave
* begins writing the data to disk. Default 50 milliseconds.
+ *
+ * Or, an options object containing:
+ * - delay: A delay in milliseconds.
+ * - finalizeAt: An AsyncShutdown blocker during which to
+ * finalize any pending writes.
*/
-this.DeferredSave = function(aPath, aDataProvider, aDelay) {
+this.DeferredSave = function(aPath, aDataProvider, aOptions = {}) {
+ if (typeof aOptions == "number") {
+ aOptions = {delay: aOptions};
+ }
+
// Create a new logger (child of 'DeferredSave' logger)
// for use by this particular instance of DeferredSave object
let leafName = OS.Path.basename(aPath);
let logger_id = DEFERREDSAVE_PARENT_LOGGER_ID + "." + leafName;
this.logger = Log.repository.getLogger(logger_id);
// @type {Deferred|null}, null when no data needs to be written
// @resolves with the result of OS.File.writeAtomic when all writes complete
@@ -136,31 +150,40 @@ this.DeferredSave = function(aPath, aDat
// The number of times the data became dirty while
// another save was in progress
this.overlappedSaves = 0;
// Error returned by the most recent write (if any)
this._lastError = null;
- if (aDelay && (aDelay > 0))
- this._delay = aDelay;
+ if (aOptions.delay && (aOptions.delay > 0))
+ this._delay = aOptions.delay;
else
this._delay = DEFAULT_SAVE_DELAY_MS;
+
+ this._finalizeAt = aOptions.finalizeAt || AsyncShutdown.profileBeforeChange;
+ this._finalize = this._finalize.bind(this);
+ this._finalizeAt.addBlocker(`DeferredSave: writing data to ${aPath}`,
+ this._finalize);
}
this.DeferredSave.prototype = {
get dirty() {
return this._pending || this.writeInProgress;
},
get lastError() {
return this._lastError;
},
+ get path() {
+ return this._path;
+ },
+
// Start the pending timer if data is dirty
_startTimer() {
if (!this._pending) {
return;
}
this.logger.debug("Starting timer");
if (!this._timer)
@@ -258,10 +281,15 @@ this.DeferredSave.prototype = {
if (this._timer) {
this._timer.cancel();
this._timer = null;
}
this._deferredSave();
}
return this._writing;
- }
+ },
+
+ _finalize() {
+ return this.flush();
+ },
+
};