Skip to content

Commit

Permalink
Avoid infinite digest loop caused by $watch and $timeout
Browse files Browse the repository at this point in the history
Backports PR #10036

**Commit 1:**
Avoid infinite digest loop in debounce

The custom debounce implementation uses Angular's `$timeout`, which
interacts unfavourably with the unconditional `$watch` handler used in
the `fixed-scroll` directive. It results in an infinite digest being
triggered about every 100ms. To avoid that, this commit uses the
`invokeApply` option of `$timeout` and instead calls `$scope.$apply`
conditionally.

* Original sha: 13c677d
* Authored by Felix Stürmer <stuermer@weltenwort.de> on 2017-01-24T11:30:41Z
  • Loading branch information
weltenwort committed Jan 31, 2017
1 parent d2af8a1 commit 6d693e0
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 12 deletions.
5 changes: 3 additions & 2 deletions src/ui/public/debounce/debounce.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ module.service('debounce', ['$timeout', function ($timeout) {
let result;
options = _.defaults(options || {}, {
leading: false,
trailing: true
trailing: true,
invokeApply: true,
});

function debounce() {
Expand All @@ -32,7 +33,7 @@ module.service('debounce', ['$timeout', function ($timeout) {
if (timeout) {
$timeout.cancel(timeout);
}
timeout = $timeout(later, wait);
timeout = $timeout(later, wait, options.invokeApply);

if (callNow) {
result = func.apply(self, args);
Expand Down
17 changes: 9 additions & 8 deletions src/ui/public/directives/__tests__/fixed_scroll.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ import Promise from 'bluebird';
describe('FixedScroll directive', function () {

let compile;
let flushPendingTasks;
let trash = [];

beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.module(function ($provide) {
$provide.service('debounce', () => {
return targetFunction => targetFunction;
});
}));
beforeEach(ngMock.inject(function ($compile, $rootScope) {
beforeEach(ngMock.inject(function ($compile, $rootScope, $timeout) {

flushPendingTasks = function flushPendingTasks() {
$rootScope.$digest();
$timeout.flush();
};

compile = function (ratioY, ratioX) {
if (ratioX == null) ratioX = ratioY;
Expand Down Expand Up @@ -46,7 +47,7 @@ describe('FixedScroll directive', function () {
}).appendTo($el);

$compile($parent)($rootScope);
$rootScope.$digest();
flushPendingTasks();

return {
$container: $el,
Expand Down Expand Up @@ -97,7 +98,7 @@ describe('FixedScroll directive', function () {

expect(off.callCount).to.be(0);
els.$container.width(els.$container.prop('scrollWidth'));
els.$container.scope().$digest();
flushPendingTasks();
expect(off.callCount).to.be(2);
checkThisVals('$.fn.off', off);

Expand Down
6 changes: 4 additions & 2 deletions src/ui/public/fixed_scroll.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,16 @@ uiModules
const newWidth = $el.width();

if (scrollWidth !== newScrollWidth || width !== newWidth) {
setup();
$scope.$apply(setup);

scrollWidth = newScrollWidth;
width = newWidth;
}
}

const debouncedCheckWidth = debounce(checkWidth, 100);
const debouncedCheckWidth = debounce(checkWidth, 100, {
invokeApply: false,
});
$scope.$watch(debouncedCheckWidth);

// cleanup when the scope is destroyed
Expand Down

0 comments on commit 6d693e0

Please sign in to comment.