Bug 887538 - Implement web components shadow element. r=mrbkap
authorWilliam Chen <wchen@mozilla.com>
Fri, 20 Dec 2013 22:43:58 -0800
changeset 161672 f592ebe28538c51975cb03cb83967513262a98e7
parent 161671 321532cf56f834aac27a7e2add12ed4126c78173
child 161673 eaf1557bb572aa96ff1ea798a10326f4862a8a06
child 161676 84eb65aca4d17558beba61b0e79cfed95a29941e
push id37950
push userwchen@mozilla.com
push dateSat, 21 Dec 2013 06:44:24 +0000
treeherdermozilla-inbound@f592ebe28538 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmrbkap
bugs887538
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 887538 - Implement web components shadow element. r=mrbkap
content/base/public/nsContentUtils.h
content/base/src/ChildIterator.cpp
content/base/src/ChildIterator.h
content/base/src/Element.cpp
content/base/src/ShadowRoot.cpp
content/base/src/ShadowRoot.h
content/base/src/nsGkAtomList.h
content/base/src/nsNodeUtils.cpp
content/html/content/src/HTMLPropertiesCollection.cpp
content/html/content/src/HTMLShadowElement.cpp
content/html/content/src/HTMLShadowElement.h
content/html/content/src/moz.build
content/html/content/src/nsGenericHTMLElement.h
dom/bindings/Bindings.conf
dom/tests/mochitest/general/test_interfaces.html
dom/tests/mochitest/webcomponents/test_shadow_element.html
dom/webidl/HTMLShadowElement.webidl
dom/webidl/moz.build
editor/libeditor/html/nsHTMLEditUtils.cpp
layout/reftests/webcomponents/adjacent-insertion-points-1-ref.html
layout/reftests/webcomponents/adjacent-insertion-points-1.html
layout/reftests/webcomponents/adjacent-insertion-points-2-ref.html
layout/reftests/webcomponents/adjacent-insertion-points-2.html
layout/reftests/webcomponents/basic-shadow-element-1-ref.html
layout/reftests/webcomponents/basic-shadow-element-1.html
layout/reftests/webcomponents/dynamic-shadow-element-1-ref.html
layout/reftests/webcomponents/dynamic-shadow-element-1.html
layout/reftests/webcomponents/nested-shadow-element-1-ref.html
layout/reftests/webcomponents/nested-shadow-element-1.html
layout/reftests/webcomponents/reftest.list
parser/htmlparser/public/nsHTMLTagList.h
parser/htmlparser/src/nsElementTable.cpp
parser/htmlparser/src/nsHTMLTags.cpp
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -2270,16 +2270,32 @@ public:
     nsContentUtils::EnterMicroTask();
   }
   ~nsAutoMicroTask()
   {
     nsContentUtils::LeaveMicroTask();
   }
 };
 
+namespace mozilla {
+namespace dom {
+
+class TreeOrderComparator {
+public:
+  bool Equals(nsINode* aElem1, nsINode* aElem2) const {
+    return aElem1 == aElem2;
+  }
+  bool LessThan(nsINode* aElem1, nsINode* aElem2) const {
+    return nsContentUtils::PositionIsBefore(aElem1, aElem2);
+  }
+};
+
+} // namespace dom
+} // namespace mozilla
+
 #define NS_INTERFACE_MAP_ENTRY_TEAROFF(_interface, _allocator)                \
   if (aIID.Equals(NS_GET_IID(_interface))) {                                  \
     foundInterface = static_cast<_interface *>(_allocator);                   \
     if (!foundInterface) {                                                    \
       *aInstancePtr = nullptr;                                                 \
       return NS_ERROR_OUT_OF_MEMORY;                                          \
     }                                                                         \
   } else
--- a/content/base/src/ChildIterator.cpp
+++ b/content/base/src/ChildIterator.cpp
@@ -3,16 +3,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ChildIterator.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/XBLChildrenElement.h"
 #include "mozilla/dom/HTMLContentElement.h"
+#include "mozilla/dom/HTMLShadowElement.h"
+#include "mozilla/dom/ShadowRoot.h"
 
 namespace mozilla {
 namespace dom {
 
 class MatchedNodes {
 public:
   MatchedNodes(HTMLContentElement* aInsertionPoint)
     : mIsContentElement(true), mContentElement(aInsertionPoint) {}
@@ -68,16 +70,27 @@ ExplicitChildIterator::GetNextChild()
     MOZ_ASSERT(!mDefaultChild);
 
     MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild);
     if (mIndexInInserted < assignedChildren.Length()) {
       return assignedChildren[mIndexInInserted++];
     }
     mIndexInInserted = 0;
     mChild = mChild->GetNextSibling();
+  } else if (mShadowIterator) {
+    // If we're inside of a <shadow> element, look through the
+    // explicit children of the projected ShadowRoot via
+    // the mShadowIterator.
+    nsIContent* nextChild = mShadowIterator->GetNextChild();
+    if (nextChild) {
+      return nextChild;
+    }
+
+    mShadowIterator = nullptr;
+    mChild = mChild->GetNextSibling();
   } else if (mDefaultChild) {
     // If we're already in default content, check if there are more nodes there
     MOZ_ASSERT(mChild);
     MOZ_ASSERT(nsContentUtils::IsContentInsertionPoint(mChild));
 
     mDefaultChild = mDefaultChild->GetNextSibling();
     if (mDefaultChild) {
       return mDefaultChild;
@@ -88,34 +101,59 @@ ExplicitChildIterator::GetNextChild()
     mChild = mParent->GetFirstChild();
     mIsFirst = false;
   } else if (mChild) { // in the middle of the child list
     mChild = mChild->GetNextSibling();
   }
 
   // Iterate until we find a non-insertion point, or an insertion point with
   // content.
-  while (mChild && nsContentUtils::IsContentInsertionPoint(mChild)) {
-    MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild);
-    if (!assignedChildren.IsEmpty()) {
-      // Iterate through elements projected on insertion point.
-      mIndexInInserted = 1;
-      return assignedChildren[0];
-    }
+  while (mChild) {
+    // If the current child being iterated is a shadow insertion point then
+    // the iterator needs to go into the projected ShadowRoot.
+    if (ShadowRoot::IsShadowInsertionPoint(mChild)) {
+      // Look for the next child in the projected ShadowRoot for the <shadow>
+      // element.
+      HTMLShadowElement* shadowElem = static_cast<HTMLShadowElement*>(mChild);
+      ShadowRoot* projectedShadow = shadowElem->GetOlderShadowRoot();
+      if (projectedShadow) {
+        mShadowIterator = new ExplicitChildIterator(projectedShadow);
+        nsIContent* nextChild = mShadowIterator->GetNextChild();
+        if (nextChild) {
+          return nextChild;
+        }
+        mShadowIterator = nullptr;
+      }
+      mChild = mChild->GetNextSibling();
+    } else if (nsContentUtils::IsContentInsertionPoint(mChild)) {
+      // If the current child being iterated is a content insertion point
+      // then the iterator needs to return the nodes distributed into
+      // the content insertion point.
+      MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild);
+      if (!assignedChildren.IsEmpty()) {
+        // Iterate through elements projected on insertion point.
+        mIndexInInserted = 1;
+        return assignedChildren[0];
+      }
 
-    // Insertion points inside fallback/default content
-    // are considered inactive and do not get assigned nodes.
-    mDefaultChild = mChild->GetFirstChild();
-    if (mDefaultChild) {
-      return mDefaultChild;
+      // Insertion points inside fallback/default content
+      // are considered inactive and do not get assigned nodes.
+      mDefaultChild = mChild->GetFirstChild();
+      if (mDefaultChild) {
+        return mDefaultChild;
+      }
+
+      // If we have an insertion point with no assigned nodes and
+      // no default content, move on to the next node.
+      mChild = mChild->GetNextSibling();
+    } else {
+      // mChild is not an insertion point, thus it is the next node to
+      // return from this iterator.
+      break;
     }
-
-    // If we have an insertion point with no assigned nodes and
-    // no default content, move on to the next node.
-    mChild = mChild->GetNextSibling();
   }
 
   return mChild;
 }
 
 FlattenedChildIterator::FlattenedChildIterator(nsIContent* aParent)
   : ExplicitChildIterator(aParent), mXBLInvolved(false)
 {
@@ -141,38 +179,49 @@ FlattenedChildIterator::FlattenedChildIt
         MOZ_ASSERT(child->GetBindingParent());
         mXBLInvolved = true;
         break;
       }
     }
   }
 }
 
-nsIContent* FlattenedChildIterator::Get()
+nsIContent*
+ExplicitChildIterator::Get()
 {
   MOZ_ASSERT(!mIsFirst);
 
   if (mIndexInInserted) {
     XBLChildrenElement* point = static_cast<XBLChildrenElement*>(mChild);
     return point->mInsertedChildren[mIndexInInserted - 1];
+  } else if (mShadowIterator)  {
+    return mShadowIterator->Get();
   }
   return mDefaultChild ? mDefaultChild : mChild;
 }
 
-nsIContent* FlattenedChildIterator::GetPreviousChild()
+nsIContent*
+ExplicitChildIterator::GetPreviousChild()
 {
   // If we're already in the inserted-children array, look there first
   if (mIndexInInserted) {
     // NB: mIndexInInserted points one past the last returned child so we need
     // to look *two* indices back in order to return the previous child.
     MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild);
     if (--mIndexInInserted) {
       return assignedChildren[mIndexInInserted - 1];
     }
     mChild = mChild->GetPreviousSibling();
+  } else if (mShadowIterator) {
+    nsIContent* previousChild = mShadowIterator->GetPreviousChild();
+    if (previousChild) {
+      return previousChild;
+    }
+    mShadowIterator = nullptr;
+    mChild = mChild->GetPreviousSibling();
   } else if (mDefaultChild) {
     // If we're already in default content, check if there are more nodes there
     mDefaultChild = mDefaultChild->GetPreviousSibling();
     if (mDefaultChild) {
       return mDefaultChild;
     }
 
     mChild = mChild->GetPreviousSibling();
@@ -181,29 +230,53 @@ nsIContent* FlattenedChildIterator::GetP
   } else if (mChild) { // in the middle of the child list
     mChild = mChild->GetPreviousSibling();
   } else { // at the end of the child list
     mChild = mParent->GetLastChild();
   }
 
   // Iterate until we find a non-insertion point, or an insertion point with
   // content.
