Merge pull request #3116 from Polymer/3115-kschaaf-reentrant-flush

Make Polymer.dom.flush reentrant-safe. Fixes #3115.
This commit is contained in:
Steve Orvell 2015-11-30 15:39:34 -08:00
commit 230ce10153
2 changed files with 41 additions and 11 deletions

View File

@ -9,16 +9,16 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
-->
<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,
@ -31,8 +31,10 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
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) {
@ -47,9 +49,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
},
_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) {
@ -76,7 +78,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
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);
},

View File

@ -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() {