Backed out changeset 295b75fac0c8 (bug 940408) for test failures in B2g Desktop Linux & B2G ICS Emulator Opt
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 03 Dec 2013 12:53:27 +0100
changeset 158481 b4284cae2bffba5feef6846866ae73e16f3302ae
parent 158480 09667d4f65b46849829523931e42395d7e6da0ac
child 158482 d1e4fed327fe09619cabb204e19274d3e42eb615
push id3760
push usercbook@mozilla.com
push dateTue, 03 Dec 2013 11:53:42 +0000
treeherderfx-team@b4284cae2bff [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs940408
milestone28.0a1
backs out295b75fac0c8fc72df85892406ab51bf3ca0c4c1
Backed out changeset 295b75fac0c8 (bug 940408) for test failures in B2g Desktop Linux & B2G ICS Emulator Opt
browser/components/customizableui/src/CustomizableUI.jsm
toolkit/components/jsdownloads/src/DownloadIntegration.jsm
toolkit/components/search/nsSearchService.js
toolkit/components/social/SocialService.jsm
toolkit/modules/DeferredTask.jsm
toolkit/modules/tests/browser/browser.ini
toolkit/modules/tests/browser/browser_DeferredTask.js
toolkit/modules/tests/xpcshell/test_DeferredTask.js
toolkit/modules/tests/xpcshell/test_task.js
toolkit/modules/tests/xpcshell/xpcshell.ini
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -2544,17 +2544,17 @@ OverflowableToolbar.prototype = {
     win.UpdateUrlbarSearchSplitterState();
   },
 
   _onResize: function(aEvent) {
     if (!this._lazyResizeHandler) {
       this._lazyResizeHandler = new DeferredTask(this._onLazyResize.bind(this),
                                                  LAZY_RESIZE_INTERVAL_MS);
     }
-    this._lazyResizeHandler.arm();
+    this._lazyResizeHandler.start();
   },
 
   _moveItemsBackToTheirOrigin: function(shouldMoveAllItems) {
     let placements = gPlacements.get(this._toolbar.id);
     while (this._list.firstChild) {
       let child = this._list.firstChild;
       let minSize = this._collapsed.get(child.id);
 
@@ -2603,17 +2603,17 @@ OverflowableToolbar.prototype = {
 
     this._moveItemsBackToTheirOrigin();
   },
 
   _disable: function() {
     this._enabled = false;
     this._moveItemsBackToTheirOrigin(true);
     if (this._lazyResizeHandler) {
-      this._lazyResizeHandler.disarm();
+      this._lazyResizeHandler.cancel();
     }
   },
 
   _enable: function() {
     this._enabled = true;
     this.onOverflow();
   },
 
--- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm
@@ -20,18 +20,16 @@ this.EXPORTED_SYMBOLS = [
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
-                                  "resource://gre/modules/DeferredTask.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadStore",
                                   "resource://gre/modules/DownloadStore.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadImport",
                                   "resource://gre/modules/DownloadImport.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
                                   "resource://gre/modules/DownloadUIHelper.jsm");
@@ -1095,17 +1093,16 @@ this.DownloadHistoryObserver = function 
  * @param aStore
  *        The DownloadStore object used for saving.
  */
 this.DownloadAutoSaveView = function (aList, aStore)
 {
   this._list = aList;
   this._store = aStore;
   this._downloadsMap = new Map();
-  this._writer = new DeferredTask(() => this._store.save(), kSaveDelayMs);
 }
 
 this.DownloadAutoSaveView.prototype = {
   /**
    * DownloadList object linked to this view.
    */
   _list: null,
 
@@ -1136,27 +1133,67 @@ this.DownloadAutoSaveView.prototype = {
   /**
    * This map contains only Download objects that should be saved to disk, and
    * associates them with the result of their getSerializationHash function, for
    * the purpose of detecting changes to the relevant properties.
    */
   _downloadsMap: null,
 
   /**
-   * DeferredTask for the save operation.
+   * This is set to true when the save operation should be triggered.  This is
+   * required so that a new operation can be scheduled while the current one is
+   * in progress, without re-entering the save method.
+   */
+  _shouldSave: false,
+
+  /**
+   * nsITimer used for triggering the save operation after a delay, or null if
+   * saving has finished and there is no operation scheduled for execution.
+   *
+   * The logic here is different from the DeferredTask module in that multiple
+   * requests will never delay the operation for longer than the expected time
+   * (no grace delay), and the operation is never re-entered during execution.
+   */
+  _timer: null,
+
+  /**
+   * Timer callback used to serialize the list of downloads.
    */
-  _writer: null,
+  _save: function ()
+  {
+    Task.spawn(function () {
+      // Any save request received during execution will be handled later.
+      this._shouldSave = false;
+
+      // Execute the asynchronous save operation.
+      try {
+        yield this._store.save();
+      } catch (ex) {
+        Cu.reportError(ex);
+      }
+
+      // Handle requests received during the operation.
+      this._timer = null;
+      if (this._shouldSave) {
+        this.saveSoon();
+      }
+    }.bind(this)).then(null, Cu.reportError);
+  },
 
   /**
    * Called when the list of downloads changed, this triggers the asynchronous
    * serialization of the list of downloads.
    */
   saveSoon: function ()
   {
-    this._writer.arm();
+    this._shouldSave = true;
+    if (!this._timer) {
+      this._timer = new Timer(this._save.bind(this), kSaveDelayMs,
+                              Ci.nsITimer.TYPE_ONE_SHOT);
+    }
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// DownloadList view
 
   onDownloadAdded: function (aDownload)
   {
     if (DownloadIntegration.shouldPersistDownload(aDownload)) {
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -6,18 +6,16 @@ const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cr = Components.results;
 const Cu = Components.utils;
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/commonjs/sdk/core/promise.js");
 
-XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
-  "resource://gre/modules/AsyncShutdown.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
   "resource://gre/modules/DeferredTask.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
   "resource://gre/modules/TelemetryStopwatch.jsm");
@@ -2652,17 +2650,17 @@ Engine.prototype = {
     var url = this._getURLOfType(aResponseType);
     if (!url)
       FAIL("Engine object has no URL for response type " + aResponseType,
            Cr.NS_ERROR_FAILURE);
 
     url.addParam(aName, aValue);
 
     // Serialize the changes to file lazily
-    this.lazySerializeTask.arm();
+    this.lazySerializeTask.start();
   },
 
 #ifdef ANDROID
   get _defaultMobileResponseType() {
     let type = URLTYPE_SEARCH_HTML;
 
     let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
     let isTablet = sysInfo.get("tablet");
@@ -3868,18 +3866,17 @@ SearchService.prototype = {
       FAIL("Invalid template passed to addEngineWithDetails!");
     if (this._engines[aName])
       FAIL("An engine with that name already exists!", Cr.NS_ERROR_FILE_ALREADY_EXISTS);
 
     var engine = new Engine(getSanitizedFile(aName), SEARCH_DATA_XML, false);
     engine._initFromMetadata(aName, aIconURL, aAlias, aDescription,
                              aMethod, aTemplate);
     this._addEngineToStore(engine);
-    this.batchTask.disarm();
-    this.batchTask.arm();
+    this.batchTask.start();
   },
 
   addEngine: function SRCH_SVC_addEngine(aEngineURL, aDataType, aIconURL,
                                          aConfirm, aCallback) {
     LOG("addEngine: Adding \"" + aEngineURL + "\".");
     this._ensureInitialized();
     try {
       var uri = makeURI(aEngineURL);
@@ -3932,20 +3929,19 @@ SearchService.prototype = {
     }
 
     if (engineToRemove._readOnly) {
       // Just hide it (the "hidden" setter will notify) and remove its alias to
       // avoid future conflicts with other engines.
       engineToRemove.hidden = true;
       engineToRemove.alias = null;
     } else {
-      // Cancel the serialized task if it's pending.  Since the task is a
-      // synchronous function, we don't need to wait on the "finalize" method.
+      // Cancel the serialized task if it's running
       if (engineToRemove._lazySerializeTask) {
-        engineToRemove._lazySerializeTask.disarm();
+        engineToRemove._lazySerializeTask.cancel();
         engineToRemove._lazySerializeTask = null;
       }
 
       // Remove the engine file from disk (this might throw)
       engineToRemove._remove();
       engineToRemove._file = null;
 
       // Remove the engine from _sortedEngines
@@ -4134,29 +4130,32 @@ SearchService.prototype = {
             var engine = aEngine.QueryInterface(Ci.nsISearchEngine);
             LOG("nsSearchService::observe: Done installation of " + engine.name
                 + ".");
             this._addEngineToStore(engine.wrappedJSObject);
             if (engine.wrappedJSObject._useNow) {
               LOG("nsSearchService::observe: setting current");
               this.currentEngine = aEngine;
             }
-            this.batchTask.disarm();
-            this.batchTask.arm();
+            this.batchTask.start();
             break;
           case SEARCH_ENGINE_CHANGED:
           case SEARCH_ENGINE_REMOVED:
-            this.batchTask.disarm();
-            this.batchTask.arm();
+            this.batchTask.start();
             break;
         }
         break;
 
       case QUIT_APPLICATION_TOPIC:
         this._removeObservers();
+        if (this._batchTask) {
+          // Flush to disk immediately
+          this._batchTask.flush();
+        }
+        engineMetadataService.flush();
         break;
 
       case "nsPref:changed":
         let currPref = BROWSER_SEARCH_PREF + "selectedEngine";
         let defPref = BROWSER_SEARCH_PREF + "defaultenginename";
         if (aVerb == currPref && !this._changingCurrentEngine) {
           this._setEngineByPref("currentEngine", currPref);
         } else if (aVerb == defPref && !this._changingDefaultEngine) {
@@ -4205,26 +4204,16 @@ SearchService.prototype = {
     } // end engine iteration
   },
 
   _addObservers: function SRCH_SVC_addObservers() {
     Services.obs.addObserver(this, SEARCH_ENGINE_TOPIC, false);
     Services.obs.addObserver(this, QUIT_APPLICATION_TOPIC, false);
     Services.prefs.addObserver(BROWSER_SEARCH_PREF + "defaultenginename", this, false);
     Services.prefs.addObserver(BROWSER_SEARCH_PREF + "selectedEngine", this, false);
-
-    AsyncShutdown.profileBeforeChange.addBlocker(
-      "Search service: shutting down",
-      () => Task.spawn(function () {
-        if (this._batchTask) {
-          yield this._batchTask.finalize().then(null, Cu.reportError);
-        }
-        yield engineMetadataService.finalize();
-      }.bind(this))
-    );
   },
 
   _removeObservers: function SRCH_SVC_removeObservers() {
     Services.obs.removeObserver(this, SEARCH_ENGINE_TOPIC);
     Services.obs.removeObserver(this, QUIT_APPLICATION_TOPIC);
     Services.prefs.removeObserver(BROWSER_SEARCH_PREF + "defaultenginename", this);
     Services.prefs.removeObserver(BROWSER_SEARCH_PREF + "selectedEngine", this);
   },
@@ -4434,18 +4423,21 @@ var engineMetadataService = {
     if (changed) {
       this._commit();
     }
   },
 
   /**
    * Flush any waiting write.
    */
-  finalize: function () this._lazyWriter ? this._lazyWriter.finalize()
-                                         : Promise.resolve(),
+  flush: function epsFlush() {
+    if (this._lazyWriter) {
+      this._lazyWriter.flush();
+    }
+  },
 
   /**
    * Commit changes to disk, asynchronously.
    *
    * Calls to this function are actually delayed by LAZY_SERIALIZE_DELAY
    * (= 100ms). If the function is called again before the expiration of
    * the delay, commits are merged and the function is again delayed by
    * the same amount of time.
@@ -4472,24 +4464,22 @@ var engineMetadataService = {
         promise = promise.then(
           function onSuccess() {
             Services.obs.notifyObservers(null,
               SEARCH_SERVICE_TOPIC,
               SEARCH_SERVICE_METADATA_WRITTEN);
             LOG("metadata writeCommit: done");
           }
         );
-        // Use our error logging instead of the default one.
-        return TaskUtils.captureErrors(promise).then(null, () => {});
+        TaskUtils.captureErrors(promise);
       }
       this._lazyWriter = new DeferredTask(writeCommit, LAZY_SERIALIZE_DELAY);
     }
     LOG("metadata _commit: (re)setting timer");
-    this._lazyWriter.disarm();
-    this._lazyWriter.arm();
+    this._lazyWriter.start();
   },
   _lazyWriter: null
 };
 
 engineMetadataService._initState = engineMetadataService._InitStates.NOT_STARTED;
 
 const SEARCH_UPDATE_LOG_PREFIX = "*** Search update: ";
 
--- a/toolkit/components/social/SocialService.jsm
+++ b/toolkit/components/social/SocialService.jsm
@@ -182,27 +182,26 @@ let ActiveProviders = {
   },
 
   has: function (origin) {
     return (origin in this._providers);
   },
 
   add: function (origin) {
     this._providers[origin] = 1;
-    this._deferredTask.arm();
+    this._deferredTask.start();
   },
 
   delete: function (origin) {
     delete this._providers[origin];
-    this._deferredTask.arm();
+    this._deferredTask.start();
   },
 
   flush: function () {
-    this._deferredTask.disarm();
-    this._persist();
+    this._deferredTask.flush();
   },
 
   get _deferredTask() {
     delete this._deferredTask;
     return this._deferredTask = new DeferredTask(this._persist.bind(this), 0);
   },
 
   _persist: function () {
--- a/toolkit/modules/DeferredTask.jsm
+++ b/toolkit/modules/DeferredTask.jsm
@@ -1,299 +1,85 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-"use strict";
+this.EXPORTED_SYMBOLS = ["DeferredTask"];
 
-this.EXPORTED_SYMBOLS = [
-  "DeferredTask",
-];
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
 
 /**
- * Sets up a function or an asynchronous task whose execution can be triggered
- * after a defined delay.  Multiple attempts to run the task before the delay
- * has passed are coalesced.  The task cannot be re-entered while running, but
- * can be executed again after a previous run finished.
- *
- * A common use case occurs when a data structure should be saved into a file
- * every time the data changes, using asynchronous calls, and multiple changes
- * to the data may happen within a short time:
- *
- *   let saveDeferredTask = new DeferredTask(function* () {
- *     yield OS.File.writeAtomic(...);
- *     // Any uncaught exception will be reported.
- *   }, 2000);
- *
- *   // The task is ready, but will not be executed until requested.
- *
- * The "arm" method can be used to start the internal timer that will result in
- * the eventual execution of the task.  Multiple attempts to arm the timer don't
- * introduce further delays:
- *
- *   saveDeferredTask.arm();
- *
- *   // The task will be executed in 2 seconds from now.
+ * A task that will run after a delay. Multiple attempts to run the same task
+ * before the delay will be coalesced
  *
- *   yield waitOneSecond();
- *   saveDeferredTask.arm();
- *
- *   // The task will be executed in 1 second from now.
- *
- * The timer can be disarmed to reset the delay, or just to cancel execution:
- *
- *   saveDeferredTask.disarm();
- *   saveDeferredTask.arm();
- *
- *   // The task will be executed in 2 seconds from now.
- *
- * When the internal timer fires and the execution of the task starts, the task
- * cannot be canceled anymore.  It is however possible to arm the timer again
- * during the execution of the task, in which case the task will need to finish
- * before the timer is started again, thus guaranteeing a time of inactivity
- * between executions that is at least equal to the provided delay.
- *
- * The "finalize" method can be used to ensure that the task terminates
- * properly.  The promise it returns is resolved only after the last execution
- * of the task is finished.  To guarantee that the task is executed for the
- * last time, the method prevents any attempt to arm the timer again.
- *
- * If the timer is already armed when the "finalize" method is called, then the
- * task is executed immediately.  If the task was already running at this point,
- * then one last execution from start to finish will happen again, immediately
- * after the current execution terminates.  If the timer is not armed, the
- * "finalize" method only ensures that any running task terminates.
+ * Use this for instance if you write data to a file and you expect that you
+ * may have to rewrite data very soon afterwards. With |DeferredTask|, the
+ * task is delayed by a few milliseconds and, should a new change to the data
+ * occur during this period,
+ * 1/ only the final version of the data is actually written;
+ * 2/ a further grace delay is added to take into account other changes.
  *
- * For example, during shutdown, you may want to ensure that any pending write
- * is processed, using the latest version of the data if the timer is armed:
- *
- *   AsyncShutdown.profileBeforeChange.addBlocker(
- *     "Example service: shutting down",
- *     () => saveDeferredTask.finalize()
- *   );
- *
- * Instead, if you are going to delete the saved data from disk anyways, you
- * might as well prevent any pending write from starting, while still ensuring
- * that any write that is currently in progress terminates, so that the file is
- * not in use anymore:
- *
- *   saveDeferredTask.disarm();
- *   saveDeferredTask.finalize().then(() => OS.File.remove(...))
- *                              .then(null, Components.utils.reportError);
+ * Constructor
+ * @param aDelay The delay time in milliseconds.
+ * @param aCallback The code to execute after the delay.
  */
-
-////////////////////////////////////////////////////////////////////////////////
-//// Globals
-
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Promise",
-                                  "resource://gre/modules/Promise.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
-                                  "resource://gre/modules/Task.jsm");
-
-const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer",
-                                     "initWithCallback");
-
-////////////////////////////////////////////////////////////////////////////////
-//// DeferredTask
-
-/**
- * Sets up a task whose execution can be triggered after a delay.
- *
- * @param aTaskFn
- *        Function or generator function to execute.  This argument is passed to
- *        the "Task.spawn" method every time the task should be executed.  This
- *        task is never re-entered while running.
- * @param aDelayMs
- *        Time between executions, in milliseconds.  Multiple attempts to run
- *        the task before the delay has passed are coalesced.  This time of
- *        inactivity is guaranteed to pass between multiple executions of the
- *        task, except on finalization, when the task may restart immediately
- *        after the previous execution finished.
- */
-this.DeferredTask = function (aTaskFn, aDelayMs) {
-  this._taskFn = aTaskFn;
-  this._delayMs = aDelayMs;
+this.DeferredTask = function DeferredTask(aCallback, aDelay) {
+  this._callback = function onCallback() {
+    this._timer = null;
+    try {
+      aCallback();
+    } catch(e) {
+      Cu.reportError(e);
+    }
+  }.bind(this);
+  this._delay = aDelay;
 }
 
-this.DeferredTask.prototype = {
-  /**
-   * Function or generator function to execute.
-   */
-  _taskFn: null,
-
-  /**
-   * Time between executions, in milliseconds.
-   */
-  _delayMs: null,
-
-  /**
-   * Indicates whether the task is currently requested to start again later,
-   * regardless of whether it is currently running.
-   */
-  get isArmed() this._armed,
-  _armed: false,
-
-  /**
-   * Indicates whether the task is currently running.  This is always true when
-   * read from code inside the task function, but can also be true when read
-   * from external code, in case the task is an asynchronous generator function.
-   */
-  get isRunning() !!this._runningPromise,
-
-  /**
-   * Promise resolved when the current execution of the task terminates, or null
-   * if the task is not currently running.
-   */
-  _runningPromise: null,
-
-  /**
-   * nsITimer used for triggering the task after a delay, or null in case the
-   * task is running or there is no task scheduled for execution.
-   */
+DeferredTask.prototype = {
+  /* Callback */
+  _callback: null,
+  /* Delay */
+  _delay: null,
+  /* Timer */
   _timer: null,
 
   /**
-   * Actually starts the timer with the delay specified on construction.
+   * Check the current task state.
+   * @returns true if pending, false otherwise.
    */
-  _startTimer: function ()
-  {
-    this._timer = new Timer(this._timerCallback.bind(this), this._delayMs,
-                            Ci.nsITimer.TYPE_ONE_SHOT);
+  get isPending() {
+    return (this._timer != null);
   },
 
   /**
-   * Requests the execution of the task after the delay specified on
-   * construction.  Multiple calls don't introduce further delays.  If the task
-   * is running, the delay will start when the current execution finishes.
-   *
-   * The task will always be executed on a different tick of the event loop,
-   * even if the delay specified on construction is zero.  Multiple "arm" calls
-   * within the same tick of the event loop are guaranteed to result in a single
-   * execution of the task.
-   *
-   * @note By design, this method doesn't provide a way for the caller to detect
-   *       when the next execution terminates, or collect a result.  In fact,
-   *       doing that would often result in duplicate processing or logging.  If
-   *       a special operation or error logging is needed on completion, it can
-   *       be better handled from within the task itself, for example using a
-   *       try/catch/finally clause in the task.  The "finalize" method can be
-   *       used in the common case of waiting for completion on shutdown.
+   * Start (or postpone) task.
    */
-  arm: function ()
-  {
-    if (this._finalized) {
-      throw new Error("Unable to arm timer, the object has been finalized.");
+  start: function DeferredTask_start() {
+    if (this._timer) {
+      this._timer.cancel();
     }
+    this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    this._timer.initWithCallback(
+      this._callback, this._delay, Ci.nsITimer.TYPE_ONE_SHOT);
+  },
 
-    this._armed = true;
-
-    // In case the timer callback is running, do not create the timer now,
-    // because this will be handled by the timer callback itself.  Also, the
-    // timer is not restarted in case it is already running.
-    if (!this._runningPromise && !this._timer) {
-      this._startTimer();
+  /**
+   * Perform any postponed task immediately.
+   */
+  flush: function DeferredTask_flush() {
+    if (this._timer) {
+      this.cancel();
+      this._callback();
     }
   },
 
   /**
-   * Cancels any request for a delayed the execution of the task, though the
-   * task itself cannot be canceled in case it is already running.
-   *
-   * This method stops any currently running timer, thus the delay will restart
-   * from its original value in case the "arm" method is called again.
+   * Cancel any pending task.
    */
-  disarm: function () {
-    this._armed = false;
+  cancel: function DeferredTask_cancel() {
     if (this._timer) {
-      // Calling the "cancel" method and discarding the timer reference makes
-      // sure that the timer callback will not be called later, even if the
-      // timer thread has already posted the timer event on the main thread.
       this._timer.cancel();
       this._timer = null;
     }
-  },
-
-  /**
-   * Ensures that any pending task is executed from start to finish, while
-   * preventing any attempt to arm the timer again.
-   *
-   * - If the task is running and the timer is armed, then one last execution
-   *   from start to finish will happen again, immediately after the current
-   *   execution terminates, then the returned promise will be resolved.
-   * - If the task is running and the timer is not armed, the returned promise
-   *   will be resolved when the current execution terminates.
-   * - If the task is not running and the timer is armed, then the task is
-   *   started immediately, and the returned promise resolves when the new
-   *   execution terminates.
-   * - If the task is not running and the timer is not armed, the method returns
-   *   a resolved promise.
-   *
-   * @return {Promise}
-   * @resolves After the last execution of the task is finished.
-   * @rejects Never.
-   */
-  finalize: function () {
-    if (this._finalized) {
-      throw new Error("The object has been already finalized.");
-    }
-    this._finalized = true;
-
-    // If the timer is armed, it means that the task is not running but it is
-    // scheduled for execution.  Cancel the timer and run the task immediately.
-    if (this._timer) {
-      this.disarm();
-      this._timerCallback();
-    }
-
-    // Wait for the operation to be completed, or resolve immediately.
-    if (this._runningPromise) {
-      return this._runningPromise;
-    }
-    return Promise.resolve();
-  },
-  _finalized: false,
-
-  /**
-   * Timer callback used to run the delayed task.
-   */
-  _timerCallback: function ()
-  {
-    let runningDeferred = Promise.defer();
-
-    // All these state changes must occur at the same time directly inside the
-    // timer callback, to prevent race conditions and to ensure that all the
-    // methods behave consistently even if called from inside the task.  This
-    // means that the assignment of "this._runningPromise" must complete before
-    // the task gets a chance to start.
-    this._timer = null;
-    this._armed = false;
-    this._runningPromise = runningDeferred.promise;
-
-    runningDeferred.resolve(Task.spawn(function () {
-      // Execute the provided function asynchronously.
-      yield Task.spawn(this._taskFn).then(null, Cu.reportError);
-
-      // Now that the task has finished, we check the state of the object to
-      // determine if we should restart the task again.
-      if (this._armed) {
-        if (!this._finalized) {
-          this._startTimer();
-        } else {
-          // Execute the task again immediately, for the last time.  The isArmed
-          // property should return false while the task is running, and should
-          // remain false after the last execution terminates.
-          this._armed = false;
-          yield Task.spawn(this._taskFn).then(null, Cu.reportError);
-        }
-      }
-
-      // Indicate that the execution of the task has finished.  This happens
-      // synchronously with the previous state changes in the function.
-      this._runningPromise = null;
-    }.bind(this)).then(null, Cu.reportError));
-  },
+  }
 };
--- a/toolkit/modules/tests/browser/browser.ini
+++ b/toolkit/modules/tests/browser/browser.ini
@@ -1,7 +1,8 @@
 [DEFAULT]
 
+[browser_DeferredTask.js]
 [browser_Deprecated.js]
 [browser_Finder.js]
 [browser_Geometry.js]
 [browser_InlineSpellChecker.js]
 [browser_Troubleshoot.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/browser/browser_DeferredTask.js
@@ -0,0 +1,166 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let DeferredTask =
+  Components.utils.import("resource://gre/modules/DeferredTask.jsm", {}).DeferredTask;
+
+function test() {
+  waitForExplicitFinish();
+
+  nextTest();
+}
+
+function nextTest() {
+  let test = tests.shift();
+  if (test) {
+    info("Starting " + test.name);
+    executeSoon(test.bind(null, nextTest));
+  } else {
+    executeSoon(finish);
+  }
+}
+
+let tests = [
+  function checkRanOnce(next) {
+    // Test that a deferred task runs only once.
+    let deferredTaskCounter = 0;
+    let deferredTask = new DeferredTask(function checkDeferred() {
+      is(++deferredTaskCounter, 1, "Deferred task ran once");
+      if (deferredTaskCounter > 1)
+        return; // shouldn't happen!
+      next();
+    }, 100);
+
+    deferredTask.start();
+    deferredTask.start();
+    deferredTask.start();
+  },
+
+  function testOrdering(next) {
+    // Test the deferred task running order.
+    let ARan, BRan;
+    let deferredTaskA = new DeferredTask(function () {
+      info("Deferred task A running");
+      ARan = true;
+      ok(!BRan, "Deferred task B shouldn't have run yet");
+    }, 10);
+    let deferredTaskB = new DeferredTask(function () {
+      info("Deferred task B running");
+      BRan = true;
+      ok(ARan, "Deferred task A should have run already");
+      executeSoon(next);
+    }, 100);
+
+    deferredTaskA.start();
+    deferredTaskB.start();
+  },
+
+  function testFlush(next) {
+    // Test the flush method.
+    let deferedTaskCounter = 0;
+    let deferredTask = new DeferredTask(function () {
+      is(++deferedTaskCounter, 1, "Deferred task should only run once");
+    }, 10);
+
+    is(deferedTaskCounter, 0, "Deferred task shouldn't run right away");
+    deferredTask.flush();
+    is(deferedTaskCounter, 0, "Deferred task shouldn't run after flush() if it wasn't started");
+    deferredTask.start();
+    is(deferedTaskCounter, 0, "Deferred task shouldn't run immediately after start");
+    deferredTask.flush();
+    is(deferedTaskCounter, 1, "Deferred task should run after flush() after being started");
+    next();
+  },
+
+  function testCancel(next) {
+    // Test the cancel method.
+    let deferredTask1, deferredTask2, deferredTask3;
+    let deferredTask2Ran = false;
+    deferredTask1 = new DeferredTask(function () {
+      info("Deferred task 1 running");
+      deferredTask2.cancel();
+      info("Deferred task 2 canceled");
+    }, 10);
+    deferredTask2 = new DeferredTask(function () {
+      info("Deferred task 2 running");
+      deferredTask2Ran = true;
+    }, 100);
+    deferredTask3 = new DeferredTask(function () {
+      info("Deferred task 3 running");
+      is(deferredTask2Ran, false, "Deferred task 2 shouldn't run");
+      next();
+    }, 200);
+
+    deferredTask1.start();
+    deferredTask2.start();
+    deferredTask3.start();
+  },
+
+  function testIsPending(next) {
+    // Test the pending property.
+    let deferredTask1, deferredTask2;
+    let deferredTask1 = new DeferredTask(function () {
+      info("Deferred task 1 running");
+      is(deferredTask1.isPending, false, "Deferred task 1 shouldn't be pending");
+      is(deferredTask2.isPending, true, "Deferred task 2 should be pending");
+    }, 100);
+    let deferredTask2 = new DeferredTask(function () {
+      info("Deferred task 2 running");
+      is(deferredTask1.isPending, false, "Deferred task 1 shouldn't be pending");
+      is(deferredTask2.isPending, false, "Deferred task 2 shouldn't be pending");
+      next();
+    }, 200);
+
+    is(deferredTask1.isPending, false, "Deferred task 1 shouldn't be pending");
+    is(deferredTask2.isPending, false, "Deferred task 2 shouldn't be pending");
+    deferredTask1.start();
+    deferredTask2.start();
+    is(deferredTask1.isPending, true, "Deferred task 1 should be pending");
+    is(deferredTask2.isPending, true, "Deferred task 2 should be pending");
+  },
+
+  function testRestartingTask(next) {
+    // Test restarting a task from other task. 
+    let deferredTask1, deferredTask2, deferredTask3;
+    let deferredTask1 = new DeferredTask(function () {
+      info("Deferred task 1 running");
+      is(deferredTask1.isPending, false, "Deferred task 1 shouldn't be pending");
+      is(deferredTask2.isPending, false, "Deferred task 2 shouldn't be pending");
+      is(deferredTask3.isPending, false, "Deferred task 3 shouldn't be pending");
+      next();
+    }, 100);
+    let deferredTask2 = new DeferredTask(function () {
+      info("Deferred task 2 running");
+      deferredTask1.start();
+      deferredTask3.start();
+    }, 50);
+    let deferredTask3 = new DeferredTask(function () {
+      info("Deferred task 3 running");
+      is(deferredTask1.isPending, true, "Deferred task 1 should be pending");
+    }, 75);
+
+    deferredTask1.start();
+    deferredTask2.start();
+  },
+
+  function testRestartItselfTask(next) {
+    // Test restarting a task from the same task callback. 
+    let deferredTask;
+    let taskReRun = false;
+    let deferredTask = new DeferredTask(function () {
+      info("Deferred task running");
+      is(deferredTask.isPending, false, "Deferred task shouldn't be pending");
+      if (!taskReRun) {
+        taskReRun = true;
+        info("Deferred task restart");
+        deferredTask.start();
+        is(deferredTask.isPending, true, "Deferred task should be pending");
+      } else {
+        next();
+      }
+    }, 100);
+
+    deferredTask.start();
+  }
+];
deleted file mode 100644
--- a/toolkit/modules/tests/xpcshell/test_DeferredTask.js
+++ /dev/null
@@ -1,392 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * This file tests the DeferredTask.jsm module.
- */
-
-////////////////////////////////////////////////////////////////////////////////
-/// Globals
-
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "DeferredTask",
-                                  "resource://gre/modules/DeferredTask.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Promise",
-                                  "resource://gre/modules/Promise.jsm");
-
-/**
- * Due to the nature of this module, most of the tests are time-dependent.  All
- * the timeouts are designed to occur at multiples of this granularity value,
- * in milliseconds, that should be high enough to prevent intermittent failures,
- * but low enough to prevent an excessive overall test execution time.
- */
-const T = 100;
-
-/**
- * Waits for the specified timeout before resolving the returned promise.
- */
-function promiseTimeout(aTimeoutMs)
-{
-  let deferred = Promise.defer();
-  do_timeout(aTimeoutMs, deferred.resolve);
-  return deferred.promise;
-}
-
-function run_test()
-{
-  run_next_test();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-//// Tests
-
-/**
- * Creates a simple DeferredTask and executes it once.
- */
-add_test(function test_arm_simple()
-{
-  new DeferredTask(run_next_test, 10).arm();
-});
-
-/**
- * Checks that the delay set for the task is respected.
- */
-add_test(function test_arm_delay_respected()
-{
-  let executed1 = false;
-  let executed2 = false;
-
-  new DeferredTask(function () {
-    executed1 = true;
-    do_check_false(executed2);
-  }, 1*T).arm();
-
-  new DeferredTask(function () {
-    executed2 = true;
-    do_check_true(executed1);
-    run_next_test();
-  }, 2*T).arm();
-});
-
-/**
- * Checks that calling "arm" again does not introduce further delay.
- */
-add_test(function test_arm_delay_notrestarted()
-{
-  let executed = false;
-
-  // Create a task that will run later.
-  let deferredTask = new DeferredTask(() => { executed = true; }, 4*T);
-  deferredTask.arm();
-
-  // Before the task starts, call "arm" again.
-  do_timeout(2*T, () => deferredTask.arm());
-
-  // The "arm" call should not have introduced further delays.
-  do_timeout(5*T, function () {
-    do_check_true(executed);
-    run_next_test();
-  });
-});
-
-/**
- * Checks that a task runs only once when armed multiple times synchronously.
- */
-add_test(function test_arm_coalesced()
-{
-  let executed = false;
-
-  let deferredTask = new DeferredTask(function () {
-    do_check_false(executed);
-    executed = true;
-    run_next_test();
-  }, 50);
-
-  deferredTask.arm();
-  deferredTask.arm();
-});
-
-/**
- * Checks that a task runs only once when armed multiple times synchronously,
- * even when it has been created with a delay of zero milliseconds.
- */
-add_test(function test_arm_coalesced_nodelay()
-{
-  let executed = false;
-
-  let deferredTask = new DeferredTask(function () {
-    do_check_false(executed);
-    executed = true;
-    run_next_test();
-  }, 0);
-
-  deferredTask.arm();
-  deferredTask.arm();
-});
-
-/**
- * Checks that a task can be armed again while running.
- */
-add_test(function test_arm_recursive()
-{
-  let executed = false;
-
-  let deferredTask = new DeferredTask(function () {
-    if (!executed) {
-      executed = true;
-      deferredTask.arm();
-    } else {
-      run_next_test();
-    }
-  }, 50);
-
-  deferredTask.arm();
-});
-
-/**
- * Checks that calling "arm" while an asynchronous task is running waits until
- * the task is finished before restarting the delay.
- */
-add_test(function test_arm_async()
-{
-  let finishedExecution = false;
-  let finishedExecutionAgain = false;
-
-  // Create a task that will run later.
-  let deferredTask = new DeferredTask(function () {
-    yield promiseTimeout(4*T);
-    if (!finishedExecution) {
-      finishedExecution = true;
-    } else if (!finishedExecutionAgain) {
-      finishedExecutionAgain = true;
-    }
-  }, 2*T);
-  deferredTask.arm();
-
-  // While the task is running, call "arm" again.  This will result in a wait
-  // of 2*T until the task finishes, then another 2*T for the normal task delay
-  // specified on construction.
-  do_timeout(4*T, function () {
-    do_check_true(deferredTask.isRunning);
-    do_check_false(finishedExecution);
-    deferredTask.arm();
-  });
-
-  // This will fail in case the task was started without waiting 2*T after it
-  // has finished.
-  do_timeout(7*T, function () {
-    do_check_false(deferredTask.isRunning);
-    do_check_true(finishedExecution);
-  });
-
-  // This is in the middle of the second execution.
-  do_timeout(10*T, function () {
-    do_check_true(deferredTask.isRunning);
-    do_check_false(finishedExecutionAgain);
-  });
-
-  // Wait enough time to verify that the task was executed as expected.
-  do_timeout(13*T, function () {
-    do_check_false(deferredTask.isRunning);
-    do_check_true(finishedExecutionAgain);
-    run_next_test();
-  });
-});
-
-/**
- * Checks that an armed task can be disarmed.
- */
-add_test(function test_disarm()
-{
-  // Create a task that will run later.
-  let deferredTask = new DeferredTask(function () {
-    do_throw("This task should not run.");
-  }, 2*T);
-  deferredTask.arm();
-
-  // Disable execution later, but before the task starts.
-  do_timeout(1*T, () => deferredTask.disarm());
-
-  // Wait enough time to verify that the task did not run.
-  do_timeout(3*T, run_next_test);
-});
-
-/**
- * Checks that calling "disarm" allows the delay to be restarted.
- */
-add_test(function test_disarm_delay_restarted()
-{
-  let executed = false;
-
-  let deferredTask = new DeferredTask(() => { executed = true; }, 4*T);
-  deferredTask.arm();
-
-  do_timeout(2*T, function () {
-    deferredTask.disarm();
-    deferredTask.arm();
-  });
-
-  do_timeout(5*T, function () {
-    do_check_false(executed);
-  });
-
-  do_timeout(7*T, function () {
-    do_check_true(executed);
-    run_next_test();
-  });
-});
-
-/**
- * Checks that calling "disarm" while an asynchronous task is running does not
- * prevent the task to finish.
- */
-add_test(function test_disarm_async()
-{
-  let finishedExecution = false;
-
-  let deferredTask = new DeferredTask(function () {
-    deferredTask.arm();
-    yield promiseTimeout(2*T);
-    finishedExecution = true;
-  }, 1*T);
-  deferredTask.arm();
-
-  do_timeout(2*T, function () {
-    do_check_true(deferredTask.isRunning);
-    do_check_true(deferredTask.isArmed);
-    do_check_false(finishedExecution);
-    deferredTask.disarm();
-  });
-
-  do_timeout(4*T, function () {
-    do_check_false(deferredTask.isRunning);
-    do_check_false(deferredTask.isArmed);
-    do_check_true(finishedExecution);
-    run_next_test();
-  });
-});
-
-/**
- * Checks that calling "arm" immediately followed by "disarm" while an
- * asynchronous task is running does not cause it to run again.
- */
-add_test(function test_disarm_immediate_async()
-{
-  let executed = false;
-
-  let deferredTask = new DeferredTask(function () {
-    do_check_false(executed);
-    executed = true;
-    yield promiseTimeout(2*T);
-  }, 1*T);
-  deferredTask.arm();
-
-  do_timeout(2*T, function () {
-    do_check_true(deferredTask.isRunning);
-    do_check_false(deferredTask.isArmed);
-    deferredTask.arm();
-    deferredTask.disarm();
-  });
-
-  do_timeout(4*T, function () {
-    do_check_true(executed);
-    do_check_false(deferredTask.isRunning);
-    do_check_false(deferredTask.isArmed);
-    run_next_test();
-  });
-});
-
-/**
- * Checks the isArmed and isRunning properties with a synchronous task.
- */
-add_test(function test_isArmed_isRunning()
-{
-  let deferredTask = new DeferredTask(function () {
-    do_check_true(deferredTask.isRunning);
-    do_check_false(deferredTask.isArmed);
-    deferredTask.arm();
-    do_check_true(deferredTask.isArmed);
-    deferredTask.disarm();
-    do_check_false(deferredTask.isArmed);
-    run_next_test();
-  }, 50);
-
-  do_check_false(deferredTask.isArmed);
-  deferredTask.arm();
-  do_check_true(deferredTask.isArmed);
-  do_check_false(deferredTask.isRunning);
-});
-
-/**
- * Checks that the "finalize" method executes a synchronous task.
- */
-add_test(function test_finalize()
-{
-  let executed = false;
-  let timePassed = false;
-
-  let deferredTask = new DeferredTask(function () {
-    do_check_false(timePassed);
-    executed = true;
-  }, 2*T);
-  deferredTask.arm();
-
-  do_timeout(1*T, () => { timePassed = true; });
-
-  // This should trigger the immediate execution of the task.
-  deferredTask.finalize().then(function () {
-    do_check_true(executed);
-    run_next_test();
-  });
-});
-
-/**
- * Checks that the "finalize" method executes the task again from start to
- * finish in case it is already running.
- */
-add_test(function test_finalize_executes_entirely()
-{
-  let executed = false;
-  let executedAgain = false;
-  let timePassed = false;
-
-  let deferredTask = new DeferredTask(function () {
-    // The first time, we arm the timer again and set up the finalization.
-    if (!executed) {
-      deferredTask.arm();
-      do_check_true(deferredTask.isArmed);
-      do_check_true(deferredTask.isRunning);
-
-      deferredTask.finalize().then(function () {
-        // When we reach this point, the task must be finished.
-        do_check_true(executedAgain);
-        do_check_false(timePassed);
-        do_check_false(deferredTask.isArmed);
-        do_check_false(deferredTask.isRunning);
-        run_next_test();
-      });
-
-      // The second execution triggered by the finalization waits 1*T for the
-      // current task to finish (see the timeout below), but then it must not
-      // wait for the 2*T specified on construction as normal task delay.  The
-      // second execution will finish after the timeout below has passed again,
-      // for a total of 2*T of wait time.
-      do_timeout(3*T, () => { timePassed = true; });
-    }
-
-    yield promiseTimeout(1*T);
-
-    // Just before finishing, indicate if we completed the second execution.
-    if (executed) {
-      do_check_true(deferredTask.isRunning);
-      executedAgain = true;
-    } else {
-      executed = true;
-    }
-  }, 2*T);
-
-  deferredTask.arm();
-});
--- a/toolkit/modules/tests/xpcshell/test_task.js
+++ b/toolkit/modules/tests/xpcshell/test_task.js
@@ -113,28 +113,16 @@ add_test(function test_spawn_function()
   }).then(function (result) {
     do_check_eq("This is not a generator.", result);
     run_next_test();
   }, function (ex) {
     do_throw("Unexpected error: " + ex);
   });
 });
 
-add_test(function test_spawn_function_returning_promise()
-{
-  Task.spawn(function () {
-    return promiseResolvedLater("Resolution value.");
-  }).then(function (result) {
-    do_check_eq("Resolution value.", result);
-    run_next_test();
-  }, function (ex) {
-    do_throw("Unexpected error: " + ex);
-  });
-});
-
 add_test(function test_spawn_function_exceptions()
 {
   Task.spawn(function () {
     throw new Error("Exception uncaught by task.");
   }).then(function (result) {
     do_throw("Unexpected success!");
   }, function (ex) {
     do_check_eq("Exception uncaught by task.", ex.message);
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -2,17 +2,16 @@
 head =
 tail =
 support-files =
   propertyLists/bug710259_propertyListBinary.plist
   propertyLists/bug710259_propertyListXML.plist
   chromeappsstore.sqlite
 
 [test_AsyncShutdown.js]
-[test_DeferredTask.js]
 [test_dict.js]
 [test_FileUtils.js]
 [test_Http.js]
 [test_Log.js]
 [test_PermissionsUtils.js]
 [test_Preferences.js]
 [test_Promise.js]
 [test_propertyListsUtils.js]