-  while (mChild && nsContentUtils::IsContentInsertionPoint(mChild)) {
-    MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild);
-    if (!assignedChildren.IsEmpty()) {
-      mIndexInInserted = assignedChildren.Length();
-      return assignedChildren[mIndexInInserted - 1];
-    }
+  while (mChild) {
+    if (ShadowRoot::IsShadowInsertionPoint(mChild)) {
+      // If the current child being iterated is a shadow insertion point then
+      // the iterator needs to go into the projected ShadowRoot.
+      HTMLShadowElement* shadowElem = static_cast<HTMLShadowElement*>(mChild);
+      ShadowRoot* projectedShadow = shadowElem->GetOlderShadowRoot();
+      if (projectedShadow) {
+        // Create a ExplicitChildIterator that begins iterating from the end.
+        mShadowIterator = new ExplicitChildIterator(projectedShadow, false);
+        nsIContent* previousChild = mShadowIterator->GetPreviousChild();
+        if (previousChild) {
+          return previousChild;
+        }
+        mShadowIterator = nullptr;
+      }
+      mChild = mChild->GetPreviousSibling();
+    } else if (nsContentUtils::IsContentInsertionPoint(mChild)) {
+      // If the current child being iterated is a content insertion point
+      // then the iterator needs to return the nodes distributed into
+      // the content insertion point.
+      MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild);
+      if (!assignedChildren.IsEmpty()) {
+        mIndexInInserted = assignedChildren.Length();
+        return assignedChildren[mIndexInInserted - 1];
+      }
 
-    mDefaultChild = mChild->GetLastChild();
-    if (mDefaultChild) {
-      return mDefaultChild;
+      mDefaultChild = mChild->GetLastChild();
+      if (mDefaultChild) {
+        return mDefaultChild;
+      }
+
+      mChild = mChild->GetPreviousSibling();
+    } else {
+      // mChild is not an insertion point, thus it is the next node to
+      // return from this iterator.
+      break;
     }
-
-    mChild = mChild->GetPreviousSibling();
   }
 
   if (!mChild) {
     mIsFirst = true;
   }
 
   return mChild;
 }
--- a/content/base/src/ChildIterator.h
+++ b/content/base/src/ChildIterator.h
@@ -19,26 +19,28 @@
 #include "nsIContent.h"
 
 namespace mozilla {
 namespace dom {
 
 // This class iterates normal DOM child nodes of a given DOM node with
 // <xbl:children> nodes replaced by the elements that have been filtered into that
 // insertion point. Any bindings on the given element are ignored for purposes
-// of determining which insertion point children are filtered into.
+// of determining which insertion point children are filtered into. The iterator
+// can be initialized to start at the end by providing false for aStartAtBeginning
+// in order to start iterating in reverse from the last child.
 class ExplicitChildIterator
 {
 public:
-  ExplicitChildIterator(nsIContent* aParent)
+  ExplicitChildIterator(nsIContent* aParent, bool aStartAtBeginning = true)
     : mParent(aParent),
       mChild(nullptr),
       mDefaultChild(nullptr),
       mIndexInInserted(0),
-      mIsFirst(true)
+      mIsFirst(aStartAtBeginning)
   {
   }
 
   nsIContent* GetNextChild();
 
   // Looks for aChildToFind respecting insertion points until aChildToFind
   // or aBound is found. If aBound is nullptr then the seek is unbounded. Returns
   // whether aChildToFind was found as an explicit child prior to encountering
@@ -52,34 +54,48 @@ public:
     nsIContent* child;
     do {
       child = GetNextChild();
     } while (child && child != aChildToFind && child != aBound);
 
     return child == aChildToFind;
   }
 
+  // Returns the current target of this iterator (which might be an explicit
+  // child of the node, fallback content of an insertion point or
+  // a node distributed to an insertion point.
+  nsIContent* Get();
+
+  // The inverse of GetNextChild. Properly steps in and out of insertion
+  // points.
+  nsIContent* GetPreviousChild();
+
 protected:
   // The parent of the children being iterated. For the FlattenedChildIterator,
   // if there is a binding attached to the original parent, mParent points to
   // the <xbl:content> element for the binding.
   nsIContent* mParent;
 
-  // The current child. When we encounter an <xbl:children> insertion point,
+  // The current child. When we encounter an insertion point,
   // mChild remains as the insertion point whose content we're iterating (and
   // our state is controled by mDefaultChild or mIndexInInserted depending on
   // whether the insertion point expands to its default content or not).
   nsIContent* mChild;
 
   // If non-null, this points to the current default content for the current
   // insertion point that we're iterating (i.e. mChild, which must be an
-  // nsXBLChildrenElement). Once this transitions back to null,
-  // we continue iterating at mChild's next sibling.
+  // nsXBLChildrenElement or HTMLContentElement). Once this transitions back
+  // to null, we continue iterating at mChild's next sibling.
   nsIContent* mDefaultChild;
 
+  // If non-null, this points to an iterator of the explicit children of
+  // the ShadowRoot projected by the current shadow element that we're
+  // iterating.
+  nsAutoPtr<ExplicitChildIterator> mShadowIterator;
+
   // If not zero, we're iterating inserted children for an insertion point. This
   // is an index into mChild's inserted children array (mChild must be an
   // nsXBLChildrenElement). The index is one past the "current" child (as
   // opposed to mChild which represents the "current" child).
   uint32_t mIndexInInserted;
 
   // A flag to let us know that we haven't started iterating yet.
   bool mIsFirst;
@@ -88,25 +104,16 @@ protected:
 // Iterates over the flattened children of a node, which accounts for anonymous
 // children and nodes moved by insertion points. If a node has anonymous
 // children, those are iterated over.
 class FlattenedChildIterator : public ExplicitChildIterator
 {
 public:
   FlattenedChildIterator(nsIContent* aParent);
 
-  // Returns the current target of this iterator (which might be an explicit
-  // child of the node, default content for an <xbl:children> element or
-  // an inserted child for an <xbl:children> element.
-  nsIContent* Get();
-
-  // The inverse of GetNextChild. Properly steps in and out of <xbl:children>
-  // elements.
-  nsIContent* GetPreviousChild();
-
   bool XBLInvolved() { return mXBLInvolved; }
 
 private:
   // For certain optimizations, nsCSSFrameConstructor needs to know if the
   // child list of the element that we're iterating matches its .childNodes.
   bool mXBLInvolved;
 };
 
--- a/content/base/src/Element.cpp
+++ b/content/base/src/Element.cpp
@@ -748,17 +748,26 @@ Element::CreateShadowRoot(ErrorResult& a
   // Unlike for XBL, false is the default for inheriting style.
   protoBinding->SetInheritsStyle(false);
 
   // Calling SetPrototypeBinding takes ownership of protoBinding.
   docInfo->SetPrototypeBinding(NS_LITERAL_CSTRING("shadowroot"), protoBinding);
 
   nsRefPtr<ShadowRoot> shadowRoot = new ShadowRoot(this, nodeInfo.forget(),
                                                    protoBinding);
+
+  // Replace the old ShadowRoot with the new one and let the old
+  // ShadowRoot know about the younger ShadowRoot because the old
+  // ShadowRoot is projected into the younger ShadowRoot's shadow
+  // insertion point (if it exists).
+  ShadowRoot* olderShadow = GetShadowRoot();
   SetShadowRoot(shadowRoot);
+  if (olderShadow) {
+    olderShadow->SetYoungerShadow(shadowRoot);
+  }
 
   // xblBinding takes ownership of docInfo.
   nsRefPtr<nsXBLBinding> xblBinding = new nsXBLBinding(shadowRoot, protoBinding);
   shadowRoot->SetAssociatedBinding(xblBinding);
   xblBinding->SetBoundElement(this);
 
   SetXBLBinding(xblBinding);
 
--- a/content/base/src/ShadowRoot.cpp
+++ b/content/base/src/ShadowRoot.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/dom/DocumentFragment.h"
 #include "ChildIterator.h"
 #include "nsContentUtils.h"
 #include "nsDOMClassInfoID.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsIStyleSheetLinkingElement.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLContentElement.h"
+#include "mozilla/dom/HTMLShadowElement.h"
 #include "nsXBLPrototypeBinding.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static PLDHashOperator
 IdentifierMapEntryTraverse(nsIdentifierMapEntry *aEntry, void *aArg)
 {
@@ -27,29 +28,33 @@ IdentifierMapEntryTraverse(nsIdentifierM
   aEntry->Traverse(cb);
   return PL_DHASH_NEXT;
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(ShadowRoot)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ShadowRoot,
                                                   DocumentFragment)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHost)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPoolHost)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStyleSheetList)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOlderShadow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mYoungerShadow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAssociatedBinding)
   tmp->mIdentifierMap.EnumerateEntries(IdentifierMapEntryTraverse, &cb);
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(ShadowRoot,
                                                 DocumentFragment)
-  if (tmp->mHost) {
-    tmp->mHost->RemoveMutationObserver(tmp);
+  if (tmp->mPoolHost) {
+    tmp->mPoolHost->RemoveMutationObserver(tmp);
   }
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mHost)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPoolHost)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mStyleSheetList)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mOlderShadow)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mYoungerShadow)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mAssociatedBinding)
   tmp->mIdentifierMap.Clear();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 DOMCI_DATA(ShadowRoot, ShadowRoot)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ShadowRoot)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContent)
@@ -57,37 +62,39 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_
 NS_INTERFACE_MAP_END_INHERITING(DocumentFragment)
 
 NS_IMPL_ADDREF_INHERITED(ShadowRoot, DocumentFragment)
 NS_IMPL_RELEASE_INHERITED(ShadowRoot, DocumentFragment)
 
 ShadowRoot::ShadowRoot(nsIContent* aContent,
                        already_AddRefed<nsINodeInfo> aNodeInfo,
                        nsXBLPrototypeBinding* aProtoBinding)
