Skip to content

Commit

Permalink
Merge pull request #3116 from Polymer/3115-kschaaf-reentrant-flush
Browse files Browse the repository at this point in the history
Make Polymer.dom.flush reentrant-safe. Fixes #3115.
  • Loading branch information
Steve Orvell committed Nov 30, 2015
2 parents e153e27 + d8ecd45 commit 230ce10
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 11 deletions.
24 changes: 13 additions & 11 deletions src/lib/dom-api-flush.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@
-->
<script>

/**
* `Polymer.dom.flush()` causes any asynchronously queued actions to be
/**
* `Polymer.dom.flush()` causes any asynchronously queued actions to be
* flushed synchronously. It should be used sparingly as calling it frequently
* can negatively impact performance since work is often deferred for
* efficiency. Calling `Polymer.dom.flush()` is useful, for example, when
* an element has to measure itself and is unsure about the state of its
* can negatively impact performance since work is often deferred for
* efficiency. Calling `Polymer.dom.flush()` is useful, for example, when
* an element has to measure itself and is unsure about the state of its
* internal or compoased DOM.
*/
Polymer.Base.extend(Polymer.dom, {

_flushGuard: 0,
_FLUSH_MAX: 100,
_needsTakeRecords: !Polymer.Settings.useNativeCustomElements,
Expand All @@ -31,8 +31,10 @@
this._flushGuard = 0;
this._prepareFlush();
while (this._debouncers.length && this._flushGuard < this._FLUSH_MAX) {
for (var i=0; i < this._debouncers.length; i++) {
this._debouncers[i].complete();
// Avoid using an index in this loop to ensure flush is safe to be
// called reentrantly from a debouncer callback being flushed
while (this._debouncers.length) {
this._debouncers.shift().complete();
}
// clear the list of debouncers
if (this._finishDebouncer) {
Expand All @@ -47,9 +49,9 @@
},

_prepareFlush: function() {
// TODO(sorvell): There is currently not a good way
// TODO(sorvell): There is currently not a good way
// to process all custom elements mutations under SD polyfill because
// these mutations may be inside shadowRoots.
// these mutations may be inside shadowRoots.
// again make any pending CE mutations that might trigger debouncer
// additions go...
if (this._needsTakeRecords) {
Expand All @@ -76,7 +78,7 @@
addDebouncer: function(debouncer) {
this._debouncers.push(debouncer);
// ensure the list of active debouncers is cleared when done.
this._finishDebouncer = Polymer.Debounce(this._finishDebouncer,
this._finishDebouncer = Polymer.Debounce(this._finishDebouncer,
this._finishFlush);
},

Expand Down
28 changes: 28 additions & 0 deletions test/unit/polymer-dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,34 @@ suite('Polymer.dom', function() {

});

test('Polymer.dom.flush reentrancy', function() {
// Setup callbacks
var order = [];
var cb1 = sinon.spy(function() { order.push(cb1); });
var cb2 = sinon.spy(function() { order.push(cb2); });
var cb3 = sinon.spy(function() { order.push(cb3); });
var cb4 = sinon.spy(function() { order.push(cb4); });
var cbReentrant = sinon.spy(function() {
order.push(cbReentrant);
Polymer.dom.addDebouncer(Polymer.Debounce(null, cb3));
Polymer.dom.flush();
Polymer.dom.addDebouncer(Polymer.Debounce(null, cb4));
});
// Enqueue debouncers
Polymer.dom.addDebouncer(Polymer.Debounce(null, cb1));
Polymer.dom.addDebouncer(Polymer.Debounce(null, cbReentrant));
Polymer.dom.addDebouncer(Polymer.Debounce(null, cb2));
// Flush
Polymer.dom.flush();
// Check callbacks called and in correct order
assert.isTrue(cb1.calledOnce);
assert.isTrue(cb2.calledOnce);
assert.isTrue(cb3.calledOnce);
assert.isTrue(cb4.calledOnce);
assert.isTrue(cbReentrant.calledOnce);
assert.sameMembers(order, [cb1, cbReentrant, cb2, cb3, cb4]);
});

});

suite('Polymer.dom accessors', function() {
Expand Down

0 comments on commit 230ce10

Please sign in to comment.