From 5b793f7a9ea7aada8fe888addde598d63b565e33 Mon Sep 17 00:00:00 2001 From: Mangala SSS Khalsa Date: Wed, 8 Feb 2017 20:16:25 -0700 Subject: [PATCH 1/4] Use 'requestIdleCallback' when available for debounce/throttle Fixes #1351 --- util/misc.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/util/misc.js b/util/misc.js index 5e44dd4ed..2e13124ab 100644 --- a/util/misc.js +++ b/util/misc.js @@ -1,4 +1,6 @@ -define(function () { +define([ + 'dojo/has' +], function (has) { // summary: // This module defines miscellaneous utility methods for purposes of // adding styles, and throttling/debouncing function calls. @@ -11,6 +13,10 @@ define(function () { rulesProperty, invalidCssChars = /([^A-Za-z0-9_\u00A0-\uFFFF-])/g; + has.add('requestidlecallback', function (global) { + return typeof global.requestIdleCallback === 'function'; + }); + function removeRule(index) { // Function called by the remove method on objects returned by addCssRule. var realIndex = extraRules[index], @@ -40,7 +46,26 @@ define(function () { // Throttle/debounce functions defaultDelay: 15, - throttle: function (cb, context, delay) { + // The presence of the 'requestIdleCallback' method indicates a browser that might + // performance optimize code by delaying execution of the callback passed to + // 'setTimeout', so use 'requestIdleCallback' to improve the likelihood of the + // callback being executed in a timely manner. Alternate implementations of each of + // the debounce and throttle methods are provided that use this function. + throttle: has('requestidlecallback') ? function (cb, context, delay) { + var ran = false; + delay = delay || util.defaultDelay; + return function () { + if (ran) { + return; + } + ran = true; + cb.apply(context, arguments); + requestIdleCallback(function () { + ran = false; + }, { timeout: delay }); + }; + } + : function (cb, context, delay) { // summary: // Returns a function which calls the given callback at most once per // delay milliseconds. (Inspired by plugd) @@ -57,7 +82,22 @@ define(function () { }, delay); }; }, - throttleDelayed: function (cb, context, delay) { + throttleDelayed: has('requestidlecallback') ? function (cb, context, delay) { + var ran = false; + delay = delay || util.defaultDelay; + return function () { + if (ran) { + return; + } + ran = true; + var a = arguments; + requestIdleCallback(function () { + ran = false; + cb.apply(context, a); + }, {timeout: delay }); + }; + } + : function (cb, context, delay) { // summary: // Like throttle, except that the callback runs after the delay, // rather than before it. @@ -75,7 +115,21 @@ define(function () { }, delay); }; }, - debounce: function (cb, context, delay) { + debounce: has('requestidlecallback') ? function (cb, context, delay) { + var timer; + delay = delay || util.defaultDelay; + return function () { + if (timer) { + clearTimeout(timer); + timer = null; + } + var a = arguments; + timer = requestIdleCallback(function () { + cb.apply(context, a); + }, { timeout: delay }); + }; + } + : function (cb, context, delay) { // summary: // Returns a function which calls the given callback only after a // certain time has passed without successive calls. (Inspired by plugd) From 95f4c164c2b1dba788c4d821186c0487384f2b20 Mon Sep 17 00:00:00 2001 From: Mangala SSS Khalsa Date: Wed, 8 Feb 2017 20:58:43 -0700 Subject: [PATCH 2/4] debounce: fix timer cancellation --- util/misc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/misc.js b/util/misc.js index 2e13124ab..a6240c3e0 100644 --- a/util/misc.js +++ b/util/misc.js @@ -120,7 +120,7 @@ define([ delay = delay || util.defaultDelay; return function () { if (timer) { - clearTimeout(timer); + cancelIdleCallback(timer); timer = null; } var a = arguments; From cc4e5eec825905c307bf8224bcd891b742d2e267 Mon Sep 17 00:00:00 2001 From: Mangala SSS Khalsa Date: Wed, 8 Feb 2017 21:14:17 -0700 Subject: [PATCH 3/4] debounce: clear timer on execution --- util/misc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/util/misc.js b/util/misc.js index a6240c3e0..dc72e7f0c 100644 --- a/util/misc.js +++ b/util/misc.js @@ -125,6 +125,7 @@ define([ } var a = arguments; timer = requestIdleCallback(function () { + timer = null; cb.apply(context, a); }, { timeout: delay }); }; From 1bcd492ccb90e2be1c51430282208c5c3bba6146 Mon Sep 17 00:00:00 2001 From: Mangala SSS Khalsa Date: Thu, 9 Feb 2017 13:52:29 -0700 Subject: [PATCH 4/4] Add delayCallback/cancelDelay to abstract setTimeout/requestIdleCallback --- util/misc.js | 85 +++++++++++++++++----------------------------------- 1 file changed, 28 insertions(+), 57 deletions(-) diff --git a/util/misc.js b/util/misc.js index dc72e7f0c..f83092607 100644 --- a/util/misc.js +++ b/util/misc.js @@ -11,12 +11,31 @@ define([ extraSheet, removeMethod, rulesProperty, - invalidCssChars = /([^A-Za-z0-9_\u00A0-\uFFFF-])/g; + invalidCssChars = /([^A-Za-z0-9_\u00A0-\uFFFF-])/g, + delayCallback = setTimeout, + cancelDelay = clearTimeout; has.add('requestidlecallback', function (global) { return typeof global.requestIdleCallback === 'function'; }); + // The presence of the 'requestIdleCallback' method indicates a browser that might + // performance optimize code by delaying execution of the callback passed to + // 'setTimeout', so use 'requestIdleCallback' to improve the likelihood of the + // callback being executed in a timely manner. + // This is not a perfect solution, but has worked well in testing. + // requestIdleCallback is designed to be called successively, performing progressive chunks + // of computation each time until the task is complete. + // setTimeout executes its callback when delay has transpired, *or later* + // requestIdleCallback executes its callback when delay has transpired, *or sooner* + // Fixes https://github.com/SitePen/dgrid/issues/1351 + if (has('requestidlecallback')) { + delayCallback = function (callback, delay) { + return requestIdleCallback(callback, { timeout: delay }); + }; + cancelDelay = cancelIdleCallback; + } + function removeRule(index) { // Function called by the remove method on objects returned by addCssRule. var realIndex = extraRules[index], @@ -46,26 +65,7 @@ define([ // Throttle/debounce functions defaultDelay: 15, - // The presence of the 'requestIdleCallback' method indicates a browser that might - // performance optimize code by delaying execution of the callback passed to - // 'setTimeout', so use 'requestIdleCallback' to improve the likelihood of the - // callback being executed in a timely manner. Alternate implementations of each of - // the debounce and throttle methods are provided that use this function. - throttle: has('requestidlecallback') ? function (cb, context, delay) { - var ran = false; - delay = delay || util.defaultDelay; - return function () { - if (ran) { - return; - } - ran = true; - cb.apply(context, arguments); - requestIdleCallback(function () { - ran = false; - }, { timeout: delay }); - }; - } - : function (cb, context, delay) { + throttle: function (cb, context, delay) { // summary: // Returns a function which calls the given callback at most once per // delay milliseconds. (Inspired by plugd) @@ -77,27 +77,12 @@ define([ } ran = true; cb.apply(context, arguments); - setTimeout(function () { + delayCallback(function () { ran = false; }, delay); }; }, - throttleDelayed: has('requestidlecallback') ? function (cb, context, delay) { - var ran = false; - delay = delay || util.defaultDelay; - return function () { - if (ran) { - return; - } - ran = true; - var a = arguments; - requestIdleCallback(function () { - ran = false; - cb.apply(context, a); - }, {timeout: delay }); - }; - } - : function (cb, context, delay) { + throttleDelayed: function (cb, context, delay) { // summary: // Like throttle, except that the callback runs after the delay, // rather than before it. @@ -109,28 +94,13 @@ define([ } ran = true; var a = arguments; - setTimeout(function () { + delayCallback(function () { ran = false; cb.apply(context, a); }, delay); }; }, - debounce: has('requestidlecallback') ? function (cb, context, delay) { - var timer; - delay = delay || util.defaultDelay; - return function () { - if (timer) { - cancelIdleCallback(timer); - timer = null; - } - var a = arguments; - timer = requestIdleCallback(function () { - timer = null; - cb.apply(context, a); - }, { timeout: delay }); - }; - } - : function (cb, context, delay) { + debounce: function (cb, context, delay) { // summary: // Returns a function which calls the given callback only after a // certain time has passed without successive calls. (Inspired by plugd) @@ -138,11 +108,12 @@ define([ delay = delay || util.defaultDelay; return function () { if (timer) { - clearTimeout(timer); + cancelDelay(timer); timer = null; } var a = arguments; - timer = setTimeout(function () { + timer = delayCallback(function () { + timer = null; cb.apply(context, a); }, delay); };