-  : DocumentFragment(aNodeInfo), mHost(aContent),
-    mProtoBinding(aProtoBinding), mInsertionPointChanged(false)
+  : DocumentFragment(aNodeInfo), mPoolHost(aContent),
+    mProtoBinding(aProtoBinding), mShadowElement(nullptr),
+    mInsertionPointChanged(false)
 {
   SetHost(aContent);
   SetFlags(NODE_IS_IN_SHADOW_TREE);
   // ShadowRoot isn't really in the document but it behaves like it is.
   SetInDocument();
   DOMSlots()->mBindingParent = aContent;
   DOMSlots()->mContainingShadow = this;
 
   // Add the ShadowRoot as a mutation observer on the host to watch
   // for mutations because the insertion points in this ShadowRoot
   // may need to be updated when the host children are modified.
-  mHost->AddMutationObserver(this);
+  mPoolHost->AddMutationObserver(this);
 }
 
 ShadowRoot::~ShadowRoot()
 {
-  if (mHost) {
-    // mHost may have been unlinked.
-    mHost->RemoveMutationObserver(this);
+  if (mPoolHost) {
+    // mPoolHost may have been unlinked or a new ShadowRoot may have been
+    // creating, making this one obsolete.
+    mPoolHost->RemoveMutationObserver(this);
   }
 
   ClearInDocument();
   SetHost(nullptr);
 }
 
 JSObject*
 ShadowRoot::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
@@ -228,46 +235,50 @@ ShadowRoot::GetElementsByClassName(const
 }
 
 bool
 ShadowRoot::PrefEnabled()
 {
   return Preferences::GetBool("dom.webcomponents.enabled", false);
 }
 
-class TreeOrderComparator {
-public:
-  bool Equals(nsINode* aElem1, nsINode* aElem2) const {
-    return aElem1 == aElem2;
-  }
-  bool LessThan(nsINode* aElem1, nsINode* aElem2) const {
-    return nsContentUtils::PositionIsBefore(aElem1, aElem2);
-  }
-};
-
 void
 ShadowRoot::AddInsertionPoint(HTMLContentElement* aInsertionPoint)
 {
   TreeOrderComparator comparator;
   mInsertionPoints.InsertElementSorted(aInsertionPoint, comparator);
 }
 
 void
 ShadowRoot::RemoveInsertionPoint(HTMLContentElement* aInsertionPoint)
 {
   mInsertionPoints.RemoveElement(aInsertionPoint);
 }
 
 void
+ShadowRoot::SetYoungerShadow(ShadowRoot* aYoungerShadow)
+{
+  mYoungerShadow = aYoungerShadow;
+  mYoungerShadow->mOlderShadow = this;
+
+  ChangePoolHost(mYoungerShadow->GetShadowElement());
+}
+
+void
 ShadowRoot::DistributeSingleNode(nsIContent* aContent)
 {
   // Find the insertion point to which the content belongs.
   HTMLContentElement* insertionPoint = nullptr;
   for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
     if (mInsertionPoints[i]->Match(aContent)) {
+      if (mInsertionPoints[i]->MatchedNodes().Contains(aContent)) {
+        // Node is already matched into the insertion point. We are done.
+        return;
+      }
+
       // Matching may cause the insertion point to drop fallback content.
       if (mInsertionPoints[i]->MatchedNodes().IsEmpty() &&
           static_cast<nsINode*>(mInsertionPoints[i])->GetFirstChild()) {
         // This match will cause the insertion point to drop all fallback
         // content and used matched nodes instead. Give up on the optimization
         // and just distribute all nodes.
         DistributeAllNodes();
         return;
@@ -278,17 +289,18 @@ ShadowRoot::DistributeSingleNode(nsICont
   }
 
   // Find the index into the insertion point.
   if (insertionPoint) {
     nsCOMArray<nsIContent>& matchedNodes = insertionPoint->MatchedNodes();
     // Find the appropriate position in the matched node list for the
     // newly distributed content.
     bool isIndexFound = false;
-    ExplicitChildIterator childIterator(GetHost());
+    MOZ_ASSERT(mPoolHost, "Where did the content come from if there is no pool host?");
+    ExplicitChildIterator childIterator(mPoolHost);
     for (uint32_t i = 0; i < matchedNodes.Length(); i++) {
       // Seek through the host's explicit children until the inserted content
       // is found or when the current matched node is reached.
       if (childIterator.Seek(aContent, matchedNodes[i])) {
         // aContent was found before the current matched node.
         matchedNodes.InsertElementAt(i, aContent);
         isIndexFound = true;
         break;
@@ -298,23 +310,42 @@ ShadowRoot::DistributeSingleNode(nsICont
     if (!isIndexFound) {
       // We have still not found an index in the insertion point,
       // thus it must be at the end.
       MOZ_ASSERT(childIterator.Seek(aContent),
                  "Trying to match a node that is not a candidate to be matched");
       matchedNodes.AppendElement(aContent);
     }
 
+    // Handle the case where the parent of the insertion point is a ShadowRoot
+    // that is projected into the younger ShadowRoot's shadow insertion point.
+    // The node distributed into the insertion point must be reprojected
+    // to the shadow insertion point.
+    if (insertionPoint->GetParent() == this &&
+        mYoungerShadow && mYoungerShadow->GetShadowElement()) {
+      mYoungerShadow->GetShadowElement()->DistributeSingleNode(aContent);
+    }
+
     // Handle the case where the parent of the insertion point has a ShadowRoot.
     // The node distributed into the insertion point must be reprojected to the
     // insertion points of the parent's ShadowRoot.
     ShadowRoot* parentShadow = insertionPoint->GetParent()->GetShadowRoot();
     if (parentShadow) {
       parentShadow->DistributeSingleNode(aContent);
     }
+
+    // Handle the case where the parent of the insertion point is the <shadow>
+    // element. The node distributed into the insertion point must be reprojected
+    // into the older ShadowRoot's insertion points.
+    if (mShadowElement && mShadowElement == insertionPoint->GetParent()) {
+      ShadowRoot* olderShadow = mShadowElement->GetOlderShadowRoot();
+      if (olderShadow) {
+        olderShadow->DistributeSingleNode(aContent);
+      }
+    }
   }
 }
 
 void
 ShadowRoot::RemoveDistributedNode(nsIContent* aContent)
 {
   // Find insertion point containing the content and remove the node.
   for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
@@ -326,69 +357,111 @@ ShadowRoot::RemoveDistributedNode(nsICon
         // Removing the matched node will cause fallback content to be
         // used instead. Give up optimization and distribute all nodes.
         DistributeAllNodes();
         return;
       }
 
       mInsertionPoints[i]->MatchedNodes().RemoveElement(aContent);
 
+      // Handle the case where the parent of the insertion point is a ShadowRoot
+      // that is projected into the younger ShadowRoot's shadow insertion point.
+      // The removed node needs to be removed from the shadow insertion point.
+      if (mInsertionPoints[i]->GetParent() == this) {
+        if (mYoungerShadow && mYoungerShadow->GetShadowElement()) {
+          mYoungerShadow->GetShadowElement()->RemoveDistributedNode(aContent);
+        }
+      }
+
       // Handle the case where the parent of the insertion point has a ShadowRoot.
       // The removed node needs to be removed from the insertion points of the
       // parent's ShadowRoot.
       ShadowRoot* parentShadow = mInsertionPoints[i]->GetParent()->GetShadowRoot();
       if (parentShadow) {
         parentShadow->RemoveDistributedNode(aContent);
       }
 
+      // Handle the case where the parent of the insertion point is the <shadow>
+      // element. The removed node must be removed from the older ShadowRoot's
+      // insertion points.
+      if (mShadowElement && mShadowElement == mInsertionPoints[i]->GetParent()) {
+        ShadowRoot* olderShadow = mShadowElement->GetOlderShadowRoot();
+        if (olderShadow) {
+          olderShadow->RemoveDistributedNode(aContent);
+        }
+      }
+
       break;
     }
   }
 }
 
 void
 ShadowRoot::DistributeAllNodes()
 {
   // Create node pool.
   nsTArray<nsIContent*> nodePool;
-  ExplicitChildIterator childIterator(GetHost());
-  for (nsIContent* content = childIterator.GetNextChild();
-       content;
-       content = childIterator.GetNextChild()) {
-    nodePool.AppendElement(content);
+
+  // Make sure there is a pool host, an older shadow may not have
+  // one if the younger shadow does not have a <shadow> element.
+  if (mPoolHost) {
+    ExplicitChildIterator childIterator(mPoolHost);
+    for (nsIContent* content = childIterator.GetNextChild();
+         content;
+         content = childIterator.GetNextChild()) {
+      nodePool.AppendElement(content);
+    }
   }
 
+  nsTArray<ShadowRoot*> shadowsToUpdate;
+
   for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
     mInsertionPoints[i]->ClearMatchedNodes();
     // Assign matching nodes from node pool.
     for (uint32_t j = 0; j < nodePool.Length(); j++) {
       if (mInsertionPoints[i]->Match(nodePool[j])) {
         mInsertionPoints[i]->MatchedNodes().AppendElement(nodePool[j]);
         nodePool[j]->SetXBLInsertionParent(mInsertionPoints[i]);
         nodePool.RemoveElementAt(j--);
       }
     }
-  }
 
-  // Distribute nodes in outer ShadowRoot for the case of an insertion point being
-  // distributed into an outer ShadowRoot.
-  for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
+    // Keep track of instances where the content insertion point is distributed
+    // (parent of insertion point has a ShadowRoot).
     nsIContent* insertionParent = mInsertionPoints[i]->GetParent();
     MOZ_ASSERT(insertionParent, "The only way for an insertion point to be in the"
                                 "mInsertionPoints array is to be a descendant of a"
                                 "ShadowRoot, in which case, it should have a parent");
 
     // If the parent of the insertion point has as ShadowRoot, the nodes distributed
     // to the insertion point must be reprojected to the insertion points of the
     // parent's ShadowRoot.
     ShadowRoot* parentShadow = insertionParent->GetShadowRoot();
-    if (parentShadow) {
-      parentShadow->DistributeAllNodes();
+    if (parentShadow && !shadowsToUpdate.Contains(parentShadow)) {
+      shadowsToUpdate.AppendElement(parentShadow);
     }
   }
+
+  // If there is a shadow insertion point in this ShadowRoot, the children
+  // of the shadow insertion point needs to be distributed into the insertion
+  // points of the older ShadowRoot.
+  if (mShadowElement && mOlderShadow) {
+    mOlderShadow->DistributeAllNodes();
+  }
+
+  // If there is a younger ShadowRoot with a shadow insertion point,
+  // then the children of this ShadowRoot needs to be distributed to
+  // the younger ShadowRoot's shadow insertion point.
+  if (mYoungerShadow && mYoungerShadow->GetShadowElement()) {
+    mYoungerShadow->GetShadowElement()->DistributeAllNodes();
+  }
+
+  for (uint32_t i = 0; i < shadowsToUpdate.Length(); i++) {
+    shadowsToUpdate[i]->DistributeAllNodes();
+  }
 }
 
 void
 ShadowRoot::GetInnerHTML(nsAString& aInnerHTML)
 {
   GetMarkup(false, aInnerHTML);
 }
 
@@ -422,26 +495,80 @@ ShadowRoot::StyleSheets()
 {
   if (!mStyleSheetList) {
     mStyleSheetList = new ShadowRootStyleSheetList(this);
   }
 
   return mStyleSheetList;
 }
 
+void
+ShadowRoot::SetShadowElement(HTMLShadowElement* aShadowElement)
+{
+  // If there is already a shadow element point, remove
+  // the projected shadow because it is no longer an insertion
+  // point.
+  if (mShadowElement) {
+    mShadowElement->SetProjectedShadow(nullptr);
+  }
+
+  if (mOlderShadow) {
+    // Nodes for distribution will come from the new shadow element.
+    mOlderShadow->ChangePoolHost(aShadowElement);
+  }
+
+  // Set the new shadow element to project the older ShadowRoot because
+  // it is the current shadow insertion point.
+  mShadowElement = aShadowElement;
+  if (mShadowElement) {
+    mShadowElement->SetProjectedShadow(mOlderShadow);
+  }
+}
+
+void
+ShadowRoot::ChangePoolHost(nsIContent* aNewHost)
+{
+  if (mPoolHost) {
+    mPoolHost->RemoveMutationObserver(this);
+  }
+
+  // Clear the nodes matched to content insertion points
+  // because it is no longer relevant.
+  for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
+    mInsertionPoints[i]->ClearMatchedNodes();
+  }
+
+  mPoolHost = aNewHost;
+  if (mPoolHost) {
+    mPoolHost->AddMutationObserver(this);
+  }
+}
+
+bool
+ShadowRoot::IsShadowInsertionPoint(nsIContent* aContent)
+{
+  if (aContent && aContent->IsHTML(nsGkAtoms::shadow)) {
+    HTMLShadowElement* shadowElem = static_cast<HTMLShadowElement*>(aContent);
+    return shadowElem->IsInsertionPoint();
+  }
+  return false;
+}
+
 /**
  * Returns whether the web components pool population algorithm
  * on the host would contain |aContent|. This function ignores
  * insertion points in the pool, thus should only be used to
  * test nodes that have not yet been distributed.
  */
-static bool
-IsPooledNode(nsIContent* aContent, nsIContent* aContainer, nsIContent* aHost)
+bool
+ShadowRoot::IsPooledNode(nsIContent* aContent, nsIContent* aContainer,
+                         nsIContent* aHost)
 {
-  if (nsContentUtils::IsContentInsertionPoint(aContent)) {
+  if (nsContentUtils::IsContentInsertionPoint(aContent) ||
+      IsShadowInsertionPoint(aContent)) {
     // Insertion points never end up in the pool.
     return false;
   }
 
   if (aContainer->IsHTML(nsGkAtoms::content)) {
     // Fallback content will end up in pool if its parent is a child of the host.
     HTMLContentElement* content = static_cast<HTMLContentElement*>(aContainer);
     return content->IsInsertionPoint() && content->MatchedNodes().IsEmpty() &&
@@ -458,19 +585,17 @@ IsPooledNode(nsIContent* aContent, nsICo
 
 void
 ShadowRoot::AttributeChanged(nsIDocument* aDocument,
                              Element* aElement,
                              int32_t aNameSpaceID,
                              nsIAtom* aAttribute,
                              int32_t aModType)
 {
-  // Watch for attribute changes to nodes in the pool because
-  // insertion points can select on attributes.
-  if (!IsPooledNode(aElement, aElement->GetParent(), mHost)) {
+  if (!IsPooledNode(aElement, aElement->GetParent(), mPoolHost)) {
     return;
   }
 
   // Attributes may change insertion point matching, find its new distribution.
   RemoveDistributedNode(aElement);
   DistributeSingleNode(aElement);
 }
 
@@ -485,17 +610,17 @@ ShadowRoot::ContentAppended(nsIDocument*
     mInsertionPointChanged = false;
     return;
   }
 
   // Watch for new nodes added to the pool because the node
   // may need to be added to an insertion point.
   nsIContent* currentChild = aFirstNewContent;
   while (currentChild) {
-    if (IsPooledNode(currentChild, aContainer, mHost)) {
+    if (IsPooledNode(currentChild, aContainer, mPoolHost)) {
       DistributeSingleNode(currentChild);
     }
     currentChild = currentChild->GetNextSibling();
   }
 }
 
 void
 ShadowRoot::ContentInserted(nsIDocument* aDocument,
@@ -506,17 +631,17 @@ ShadowRoot::ContentInserted(nsIDocument*
   if (mInsertionPointChanged) {
     DistributeAllNodes();
     mInsertionPointChanged = false;
     return;
   }
 
   // Watch for new nodes added to the pool because the node
   // may need to be added to an insertion point.
-  if (IsPooledNode(aChild, aContainer, mHost)) {
+  if (IsPooledNode(aChild, aContainer, mPoolHost)) {
     DistributeSingleNode(aChild);
   }
 }
 
 void
 ShadowRoot::ContentRemoved(nsIDocument* aDocument,
                            nsIContent* aContainer,
                            nsIContent* aChild,
@@ -526,17 +651,17 @@ ShadowRoot::ContentRemoved(nsIDocument* 
   if (mInsertionPointChanged) {
     DistributeAllNodes();
     mInsertionPointChanged = false;
     return;
   }
 
   // Watch for node that is removed from the pool because
   // it may need to be removed from an insertion point.
-  if (IsPooledNode(aChild, aContainer, mHost)) {
+  if (IsPooledNode(aChild, aContainer, mPoolHost)) {
     RemoveDistributedNode(aChild);
   }
 }
 
 NS_IMPL_CYCLE_COLLECTION_1(ShadowRootStyleSheetList, mShadowRoot)
 
 NS_INTERFACE_TABLE_HEAD(ShadowRootStyleSheetList)
   NS_INTERFACE_TABLE1(ShadowRootStyleSheetList, nsIDOMStyleSheetList)
--- a/content/base/src/ShadowRoot.h
+++ b/content/base/src/ShadowRoot.h
@@ -20,16 +20,17 @@ class nsPIDOMWindow;
 class nsXBLPrototypeBinding;
 class nsTagNameMapEntry;
 
 namespace mozilla {
 namespace dom {
 
 class Element;
 class HTMLContentElement;
+class HTMLShadowElement;
 class ShadowRootStyleSheetList;
 
 class ShadowRoot : public DocumentFragment,
                    public nsStubMutationObserver
 {
   friend class ShadowRootStyleSheetList;
 public:
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ShadowRoot,
@@ -48,89 +49,125 @@ public:
   void AddToIdTable(Element* aElement, nsIAtom* aId);
   void RemoveFromIdTable(Element* aElement, nsIAtom* aId);
   static bool PrefEnabled();
   void InsertSheet(nsCSSStyleSheet* aSheet, nsIContent* aLinkingContent);
   void RemoveSheet(nsCSSStyleSheet* aSheet);
   bool ApplyAuthorStyles();
   void SetApplyAuthorStyles(bool aApplyAuthorStyles);
   nsIDOMStyleSheetList* StyleSheets();
+  HTMLShadowElement* GetShadowElement() { return mShadowElement; }
+
+  /**
+   * Sets the current shadow insertion point where the older
+   * ShadowRoot will be projected.
+   */
+  void SetShadowElement(HTMLShadowElement* aShadowElement);
 
   /**
-   * Distributes a single explicit child of the host to the content
+   * Change the node that populates the distribution pool with
+   * its children. This is distinct from the ShadowRoot host described
+   * in the specifications. The ShadowRoot host is the element
+   * which created this ShadowRoot and does not change. The pool host
+   * is the same as the ShadowRoot host if this is the youngest
+   * ShadowRoot. If this is an older ShadowRoot, the pool host is
+   * the <shadow> element in the younger ShadowRoot (if it exists).
+   */
+  void ChangePoolHost(nsIContent* aNewHost);
+
+  /**
+   * Distributes a single explicit child of the pool host to the content
    * insertion points in this ShadowRoot.
    */
   void DistributeSingleNode(nsIContent* aContent);
 
   /**
-   * Removes a single explicit child of the host from the content
+   * Removes a single explicit child of the pool host from the content
    * insertion points in this ShadowRoot.
    */
   void RemoveDistributedNode(nsIContent* aContent);
 
   /**
-   * Distributes all the explicit children of the host to the content
+   * Distributes all the explicit children of the pool host to the content
    * insertion points in this ShadowRoot.
    */
   void DistributeAllNodes();
 
   void AddInsertionPoint(HTMLContentElement* aInsertionPoint);
   void RemoveInsertionPoint(HTMLContentElement* aInsertionPoint);
 
+  void SetYoungerShadow(ShadowRoot* aYoungerShadow);
+  ShadowRoot* GetOlderShadow() { return mOlderShadow; }
+  ShadowRoot* GetYoungerShadow() { return mYoungerShadow; }
   void SetInsertionPointChanged() { mInsertionPointChanged = true; }
 
-  void SetAssociatedBinding(nsXBLBinding* aBinding)
-  {
-    mAssociatedBinding = aBinding;
-  }
+  void SetAssociatedBinding(nsXBLBinding* aBinding) { mAssociatedBinding = aBinding; }
 
-  nsISupports* GetParentObject() const
-  {
-    return mHost;
-  }
+  nsISupports* GetParentObject() const { return mPoolHost; }
 
-  nsIContent* GetHost() { return mHost; }
+  nsIContent* GetPoolHost() { return mPoolHost; }
+  nsTArray<HTMLShadowElement*>& ShadowDescendants() { return mShadowDescendants; }
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
+  static bool IsPooledNode(nsIContent* aChild, nsIContent* aContainer,
+                           nsIContent* aHost);
   static ShadowRoot* FromNode(nsINode* aNode);
+  static bool IsShadowInsertionPoint(nsIContent* aContent);
 
   // WebIDL methods.
   Element* GetElementById(const nsAString& aElementId);
   already_AddRefed<nsContentList>
     GetElementsByTagName(const nsAString& aNamespaceURI);
   already_AddRefed<nsContentList>
     GetElementsByTagNameNS(const nsAString& aNamespaceURI,
                            const nsAString& aLocalName);
   already_AddRefed<nsContentList>
     GetElementsByClassName(const nsAString& aClasses);
   void GetInnerHTML(nsAString& aInnerHTML);
   void SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError);
 protected:
   void Restyle();
 
-  nsCOMPtr<nsIContent> mHost;
+  // The pool host is the parent of the nodes that will be distributed
+  // into the insertion points in this ShadowRoot. See |ChangeShadowRoot|.
+  nsCOMPtr<nsIContent> mPoolHost;
 
   // An array of content insertion points that are a descendant of the ShadowRoot
   // sorted in tree order. Insertion points are responsible for notifying
   // the ShadowRoot when they are removed or added as a descendant. The insertion
   // points are kept alive by the parent node, thus weak references are held
   // by the array.
   nsTArray<HTMLContentElement*> mInsertionPoints;
 
+  // An array of the <shadow> elements that are descendant of the ShadowRoot
+  // sorted in tree order. Only the first may be a shadow insertion point.
+  nsTArray<HTMLShadowElement*> mShadowDescendants;
+
   nsTHashtable<nsIdentifierMapEntry> mIdentifierMap;
   nsXBLPrototypeBinding* mProtoBinding;
 
   // It is necessary to hold a reference to the associated nsXBLBinding
   // because the binding holds a reference on the nsXBLDocumentInfo that
   // owns |mProtoBinding|.
   nsRefPtr<nsXBLBinding> mAssociatedBinding;
 
   nsRefPtr<ShadowRootStyleSheetList> mStyleSheetList;
 
+  // The current shadow insertion point of this ShadowRoot.
+  HTMLShadowElement* mShadowElement;
+
+  // The ShadowRoot that was created by the host element before
+  // this ShadowRoot was created.
+  nsRefPtr<ShadowRoot> mOlderShadow;
+
+  // The ShadowRoot that was created by the host element after
+  // this ShadowRoot was created.
+  nsRefPtr<ShadowRoot> mYoungerShadow;
+
   // A boolean that indicates that an insertion point was added or removed
   // from this ShadowRoot and that the nodes need to be redistributed into
   // the insertion points. After this flag is set, nodes will be distributed
   // on the next mutation event.
   bool mInsertionPointChanged;
 };
 
 class ShadowRootStyleSheetList : public nsIDOMStyleSheetList
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -1379,16 +1379,17 @@ GK_ATOM(refY, "refY")
 GK_ATOM(requiredExtensions, "requiredExtensions")
 GK_ATOM(requiredFeatures, "requiredFeatures")
 GK_ATOM(rotate, "rotate")
 GK_ATOM(rx, "rx")
 GK_ATOM(ry, "ry")
 GK_ATOM(saturate, "saturate")
 GK_ATOM(set, "set")
 GK_ATOM(seed, "seed")
+GK_ATOM(shadow, "shadow")
 GK_ATOM(shape_rendering, "shape-rendering")
 GK_ATOM(skewX, "skewX")
 GK_ATOM(skewY, "skewY")
 GK_ATOM(slope, "slope")
 GK_ATOM(spacing, "spacing")
 GK_ATOM(spacingAndGlyphs, "spacingAndGlyphs")
 GK_ATOM(specularConstant, "specularConstant")
 GK_ATOM(specularExponent, "specularExponent")
--- a/content/base/src/nsNodeUtils.cpp
+++ b/content/base/src/nsNodeUtils.cpp
@@ -56,17 +56,17 @@ using mozilla::AutoJSContext;
       /* No need to explicitly notify the first observer first    \
          since that'll happen anyway. */                          \
       NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(                         \
         slots->mMutationObservers, nsIMutationObserver,           \
         func_, params_);                                          \
     }                                                             \
     ShadowRoot* shadow = ShadowRoot::FromNode(node);              \
     if (shadow) {                                                 \
-      node = shadow->GetHost();                                   \
+      node = shadow->GetPoolHost();                               \
     } else {                                                      \
       node = node->GetParentNode();                               \
     }                                                             \
   } while (node);                                                 \
   if (needsEnterLeave) {                                          \
     nsDOMMutationObserver::LeaveMutationHandling();               \
   }                                                               \
   PR_END_MACRO
--- a/content/html/content/src/HTMLPropertiesCollection.cpp
+++ b/content/html/content/src/HTMLPropertiesCollection.cpp
@@ -185,29 +185,16 @@ HTMLPropertiesCollection::ContentRemoved
                                          nsIContent* aContainer,
                                          nsIContent* aChild,
                                          int32_t aIndexInContainer,
                                          nsIContent* aPreviousSibling)
 {
   mIsDirty = true;
 }
 
-class TreeOrderComparator {
-  public:
-    bool Equals(const nsGenericHTMLElement* aElem1,
-                const nsGenericHTMLElement* aElem2) const {
-      return aElem1 == aElem2;
-    }
-    bool LessThan(const nsGenericHTMLElement* aElem1,
-                  const nsGenericHTMLElement* aElem2) const {
-      return nsContentUtils::PositionIsBefore(const_cast<nsGenericHTMLElement*>(aElem1),
-                                              const_cast<nsGenericHTMLElement*>(aElem2));
-    }
-};
-
 static PLDHashOperator
 MarkDirty(const nsAString& aKey, PropertyNodeList* aEntry, void* aData)
 {
   aEntry->SetDirty();
   return PL_DHASH_NEXT;
 }
 
 void
new file mode 100644
--- /dev/null
+++ b/content/html/content/src/HTMLShadowElement.cpp
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ShadowRoot.h"
+
+#include "nsContentUtils.h"
+#include "mozilla/dom/HTMLShadowElement.h"
+#include "mozilla/dom/HTMLShadowElementBinding.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Shadow)
+
+using namespace mozilla::dom;
+
+HTMLShadowElement::HTMLShadowElement(already_AddRefed<nsINodeInfo> aNodeInfo)
+  : nsGenericHTMLElement(aNodeInfo), mIsInsertionPoint(false)
+{
+  SetIsDOMBinding();
+}
+
+HTMLShadowElement::~HTMLShadowElement()
+{
+  if (mProjectedShadow) {
+    mProjectedShadow->RemoveMutationObserver(this);
+  }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLShadowElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLShadowElement,
+                                                  nsGenericHTMLElement)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mProjectedShadow)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLShadowElement,
+                                                nsGenericHTMLElement)
+  if (tmp->mProjectedShadow) {
+    tmp->mProjectedShadow->RemoveMutationObserver(tmp);
+    tmp->mProjectedShadow = nullptr;
+  }
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(HTMLShadowElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLShadowElement, Element)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLShadowElement)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLShadowElement)
+
+JSObject*
+HTMLShadowElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aScope)
+{
+  return HTMLShadowElementBinding::Wrap(aCx, aScope, this);
+}
+
+void
+HTMLShadowElement::SetProjectedShadow(ShadowRoot* aProjectedShadow)
+{
+  if (mProjectedShadow) {
+    mProjectedShadow->RemoveMutationObserver(this);
+  }
+
+  mProjectedShadow = aProjectedShadow;
+  if (mProjectedShadow) {
+    // Watch for mutations on the projected shadow because
+    // it affects the nodes that are distributed to this shadow
+    // insertion point.
+    mProjectedShadow->AddMutationObserver(this);
+  }
+}
+
+static bool
+IsInFallbackContent(nsIContent* aContent)
+{
+  nsINode* parentNode = aContent->GetParentNode();
+  while (parentNode) {
+    if (parentNode->IsElement() &&
+        parentNode->AsElement()->IsHTML(nsGkAtoms::content)) {
+      return true;
+    }
+    parentNode = parentNode->GetParentNode();
+  }
+
+  return false;
+}
+
+nsresult
+HTMLShadowElement::BindToTree(nsIDocument* aDocument,
+                              nsIContent* aParent,
+                              nsIContent* aBindingParent,
+                              bool aCompileEventHandlers)
+{
+  nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
+                                                 aBindingParent,
+                                                 aCompileEventHandlers);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  ShadowRoot* containingShadow = GetContainingShadow();
+  if (containingShadow) {
+    // Keep track of all descendant <shadow> elements in tree order so
+    // that when the current shadow insertion point is removed, the next
+    // one can be found quickly.
+    TreeOrderComparator comparator;
+    containingShadow->ShadowDescendants().InsertElementSorted(this, comparator);
+
+    if (containingShadow->ShadowDescendants()[0] != this) {
+      // Only the first <shadow> (in tree order) of a ShadowRoot can be an insertion point.
+      return NS_OK;
+    }
+
+    if (IsInFallbackContent(this)) {
+      // If the first shadow element in tree order is invalid (in fallback content),
+      // the containing ShadowRoot will not have a shadow insertion point.
+      containingShadow->SetShadowElement(nullptr);
+    } else {
+      mIsInsertionPoint = true;
+      containingShadow->SetShadowElement(this);
+    }
+
+    containingShadow->SetInsertionPointChanged();
+  }
+
+  return NS_OK;
+}
+
+void
+HTMLShadowElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+  if (mIsInsertionPoint) {
+    ShadowRoot* containingShadow = GetContainingShadow();
+    // Make sure that containingShadow exists, it may have been nulled
+    // during unlinking in which case the ShadowRoot is going away.
+    if (containingShadow) {
+      nsTArray<HTMLShadowElement*>& shadowDescendants =
+        containingShadow->ShadowDescendants();
+      shadowDescendants.RemoveElement(this);
+      containingShadow->SetShadowElement(nullptr);
+
+      // Find the next shadow insertion point.
+      if (shadowDescendants.Length() > 0 &&
+          !IsInFallbackContent(shadowDescendants[0])) {
+        containingShadow->SetShadowElement(shadowDescendants[0]);
+      }
+
+      containingShadow->SetInsertionPointChanged();
+    }
+
+    mIsInsertionPoint = false;
+  }
+
+  nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+void
+HTMLShadowElement::DistributeSingleNode(nsIContent* aContent)
+{
+  // Handle the case where the shadow element is a child of
+  // a node with a ShadowRoot. The nodes that have been distributed to
+  // this shadow insertion point will need to be reprojected into the
+  // insertion points of the parent's ShadowRoot.
+  ShadowRoot* parentShadowRoot = GetParent()->GetShadowRoot();
+  if (parentShadowRoot) {
+    parentShadowRoot->DistributeSingleNode(aContent);
+    return;
+  }
+
+  // Handle the case where the parent of this shadow element is a ShadowRoot
+  // that is projected into a shadow insertion point in the younger ShadowRoot.
+  ShadowRoot* containingShadow = GetContainingShadow();
+  ShadowRoot* youngerShadow = containingShadow->GetYoungerShadow();
+  if (youngerShadow && GetParent() == containingShadow) {
+    HTMLShadowElement* youngerShadowElement = youngerShadow->GetShadowElement();
+    if (youngerShadowElement) {
+      youngerShadowElement->DistributeSingleNode(aContent);
+    }
+  }
+}
+
+void
+HTMLShadowElement::RemoveDistributedNode(nsIContent* aContent)
+{
+  // Handle the case where the shadow element is a child of
+  // a node with a ShadowRoot. The nodes that have been distributed to
+  // this shadow insertion point will need to be removed from the
+  // insertion points of the parent's ShadowRoot.
+  ShadowRoot* parentShadowRoot = GetParent()->GetShadowRoot();
+  if (parentShadowRoot) {
+    parentShadowRoot->RemoveDistributedNode(aContent);
+    return;
+  }
+
+  // Handle the case where the parent of this shadow element is a ShadowRoot
+  // that is projected into a shadow insertion point in the younger ShadowRoot.
+  ShadowRoot* containingShadow = GetContainingShadow();
+  ShadowRoot* youngerShadow = containingShadow->GetYoungerShadow();
+  if (youngerShadow && GetParent() == containingShadow) {
+    HTMLShadowElement* youngerShadowElement = youngerShadow->GetShadowElement();
+    if (youngerShadowElement) {
+      youngerShadowElement->RemoveDistributedNode(aContent);
+    }
+  }
+}
+
+void
+HTMLShadowElement::DistributeAllNodes()
+{
+  // Handle the case where the shadow element is a child of
+  // a node with a ShadowRoot. The nodes that have been distributed to
+  // this shadow insertion point will need to be reprojected into the
+  // insertion points of the parent's ShadowRoot.
+  ShadowRoot* parentShadowRoot = GetParent()->GetShadowRoot();
+  if (parentShadowRoot) {
+    parentShadowRoot->DistributeAllNodes();
+    return;
+  }
+
+  // Handle the case where the parent of this shadow element is a ShadowRoot
+  // that is projected into a shadow insertion point in the younger ShadowRoot.
+  ShadowRoot* containingShadow = GetContainingShadow();
+  ShadowRoot* youngerShadow = containingShadow->GetYoungerShadow();
+  if (youngerShadow && GetParent() == containingShadow) {
+    HTMLShadowElement* youngerShadowElement = youngerShadow->GetShadowElement();
+    if (youngerShadowElement) {
+      youngerShadowElement->DistributeAllNodes();
+    }
+  }
+}
+
+void
+HTMLShadowElement::ContentAppended(nsIDocument* aDocument,
+                                   nsIContent* aContainer,
+                                   nsIContent* aFirstNewContent,
+                                   int32_t aNewIndexInContainer)
+{
+  // Watch for content appended to the projected shadow (the ShadowRoot that
+  // will be rendered in place of this shadow insertion point) because the
+  // nodes may need to be distributed into other insertion points.
+  nsIContent* currentChild = aFirstNewContent;
+  while (currentChild) {
+    if (ShadowRoot::IsPooledNode(currentChild, aContainer, mProjectedShadow)) {
+      DistributeSingleNode(currentChild);
+    }
+    currentChild = currentChild->GetNextSibling();
+  }
+}
+
+void
+HTMLShadowElement::ContentInserted(nsIDocument* aDocument,
+                                   nsIContent* aContainer,
+                                   nsIContent* aChild,
+                                   int32_t aIndexInContainer)
+{
+  // Watch for content appended to the projected shadow (the ShadowRoot that
+  // will be rendered in place of this shadow insertion point) because the
+  // nodes may need to be distributed into other insertion points.
+  if (!ShadowRoot::IsPooledNode(aChild, aContainer, mProjectedShadow)) {
+    return;
+  }
+
+  DistributeSingleNode(aChild);
+}
+
+void
+HTMLShadowElement::ContentRemoved(nsIDocument* aDocument,
+                                  nsIContent* aContainer,
+                                  nsIContent* aChild,
+                                  int32_t aIndexInContainer,
+                                  nsIContent* aPreviousSibling)
+{
+  // Watch for content removed to the projected shadow (the ShadowRoot that
+  // will be rendered in place of this shadow insertion point) because the
+  // nodes may need to be removed from other insertion points.
+  if (!ShadowRoot::IsPooledNode(aChild, aContainer, mProjectedShadow)) {
+    return;
+  }
+
+  RemoveDistributedNode(aChild);
+}
+
new file mode 100644
--- /dev/null
+++ b/content/html/content/src/HTMLShadowElement.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_HTMLShadowElement_h__
+#define mozilla_dom_HTMLShadowElement_h__
+
+#include "nsGenericHTMLElement.h"
+
+namespace mozilla {
+namespace dom {
+
+class HTMLShadowElement MOZ_FINAL : public nsGenericHTMLElement,
+                                    public nsStubMutationObserver
+{
+public:
+  HTMLShadowElement(already_AddRefed<nsINodeInfo> aNodeInfo);
+  virtual ~HTMLShadowElement();
+
+  // nsISupports
+  NS_DECL_ISUPPORTS_INHERITED
+
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLShadowElement,
+                                           nsGenericHTMLElement)
+
+  virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const MOZ_OVERRIDE;
+
+  virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+                              nsIContent* aBindingParent,
+                              bool aCompileEventHandlers);
+
+  virtual void UnbindFromTree(bool aDeep = true,
+                              bool aNullParent = true);
+
+  bool IsInsertionPoint() { return mIsInsertionPoint; }
+
+  /**
+   * Sets the ShadowRoot that will be rendered in place of
+   * this shadow insertion point.
+   */
+  void SetProjectedShadow(ShadowRoot* aProjectedShadow);
+
+  /**
+   * Distributes a single explicit child of the projected ShadowRoot
+   * to relevant insertion points.
+   */
+  void DistributeSingleNode(nsIContent* aContent);
+
+  /**
+   * Removes a single explicit child of the projected ShadowRoot
+   * from relevant insertion points.
+   */
+  void RemoveDistributedNode(nsIContent* aContent);
+
+  /**
+   * Distributes all the explicit children of the projected ShadowRoot
+   * to the shadow insertion point in the younger ShadowRoot and
+   * the content insertion point of the parent node's ShadowRoot.
+   */
+  void DistributeAllNodes();
+
+  // WebIDL methods.
+  ShadowRoot* GetOlderShadowRoot() { return mProjectedShadow; }
+
+protected:
+  virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
+  // The ShadowRoot that will be rendered in place of this shadow insertion point.
+  nsRefPtr<ShadowRoot> mProjectedShadow;
+
+  bool mIsInsertionPoint;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLShadowElement_h__
+
--- a/content/html/content/src/moz.build
+++ b/content/html/content/src/moz.build
@@ -46,16 +46,17 @@ EXPORTS.mozilla.dom += [
     'HTMLOptionElement.h',
     'HTMLOptionsCollection.h',
     'HTMLOutputElement.h',
     'HTMLParagraphElement.h',
     'HTMLPreElement.h',
     'HTMLProgressElement.h',
     'HTMLScriptElement.h',
     'HTMLSelectElement.h',
+    'HTMLShadowElement.h',
     'HTMLSharedElement.h',
     'HTMLSharedListElement.h',
     'HTMLSharedObjectElement.h',
     'HTMLSourceElement.h',
     'HTMLSpanElement.h',
     'HTMLStyleElement.h',
     'HTMLTableCaptionElement.h',
     'HTMLTableCellElement.h',
@@ -117,16 +118,17 @@ UNIFIED_SOURCES += [
     'HTMLOptionsCollection.cpp',
     'HTMLOutputElement.cpp',
     'HTMLParagraphElement.cpp',
     'HTMLPreElement.cpp',
     'HTMLProgressElement.cpp',
     'HTMLPropertiesCollection.cpp',
     'HTMLScriptElement.cpp',
     'HTMLSelectElement.cpp',
+    'HTMLShadowElement.cpp',
     'HTMLSharedElement.cpp',
     'HTMLSharedListElement.cpp',
     'HTMLSharedObjectElement.cpp',
     'HTMLSourceElement.cpp',
     'HTMLSpanElement.cpp',
     'HTMLStyleElement.cpp',
     'HTMLTableCaptionElement.cpp',
     'HTMLTableCellElement.cpp',
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -1781,16 +1781,17 @@ NS_DECLARE_NS_NEW_HTML_ELEMENT(Object)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(OptGroup)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Option)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Output)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Paragraph)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Pre)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Progress)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Script)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Select)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Shadow)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Source)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Span)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Style)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(TableCaption)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(TableCell)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(TableCol)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Table)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(TableRow)
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -971,16 +971,22 @@ DOMInterfaces = {
 },
 
 'ShadowRoot': {
     'resultNotAddRefed': [
         'styleSheets'
     ]
 },
 
