Skip to content
This repository has been archived by the owner on Sep 20, 2019. It is now read-only.

Commit

Permalink
Ensure attached & detached occur for moves in the same turn.
Browse files Browse the repository at this point in the history
Fixes #311
  • Loading branch information
kevinpschaaf committed Jul 2, 2015
1 parent 0ced076 commit 1cc115d
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 24 deletions.
44 changes: 21 additions & 23 deletions src/CustomElements/observe.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,34 @@ var forDocumentTree = scope.forDocumentTree;
// manage lifecycle on added node and it's subtree; upgrade the node and
// entire subtree if necessary and process attached for the node and entire
// subtree
function addedNode(node) {
return added(node) || addedSubtree(node);
function addedNode(node, isAttached) {
return added(node, isAttached) || addedSubtree(node, isAttached);
}

// manage lifecycle on added node; upgrade if necessary and process attached
function added(node) {
if (scope.upgrade(node)) {
function added(node, isAttached) {
if (scope.upgrade(node, isAttached)) {
// Return true to indicate
return true;
}
attached(node);
if (isAttached) {
attached(node);
}
}

// manage lifecycle on added node's subtree only; allows the entire subtree
// to upgrade if necessary and process attached
function addedSubtree(node) {
function addedSubtree(node, isAttached) {
forSubtree(node, function(e) {
if (added(e)) {
if (added(e, isAttached)) {
return true;
}
});
}

function attachedNode(node) {
attached(node);
// only check subtree if node is actually in document
if (inDocument(node)) {
forSubtree(node, function(e) {
attached(e);
});
attached(node);
}
}

Expand Down Expand Up @@ -104,9 +103,8 @@ function _attached(element) {
// track element for insertion if it's upgraded and cares about insertion
if (element.__upgraded__ &&
(element.attachedCallback || element.detachedCallback)) {
// bail if the element is already marked as attached and proceed only
// if it's actually in the document at this moment.
if (!element.__attached && inDocument(element)) {
// bail if the element is already marked as attached
if (!element.__attached) {
element.__attached = true;
if (element.attachedCallback) {
element.attachedCallback();
Expand Down Expand Up @@ -144,9 +142,8 @@ function _detached(element) {
// track element for removal if it's upgraded and cares about removal
if (element.__upgraded__ &&
(element.attachedCallback || element.detachedCallback)) {
// bail if the element is already marked as not attached and proceed only
// if it's actually *not* in the document at this moment.
if (element.__attached && !inDocument(element)) {
// bail if the element is already marked as not attached
if (element.__attached) {
element.__attached = false;
if (element.detachedCallback) {
element.detachedCallback();
Expand Down Expand Up @@ -196,7 +193,7 @@ function watchShadow(node) {
node.appendChild(div).appendChild(child);
*/
function handler(mutations) {
function handler(root, mutations) {
// for logging only
if (flags.dom) {
var mx = mutations[0];
Expand All @@ -213,13 +210,14 @@ function handler(mutations) {
console.group('mutations (%d) [%s]', mutations.length, u || '');
}
// handle mutations
var isAttached = inDocument(root);
mutations.forEach(function(mx) {
if (mx.type === 'childList') {
forEach(mx.addedNodes, function(n) {
if (!n.localName) {
return;
}
addedNode(n);
addedNode(n, isAttached);
});
forEach(mx.removedNodes, function(n) {
if (!n.localName) {
Expand Down Expand Up @@ -250,7 +248,7 @@ function takeRecords(node) {
}
var observer = node.__observer;
if (observer) {
handler(observer.takeRecords());
handler(node, observer.takeRecords());
takeMutations();
}
}
Expand All @@ -265,7 +263,7 @@ function observe(inRoot) {
}
// For each ShadowRoot, we create a new MutationObserver, so the root can be
// garbage collected once all references to the `inRoot` node are gone.
var observer = new MutationObserver(handler);
var observer = new MutationObserver(handler.bind(this, inRoot));
observer.observe(inRoot, {childList: true, subtree: true});
inRoot.__observer = observer;
}
Expand All @@ -274,7 +272,7 @@ function observe(inRoot) {
function upgradeDocument(doc) {
doc = window.wrap(doc);
flags.dom && console.group('upgradeDocument: ', (doc.baseURI).split('/').pop());
addedNode(doc);
addedNode(doc, doc === window.wrap(document));
observe(doc);
flags.dom && console.groupEnd();
}
Expand Down
2 changes: 1 addition & 1 deletion src/CustomElements/upgrade.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function upgradeWithDefinition(element, definition) {
// attachedCallback fires in tree order, call before recursing
scope.attachedNode(element);
// there should never be a shadow root on element at this point
scope.upgradeSubtree(element);
scope.upgradeSubtree(element, element.__attached);
flags.upgrade && console.groupEnd();
// OUTPUT
return element;
Expand Down
41 changes: 41 additions & 0 deletions tests/CustomElements/js/customElements.js
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,47 @@ suite('customElements', function() {
assert.deepEqual(['a', 'b', 'c', 'd', 'e'], log);
});

test('attached and detached in same turn', function(done) {
var log = [];
var p = Object.create(HTMLElement.prototype);
p.attachedCallback = function() {
log.push('attached');
};
p.detachedCallback = function() {
log.push('detached');
};
document.registerElement('x-ad', {prototype: p});
var el = document.createElement('x-ad');
work.appendChild(el);
work.removeChild(el);
setTimeout(function() {
assert.deepEqual(['attached', 'detached'], log);
done();
});
});

test('detached and re-attached in same turn', function(done) {
var log = [];
var p = Object.create(HTMLElement.prototype);
p.attachedCallback = function() {
log.push('attached');
};
p.detachedCallback = function() {
log.push('detached');
};
document.registerElement('x-da', {prototype: p});
var el = document.createElement('x-da');
work.appendChild(el);
CustomElements.takeRecords();
log = [];
work.removeChild(el);
work.appendChild(el);
setTimeout(function() {
assert.deepEqual(['detached', 'attached'], log);
done();
});
});

test('detachedCallback ordering', function() {
var log = [];
var p = Object.create(HTMLElement.prototype);
Expand Down

0 comments on commit 1cc115d

Please sign in to comment.