Bug 827976 - Add requestIdleCallback support to Timer.jsm. r=mconley
☠☠ backed out by bf3cbcc82527 ☠ ☠
authorFelipe Gomes <felipc@gmail.com>
Fri, 01 Mar 2019 21:07:11 +0000
changeset 462021 66cffb171024564fa70e9f8d237c76181cc51514
parent 462020 845e1d0b24022796c587f7121825046cbcbc02f9
child 462022 f976c2d4cebb29588222e0c5122c475aae3be509
push id35634
push userrmaries@mozilla.com
push dateSat, 02 Mar 2019 09:26:10 +0000
treeherdermozilla-central@4166cae81546 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmconley
bugs827976
milestone67.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 827976 - Add requestIdleCallback support to Timer.jsm. r=mconley Differential Revision: https://phabricator.services.mozilla.com/D21084
toolkit/modules/Timer.jsm
toolkit/modules/tests/xpcshell/test_timer.js
tools/lint/eslint/modules.json
--- a/toolkit/modules/Timer.jsm
+++ b/toolkit/modules/Timer.jsm
@@ -4,22 +4,23 @@
 
 "use strict";
 
 /**
  * JS module implementation of setTimeout and clearTimeout.
  */
 
 var EXPORTED_SYMBOLS = ["setTimeout", "setTimeoutWithTarget", "clearTimeout",
-                        "setInterval", "setIntervalWithTarget", "clearInterval"];
+                        "setInterval", "setIntervalWithTarget", "clearInterval",
+                        "requestIdleCallback", "cancelIdleCallback"];
 
 // This gives us >=2^30 unique timer IDs, enough for 1 per ms for 12.4 days.
 var gNextId = 1; // setTimeout and setInterval must return a positive integer
 
