Skip to content
This repository has been archived by the owner on Oct 26, 2021. It is now read-only.

Commit

Permalink
Merge branch 'flush-upgrade-order'
Browse files Browse the repository at this point in the history
  • Loading branch information
bicknellr committed Aug 3, 2017
2 parents f27a939 + 5733950 commit 1464c3f
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 34 deletions.
40 changes: 21 additions & 19 deletions custom-elements.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion custom-elements.min.js.map

Large diffs are not rendered by default.

14 changes: 10 additions & 4 deletions src/CustomElementInternals.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,15 @@ export default class CustomElementInternals {
* Reactions in the popped stack are invoked.)
*
* @param {!Node} root
* @param {!Set<Node>=} visitedImports
* @param {{
* visitedImports: (!Set<!Node>|undefined),
* upgrade: (!function(!Element)|undefined),
* }=} options
*/
patchAndUpgradeTree(root, visitedImports = new Set()) {
patchAndUpgradeTree(root, options = {}) {
const visitedImports = options.visitedImports || new Set();
const upgrade = options.upgrade || (element => this.upgradeElement(element));

const elements = [];

const gatherElements = element => {
Expand Down Expand Up @@ -205,7 +211,7 @@ export default class CustomElementInternals {
const clonedVisitedImports = new Set(visitedImports);
visitedImports.delete(importNode);

this.patchAndUpgradeTree(importNode, visitedImports);
this.patchAndUpgradeTree(importNode, {visitedImports, upgrade});
});
}
} else {
Expand All @@ -224,7 +230,7 @@ export default class CustomElementInternals {
}

for (let i = 0; i < elements.length; i++) {
this.upgradeElement(elements[i]);
upgrade(elements[i]);
}
}

Expand Down
51 changes: 41 additions & 10 deletions src/CustomElementRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ export default class CustomElementRegistry {

/**
* @private
* @type {!Array<string>}
* @type {!Array<!CustomElementDefinition>}
*/
this._unflushedLocalNames = [];
this._pendingDefinitions = [];

/**
* @private
Expand Down Expand Up @@ -120,9 +120,7 @@ export default class CustomElementRegistry {
constructionStack: [],
};

this._internals.setDefinition(localName, definition);

this._unflushedLocalNames.push(localName);
this._pendingDefinitions.push(definition);

// If we've already called the flush callback and it hasn't called back yet,
// don't call it again.
Expand All @@ -137,12 +135,45 @@ export default class CustomElementRegistry {
// happen if a flush callback keeps the function it is given and calls it
// multiple times.
if (this._flushPending === false) return;

this._flushPending = false;
this._internals.patchAndUpgradeTree(document);

while (this._unflushedLocalNames.length > 0) {
const localName = this._unflushedLocalNames.shift();
const pendingDefinitions = this._pendingDefinitions;
/** @type {!Map<string, !Array<!Element>>} */
const localNameToUpgradableElements = new Map();
for (let i = 0; i < pendingDefinitions.length; i++) {
localNameToUpgradableElements.set(pendingDefinitions[i].localName, []);
}

this._internals.patchAndUpgradeTree(document, {
upgrade: element => {
// Attempt to upgrade using *non-pending* definitions.
this._internals.upgradeElement(element);

// If the element was upgraded, then no pending definition applies to it.
if (element.__CE_state !== undefined) return;

// If there is an applicable pending definition for the element, add the
// element to the set of upgradable elements for that definition.
let upgradableElements = localNameToUpgradableElements.get(element.localName);
if (upgradableElements) {
upgradableElements.push(element);
}
},
});

while (pendingDefinitions.length > 0) {
const definition = pendingDefinitions.shift();
const localName = definition.localName;

this._internals.setDefinition(localName, definition);

// Attempt to upgrade all applicable elements.
const upgradableElements = localNameToUpgradableElements.get(definition.localName);
for (let i = 0; i < upgradableElements.length; i++) {
this._internals.upgradeElement(upgradableElements[i]);
}

// Resolve any promises created by `whenDefined` for the definition.
const deferred = this._whenDefinedDeferred.get(localName);
if (deferred) {
deferred.resolve(undefined);
Expand Down Expand Up @@ -184,7 +215,7 @@ export default class CustomElementRegistry {
// Resolve immediately only if the given local name has a definition *and*
// the full document walk to upgrade elements with that local name has
// already happened.
if (definition && this._unflushedLocalNames.indexOf(localName) === -1) {
if (definition && !this._pendingDefinitions.some(d => d.localName === localName)) {
deferred.resolve(undefined);
}

Expand Down
1 change: 1 addition & 0 deletions tests/html/polyfillWrapFlushCallback/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
'./defaultSyncFlush.html',
'./whenDefined_before.html',
'./whenDefined_after.html',
'./upgradeInDefineCallOrder.html',
]);
</script>
79 changes: 79 additions & 0 deletions tests/html/polyfillWrapFlushCallback/upgradeInDefineCallOrder.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<!doctype html>
<html>
<head>
<title>customElements#polyfillWrapFlushCallback</title>
<script>
(window.customElements = window.customElements || {}).forcePolyfill = true;
</script>
<script src="../../../../es6-promise/dist/es6-promise.auto.min.js"></script>
<script src="../../../../web-component-tester/browser.js"></script>
<script src="../../../custom-elements.min.js"></script>
</head>
<body>
<custom-element-0 id="elt_0_0">
<custom-element-1 id="elt_1_0">
<custom-element-2 id="elt_2_0"></custom-element-2>
<custom-element-0 id="elt_0_1">
</custom-element-0>
</custom-element-1>
<custom-element-2 id="elt_2_1">
<custom-element-2 id="elt_2_2"></custom-element-2>
</custom-element-2>
<custom-element-0 id="elt_0_2">
<custom-element-1 id="elt_1_1">
</custom-element-1>
<custom-element-2 id="elt_2_3"></custom-element-2>
</custom-element-0>
<custom-element-1 id="elt_1_2"></custom-element-1>
</custom-element-0>
<script>
/**
* @license
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/

test('When a flush callback is installed and multiple calls to define are ' +
'made calling the flush callback causes elements to upgrade in define-call ' +
'order and then document order.', function() {
let flush = undefined;
customElements.polyfillWrapFlushCallback(fn => {
flush = fn;
});

const upgradeLog = [];
class LogIDOnConstruct extends HTMLElement {
constructor() {
super();
upgradeLog.push(this.id);
}
}

customElements.define('custom-element-0', class extends LogIDOnConstruct {});
customElements.define('custom-element-1', class extends LogIDOnConstruct {});
customElements.define('custom-element-2', class extends LogIDOnConstruct {});

assert.deepEqual(upgradeLog, []);

flush();

assert.deepEqual(upgradeLog, [
"elt_0_0",
"elt_0_1",
"elt_0_2",
"elt_1_0",
"elt_1_1",
"elt_1_2",
"elt_2_0",
"elt_2_1",
"elt_2_2",
"elt_2_3",
]);
});
</script>
</body>
</html>

0 comments on commit 1464c3f

Please sign in to comment.