@@ -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).
123128function 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+
208300window . Basic = window . Basic || { } ;
209301
210302/*
0 commit comments