Skip to content
Permalink
Browse files

* make `ready` independent of attached state

* make distribution go top-down logically and composition unwind bottom-up, this naturally avoids `attached` thrashing
  • Loading branch information
sorvell committed Dec 18, 2014
1 parent fbae0b0 commit eed5b2aede0a6104be6431b77d2eafbcb961be2b
Showing with 141 additions and 143 deletions.
  1. +10 −1 polymer-simplex.html
  2. +5 −5 polymer.html
  3. +31 −27 src/features/base/content.html
  4. +38 −55 src/features/base/ready.html
  5. +10 −12 src/features/base/template.html
  6. +47 −43 test/unit/content.html
@@ -11,8 +11,8 @@

<link rel="import" href="src/features/base/mixins.html">
<link rel="import" href="src/features/base/extends.html">
<link rel="import" href="src/features/base/ready.html">
<link rel="import" href="src/features/base/template.html">
<link rel="import" href="src/features/base/ready.html">
<link rel="import" href="src/features/base/content.html">

<script>
@@ -39,6 +39,15 @@
_simplexInit: function() {
this.poolContent();
this.stampTemplate();
this._finishInit();
},

_finishInit: function() {
this._finishSimplexInit();
},

_finishSimplexInit: function() {
this._checkReady();
}

});
@@ -37,12 +37,11 @@
this._bootBindEffects();
},

initFeatures: function() {
this._standardInit();
},
// initFeatures: function() {
// this._standardInit();
// },

_standardInit: function() {
this._simplexInit();
_finishInit: function() {
if (this._template) {
this._marshalIdNodes();
this._marshalAnnotatedNodes();
@@ -54,6 +53,7 @@
this._installHostAttributes();
this._listenListeners();
this._listenKeyPresses();
this._finishSimplexInit();
}

});
@@ -33,37 +33,41 @@
if (this._useContent) {
// capture lightChildren to help reify dom scoping
saveLightChildrenIfNeeded(this);
// create our lite ShadowRoot document fragment
// this is where the <template> contents will be stamped
var root = document.createDocumentFragment();
// add a pointer back from the lite ShadowRoot to this node.
root.host = this;
// initialize the `root` pointers: `root` is guarenteed to always be
// available, and be either `this` or `this.contentRoot`. By contrast,
// `contentRoot` is only set if _useContent is true.
this.contentRoot = root;
this.root = root;
// TODO(jmesserly): ad-hoc signal for `ShadowDOM-lite-enhanced` root
root.isShadowRoot = true;
// TODO(sorvell): ad-hoc signal for `ShadowDOM-lite-enhanced` root
this.hasShadyRoot = true;
}
},

