Skip to content

Commit 0afbdfd

Browse files
author
Jan Miksovsky
committed
Pick up changes for 0.6.1-pre
1 parent 41551d4 commit 0afbdfd

File tree

3 files changed

+131
-3
lines changed

3 files changed

+131
-3
lines changed

ContentChanged.js

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ function isLightDomDescendant(node, component) {
113113
// Walk up to see if the parent is in component's light DOM.
114114
return isLightDomDescendant(parent, component);
115115
} else {
116+
// We've done one of the following:
117+
// * walked up in to a shadow root
118+
// * walked up out of the document
119+
// In either case, the original node wasn't a light DOM child of the
120+
// component.
116121
return false;
117122
}
118123
}
@@ -122,9 +127,16 @@ function isLightDomDescendant(node, component) {
122127
// (and not Shady DOM).
123128
function isLightDomMutation(mutation, component) {
124129
// See if at least one added node was in light DOM.
125-
return Array.prototype.some.call(mutation.addedNodes, function(addedNode) {
130+
var lightDomAddition = Array.prototype.some.call(mutation.addedNodes, function(addedNode) {
126131
return isLightDomDescendant(addedNode, component);
127132
});
133+
if (lightDomAddition) {
134+
return true;
135+
}
136+
var lightDomRemoval = Array.prototype.some.call(mutation.removedNodes, function(removedNode) {
137+
return wasLightDomDescendant(removedNode, mutation.target, component);
138+
});
139+
return lightDomRemoval;
128140
}
129141

130142

@@ -205,6 +217,86 @@ function stopObservingContentMutations(node) {
205217
}
206218

207219

220+
// Return true if the given node *was* a child of the given container in the
221+
// component's Shady DOM. The container may be the component itself, or an
222+
// element somewhere in the component's simulated shadow tree.
223+
//
224+
// This work is pretty hard to do without more cooperation from Shady DOM. By
225+
// the time a removal mutation occurs, all internal Shady DOM state has been
226+
// scraped off the node. We do our best to try to guess whether the indicated
227+
// node was projected from light DOM into the container, or whether the node
228+
// was part of the shadow tree.
229+
function wasLightDomDescendant(node, container, component) {
230+
231+
if (container === component) {
232+
// The node is (or was) a direct child of the component.
233+
// This is an important but ambiguous case. In Shady DOM, we can't tell for
234+
// sure after a removal whether the node was removed from the component's
235+
// light DOM or simulated shadow DOM. As an approximation, we look at the
236+
// node's classList for a hint.
237+
return !wasNodeInShadowOfComponent(node, component);
238+
}
239+
240+
if (isLightDomDescendant(container, component)) {
241+
// The container was in the light DOM.
242+
// REVIEW: What if the container is in the shadow of an element in the
243+
// component's light DOM?
244+
return true;
245+
}
246+
247+
// The node must have been in the shadow tree somewhere
248+
// Walk up the hierarchy from the point where the node was removed.
249+
return wasNodeProjectedIntoContainer(node, container, component);
250+
}
251+
252+
253+
// Return true if the node appears to have been projected into the given
254+
// container within the component's shadow subtree.
255+
//
256+
// For a remove mutation to be in light DOM, the mutation target should be the
257+
// component itself, or a container within the component's composed shadow tree.
258+
// If the latter, the container should contain a content element, and the
259+
// container's host should either be the component, or an element that similarly
260+
// contains a content element is is hosted by the component (or... and so on).
261+
function wasNodeProjectedIntoContainer(node, container, component) {
262+
var content = Polymer.dom(container).querySelector('content');
263+
if (!content) {
264+
// No content element, so node couldn't have been projected into container.
265+
return false;
266+
}
267+
var containerHost = Basic.ContentHelpers.getHost(container);
268+
if (!containerHost) {
269+
// No host, so node couldn't have been projected into container.
270+
return false;
271+
}
272+
if (wasNodeInShadowOfComponent(node, containerHost)) {
273+
// Node was inside the container, but was actually put there by the
274+
// container's host as part of that host's shadow.
275+
return false;
276+
}
277+
return (containerHost === component) ?
278+
true :
279+
wasNodeProjectedIntoContainer(node, containerHost, component);
280+
}
281+
282+
283+
// Take a guess at whether the indicated node was in the shadow of the given
284+
// component. This relies on the fact that Shady DOM stamps a "style-scope"
285+
// CSS class onto elements in the simulated shadow, along with a CSS class name
286+
// matching the component's tag (localName). These CSS classes remain even after
287+
// a node has been removed from the document and all other Shady DOM residue has
288+
// been scraped away, so we can look at these classes as a heuristic.
289+
//
290+
// Because this function relies on CSS classes, it always returns false for text
291+
// nodes and other nodes that can't take classes.
292+
function wasNodeInShadowOfComponent(node, component) {
293+
var classList = node.classList;
294+
return classList &&
295+
(classList.contains('style-scope') && classList.contains(component.localName));
296+
}
297+
298+
299+
208300
window.Basic = window.Basic || {};
209301

210302
/*

bower.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"license": "MIT",
66
"main": "basic-shared.html",
77
"dependencies": {
8-
"polymer": "Polymer/polymer#0.9.0"
8+
"polymer": "Polymer/polymer#1.0.0"
99
},
1010
"devDependencies": {
1111
"web-component-tester": "*"

test/ContentChanged.tests.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ suite('ContentHelpers and ContentChanged', function() {
8383
});
8484
});
8585

86-
test('modifying shadow does not trigger contentChanged', function(done) {
86+
test('adding node to shadow does not trigger contentChanged', function(done) {
8787
var fixture = document.createElement('content-test-element');
8888
fixture.contentChangedHook = function() {
8989
var childNodes = Polymer.dom(fixture).childNodes;
@@ -104,6 +104,42 @@ suite('ContentHelpers and ContentChanged', function() {
104104
});
105105
});
106106

107+
test('removing node from shadow does not trigger contentChanged', function(done) {
108+
var fixture = document.createElement('content-test-element');
109+
fixture.contentChangedHook = function() {
110+
var childNodes = Polymer.dom(fixture).childNodes;
111+
assert.equal(childNodes.length, 1);
112+
done();
113+
};
114+
container.appendChild(fixture);
115+
flush(function() {
116+
// Remove an element from the shadow, which shouldn't trigger contentChanged.
117+
fixture.$.static.remove();
118+
flush(function() {
119+
// Now add an element to the light DOM, which we do expect to trigger
120+
// contentChanged.
121+
Polymer.dom(fixture).textContent = 'Hello';
122+
});
123+
});
124+
});
125+
126+
test('removing node from light DOM *does* trigger contentChanged', function(done) {
127+
var fixture = document.createElement('content-test-element');
128+
var div = document.createElement('div');
129+
div.textContent = 'div';
130+
Polymer.dom(fixture).appendChild(div);
131+
container.appendChild(fixture);
132+
flush(function() {
133+
fixture.contentChangedHook = function() {
134+
var childNodes = Polymer.dom(fixture).childNodes;
135+
assert.equal(childNodes.length, 0);
136+
done();
137+
};
138+
// Remove a light DOM child, which should trigger contentChanged.
139+
Polymer.dom(fixture).removeChild(div);
140+
});
141+
});
142+
107143
test.skip('observe changes in child attribute', function(done) {
108144
var fixture = document.createElement('content-test-element');
109145
var button = document.createElement('button');

0 commit comments

Comments
 (0)