Bug 1364768: Part 2 - Add AsyncShutdown finalizer support to DeferredSave. r?rhelmer draft
authorKris Maglione <maglione.k@gmail.com>
Sun, 14 May 2017 15:55:42 -0700
changeset 577571 c23fc0dedeae5778ae549588a04164734565489e
parent 577570 ee313b675c825a0510354280b4fb6c0588e3f589
child 577572 a965c65dccbc7e570d74fb5007242a3197ec52b4
push id58718
push usermaglione.k@gmail.com
push dateSun, 14 May 2017 23:25:47 +0000
reviewersrhelmer
bugs1364768
milestone55.0a1
Bug 1364768: Part 2 - Add AsyncShutdown finalizer support to DeferredSave. r?rhelmer MozReview-Commit-ID: LAvOErU1YB8
toolkit/mozapps/extensions/DeferredSave.jsm
--- 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();
+  },
+
 };