distributeContent: function() {
// sanity check to guard against uninitialized state
if (!this.contentRoot) {
throw Error('poolContent() must be called before distributeContent()');
// logically distribute self
if (this._useContent) {
// NOTE: contentRoot is primarily available to enable testing
if (!this.contentRoot) {
this.contentRoot = this.root;
this.root = this;
}
// reset distributions
// NOTE: `contentRoot` is populated only for the first
// distribution. After that dom should be in the composed tree and
// distribution info should not be reset.
this._resetDistribution(this.contentRoot);
// compute which nodes should be distributed where
// TODO(jmesserly): this is simplified because we assume a single
// ShadowRoot per host and no `<shadow>`.
this._distributePool(this.contentRoot, this._collectPool());
}
// now fully distribute/compose "clients"
var l = this._clients.length;
for (var i=0, c; (i<l) && (c=this._clients[i]); i++) {
c.distributeContent();
}
// compose self
if (this._useContent) {
this._composeTree(this);
} else if (this.root !== this) {
this.appendChild(this.root);
this.root = this;
}
// NOTE: `contentRoot` is populated only for the first
// distribution. After that dom should be in the composed tree and
// distribution info should not be reset.
// reset distributions
this._resetDistribution(this.contentRoot);
// compute which nodes should be distributed where
// TODO(jmesserly): this is simplified because we assume a single
// ShadowRoot per host and no `<shadow>`.
this._distributePool(this.contentRoot, this._collectPool());
// update the real DOM to be the composed tree
this._composeTree(this);
},

// TODO(jmesserly): these methods will perform in O(N^2) where N is the
@@ -175,7 +179,7 @@
for (var i = 0; i < children.length; i++) {
var child = children[i];
// If the child has a content root, let it compose itself.
if (!child.contentRoot) {
if (!child.hasShadyRoot) {
this._composeTree(child);
}
}
@@ -31,82 +31,65 @@
*/

var hostStack = [];

Base.addFeature({

// for overriding
ready: function() {
},

originalAttachedCallback: Base.attachedCallback,

// Checks if this element is inside another Polymer element. If so,
// then pushes this element into an array of `_readyListeners`; if
// not, starts the notify cascade.
attachedCallback: function() {
this.originalAttachedCallback();
this._checkReady();
queryHost: function() {
return this.host;
},

_checkReady: function() {
if (!this._readied) {
var host = this.queryHost();
if (host && !host._readied) {
host._listenReady(this);
} else {
this._notifyReady();
}
// 1. set this element's `host` and push this element onto the `host`'s
// list of `client` elements
// 2. establish this element as the current hosting element (allows
// any elements we stamp to easily set host to us).
_prepareStamping: function() {
this.host = hostStack[hostStack.length-1];
if (this.host) {
this.host._clients.push(this);
}
if (!this._clients) {
this._clients = [];
}
hostStack.push(this);
},

queryHost: function(node) {
return this.host || this._queryHost(this);
},

_queryHost: function(node) {
return node &&
(node.host || (node.host = this._queryHost(node.parentNode)));
},

_listenReady: function(element) {
if (!this._readyListeners) {
this._readyListeners = [];
// 1. this element is no longer the current hosting element
// 2. if necessary, perform dom composition.
_finishStamping: function() {
hostStack.pop();
if (this._readied || !this.host || this.host._readied) {
this.distributeContent();
}
this._readyListeners.push(element);
},

// TODO(sorvell): this will need more modification.
// We want distribution to run to completion prior to any user `ready`
// methods running. Therefore we have 2 ready passes, one for distribution
// and one for user methods.
_notifyReady: function() {
this._broadcast('_ready');
this._broadcast('ready', true);
_checkReady: function() {
if (!this.host || this.host._readied) {
this._ready();
}
},

_broadcast: function(method, cleanup) {
// console.group(method, '[' + this.tag + '#' + this.id + ']', 'ready');
this[method]();
var n$ = this._readyListeners;
if (n$) {
// call `ready` if necessary ensuring host -> client ordering.
_ready: function() {
if (!this._readied) {
// ready myself
this._readied = true;
this.ready();
// ready my clients...
var n$ = this._clients;
for (var i=0, l=n$.length, n; (i<l) && (n=n$[i]); i++) {
n._broadcast(method, cleanup);
n._ready();
}
// TODO(sorvell): this leaks if we don't clean it up here, but
// it's currently needed for dynamic shadyDOM distribution... =(
//this._clients = [];
}
if (cleanup) {
this._readyListeners = null;
}
// console.groupEnd(method, '[' + this.tag + '#' + this.id + ']', 'ready');
},

_ready: function() {
this._readied = true;
// TODO(jmesserly): this is a hook to allow content.html to be called
// before "ready". This needs to be factored better.
if (this._useContent) {
this.distributeContent();
}
}

});

});
@@ -22,24 +22,22 @@
},

stampTemplate: function() {
// establishes this element as the current `host`
// so stamped elements can lookup host
this._prepareStamping();
if (this._template) {
this._stampTemplate(this._template, this.root);
// note: root is now a fragment which can be manipulated
// while not attached to the element.
this.root = this.instanceTemplate(this._template);
// TODO(sjmiles): hello prollyfill
if (window.CustomElements && CustomElements.upgradeSubtree) {
CustomElements.upgradeSubtree(this.root);
}
}
},

_stampTemplate: function(template, target) {
var instance = this.instanceTemplate(template);
// identify host
// NOTE: must use node tree, not element tree because Safari doesn't have
// a concept of elements in fragments
for (var e = instance.firstChild; e; e=e.nextSibling) {
e.host = this;
}
target.insertBefore(instance, target.firstChild);
// ensures dom is fully composed and attached to the element;
// removes this element as the current `host`
// after this, `this.root` points to the element itself.
this._finishStamping();
},

instanceTemplate: function(template) {

0 comments on commit eed5b2a

Please sign in to comment.
You can’t perform that action at this time.