Bug 1365970 - Move data collector timer in the content process to idle dispatch. r=mikedeboer
authorWill Wang <wiwang@mozilla.com>
Wed, 13 Sep 2017 21:06:06 +0800
changeset 380707 197a9e821ce8a565b1ec104b77a28b0de30b90fa
parent 380706 cfcce8492eb2bcc3fdf2bdd8c75c01b1836028ad
child 380708 20ed0d0bd4b8412acf613ed586f7b17fa24ece2d
push id32492
push userarchaeopteryx@coole-files.de
push dateWed, 13 Sep 2017 21:59:20 +0000
treeherdermozilla-central@8645a74bbbd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmikedeboer
bugs1365970
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1365970 - Move data collector timer in the content process to idle dispatch. r=mikedeboer
browser/components/sessionstore/content/content-sessionStore.js
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -46,16 +46,18 @@ var gCurrentEpoch = 0;
 
 // A bound to the size of data to store for DOM Storage.
 const DOM_STORAGE_LIMIT_PREF = "browser.sessionstore.dom_storage_limit";
 
 // This pref controls whether or not we send updates to the parent on a timeout
 // or not, and should only be used for tests or debugging.
 const TIMEOUT_DISABLED_PREF = "browser.sessionstore.debug.no_auto_updates";
 
+const PREF_INTERVAL = "browser.sessionstore.interval";
+
 const kNoIndex = Number.MAX_SAFE_INTEGER;
 const kLastIndex = Number.MAX_SAFE_INTEGER - 1;
 
 /**
  * A function that will recursively call |cb| to collected data for all
  * non-dynamic frames in the current frame/docShell tree.
  */
 function mapFrameTree(callback) {
@@ -728,29 +730,46 @@ var MessageQueue = {
 
   /**
    * The delay (in ms) used to delay sending changes after data has been
    * invalidated.
    */
   BATCH_DELAY_MS: 1000,
 
   /**
+   * The minimum idle period (in ms) we need for sending data to chrome process.
+   */
+  NEEDED_IDLE_PERIOD_MS: 5,
+
+  /**
+   * Timeout for waiting an idle period to send data. We will set this from
+   * the pref "browser.sessionstore.interval".
+   */
+  _timeoutWaitIdlePeriodMs: null,
+
+  /**
    * The current timeout ID, null if there is no queue data. We use timeouts
    * to damp a flood of data changes and send lots of changes as one batch.
    */
   _timeout: null,
 
   /**
    * Whether or not sending batched messages on a timer is disabled. This should
    * only be used for debugging or testing. If you need to access this value,
    * you should probably use the timeoutDisabled getter.
    */
   _timeoutDisabled: false,
 
   /**
+   * The idle callback ID referencing an active idle callback. When no idle
+   * callback is pending, this is null.
+   * */
+  _idleCallbackID: null,
+
+  /**
    * True if batched messages are not being fired on a timer. This should only
    * ever be true when debugging or during tests.
    */
   get timeoutDisabled() {
     return this._timeoutDisabled;
   },
 
   /**
@@ -766,28 +785,58 @@ var MessageQueue = {
     }
 
     return val;
   },
 
   init() {
     this.timeoutDisabled =
       Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
+    this._timeoutWaitIdlePeriodMs =
+      Services.prefs.getIntPref(PREF_INTERVAL);
 
     Services.prefs.addObserver(TIMEOUT_DISABLED_PREF, this);
+    Services.prefs.addObserver(PREF_INTERVAL, this);
   },
 
   uninit() {
     Services.prefs.removeObserver(TIMEOUT_DISABLED_PREF, this);
+    Services.prefs.removeObserver(PREF_INTERVAL, this);
+    this.cleanupTimers();
+  },
+
+  /**
+   * Cleanup pending idle callback and timer.
+   */
+  cleanupTimers() {
+    if (this._idleCallbackID) {
+      content.cancelIdleCallback(this._idleCallbackID);
+      this._idleCallbackID = null;
+    }
+    if (this._timeout) {
+      clearTimeout(this._timeout);
+      this._timeout = null;
+    }
   },
 
   observe(subject, topic, data) {
-    if (topic == "nsPref:changed" && data == TIMEOUT_DISABLED_PREF) {
-      this.timeoutDisabled =
-        Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
+    if (topic == "nsPref:changed") {
+      switch (data) {
+        case TIMEOUT_DISABLED_PREF:
+          this.timeoutDisabled =
+            Services.prefs.getBoolPref(TIMEOUT_DISABLED_PREF);
+          break;
+        case PREF_INTERVAL:
+          this._timeoutWaitIdlePeriodMs =
+            Services.prefs.getIntPref(PREF_INTERVAL);
+          break;
+        default:
+          debug("received unknown message '" + data + "'");
+          break;
+      }
     }
   },
 
   /**
    * Pushes a given |value| onto the queue. The given |key| represents the type
    * of data that is stored and can override data that has been queued before
    * but has not been sent to the parent process, yet.
    *
@@ -798,39 +847,58 @@ var MessageQueue = {
    *        process.
    */
   push(key, fn) {
     this._data.set(key, fn);
 
     if (!this._timeout && !this._timeoutDisabled) {
       // Wait a little before sending the message to batch multiple changes.
       this._timeout = setTimeoutWithTarget(
-        () => this.send(), this.BATCH_DELAY_MS, tabEventTarget);
+        () => this.sendWhenIdle(), this.BATCH_DELAY_MS, tabEventTarget);
     }
   },
 
   /**
+   * Sends queued data when the remaining idle time is enough or waiting too
+   * long; otherwise, request an idle time again. If the |deadline| is not
+   * given, this function is going to schedule the first request.
+   *
+   * @param deadline (object)
+   *        An IdleDeadline object passed by requestIdleCallback().
+   */
+  sendWhenIdle(deadline) {
+    if (deadline) {
+      if (deadline.didTimeout || deadline.timeRemaining() > MessageQueue.NEEDED_IDLE_PERIOD_MS) {
+        MessageQueue.send();
+        return;
+      }
+    } else if (MessageQueue._idleCallbackID) {
+      // Bail out if there's a pending run.
+      return;
+    }
+    MessageQueue._idleCallbackID =
+      content.requestIdleCallback(MessageQueue.sendWhenIdle, {timeout: MessageQueue._timeoutWaitIdlePeriodMs});
+   },
+
+  /**
    * Sends queued data to the chrome process.
    *
    * @param options (object)
    *        {flushID: 123} to specify that this is a flush
    *        {isFinal: true} to signal this is the final message sent on unload
    */
   send(options = {}) {
     // Looks like we have been called off a timeout after the tab has been
     // closed. The docShell is gone now and we can just return here as there
     // is nothing to do.
     if (!docShell) {
       return;
     }
 
-    if (this._timeout) {
-      clearTimeout(this._timeout);
-      this._timeout = null;
-    }
+    this.cleanupTimers();
 
     let flushID = (options && options.flushID) || 0;
     let histID = "FX_SESSION_RESTORE_CONTENT_COLLECT_DATA_MS";
 
     let data = {};
     for (let [key, func] of this._data) {
       if (key != "isPrivate") {
         TelemetryStopwatch.startKeyed(histID, key);