-var gTimerTable = new Map(); // int -> nsITimer
+var gTimerTable = new Map(); // int -> nsITimer or idleCallback
 
 // Don't generate this for every timer.
 var setTimeout_timerCallbackQI = ChromeUtils.generateQI([Ci.nsITimerCallback, Ci.nsINamed]);
 
 function _setTimeoutOrIsInterval(aCallback, aMilliseconds, aIsInterval,
                                  aTarget, aArgs) {
   if (typeof aCallback !== "function") {
     throw new Error(`callback is not a function in ${aIsInterval ? "setInterval" : "setTimeout"}`);
@@ -81,8 +82,32 @@ function setIntervalWithTarget(aCallback
 }
 
 var clearInterval = this.clearTimeout = function clearTimeout(aId) {
   if (gTimerTable.has(aId)) {
     gTimerTable.get(aId).cancel();
     gTimerTable.delete(aId);
   }
 };
+
+function requestIdleCallback(aCallback, aOptions) {
+  if (typeof aCallback !== "function") {
+    throw new Error("callback is not a function in requestIdleCallback");
+  }
+  let id = gNextId++;
+
+  let callback = (...aArgs) => {
+    if (gTimerTable.has(id)) {
+      gTimerTable.delete(id);
+      aCallback(...aArgs);
+    }
+  };
+
+  ChromeUtils.idleDispatch(callback, aOptions);
+  gTimerTable.set(id, callback);
+  return id;
+}
+
+function cancelIdleCallback(aId) {
+  if (gTimerTable.has(aId)) {
+    gTimerTable.delete(aId);
+  }
+}
--- a/toolkit/modules/tests/xpcshell/test_timer.js
+++ b/toolkit/modules/tests/xpcshell/test_timer.js
@@ -58,21 +58,22 @@ add_task(async function test_setInterval
   Assert.ok(interval1 > 0, "setTimeout returns a positive number");
 
   imported.clearInterval(interval1);
 
   const EXPECTED_CALLS = 5;
   let calls = 0;
 
   await new Promise((resolve) => {
-    imported.setInterval((param1, param2) => {
+    let interval2 = imported.setInterval((param1, param2) => {
       Assert.ok(true, "Should be called");
       Assert.equal(param1, 15, "first parameter is correct");
       Assert.equal(param2, "hola", "second parameter is correct");
       if (calls >= EXPECTED_CALLS) {
+        imported.clearInterval(interval2);
         resolve();
       }
       calls++;
     }, 100, 15, "hola");
   });
 });
 
 add_task(async function test_setIntervalWithTarget() {
@@ -83,21 +84,22 @@ add_task(async function test_setInterval
   Assert.ok(interval1 > 0, "setTimeout returns a positive number");
 
   imported.clearInterval(interval1);
 
   const EXPECTED_CALLS = 5;
   let calls = 0;
 
   await new Promise((resolve) => {
-    imported.setIntervalWithTarget((param1, param2) => {
+    let interval2 = imported.setIntervalWithTarget((param1, param2) => {
       Assert.ok(true, "Should be called");
       Assert.equal(param1, 15, "first parameter is correct");
       Assert.equal(param2, "hola", "second parameter is correct");
       if (calls >= EXPECTED_CALLS) {
+        imported.clearInterval(interval2);
         resolve();
       }
       calls++;
     }, 100, target, 15, "hola");
   });
 });
 
 add_task(async function test_setTimeoutNonFunction() {
@@ -114,8 +116,29 @@ add_task(async function test_setTimeoutW
   Assert.throws(() => { imported.setTimeoutWithTarget({}, 0); },
                 /callback is not a function in setTimeout/);
 });
 
 add_task(async function test_setIntervalWithTargetNonFunction() {
   Assert.throws(() => { imported.setIntervalWithTarget({}, 0); },
                 /callback is not a function in setInterval/);
 });
+
+add_task(async function test_requestIdleCallback() {
+  let request1 = imported.requestIdleCallback(() => do_throw("Should not be called"));
+  Assert.equal(typeof request1, "number", "requestIdleCallback returns a number");
+  Assert.ok(request1 > 0, "setTimeout returns a positive number");
+
+  imported.cancelIdleCallback(request1);
+
+  await new Promise((resolve) => {
+    let request2 = imported.requestIdleCallback((deadline) => {
+      Assert.ok(true, "Should be called");
+      Assert.equal(typeof deadline.didTimeout, "boolean", "deadline parameter has .didTimeout property");
+      Assert.equal(typeof deadline.timeRemaining(), "number", "deadline parameter has .timeRemaining() function");
+      resolve();
+    }, {timeout: 100});
+
+    Assert.equal(typeof request2, "number", "requestIdleCallback returns a number");
+    Assert.ok(request2 > 0, "requestIdleCallback returns a positive number");
+    Assert.notEqual(request1, request2, "Calling requestIdleCallback again returns a different value");
+  });
+});
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -220,17 +220,17 @@
   "SyncedBookmarksMirror.jsm": ["SyncedBookmarksMirror"],
   "tabs.js": ["TabEngine", "TabSetRecord"],
   "tabs.jsm": ["BrowserTabs"],
   "tcpsocket_test.jsm": ["createSocket", "createServer", "enablePrefsAndPermissions", "socketCompartmentInstanceOfArrayBuffer"],
   "telemetry.js": ["SyncTelemetry"],
   "test.jsm": ["Foo"],
   "test2.jsm": ["Bar"],
   "test_bug883784.jsm": ["Test"],
-  "Timer.jsm": ["setTimeout", "setTimeoutWithTarget", "clearTimeout", "setInterval", "setIntervalWithTarget", "clearInterval"],
+  "Timer.jsm": ["setTimeout", "setTimeoutWithTarget", "clearTimeout", "setInterval", "setIntervalWithTarget", "clearInterval", "requestIdleCallback", "cancelIdleCallback"],
   "TippyTopProvider.jsm": ["TippyTopProvider", "getDomain"],
   "Tokenize.jsm": ["tokenize", "toksToTfIdfVector"],
   "tokenserverclient.js": ["TokenServerClient", "TokenServerClientError", "TokenServerClientNetworkError", "TokenServerClientServerError"],
   "ToolboxProcess.jsm": ["BrowserToolboxProcess"],
   "tps.jsm": ["ACTIONS", "Addons", "Addresses", "Bookmarks", "CreditCards", "Formdata", "History", "Passwords", "Prefs", "Tabs", "TPS", "Windows"],
   "Translation.jsm": ["Translation", "TranslationTelemetry"],
   "Traversal.jsm": ["TraversalRules", "TraversalHelper"],
   "UpdateTelemetry.jsm": ["AUSTLMY"],