+'HTMLShadowElement': {
+    'resultNotAddRefed': [
+        'getOldShadowRoot'
+    ]
+},
+
 'SharedWorker': {
     'nativeType': 'mozilla::dom::workers::SharedWorker',
     'headerFile': 'mozilla/dom/workers/bindings/SharedWorker.h',
     'implicitJSContext': [ 'constructor' ],
 },
 
 'SharedWorkerGlobalScope': {
     'headerFile': 'mozilla/dom/WorkerScope.h',
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -300,16 +300,17 @@ var interfaceNamesInGlobalScope =
     "HTMLParagraphElement",
     "HTMLParamElement",
     "HTMLPreElement",
     "HTMLProgressElement",
     "HTMLPropertiesCollection",
     "HTMLQuoteElement",
     "HTMLScriptElement",
     "HTMLSelectElement",
+    "HTMLShadowElement",
     "HTMLSourceElement",
     "HTMLSpanElement",
     "HTMLStyleElement",
     "HTMLTableCaptionElement",
     "HTMLTableCellElement",
     "HTMLTableColElement",
     "HTMLTableElement",
     "HTMLTableRowElement",
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_shadow_element.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=887538
+-->
+<head>
+  <title>Test for HTMLShadowElement</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<div id="grabme"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=887538">Bug 887538</a>
+<script>
+var host = document.createElement("span");
+
+// Create three shadow roots on a single host and make sure that shadow elements
+// are associated with the correct shadow root.
+var firstShadow = host.createShadowRoot();
+firstShadow.innerHTML = '<shadow id="shadowone"></shadow>';
+var secondShadow = host.createShadowRoot();
+secondShadow.innerHTML = '<shadow id="shadowtwo"></shadow>';
+var thirdShadow = host.createShadowRoot();
+thirdShadow.innerHTML = '<shadow id="shadowthree"></shadow>';
+
+is(firstShadow.getElementById("shadowone").olderShadowRoot, null, "Shadow element in oldest ShadowRoot should not be associated with a ShadowRoot.");
+is(secondShadow.getElementById("shadowtwo").olderShadowRoot, firstShadow, "Shadow element should be associated with older ShadowRoot.");
+is(thirdShadow.getElementById("shadowthree").olderShadowRoot, secondShadow, "Shadow element should be associated with older ShadowRoot.");
+
+// Only the first ShadowRoot in tree order is an insertion point.
+host = document.createElement("span");
+firstShadow = host.createShadowRoot();
+secondShadow = host.createShadowRoot();
+secondShadow.innerHTML = '<shadow id="shadowone"></shadow><shadow id="shadowtwo"></shadow>';
+var shadowElemOne = secondShadow.getElementById("shadowone");
+var shadowElemTwo = secondShadow.getElementById("shadowtwo");
+
+is(shadowElemOne.olderShadowRoot, firstShadow, "First <shadow> in tree order should be an insertion point.");
+is(shadowElemTwo.olderShadowRoot, null, "Second <shadow> in tree order should not be an insertion point.");
+
+// Remove the first <shadow> element and make sure the second <shadow> element becomes an insertion point.
+secondShadow.removeChild(shadowElemOne);
+is(shadowElemOne.olderShadowRoot, null, "<shadow> element not in a ShadowRoot is not an insertion point.");
+is(shadowElemTwo.olderShadowRoot, firstShadow, "Second <shadow> element should become insertion point after first is removed.");
+
+// Insert a <shadow> element before the current shadow insertion point and make sure that it becomes an insertion point.
+secondShadow.insertBefore(shadowElemOne, shadowElemTwo);
+is(shadowElemOne.olderShadowRoot, firstShadow, "<shadow> element inserted as first in tree order should become an insertion point.");
+is(shadowElemTwo.olderShadowRoot, null, "<shadow> element should no longer be an insertion point it another is inserted before.");
+
+// <shadow> element in fallback content is not an insertion point.
+host = document.createElement("span");
+firstShadow = host.createShadowRoot();
+secondShadow = host.createShadowRoot();
+secondShadow.innerHTML = '<content><shadow id="shadowone"></shadow></content><shadow id="shadowtwo"></shadow>';
+shadowElemOne = secondShadow.getElementById("shadowone");
+shadowElemTwo = secondShadow.getElementById("shadowtwo");
+
+is(shadowElemOne.olderShadowRoot, null, "<shadow> element in fallback content is not an insertion point.");
+is(shadowElemTwo.olderShadowRoot, null, "<shadow> element preceeded by another <shadow> element is not an insertion point.");
+
+// <shadow> element that is descendant of shadow element is not an insertion point.
+host = document.createElement("span");
+firstShadow = host.createShadowRoot();
+secondShadow = host.createShadowRoot();
+secondShadow.innerHTML = '<shadow><shadow id="shadowone"></shadow></shadow>';
+shadowElemOne = secondShadow.getElementById("shadowone");
+is(shadowElemOne.olderShadowRoot, null, "<shadow> element that is descendant of shadow element is not an insertion point.");
+
+// Check projection of <content> elements through <shadow> elements.
+host = document.createElement("span");
+firstShadow = host.createShadowRoot();
+secondShadow = host.createShadowRoot();
+firstShadow.innerHTML = '<content id="firstcontent"></content>';
+secondShadow.innerHTML = '<shadow><span id="one"></span><content id="secondcontent"></content><span id="four"></span></shadow>';
+host.innerHTML = '<span id="two"></span><span id="three"></span>';
+var firstContent = firstShadow.getElementById("firstcontent");
+var secondContent = secondShadow.getElementById("secondcontent");
+var firstDistNodes = firstContent.getDistributedNodes();
+var secondDistNodes = secondContent.getDistributedNodes();
+
+is(secondDistNodes.length, 2, "There should be two distributed nodes from the host.");
+ok(secondDistNodes[0].id == "two" &&
+   secondDistNodes[1].id == "three", "Nodes projected from host should preserve order.");
+
+is(firstDistNodes.length, 4, "There should be four distributed nodes, two from the first shadow, two from the second shadow.");
+ok(firstDistNodes[0].id == "one" &&
+   firstDistNodes[1].id == "two" &&
+   firstDistNodes[2].id == "three" &&
+   firstDistNodes[3].id == "four", "Reprojection through shadow should preserve node order.");
+
+// Remove a node from the host and make sure that it is removed from all insertion points.
+host.removeChild(host.firstChild);
+firstDistNodes = firstContent.getDistributedNodes();
+secondDistNodes = secondContent.getDistributedNodes();
+
+is(secondDistNodes.length, 1, "There should be one distriubted node remaining after removing node from host.");
+ok(secondDistNodes[0].id == "three", "Span with id=two should have been removed from content element.");
+is(firstDistNodes.length, 3, "There should be three distributed nodes remaining after removing node from host.");
+ok(firstDistNodes[0].id == "one" &&
+   firstDistNodes[1].id == "three" &&
+   firstDistNodes[2].id == "four", "Reprojection through shadow should preserve node order.");
+
+// Check projection of <shadow> elements to <content> elements.
+host = document.createElement("span");
+firstShadow = host.createShadowRoot();
+secondShadow = host.createShadowRoot();
+secondShadow.innerHTML = '<span id="firstspan"><shadow></shadow></span>';
+thirdShadow = secondShadow.getElementById("firstspan").createShadowRoot();
+thirdShadow.innerHTML = '<content id="firstcontent"></content>';
+firstContent = thirdShadow.getElementById("firstcontent");
+var shadowChild = document.createElement("span");
+firstShadow.appendChild(shadowChild);
+
+is(firstContent.getDistributedNodes()[0], shadowChild, "Elements in shadow insertioin point should be projected into content insertion points.");
+
+// Remove child of ShadowRoot and check that projected node is removed from insertion point.
+firstShadow.removeChild(firstShadow.firstChild);
+
+is(firstContent.getDistributedNodes().length, 0, "Reprojected element was removed from ShadowRoot, thus it should be removed from content insertion point.");
+
+// Check deeply nested projection of <shadow> elements.
+host = document.createElement("span");
+firstShadow = host.createShadowRoot();
+firstShadow.innerHTML = '<content></content>';
+secondShadow = host.createShadowRoot();
+secondShadow.innerHTML = '<shadow><content></content></shadow>';
+thirdShadow = host.createShadowRoot();
+thirdShadow.innerHTML = '<span id="firstspan"><shadow><content></content></shadow></span>';
+var fourthShadow = thirdShadow.getElementById("firstspan").createShadowRoot();
+fourthShadow.innerHTML = '<content id="firstcontent"></content>';
+firstContent = fourthShadow.getElementById("firstcontent");
+host.innerHTML = '<span></span>';
+
+is(firstContent.getDistributedNodes()[0], host.firstChild, "Child of host should be projected to insertion point.");
+
+// Remove node and make sure that it is removed from distributed nodes.
+host.removeChild(host.firstChild);
+
+is(firstContent.getDistributedNodes().length, 0, "Node removed from host should be removed from insertion point.");
+
+// Check projection of fallback content through <shadow> elements.
+host = document.createElement("span");
+firstShadow = host.createShadowRoot();
+firstShadow.innerHTML = '<content><span id="firstspan"></span></content>';
+secondShadow = host.createShadowRoot();
+secondShadow.innerHTML = '<span id="secondspan"><shadow id="firstshadow"></shadow></span>';
+firstShadowElem = secondShadow.getElementById("firstshadow");
+thirdShadow = secondShadow.getElementById("secondspan").createShadowRoot();
+thirdShadow.innerHTML = '<content id="firstcontent"></content>';
+firstContent = thirdShadow.getElementById("firstcontent");
+
+is(firstContent.getDistributedNodes().length, 1, "There should be one node distributed from fallback content.");
+is(firstContent.getDistributedNodes()[0], firstShadow.getElementById("firstspan"), "Fallback content should be distributed.");
+
+// Add some content to the host (causing the fallback content to be dropped) and make sure distribution nodes are updated.
+
+var newElem = document.createElement("div");
+firstShadowElem.appendChild(newElem);
+
+is(firstContent.getDistributedNodes().length, 1, "There should be one node distributed from the host.");
+is(firstContent.getDistributedNodes()[0], newElem, "Distributed node should be from host, not fallback content.");
+
+// Remove the distribution node and check that fallback content is used.
+firstShadowElem.removeChild(newElem);
+
+is(firstContent.getDistributedNodes().length, 1, "There should be one node distributed from fallback content.");
+is(firstContent.getDistributedNodes()[0], firstShadow.getElementById("firstspan"), "Fallback content should be distributed after removing node from host.");
+
+</script>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/webidl/HTMLShadowElement.webidl
@@ -0,0 +1,18 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html
+ *
+ * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and
+ * Opera Software ASA. You are granted a license to use, reproduce
+ * and create derivative works of this document.
+ */
+
+interface HTMLShadowElement : HTMLElement
+{
+  readonly attribute ShadowRoot? olderShadowRoot;
+};
+
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -162,16 +162,17 @@ WEBIDL_FILES = [
     'HTMLParagraphElement.webidl',
     'HTMLParamElement.webidl',
     'HTMLPreElement.webidl',
     'HTMLProgressElement.webidl',
     'HTMLPropertiesCollection.webidl',
     'HTMLQuoteElement.webidl',
     'HTMLScriptElement.webidl',
     'HTMLSelectElement.webidl',
+    'HTMLShadowElement.webidl',
     'HTMLSourceElement.webidl',
     'HTMLSpanElement.webidl',
     'HTMLStyleElement.webidl',
     'HTMLTableCaptionElement.webidl',
     'HTMLTableCellElement.webidl',
     'HTMLTableColElement.webidl',
     'HTMLTableElement.webidl',
     'HTMLTableRowElement.webidl',
--- a/editor/libeditor/html/nsHTMLEditUtils.cpp
+++ b/editor/libeditor/html/nsHTMLEditUtils.cpp
@@ -725,16 +725,17 @@ static const nsElementInfo kElements[eHT
   ELEM(progress, true, false, GROUP_SPECIAL, GROUP_FLOW_ELEMENT),
   ELEM(q, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
   ELEM(s, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
   ELEM(samp, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   ELEM(script, true, false, GROUP_HEAD_CONTENT | GROUP_SPECIAL,
        GROUP_LEAF),
   ELEM(section, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   ELEM(select, true, false, GROUP_FORMCONTROL, GROUP_SELECT_CONTENT),
+  ELEM(shadow, true, false, GROUP_NONE, GROUP_INLINE_ELEMENT),
   ELEM(small, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
   ELEM(source, false, false, GROUP_NONE, GROUP_NONE),
   ELEM(span, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
   ELEM(strike, true, true, GROUP_FONTSTYLE, GROUP_INLINE_ELEMENT),
   ELEM(strong, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   ELEM(style, true, false, GROUP_HEAD_CONTENT, GROUP_LEAF),
   ELEM(sub, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
   ELEM(sup, true, true, GROUP_SPECIAL, GROUP_INLINE_ELEMENT),
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/adjacent-insertion-points-1-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div><span>Hello</span><span>World</span></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/adjacent-insertion-points-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script>
+    function tweak() {
+      var oldShadowRoot = document.getElementById('outer').createShadowRoot();
+      oldShadowRoot.innerHTML = 'World';
+
+      var youngShadowRoot = document.getElementById('outer').createShadowRoot();
+      youngShadowRoot.innerHTML = 'Hello<content></content><shadow></shadow>';
+    }
+  </script>
+</head>
+<body onload="tweak()">
+<div id="outer"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/adjacent-insertion-points-2-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div><span>Hello</span><span>World</span></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/adjacent-insertion-points-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script>
+    function tweak() {
+      var oldShadowRoot = document.getElementById('outer').createShadowRoot();
+      oldShadowRoot.innerHTML = 'Hello';
+
+      var youngShadowRoot = document.getElementById('outer').createShadowRoot();
+      youngShadowRoot.innerHTML = '<shadow></shadow><content></content>World';
+    }
+  </script>
+</head>
+<body onload="tweak()">
+<div id="outer"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/basic-shadow-element-1-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+</head>
+<body>
+<div>
+  <div style="width:100px; height:100px; background-color:green"></div><div style="width:100px; height:100px; background-color:orange">Hello World</div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/basic-shadow-element-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script>
+    function tweak() {
+      var olderShadow = document.getElementById('outer').createShadowRoot();
+      olderShadow.innerHTML = '<div style="width:100px; height:100px; background-color: orange"><content></content></div>';
+
+      var youngerShadow = document.getElementById('outer').createShadowRoot();
+      youngerShadow.innerHTML = '<div style="width:100px; height:100px; background-color: green"></div><shadow>Hello World</shadow>';
+    }
+  </script>
+</head>
+<body onload="tweak()">
+<div id="outer">
+  <div style="width:300px; height:100px; background-color:red;"></div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/dynamic-shadow-element-1-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+</head>
+<body>
+<div>
+  <div style="background-color: green"><span>Hello</span><span> </span><span>World</span></div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/dynamic-shadow-element-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script>
+    function tweak() {
+      var oldestShadow = document.getElementById('outer').createShadowRoot();
+      oldestShadow.innerHTML = 'Hello';
+
+      var olderShadow = document.getElementById('outer').createShadowRoot();
+
+      var youngerShadow = document.getElementById('outer').createShadowRoot();
+      youngerShadow.innerHTML = '<div style="background-color:green"><shadow></shadow></div>';
+
+      olderShadow.innerHTML = '<shadow></shadow> World';
+    }
+  </script>
+</head>
+<body onload="tweak()">
+<div id="outer">
+  <div style="width:300px; height:100px; background-color:red;"></div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/nested-shadow-element-1-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+</head>
+<body>
+<div>
+  <div style="background-color: green"><span>Hello</span><span> </span><span>World</span></div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/nested-shadow-element-1.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script>
+    function tweak() {
+      var olderShadow = document.getElementById('outer').createShadowRoot();
+      olderShadow.innerHTML = '<content></content><span>World</span>';
+
+      var youngerShadow = document.getElementById('outer').createShadowRoot();
+      youngerShadow.innerHTML = '<div id="shadowparent"><shadow id="youngshadow"><span>Hello</span></shadow></div>';
+
+      var shadowParent = youngerShadow.getElementById("shadowparent");
+      var nestedShadow = shadowParent.createShadowRoot();
+      nestedShadow.innerHTML = '<div style="background-color: green"><content></content></div>';
+
+      // Dynamically append a span to the shadow element in the younger ShadowRoot to make sure
+      // it is projected into the nested shadow.
+      var appendedSpan = document.createElement("span");
+      appendedSpan.textContent = ' ';
+      youngerShadow.getElementById("youngshadow").appendChild(appendedSpan);
+    }
+  </script>
+</head>
+<body onload="tweak()">
+<div id="outer">
+  <div style="width:300px; height:100px; background-color:red;"></div>
+</div>
+</body>
+</html>
--- a/layout/reftests/webcomponents/reftest.list
+++ b/layout/reftests/webcomponents/reftest.list
@@ -1,10 +1,14 @@
 pref(dom.webcomponents.enabled,true) == cross-tree-selection-1.html cross-tree-selection-1-ref.html
 pref(dom.webcomponents.enabled,true) == basic-shadow-1.html basic-shadow-1-ref.html
 pref(dom.webcomponents.enabled,true) == basic-shadow-2.html basic-shadow-2-ref.html
 pref(dom.webcomponents.enabled,true) == basic-shadow-3.html basic-shadow-3-ref.html
 pref(dom.webcomponents.enabled,true) == basic-shadow-4.html basic-shadow-4-ref.html
 pref(dom.webcomponents.enabled,true) == basic-insertion-point-1.html basic-insertion-point-1-ref.html
 pref(dom.webcomponents.enabled,true) == basic-insertion-point-2.html basic-insertion-point-2-ref.html
+pref(dom.webcomponents.enabled,true) == adjacent-insertion-points-1.html adjacent-insertion-points-1-ref.html
+pref(dom.webcomponents.enabled,true) == adjacent-insertion-points-2.html adjacent-insertion-points-2-ref.html
 pref(dom.webcomponents.enabled,true) == fallback-content-1.html fallback-content-1-ref.html
 pref(dom.webcomponents.enabled,true) == remove-insertion-point-1.html remove-insertion-point-1-ref.html
 pref(dom.webcomponents.enabled,true) == nested-insertion-point-1.html nested-insertion-point-1-ref.html
+pref(dom.webcomponents.enabled,true) == basic-shadow-element-1.html basic-shadow-element-1-ref.html
+pref(dom.webcomponents.enabled,true) == nested-shadow-element-1.html nested-shadow-element-1-ref.html
--- a/parser/htmlparser/public/nsHTMLTagList.h
+++ b/parser/htmlparser/public/nsHTMLTagList.h
@@ -130,16 +130,17 @@ HTML_HTMLELEMENT_TAG(plaintext)
 HTML_TAG(pre, Pre)
 HTML_TAG(progress, Progress)
 HTML_TAG(q, Shared)
 HTML_HTMLELEMENT_TAG(s)
 HTML_HTMLELEMENT_TAG(samp)
 HTML_TAG(script, Script)
 HTML_HTMLELEMENT_TAG(section)
 HTML_TAG(select, Select)
+HTML_TAG(shadow, Shadow)
 HTML_HTMLELEMENT_TAG(small)
 HTML_TAG(source, Source)
 HTML_TAG(span, Span)
 HTML_HTMLELEMENT_TAG(strike)
 HTML_HTMLELEMENT_TAG(strong)
 HTML_TAG(style, Style)
 HTML_HTMLELEMENT_TAG(sub)
 HTML_HTMLELEMENT_TAG(sup)
--- a/parser/htmlparser/src/nsElementTable.cpp
+++ b/parser/htmlparser/src/nsElementTable.cpp
@@ -415,16 +415,20 @@ const nsHTMLElement gHTMLElements[] = {
     /*tag*/         eHTMLTag_section,
     /*parent,leaf*/ kBlock, false
   },
   {
     /*tag*/         eHTMLTag_select,
     /*parent,leaf*/ kFormControl, false
   },
   {
+    /*tag*/         eHTMLTag_shadow,
+    /*parent,leaf*/ kFlowEntity, false
+  },
+  {
     /*tag*/         eHTMLTag_small,
     /*parent,leaf*/ kFontStyle, false
   },
   {
     /*tag*/         eHTMLTag_source,
     /*parent,leaf*/ kSpecial, true
   },
   {
--- a/parser/htmlparser/src/nsHTMLTags.cpp
+++ b/parser/htmlparser/src/nsHTMLTags.cpp
@@ -212,16 +212,18 @@ static const PRUnichar sHTMLTagUnicodeNa
 static const PRUnichar sHTMLTagUnicodeName_samp[] =
   {'s', 'a', 'm', 'p', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_script[] =
   {'s', 'c', 'r', 'i', 'p', 't', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_section[] =
   {'s', 'e', 'c', 't', 'i', 'o', 'n', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_select[] =
   {'s', 'e', 'l', 'e', 'c', 't', '\0'};
+static const PRUnichar sHTMLTagUnicodeName_shadow[] =
+  {'s', 'h', 'a', 'd', 'o', 'w', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_small[] =
   {'s', 'm', 'a', 'l', 'l', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_source[] =
   {'s', 'o', 'u', 'r', 'c', 'e', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_span[] =
   {'s', 'p', 'a', 'n', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_strike[] =
   {'s', 't', 'r', 'i', 'k', 'e', '\0'};