Bug 854736 - Implement HTML content element. r=mrbkap
authorWilliam Chen <wchen@mozilla.com>
Mon, 02 Dec 2013 02:26:12 -0800
changeset 158553 79b61f9909c0482f89972966d861346e9fdbc769
parent 158552 28a139b0cf38ef3844b95d191573cb1f06b74e9d
child 158554 1ac0576fa66f9a9ccfc7aefd8b1ab9c9ecaf8001
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersmrbkap
bugs854736
milestone28.0a1
Bug 854736 - Implement HTML content element. r=mrbkap
content/base/public/nsContentUtils.h
content/base/src/ChildIterator.cpp
content/base/src/ChildIterator.h
content/base/src/ShadowRoot.cpp
content/base/src/ShadowRoot.h
content/base/src/nsContentUtils.cpp
content/html/content/src/HTMLContentElement.cpp
content/html/content/src/HTMLContentElement.h
content/html/content/src/moz.build
content/html/content/src/nsGenericHTMLElement.h
dom/tests/mochitest/general/test_interfaces.html
dom/tests/mochitest/webcomponents/mochitest.ini
dom/tests/mochitest/webcomponents/test_content_element.html
dom/tests/mochitest/webcomponents/test_dynamic_content_element_matching.html
dom/tests/mochitest/webcomponents/test_nested_content_element.html
dom/webidl/HTMLContentElement.webidl
dom/webidl/moz.build
editor/libeditor/html/nsHTMLEditUtils.cpp
layout/reftests/reftest.list
layout/reftests/webcomponents/basic-insertion-point-1-ref.html
layout/reftests/webcomponents/basic-insertion-point-1.html
layout/reftests/webcomponents/basic-insertion-point-2-ref.html
layout/reftests/webcomponents/basic-insertion-point-2.html
layout/reftests/webcomponents/basic-shadow-1-ref.html
layout/reftests/webcomponents/basic-shadow-1.html
layout/reftests/webcomponents/basic-shadow-2-ref.html
layout/reftests/webcomponents/basic-shadow-2.html
layout/reftests/webcomponents/basic-shadow-3-ref.html
layout/reftests/webcomponents/basic-shadow-3.html
layout/reftests/webcomponents/basic-shadow-4-ref.html
layout/reftests/webcomponents/basic-shadow-4.html
layout/reftests/webcomponents/fallback-content-1-ref.html
layout/reftests/webcomponents/fallback-content-1.html
layout/reftests/webcomponents/nested-insertion-point-1-ref.html
layout/reftests/webcomponents/nested-insertion-point-1.html
layout/reftests/webcomponents/reftest.list
layout/reftests/webcomponents/remove-insertion-point-1-ref.html
layout/reftests/webcomponents/remove-insertion-point-1.html
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
@@ -2105,16 +2105,27 @@ public:
                                   const nsAString& aFeature,
                                   const nsAString& aVersion);
 
   /**
    * Return true if the browser.dom.window.dump.enabled pref is set.
    */
   static bool DOMWindowDumpEnabled();
 
+  /**
+   * Returns whether a content is an insertion point for XBL
+   * bindings or web components ShadowRoot. In web components,
+   * this corresponds to a <content> element that participates
+   * in node distribution. In XBL this corresponds to an
+   * <xbl:children> element in anonymous content.
+   *
+   * @param aContent The content to test for being an insertion point.
+   */
+  static bool IsContentInsertionPoint(const nsIContent* aContent);
+
 private:
   static bool InitializeEventTable();
 
   static nsresult EnsureStringBundle(PropertiesFile aFile);
 
   static bool CanCallerAccess(nsIPrincipal* aSubjectPrincipal,
                                 nsIPrincipal* aPrincipal);
 
--- a/content/base/src/ChildIterator.cpp
+++ b/content/base/src/ChildIterator.cpp
@@ -1,66 +1,120 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 sw=2 et tw=80: */
 /* 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"
 
 namespace mozilla {
 namespace dom {
 
+class MatchedNodes {
+public:
+  MatchedNodes(HTMLContentElement* aInsertionPoint)
+    : mIsContentElement(true), mContentElement(aInsertionPoint) {}
+
+  MatchedNodes(XBLChildrenElement* aInsertionPoint)
+    : mIsContentElement(false), mChildrenElement(aInsertionPoint) {}
+
+  uint32_t Length() const
+  {
+    return mIsContentElement ? mContentElement->MatchedNodes().Length()
+                             : mChildrenElement->mInsertedChildren.Length();
+  }
+
+  nsIContent* operator[](int32_t aIndex) const
+  {
+    return mIsContentElement ? mContentElement->MatchedNodes()[aIndex]
+                             : mChildrenElement->mInsertedChildren[aIndex];
+  }
+
+  bool IsEmpty() const
+  {
+    return mIsContentElement ? mContentElement->MatchedNodes().IsEmpty()
+                             : mChildrenElement->mInsertedChildren.IsEmpty();
+  }
+protected:
+  bool mIsContentElement;
+  union {
+    HTMLContentElement* mContentElement;
+    XBLChildrenElement* mChildrenElement;
+  };
+};
+
+static inline MatchedNodes
+GetMatchedNodesForPoint(nsIContent* aContent)
+{
+  if (aContent->NodeInfo()->Equals(nsGkAtoms::children, kNameSpaceID_XBL)) {
+    // XBL case
+    return MatchedNodes(static_cast<XBLChildrenElement*>(aContent));
+  }
+
+  // Web components case
+  MOZ_ASSERT(aContent->IsHTML(nsGkAtoms::content));
+  return MatchedNodes(static_cast<HTMLContentElement*>(aContent));
+}
+
 nsIContent*
 ExplicitChildIterator::GetNextChild()
 {
   // If we're already in the inserted-children array, look there first
   if (mIndexInInserted) {
     MOZ_ASSERT(mChild);
-    MOZ_ASSERT(mChild->IsActiveChildrenElement());
+    MOZ_ASSERT(nsContentUtils::IsContentInsertionPoint(mChild));
     MOZ_ASSERT(!mDefaultChild);
 
-    XBLChildrenElement* point = static_cast<XBLChildrenElement*>(mChild);
-    if (mIndexInInserted < point->mInsertedChildren.Length()) {
-      return point->mInsertedChildren[mIndexInInserted++];
+    MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild);
+    if (mIndexInInserted < assignedChildren.Length()) {
+      return assignedChildren[mIndexInInserted++];
     }
     mIndexInInserted = 0;
     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(mChild->IsActiveChildrenElement());
+    MOZ_ASSERT(nsContentUtils::IsContentInsertionPoint(mChild));
 
     mDefaultChild = mDefaultChild->GetNextSibling();
     if (mDefaultChild) {
       return mDefaultChild;
     }
 
     mChild = mChild->GetNextSibling();
   } else if (mIsFirst) {  // at the beginning of the child list
     mChild = mParent->GetFirstChild();
     mIsFirst = false;
   } else if (mChild) { // in the middle of the child list
     mChild = mChild->GetNextSibling();
   }
 
-  // Iterate until we find a non-<children>, or a <children> with content.
-  while (mChild && mChild->IsActiveChildrenElement()) {
-    XBLChildrenElement* point = static_cast<XBLChildrenElement*>(mChild);
-    if (!point->mInsertedChildren.IsEmpty()) {
+  // 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 point->mInsertedChildren[0];
+      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;
     }
 
+    // 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)
@@ -104,19 +158,19 @@ nsIContent* FlattenedChildIterator::Get(
 }
 
 nsIContent* FlattenedChildIterator::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.
-    XBLChildrenElement* point = static_cast<XBLChildrenElement*>(mChild);
+    MatchedNodes assignedChildren = GetMatchedNodesForPoint(mChild);
     if (--mIndexInInserted) {
-      return point->mInsertedChildren[mIndexInInserted - 1];
+      return assignedChildren[mIndexInInserted - 1];
     }
     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;
     }
@@ -125,22 +179,23 @@ nsIContent* FlattenedChildIterator::GetP
   } else if (mIsFirst) { // at the beginning of the child list
     return nullptr;
   } 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-<children>, or a <children> with content.
-  while (mChild && mChild->IsActiveChildrenElement()) {
-    XBLChildrenElement* point = static_cast<XBLChildrenElement*>(mChild);
-    if (!point->mInsertedChildren.IsEmpty()) {
-      mIndexInInserted = point->InsertedChildrenLength();
-      return point->mInsertedChildren[mIndexInInserted - 1];
+  // 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];
     }
 
     mDefaultChild = mChild->GetLastChild();
     if (mDefaultChild) {
       return mDefaultChild;
     }
 
     mChild = mChild->GetPreviousSibling();
--- a/content/base/src/ChildIterator.h
+++ b/content/base/src/ChildIterator.h
@@ -34,16 +34,34 @@ public:
       mDefaultChild(nullptr),
       mIndexInInserted(0),
       mIsFirst(true)
   {
   }
 
   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
+  // aBound.
+  bool Seek(nsIContent* aChildToFind, nsIContent* aBound = nullptr)
+  {
+    // It would be nice to assert that we find aChildToFind, but bz thinks that
+    // we might not find aChildToFind when called from ContentInserted
+    // if first-letter frames are about.
+
+    nsIContent* child;
+    do {
+      child = GetNextChild();
+    } while (child && child != aChildToFind && child != aBound);
+
+    return child == aChildToFind;
+  }
+
 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,
   // mChild remains as the insertion point whose content we're iterating (and
@@ -79,29 +97,16 @@ public:
   // 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();
 
-  // Looks for aChildToFind respecting XBL insertion points.
-  void Seek(nsIContent* aChildToFind)
-  {
-    // It would be nice to assert that we find aChildToFind, but bz thinks that
-    // we might not find aChildToFind when called from ContentInserted
-    // if first-letter frames are about.
-
-    nsIContent* child;
-    do {
-      child = GetNextChild();
-    } while (child && child != aChildToFind);
-  }
-
   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/ShadowRoot.cpp
+++ b/content/base/src/ShadowRoot.cpp
@@ -2,20 +2,22 @@
 /* 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/Preferences.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/dom/ShadowRootBinding.h"
 #include "mozilla/dom/DocumentFragment.h"
+#include "ChildIterator.h"
 #include "nsContentUtils.h"
 #include "nsDOMClassInfoID.h"
 #include "nsIDOMHTMLElement.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLContentElement.h"
 #include "nsXBLPrototypeBinding.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static PLDHashOperator
 IdentifierMapEntryTraverse(nsIdentifierMapEntry *aEntry, void *aArg)
 {
@@ -30,43 +32,58 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(ShadowRoo
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(ShadowRoot,
                                                   DocumentFragment)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHost)
   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);
+  }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mHost)
   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)
+  NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
 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)
-  : DocumentFragment(aNodeInfo), mHost(aContent)
+  : DocumentFragment(aNodeInfo), mHost(aContent),
+    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);
 }
 
 ShadowRoot::~ShadowRoot()
 {
+  if (mHost) {
+    // mHost may have been unlinked.
+    mHost->RemoveMutationObserver(this);
+  }
+
   ClearInDocument();
   SetHost(nullptr);
 }
 
 JSObject*
 ShadowRoot::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return mozilla::dom::ShadowRootBinding::Wrap(aCx, aScope, this);
@@ -145,20 +162,282 @@ 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::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)) {
+      // 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;
+      }
+      insertionPoint = mInsertionPoints[i];
+      break;
+    }
+  }
+
+  // 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());
+    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;
+      }
+    }
+
+    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 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);
+    }
+  }
+}
+
+void
+ShadowRoot::RemoveDistributedNode(nsIContent* aContent)
+{
+  // Find insertion point containing the content and remove the node.
+  for (uint32_t i = 0; i < mInsertionPoints.Length(); i++) {
+    if (mInsertionPoints[i]->MatchedNodes().Contains(aContent)) {
+      // Removing the matched node may cause the insertion point to use
+      // fallback content.
+      if (mInsertionPoints[i]->MatchedNodes().Length() == 1 &&
+          static_cast<nsINode*>(mInsertionPoints[i])->GetFirstChild()) {
+        // 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 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);
+      }
+
+      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);
+  }
+
+  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++) {
+    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();
+    }
+  }
+}
+
 void
 ShadowRoot::GetInnerHTML(nsAString& aInnerHTML)
 {
   GetMarkup(false, aInnerHTML);
 }
 
 void
 ShadowRoot::SetInnerHTML(const nsAString& aInnerHTML, ErrorResult& aError)
 {
   SetInnerHTMLInternal(aInnerHTML, aError);
 }
 
+/**
+ * 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)
+{
+  if (nsContentUtils::IsContentInsertionPoint(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() &&
+           aContainer->GetParentNode() == aHost;
+  }
+
+  if (aContainer == aHost) {
+    // Any other child nodes of the host will end up in the pool.
+    return true;
+  }
+
+  return false;
+}
+
+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)) {
+    return;
+  }
+
+  // Attributes may change insertion point matching, find its new distribution.
+  RemoveDistributedNode(aElement);
+  DistributeSingleNode(aElement);
+}
+
+void
+ShadowRoot::ContentAppended(nsIDocument* aDocument,
+                            nsIContent* aContainer,
+                            nsIContent* aFirstNewContent,
+                            int32_t aNewIndexInContainer)
+{
+  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.
+  nsIContent* currentChild = aFirstNewContent;
+  while (currentChild) {
+    if (IsPooledNode(currentChild, aContainer, mHost)) {
+      DistributeSingleNode(currentChild);
+    }
+    currentChild = currentChild->GetNextSibling();
+  }
+}
+
+void
+ShadowRoot::ContentInserted(nsIDocument* aDocument,
+                            nsIContent* aContainer,
+                            nsIContent* aChild,
+                            int32_t aIndexInContainer)
+{
+  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)) {
+    DistributeSingleNode(aChild);
+  }
+}
+
+void
+ShadowRoot::ContentRemoved(nsIDocument* aDocument,
+                           nsIContent* aContainer,
+                           nsIContent* aChild,
+                           int32_t aIndexInContainer,
+                           nsIContent* aPreviousSibling)
+{
+  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)) {
+    RemoveDistributedNode(aChild);
+  }
+}
+
--- a/content/base/src/ShadowRoot.h
+++ b/content/base/src/ShadowRoot.h
@@ -19,36 +19,68 @@ class nsINodeInfo;
 class nsPIDOMWindow;
 class nsXBLPrototypeBinding;
 class nsTagNameMapEntry;
 
 namespace mozilla {
 namespace dom {
 
 class Element;
+class HTMLContentElement;
 
-class ShadowRoot : public DocumentFragment
+class ShadowRoot : public DocumentFragment,
+                   public nsStubMutationObserver
 {
 public:
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ShadowRoot,
                                            DocumentFragment)
   NS_DECL_ISUPPORTS_INHERITED
 
+  NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+  NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
   ShadowRoot(nsIContent* aContent, already_AddRefed<nsINodeInfo> aNodeInfo);
   virtual ~ShadowRoot();
 
   void AddToIdTable(Element* aElement, nsIAtom* aId);
   void RemoveFromIdTable(Element* aElement, nsIAtom* aId);
   static bool PrefEnabled();
 
+  /**
+   * Distributes a single explicit child of the host to the content
+   * insertion points in this ShadowRoot.
+   */
+  void DistributeSingleNode(nsIContent* aContent);
+
+  /**
+   * Removes a single explicit child of the host from the content
+   * insertion points in this ShadowRoot.
+   */
+  void RemoveDistributedNode(nsIContent* aContent);
+
+  /**
+   * Distributes all the explicit children of the host to the content
+   * insertion points in this ShadowRoot.
+   */
+  void DistributeAllNodes();
+
+  void AddInsertionPoint(HTMLContentElement* aInsertionPoint);
+  void RemoveInsertionPoint(HTMLContentElement* aInsertionPoint);
+
+  void SetInsertionPointChanged() { mInsertionPointChanged = true; }
+
   nsISupports* GetParentObject() const
   {
     return mHost;
   }
 
+  nsIContent* GetHost() { return mHost; }
+
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 
   static ShadowRoot* FromNode(nsINode* aNode);
 
   // WebIDL methods.
   Element* GetElementById(const nsAString& aElementId);
   already_AddRefed<nsContentList>
     GetElementsByTagName(const nsAString& aNamespaceURI);
@@ -56,16 +88,30 @@ public:
     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:
   nsCOMPtr<nsIContent> mHost;
+
+  // 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;
+
   nsTHashtable<nsIdentifierMapEntry> mIdentifierMap;
+
+  // 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;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_shadowroot_h__
 
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -31,16 +31,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/Base64.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/HTMLTemplateElement.h"
+#include "mozilla/dom/HTMLContentElement.h"
 #include "mozilla/dom/TextDecoder.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/MutationEvent.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Selection.h"
 #include "mozilla/TextEvents.h"
@@ -6585,16 +6586,32 @@ nsContentUtils::InternalIsSupported(nsIS
            nsSVGFeatures::HasFeature(aObject, aFeature);
   }
 
   // Otherwise, we claim to support everything
   return true;
 }
 
 bool
+nsContentUtils::IsContentInsertionPoint(const nsIContent* aContent)
+{
+  // Check if the content is a XBL insertion point.
+  if (aContent->IsActiveChildrenElement()) {
+    return true;
+  }
+
+  // Check if the content is a web components content insertion point.
+  if (aContent->IsHTML(nsGkAtoms::content)) {
+    return static_cast<const HTMLContentElement*>(aContent)->IsInsertionPoint();
+  }
+
+  return false;
+}
+
+bool
 nsContentUtils::DOMWindowDumpEnabled()
 {
 #if !(defined(DEBUG) || defined(MOZ_ENABLE_JS_DUMP))
   // In optimized builds we check a pref that controls if we should
   // enable output from dump() or not, in debug builds it's always
   // enabled.
   return nsContentUtils::sDOMWindowDumpEnabled;
 #else
new file mode 100644
--- /dev/null
+++ b/content/html/content/src/HTMLContentElement.cpp
@@ -0,0 +1,322 @@
+/* -*- 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/HTMLContentElement.h"
+#include "mozilla/dom/HTMLContentElementBinding.h"
+#include "mozilla/dom/NodeListBinding.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/css/StyleRule.h"
+#include "nsGkAtoms.h"
+#include "nsStyleConsts.h"
+#include "nsIAtom.h"
+#include "nsCSSRuleProcessor.h"
+#include "nsRuleData.h"
+#include "nsRuleProcessorData.h"
+#include "nsRuleWalker.h"
+#include "nsCSSParser.h"
+
+NS_IMPL_NS_NEW_HTML_ELEMENT(Content)
+
+using namespace mozilla::dom;
+
+HTMLContentElement::HTMLContentElement(already_AddRefed<nsINodeInfo> aNodeInfo)
+  : nsGenericHTMLElement(aNodeInfo), mValidSelector(true), mIsInsertionPoint(false)
+{
+  SetIsDOMBinding();
+}
+
+HTMLContentElement::~HTMLContentElement()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED_1(HTMLContentElement,
+                                     nsGenericHTMLElement,
+                                     mMatchedNodes)
+
+NS_IMPL_ADDREF_INHERITED(HTMLContentElement, Element)
+NS_IMPL_RELEASE_INHERITED(HTMLContentElement, Element)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLContentElement)
+NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
+
+NS_IMPL_ELEMENT_CLONE(HTMLContentElement)
+
+JSObject*
+HTMLContentElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aScope)
+{
+  return HTMLContentElementBinding::Wrap(aCx, aScope, this);
+}
+
+nsresult
+HTMLContentElement::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) {
+    nsINode* parentNode = nsINode::GetParentNode();
+    while (parentNode && parentNode != containingShadow) {
+      if (parentNode->IsElement() &&
+          parentNode->AsElement()->IsHTML(nsGkAtoms::content)) {
+        // Content element in fallback content is not an insertion point.
+        return NS_OK;
+      }
+      parentNode = parentNode->GetParentNode();
+    }
+
+    // If the content element is being inserted into a ShadowRoot,
+    // add this element to the list of insertion points.
+    mIsInsertionPoint = true;
+    containingShadow->AddInsertionPoint(this);
+    containingShadow->SetInsertionPointChanged();
+  }
+
+  return NS_OK;
+}
+
+void
+HTMLContentElement::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) {
+      containingShadow->RemoveInsertionPoint(this);
+
+      // Remove all the assigned nodes now that the
+      // insertion point now that the insertion point is
+      // no longer a descendant of a ShadowRoot.
+      ClearMatchedNodes();
+      containingShadow->SetInsertionPointChanged();
+    }
+
+    mIsInsertionPoint = false;
+  }
+
+  nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+void
+HTMLContentElement::ClearMatchedNodes()
+{
+  for (uint32_t i = 0; i < mMatchedNodes.Length(); i++) {
+    mMatchedNodes[i]->SetXBLInsertionParent(nullptr);
+  }
+  mMatchedNodes.Clear();
+}
+
+static bool
+IsValidContentSelectors(nsCSSSelector* aSelector)
+{
+  nsCSSSelector* currentSelector = aSelector;
+  while (currentSelector) {
+    // Blacklist invalid selector fragments.
+    if (currentSelector->IsPseudoElement() ||
+        currentSelector->mPseudoClassList ||
+        currentSelector->mNegations ||
+        currentSelector->mOperator) {
+      return false;
+    }
+
+    currentSelector = currentSelector->mNext;
+  }
+
+  return true;
+}
+
+nsresult
+HTMLContentElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                            nsIAtom* aPrefix, const nsAString& aValue,
+                            bool aNotify)
+{
+  nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix,
+                                              aValue, aNotify);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::select) {
+    // Select attribute was updated, the insertion point may match different
+    // elements.
+    nsIDocument* doc = OwnerDoc();
+    nsCSSParser parser(doc->CSSLoader());
+
+    mValidSelector = true;
+    mSelectorList = nullptr;
+
+    nsresult rv = parser.ParseSelectorString(aValue,
+                                             doc->GetDocumentURI(),
+                                             // Bug 11240
+                                             0, // XXX get the line number!
+                                             getter_Transfers(mSelectorList));
+
+    // We don't want to return an exception if parsing failed because
+    // the spec does not define it as an exception case.
+    if (NS_SUCCEEDED(rv)) {
+      // Ensure that all the selectors are valid
+      nsCSSSelectorList* selectors = mSelectorList;
+      while (selectors) {
+        if (!IsValidContentSelectors(selectors->mSelectors)) {
+          // If we have an invalid selector, we can not match anything.
+          mValidSelector = false;
+          mSelectorList = nullptr;
+          break;
+        }
+        selectors = selectors->mNext;
+      }
+    }
+
+    ShadowRoot* containingShadow = GetContainingShadow();
+    if (containingShadow) {
+      containingShadow->DistributeAllNodes();
+    }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+HTMLContentElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+                              bool aNotify)
+{
+  nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID,
+                                                aAttribute, aNotify);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::select) {
+    // The select attribute was removed. This insertion point becomes
+    // a universal selector.
+    mValidSelector = true;
+    mSelectorList = nullptr;
+
+    ShadowRoot* containingShadow = GetContainingShadow();
+    if (containingShadow) {
+      containingShadow->DistributeAllNodes();
+    }
+  }
+
+  return NS_OK;
+}
+
+bool
+HTMLContentElement::Match(nsIContent* aContent)
+{
+  if (!mValidSelector) {
+    return false;
+  }
+
+  if (mSelectorList) {
+    nsIDocument* doc = OwnerDoc();
+    ShadowRoot* containingShadow = GetContainingShadow();
+    nsIContent* host = containingShadow->GetHost();
+
+    TreeMatchContext matchingContext(false, nsRuleWalker::eRelevantLinkUnvisited,
+                                     doc, TreeMatchContext::eNeverMatchVisited);
+    doc->FlushPendingLinkUpdates();
+    matchingContext.SetHasSpecifiedScope();
+    matchingContext.AddScopeElement(host->AsElement());
+
+    if (!aContent->IsElement()) {
+      return false;
+    }
+
+    return nsCSSRuleProcessor::SelectorListMatches(aContent->AsElement(),
+                                                   matchingContext,
+                                                   mSelectorList);
+  }
+
+  return true;
+}
+
+already_AddRefed<DistributedContentList>
+HTMLContentElement::GetDistributedNodes()
+{
+  nsRefPtr<DistributedContentList> list = new DistributedContentList(this);
+  return list.forget();
+}
+
+NS_IMPL_CYCLE_COLLECTION_2(DistributedContentList, mParent, mDistributedNodes)
+
+NS_INTERFACE_TABLE_HEAD(DistributedContentList)
+  NS_INTERFACE_TABLE1(DistributedContentList, nsINodeList)
+  NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(DistributedContentList)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DistributedContentList)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DistributedContentList)
+
+DistributedContentList::DistributedContentList(HTMLContentElement* aHostElement)
+  : mParent(aHostElement)
+{
+  MOZ_COUNT_CTOR(DistributedContentList);
+  SetIsDOMBinding();
+
+  if (aHostElement->IsInsertionPoint()) {
+    if (aHostElement->MatchedNodes().IsEmpty()) {
+      // Fallback content.
+      nsINode* contentNode = aHostElement;
+      for (nsIContent* content = contentNode->GetFirstChild();
+           content;
+           content = content->GetNextSibling()) {
+        mDistributedNodes.AppendElement(content);
+      }
+    } else {
+      mDistributedNodes.AppendElements(aHostElement->MatchedNodes());
+    }
+  }
+}
+
+DistributedContentList::~DistributedContentList()
+{
+  MOZ_COUNT_DTOR(DistributedContentList);
+}
+
+nsIContent*
+DistributedContentList::Item(uint32_t aIndex)
+{
+  return mDistributedNodes.SafeElementAt(aIndex);
+}
+
+NS_IMETHODIMP
+DistributedContentList::Item(uint32_t aIndex, nsIDOMNode** aReturn)
+{
+  nsIContent* item = Item(aIndex);
+  if (!item) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return CallQueryInterface(item, aReturn);
+}
+
+uint32_t
+DistributedContentList::Length() const
+{
+  return mDistributedNodes.Length();
+}
+
+NS_IMETHODIMP
+DistributedContentList::GetLength(uint32_t* aLength)
+{
+  *aLength = Length();
+  return NS_OK;
+}
+
+int32_t
+DistributedContentList::IndexOf(nsIContent* aContent)
+{
+  return mDistributedNodes.IndexOf(aContent);
+}
+
+JSObject*
+DistributedContentList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return NodeListBinding::Wrap(aCx, aScope, this);
+}
+
new file mode 100644
--- /dev/null
+++ b/content/html/content/src/HTMLContentElement.h
@@ -0,0 +1,110 @@
+/* -*- 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_HTMLContentElement_h__
+#define mozilla_dom_HTMLContentElement_h__
+
+#include "nsINodeList.h"
+#include "nsGenericHTMLElement.h"
+
+class nsCSSSelectorList;
+
+namespace mozilla {
+namespace dom {
+
+class DistributedContentList;
+
+class HTMLContentElement MOZ_FINAL : public nsGenericHTMLElement
+{
+public:
+  HTMLContentElement(already_AddRefed<nsINodeInfo> aNodeInfo);
+  virtual ~HTMLContentElement();
+
+  // nsISupports
+  NS_DECL_ISUPPORTS_INHERITED
+
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLContentElement,
+                                           nsGenericHTMLElement)
+
+  virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
+
+  virtual nsIDOMNode* AsDOMNode() { return this; }
+
+  virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+                              nsIContent* aBindingParent,
+                              bool aCompileEventHandlers);
+
+  virtual void UnbindFromTree(bool aDeep = true,
+                              bool aNullParent = true);
+
+  /**
+   * Returns whether if the selector of this insertion point
+   * matches the provided content.
+   */
+  bool Match(nsIContent* aContent);
+  bool IsInsertionPoint() const { return mIsInsertionPoint; }
+  nsCOMArray<nsIContent>& MatchedNodes() { return mMatchedNodes; }
+  void ClearMatchedNodes();
+
+  virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
+                           nsIAtom* aPrefix, const nsAString& aValue,
+                           bool aNotify);
+
+  virtual nsresult UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
+                             bool aNotify);
+
+  // WebIDL methods.
+  already_AddRefed<DistributedContentList> GetDistributedNodes();
+  void GetSelect(nsAString& aSelect)
+  {
+    Element::GetAttr(kNameSpaceID_None, nsGkAtoms::select, aSelect);
+  }
+  void SetSelect(const nsAString& aSelect)
+  {
+    Element::SetAttr(kNameSpaceID_None, nsGkAtoms::select, aSelect, true);
+  }
+
+protected:
+  virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
+  /**
+   * An array of nodes from the ShadowRoot host that match the
+   * content insertion selector.
+   */
+  nsCOMArray<nsIContent> mMatchedNodes;
+
+  nsAutoPtr<nsCSSSelectorList> mSelectorList;
+  bool mValidSelector;
+  bool mIsInsertionPoint;
+};
+
+class DistributedContentList : public nsINodeList
+{
+public:
+  DistributedContentList(HTMLContentElement* aHostElement);
+  virtual ~DistributedContentList();
+
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(DistributedContentList)
+
+  // nsIDOMNodeList
+  NS_DECL_NSIDOMNODELIST
+
+  // nsINodeList
+  virtual nsIContent* Item(uint32_t aIndex);
+  virtual int32_t IndexOf(nsIContent* aContent);
+  virtual nsINode* GetParentObject() { return mParent; }
+  virtual uint32_t Length() const;
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+protected:
+  nsRefPtr<HTMLContentElement> mParent;
+  nsCOMArray<nsIContent> mDistributedNodes;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_HTMLContentElement_h__
+
--- a/content/html/content/src/moz.build
+++ b/content/html/content/src/moz.build
@@ -11,16 +11,17 @@ EXPORTS += [
 ]
 
 EXPORTS.mozilla.dom += [
     'HTMLAnchorElement.h',
     'HTMLAreaElement.h',
     'HTMLBodyElement.h',
     'HTMLBRElement.h',
     'HTMLButtonElement.h',
+    'HTMLContentElement.h',
     'HTMLDataElement.h',
     'HTMLDataListElement.h',
     'HTMLDivElement.h',
     'HTMLFieldSetElement.h',
     'HTMLFontElement.h',
     'HTMLFormControlsCollection.h',
     'HTMLFormElement.h',
     'HTMLFrameElement.h',
@@ -78,16 +79,17 @@ EXPORTS.mozilla.dom += [
 UNIFIED_SOURCES += [
     'HTMLAnchorElement.cpp',
     'HTMLAreaElement.cpp',
     'HTMLAudioElement.cpp',
     'HTMLBodyElement.cpp',
     'HTMLBRElement.cpp',
     'HTMLButtonElement.cpp',
     'HTMLCanvasElement.cpp',
+    'HTMLContentElement.cpp',
     'HTMLDataElement.cpp',
     'HTMLDataListElement.cpp',
     'HTMLDivElement.cpp',
     'HTMLElement.cpp',
     'HTMLFieldSetElement.cpp',
     'HTMLFontElement.cpp',
     'HTMLFormControlsCollection.cpp',
     'HTMLFormElement.cpp',
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -1746,16 +1746,17 @@ NS_DECLARE_NS_NEW_HTML_ELEMENT(SharedObj
 
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Anchor)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Area)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Audio)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(BR)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Body)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Button)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Canvas)
+NS_DECLARE_NS_NEW_HTML_ELEMENT(Content)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Mod)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Data)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(DataList)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Div)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(FieldSet)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Font)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Form)
 NS_DECLARE_NS_NEW_HTML_ELEMENT(Frame)
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -253,16 +253,17 @@ var interfaceNamesInGlobalScope =
     "HTMLAreaElement",
     "HTMLAudioElement",
     "HTMLBaseElement",
     "HTMLBodyElement",
     "HTMLBRElement",
     "HTMLButtonElement",
     "HTMLCanvasElement",
     "HTMLCollection",
+    "HTMLContentElement",
     "HTMLDataElement",
     "HTMLDataListElement",
     "HTMLDirectoryElement",
     "HTMLDivElement",
     "HTMLDListElement",
     "HTMLDocument",
     "HTMLElement",
     "HTMLEmbedElement",
--- a/dom/tests/mochitest/webcomponents/mochitest.ini
+++ b/dom/tests/mochitest/webcomponents/mochitest.ini
@@ -1,7 +1,10 @@
 [DEFAULT]
 
 [test_bug900724.html]
+[test_content_element.html]
+[test_nested_content_element.html]
+[test_dyanmic_content_element_matching.html]
 [test_document_register.html]
 [test_document_register_lifecycle.html]
 [test_template.html]
 [test_shadow_root.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_content_element.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=806506
+-->
+<head>
+  <title>Test for HTMLContent element</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=806506">Bug 806506</a>
+<script>
+// Create a ShadowRoot and append some nodes, containing an insertion point with a universal selector.
+var shadow = $("grabme").createShadowRoot();
+shadow.innerHTML = '<span><content id="point"></content></span>';
+
+// Get the insertion point from the ShadowRoot and check that child of host is distributed.
+// Insertion point should match everything because the selector set is empty.
+var insertionPoint = shadow.getElementById("point");
+$("grabme").innerHTML = '<div id="distme"></div>';
+var distNodes = insertionPoint.getDistributedNodes();
+is(distNodes[0], $("distme"), "Child of bound content should be distributed into insertion point with universal selector.");
+is(distNodes.length, 1, "Should only have one child distributed into insertion point.");
+
+// Add another node to bound content and make sure that the node list is static and does not change.
+var someSpan = document.createElement("span");
+$("grabme").appendChild(someSpan);
+is(distNodes.length, 1, "NodeList from getDistributedNodes should be static.");
+
+// Test content select.
+$("grabme").innerHTML = '<div id="first" class="tall"></div><div id="second" class="skinny"></div>';
+shadow.innerHTML = '<span><content select=".tall" id="point"></content></span>';
+var insertionPoint = shadow.getElementById("point");
+distNodes = insertionPoint.getDistributedNodes();
+is(distNodes.length, 1, "Insertion point should only match element with the 'tall' class.");
+is(distNodes[0], $("first"), "Insertion point should only match element with the 'tall' class.");
+
+// Get rid of the select attribute and check that the insertion point matches everything.
+insertionPoint.removeAttribute("select");
+is(insertionPoint.getDistributedNodes().length, 2, "After removing the 'select' attribute, the insertion point should match everything.");
+
+// Set an invalid selector and make sure that nothing is matched.
+insertionPoint.setAttribute("select", "div:first-child");
+is(insertionPoint.getDistributedNodes().length, 0, "Invalid selectors should match nothing.");
+
+// all compound selectors must only be permitted simple selectors.
+insertionPoint.setAttribute("select", "div:first-child, span");
+is(insertionPoint.getDistributedNodes().length, 0, "Invalid selectors should match nothing.");
+
+// Test multiple compound selectors.
+$("grabme").innerHTML = '<div id="first"></div><span id="second"></span><span data-match-me="pickme" id="third"></span>';
+insertionPoint.setAttribute("select", "span[data-match-me=pickme], div");
+distNodes = insertionPoint.getDistributedNodes();
+is(distNodes.length, 2, "Insertion point selector should only match two nodes.");
+is(distNodes[0], $("first"), "First child node should match selector.");
+is(distNodes[1], $("third"), "Third child node should match selector.");
+
+// Test select property
+insertionPoint.select = "#second, #third";
+distNodes = insertionPoint.getDistributedNodes();
+is(distNodes.length, 2, "Insertion point selector (set using property) should only match two nodes.");
+is(distNodes[0], $("second"), "First child node should match selector.");
+is(distNodes[1], $("third"), "Third child node should match selector.");
+is(insertionPoint.select, "#second, #third", "select property should be transparent.");
+
+// Empty set of selectors should match everything.
+insertionPoint.select = "";
+is(insertionPoint.getDistributedNodes().length, 3, "Empty set of selectors (set using property) should match everything.");
+
+// Remove insertion point and make sure that the point does not have any nodes distributed.
+$("grabme").innerHTML = '<div></div><span></span>';
+insertionPoint.removeAttribute("select");
+is(insertionPoint.getDistributedNodes().length, 2, "Insertion point with univeral selector should match two nodes.");
+var insertionParent = insertionPoint.parentNode;
+insertionParent.removeChild(insertionPoint);
+is(insertionPoint.getDistributedNodes().length, 0, "Insertion point should match no nodes after removal.");
+insertionParent.appendChild(insertionPoint);
+is(insertionPoint.getDistributedNodes().length, 2, "Insertion point should match two nodes after appending.");
+
+// Test multiple insertion points and check tree order distribution of points.
+// Append two divs and three spans into host.
+$("grabme").innerHTML = '<div></div><span></span><div></div><span></span><span></span>';
+shadow.innerHTML = '<content select="div" id="divpoint"></content><content select="div, span" id="allpoint"></content>';
+// Insertion point matching div
+var divPoint = shadow.getElementById("divpoint");
+// Insertion point matching span and div
+var allPoint = shadow.getElementById("allpoint");
+
+is(divPoint.getDistributedNodes().length, 2, "Two div nodes should be distributed into divPoint.");
+is(allPoint.getDistributedNodes().length, 3, "Remaining nodes should be distributed into allPoint.");
+
+shadow.removeChild(allPoint);
+is(divPoint.getDistributedNodes().length, 2, "Number of div distributed into insertion point should not change.");
+is(allPoint.getDistributedNodes().length, 0, "Removed insertion point should not have any nodes.");
+
+shadow.insertBefore(allPoint, divPoint);
+is(allPoint.getDistributedNodes().length, 5, "allPoint should have nodes distributed before divPoint.");
+is(divPoint.getDistributedNodes().length, 0, "divPoint should have no distributed nodes because they are all distributed to allPoint.");
+
+// Make sure that fallback content are in the distributed nodes.
+$("grabme").innerHTML = '<div id="one"></div><div id="two"></div>';
+shadow.innerHTML = '<content select="#nothing" id="point"><span id="fallback"></span></content>';
+insertionPoint = shadow.getElementById("point");
+is(insertionPoint.getDistributedNodes().length, 1, "There should be one distributed node from fallback content.");
+is(insertionPoint.getDistributedNodes()[0].id, "fallback", "Distributed node should be fallback content.");
+
+$("grabme").innerHTML = '';
+shadow.innerHTML = '<content select="div" id="point"><span id="one"></span><span id="two"></span></content>';
+insertionPoint = shadow.getElementById("point");
+// Make sure that two fallback nodes are distributed into the insertion point.
+is(insertionPoint.getDistributedNodes().length, 2, "There should be two distributed nodes from fallback content.");
+is(insertionPoint.getDistributedNodes()[0].id, "one", "First distributed node should have an ID of one.");
+is(insertionPoint.getDistributedNodes()[1].id, "two", "Second distributed node should have an ID of two.");
+
+// Append a node that gets matched by the insertion point, thus causing the fallback content to be removed.
+var matchingDiv = document.createElement("div");
+matchingDiv.id = "three";
+$("grabme").appendChild(matchingDiv);
+is(insertionPoint.getDistributedNodes().length, 1, "There should be one node distributed from the host.");
+is(insertionPoint.getDistributedNodes()[0].id, "three", "Node distriubted from host should have id of three.");
+
+// Remove the matching node from the host and make sure that the fallback content gets distributed.
+$("grabme").removeChild(matchingDiv);
+is(insertionPoint.getDistributedNodes().length, 2, "There should be two distributed nodes from fallback content.");
+is(insertionPoint.getDistributedNodes()[0].id, "one", "First distributed node should have an ID of one.");
+is(insertionPoint.getDistributedNodes()[1].id, "two", "Second distributed node should have an ID of two.");
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_dynamic_content_element_matching.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=806506
+-->
+<head>
+  <title>Test for dynamic changes to content matching content elements</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 class="tall" id="bodydiv"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=806506">Bug 806506</a>
+<script>
+// Create ShadowRoot.
+var elem = document.createElement("div");
+var root = elem.createShadowRoot();
+
+var redInsertionPoint = document.createElement("content");
+redInsertionPoint.select = "*[data-color=red]";
+
+var blueInsertionPoint = document.createElement("content");
+blueInsertionPoint.select = "*[data-color=blue]";
+
+root.appendChild(redInsertionPoint);
+root.appendChild(blueInsertionPoint);
+
+is(blueInsertionPoint.getDistributedNodes().length, 0, "Blue insertion point should have no distributed nodes.");
+is(redInsertionPoint.getDistributedNodes().length, 0, "Red insertion point should have no distrubted nodes.");
+
+var matchElement = document.createElement("div");
+matchElement.setAttribute("data-color", "red");
+elem.appendChild(matchElement);
+
+is(blueInsertionPoint.getDistributedNodes().length, 0, "Blue insertion point should have no distributed nodes.");
+is(redInsertionPoint.getDistributedNodes().length, 1, "Red insertion point should match recently inserted div.");
+
+matchElement.setAttribute("data-color", "blue");
+is(blueInsertionPoint.getDistributedNodes().length, 1, "Blue insertion point should match element after changing attribute value.");
+is(redInsertionPoint.getDistributedNodes().length, 0, "Red insertion point should not match element after changing attribute value.");
+
+matchElement.removeAttribute("data-color");
+
+is(blueInsertionPoint.getDistributedNodes().length, 0, "Blue insertion point should have no distributed nodes after removing the matching attribute.");
+is(redInsertionPoint.getDistributedNodes().length, 0, "Red insertion point should have no distrubted nodes after removing the matching attribute.");
+
+</script>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/webcomponents/test_nested_content_element.html
@@ -0,0 +1,127 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=806506
+-->
+<head>
+  <title>Test for HTMLContent element</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=806506">Bug 806506</a>
+<script>
+
+/**
+ * Constructs a node with a nested ShadowRoot with the following structure:
+ * <span> - - - - - - - - - - <ShadowRoot>
+ *   <span>                     <span> - - - - - - - - - - <ShadowRoot>
+ *    id=one                      id=four                    <span>
+ *    data-color=red              data-color=orange            id=eleven
+ *   <span>                       <span>                     <content>
+ *    id=two                        id=five                    id=twelve
+ *    data-color=blue               data-color=purple          select=secondSelect
+ *   <span>                       <content>                  <span>
+ *    id=three                      id=six                     id=thirteen
+ *    data-color=green              select=firstSelect
+ *                                  <span>
+ *                                    id=seven
+ *                                  <content>
+ *                                    id=eight
+ *                                  <span>
+ *                                    id=nine
+ *                                <span>
+ *                                  id=ten
+ *                                  data-color=grey
+ */
+function constructTree(firstSelect, secondSelect) {
+  var rootSpan = document.createElement("span");
+  rootSpan.innerHTML = '<span id="one" data-color="red"></span><span id="two" data-color="blue"></span><span id="three" data-color="green"></span>';
+  var firstShadow = rootSpan.createShadowRoot();
+  firstShadow.innerHTML = '<span id="four" data-color="orange"><span id="five" data-color="purple"></span><content id="six" select="' + firstSelect + '"><span id="seven"></span><content id="eight"></content><span id="nine"></span></content><span id="ten"></span></span>';
+  var secondShadow = firstShadow.firstChild.createShadowRoot();
+  secondShadow.innerHTML = '<span id="eleven"></span><content id="twelve" select="' + secondSelect + '"></content><span id="thirteen"></span>';
+  return rootSpan;
+}
+
+// Create a tree with content that matches on everything and check node distribution.
+var allSpan = constructTree("*", "*");
+var firstContent = allSpan.shadowRoot.getElementById("six");
+var firstDistNodes = firstContent.getDistributedNodes();
+is(firstDistNodes.length, 3, "Universal selector should match all nodes.");
+// Check the order of the distributed nodes.
+is(firstDistNodes.item(0).id, "one", "First distributed node should have id of 'one'");
+is(firstDistNodes.item(1).id, "two", "Second distributed node should have id of 'two'");
+is(firstDistNodes.item(2).id, "three", "Third distributed node should have id of 'three'");
+var secondContent = allSpan.shadowRoot.firstChild.shadowRoot.getElementById("twelve");
+var secondDistNodes = secondContent.getDistributedNodes();
+is(secondDistNodes.length, 5, "Universial selector should match all nodes including those distributed into content.");
+// Check the order of the distribute nodes.
+is(secondDistNodes.item(0).id, "five", "First distributed node should have id of 'five'");
+is(secondDistNodes.item(1).id, "one", "Second distributed (reprojected) node should have id of 'one'");
+is(secondDistNodes.item(2).id, "two", "Third distributed (reprojected) node should have id of 'two'");
+is(secondDistNodes.item(3).id, "three", "Fourth distributed (reprojected) node should have id of 'three'");
+is(secondDistNodes.item(4).id, "ten", "Fifth distributed node should have id of 'ten'");
+
+// Append an element after id=two and make sure that it is inserted into the corrent
+// position in the insertion points.
+var additionalSpan = document.createElement("span");
+additionalSpan.id = "additional";
+
+// Insert the additional span in the third position, before the span with id=three.
+allSpan.insertBefore(additionalSpan, allSpan.childNodes.item(2));
+firstDistNodes = firstContent.getDistributedNodes();
+secondDistNodes = secondContent.getDistributedNodes();
+is(firstDistNodes.length, 4, "First insertion point should match one more node.");
+is(firstDistNodes.item(2).id, "additional", "Additional span should have been inserted into the third position of the first insertion point.");
+
+is(secondDistNodes.length, 6, "Second insertion point should match one more node.");
+is(secondDistNodes.item(3).id, "additional", "Additional span should have been inserted into the fourth position of the second insertion point.");
+
+function nodeListDoesNotContain(nodeList, element) {
+  for (var i = 0; i < nodeList.length; i++) {
+    if (nodeList[i] == element) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// Remove the span with id=one and check that it is removed from all insertion points.
+allSpan = constructTree("*", "*");
+var spanOne = allSpan.firstChild;
+allSpan.removeChild(spanOne);
+firstContent = allSpan.shadowRoot.getElementById("six");
+ok(nodeListDoesNotContain(firstContent.getDistributedNodes(), spanOne), "Child removed from host should not appear in insertion point node list.");
+secondContent = allSpan.shadowRoot.firstChild.shadowRoot.getElementById("twelve");
+ok(nodeListDoesNotContain(secondContent.getDistributedNodes(), spanOne), "Child removed from host should not appear in nested insertion point node list.");
+
+// Make sure <content> in fallback content is inactive.
+// First insertion point will not match anything and will use fallback content.
+allSpan = constructTree("#nomatch", "*");
+var fallbackInsertionPoint = allSpan.shadowRoot.getElementById("eight");
+is(fallbackInsertionPoint.getDistributedNodes().length, 0, "Insertion points in default content should be inactive.");
+
+// Insertion points with non-universal selectors.
+allSpan = constructTree("span[data-color=blue]", "*");
+firstContent = allSpan.shadowRoot.getElementById("six");
+is(firstContent.getDistributedNodes().length, 1, "Insertion point selector should only match one node.");
+is(firstContent.getDistributedNodes()[0].dataset.color, "blue", "Projected node should match selector.");
+secondContent = allSpan.shadowRoot.firstChild.shadowRoot.getElementById("twelve");
+is(secondContent.getDistributedNodes().length, 3, "Second insertion point should match two children and one reprojected node.");
+is(secondContent.getDistributedNodes()[1].dataset.color, "blue", "Projected node should match selector.");
+
+allSpan = constructTree("span[data-color=blue]", "span[data-color=blue]");
+firstContent = allSpan.shadowRoot.getElementById("six");
+is(firstContent.getDistributedNodes().length, 1, "Insertion point selector should only match one node.");
+is(firstContent.getDistributedNodes()[0].dataset.color, "blue", "Projected node should match selector.");
+secondContent = allSpan.shadowRoot.firstChild.shadowRoot.getElementById("twelve");
+is(secondContent.getDistributedNodes().length, 1, "Insertion point should only match reprojected node.");
+is(secondContent.getDistributedNodes()[0].dataset.color, "blue", "Projected node should match selector.");
+
+// Make sure that dynamically appended default content will get distributed.
+</script>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/webidl/HTMLContentElement.webidl
@@ -0,0 +1,19 @@
+/* -*- 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 HTMLContentElement : HTMLElement
+{
+  attribute DOMString select;
+  NodeList getDistributedNodes();
+};
+
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -115,16 +115,17 @@ WEBIDL_FILES = [
     'HTMLAreaElement.webidl',
     'HTMLAudioElement.webidl',
     'HTMLBaseElement.webidl',
     'HTMLBodyElement.webidl',
     'HTMLBRElement.webidl',
     'HTMLButtonElement.webidl',
     'HTMLCanvasElement.webidl',
     'HTMLCollection.webidl',
+    'HTMLContentElement.webidl',
     'HTMLDataElement.webidl',
     'HTMLDataListElement.webidl',
     'HTMLDirectoryElement.webidl',
     'HTMLDivElement.webidl',
     'HTMLDListElement.webidl',
     'HTMLDocument.webidl',
     'HTMLElement.webidl',
     'HTMLEmbedElement.webidl',
--- a/editor/libeditor/html/nsHTMLEditUtils.cpp
+++ b/editor/libeditor/html/nsHTMLEditUtils.cpp
@@ -636,16 +636,17 @@ static const nsElementInfo kElements[eHT
   ELEM(canvas, false, false, GROUP_NONE, GROUP_NONE),
   ELEM(caption, true, true, GROUP_NONE, GROUP_INLINE_ELEMENT),
   ELEM(center, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   ELEM(cite, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   ELEM(code, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   ELEM(col, false, false, GROUP_TABLE_CONTENT | GROUP_COLGROUP_CONTENT,
        GROUP_NONE),
   ELEM(colgroup, true, false, GROUP_NONE, GROUP_COLGROUP_CONTENT),
+  ELEM(content, true, false, GROUP_NONE, GROUP_INLINE_ELEMENT),
   ELEM(data, true, false, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   ELEM(datalist, true, false, GROUP_PHRASE,
        GROUP_OPTIONS | GROUP_INLINE_ELEMENT),
   ELEM(dd, true, false, GROUP_DL_CONTENT, GROUP_FLOW_ELEMENT),
   ELEM(del, true, true, GROUP_PHRASE | GROUP_BLOCK, GROUP_FLOW_ELEMENT),
   ELEM(dfn, true, true, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
   ELEM(dir, true, false, GROUP_BLOCK, GROUP_LI),
   ELEM(div, true, true, GROUP_BLOCK, GROUP_FLOW_ELEMENT),
--- a/layout/reftests/reftest.list
+++ b/layout/reftests/reftest.list
@@ -319,16 +319,19 @@ skip-if(B2G) include ../../widget/reftes
 skip-if(B2G) include ../../content/test/reftest/xml-stylesheet/reftest.list
 
 # xul-document-load/
 skip-if(B2G) include xul-document-load/reftest.list
 
 # xul/
 skip-if(B2G) include xul/reftest.list
 
+# webcomonents/
+include webcomponents/reftest.list
+
 # xul
 skip-if(B2G) include ../xul/base/reftest/reftest.list
 
 # xul grid
 skip-if(B2G) include ../xul/grid/reftests/reftest.list
 
 # z-index/
 skip-if(B2G) include z-index/reftest.list
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/basic-insertion-point-1-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div>
+  <div style="border: 10px solid green">Hello</div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/basic-insertion-point-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script>
+    function tweak() {
+      // div with style "border: 10px solid green"
+      var shadowDiv = document.createElement("div");
+      shadowDiv.style.border = "10px solid green";
+
+      var insertionPoint = document.createElement("content");
+      shadowDiv.appendChild(insertionPoint);
+
+      var shadowRoot = document.getElementById('outer').createShadowRoot();
+      shadowRoot.appendChild(shadowDiv);
+    }
+  </script>
+</head>
+<body onload="tweak()">
+<div id="outer">Hello</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/basic-insertion-point-2-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div>
+  <div style="border: 10px solid green">
+    <span style="background-color: purple">Hello</span>
+    <span style="background-color: orange">World</span>
+  </div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/basic-insertion-point-2.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script>
+    function tweak() {
+      // div with style "border: 10px solid green"
+      var shadowDiv = document.createElement("div");
+      shadowDiv.style.border = "10px solid green";
+
+      var insertionPoint = document.createElement("content");
+      shadowDiv.appendChild(insertionPoint);
+
+      var shadowRoot = document.getElementById('outer').createShadowRoot();
+      shadowRoot.appendChild(shadowDiv);
+    }
+  </script>
+</head>
+<body onload="tweak()">
+<div id="outer">
+  <span style="background-color: purple">Hello</span>
+  <span style="background-color: orange">World</span>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/basic-shadow-1-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div>
+  <div style="width:300px; height:100px; background-color:green;"></div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/basic-shadow-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script>
+    function tweak() {
+      // div with style "width:300px; height:100px; background-color:green"
+      var shadowDiv = document.createElement("div");
+      shadowDiv.style.width = "300px";
+      shadowDiv.style.height = "100px";
+      shadowDiv.style.backgroundColor = "green";
+
+      var shadowRoot = document.getElementById('outer').createShadowRoot();
+      shadowRoot.appendChild(shadowDiv);
+    }
+  </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/basic-shadow-2-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div>
+  <div style="border: 10px solid green">
+    <div style="border: 10px solid orange"></div>
+    <div style="border: 10px solid purple"></div>
+  </div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/basic-shadow-2.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script>
+    function tweak() {
+      var shadowDiv = document.createElement("div");
+      shadowDiv.style.border = "10px solid green";
+
+      var shadowRoot = document.getElementById('outer').createShadowRoot();
+      shadowRoot.appendChild(shadowDiv);
+
+      var orangeDiv = document.createElement("div");
+      orangeDiv.style.border = "10px solid orange";
+
+      var purpleDiv = document.createElement("div");
+      purpleDiv.style.border = "10px solid purple";
+
+      shadowDiv.appendChild(purpleDiv);
+      shadowDiv.insertBefore(orangeDiv, purpleDiv);
+    }
+  </script>
+</head>
+<body onload="tweak()">
+<div id="outer">
+  <div style="background-color:red;"></div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/basic-shadow-3-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div>
+  <div style="border: 10px solid green">
+    <div style="border: 10px solid orange"></div>
+  </div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/basic-shadow-3.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script>
+    function tweak() {
+      var shadowDiv = document.createElement("div");
+      shadowDiv.style.border = "10px solid green";
+
+      var shadowRoot = document.getElementById('outer').createShadowRoot();
+      shadowRoot.appendChild(shadowDiv);
+
+      var orangeDiv = document.createElement("div");
+      orangeDiv.style.border = "10px solid orange";
+
+      var purpleDiv = document.createElement("div");
+      purpleDiv.style.border = "10px solid purple";
+
+      shadowDiv.appendChild(purpleDiv);
+      shadowDiv.insertBefore(orangeDiv, purpleDiv);
+      shadowDiv.removeChild(purpleDiv);
+    }
+  </script>
+</head>
+<body onload="tweak()">
+<div id="outer">
+  <div style="background-color:red;"></div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/basic-shadow-4-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div>
+  <div style="border: 10px solid green"></div>
+  <div style="border: 10px solid orange"></div>
+  <div style="border: 10px solid purple"></div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/basic-shadow-4.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script>
+    function tweak() {
+      var shadowDiv = document.createElement("div");
+      shadowDiv.style.border = "10px solid green";
+
+      var shadowRoot = document.getElementById('outer').createShadowRoot();
+      shadowRoot.appendChild(shadowDiv);
+
+      var orangeDiv = document.createElement("div");
+      orangeDiv.style.border = "10px solid orange";
+
+      var purpleDiv = document.createElement("div");
+      purpleDiv.style.border = "10px solid purple";
+
+      shadowRoot.appendChild(purpleDiv);
+      shadowRoot.insertBefore(orangeDiv, purpleDiv);
+    }
+  </script>
+</head>
+<body onload="tweak()">
+<div id="outer">
+  <div style="background-color:red;"></div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/fallback-content-1-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div>
+  <div style="border: 10px solid green"><span style="background-color: orange">Hello</span> <span style="background-color: green">World</span></div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/fallback-content-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script>
+    function tweak() {
+      // div with style "border: 10px solid green"
+      var shadowDiv = document.createElement("div");
+      shadowDiv.style.border = "10px solid green";
+
+      // Insertion point will match nothing and use fallback content.
+      var insertionPoint = document.createElement("content");
+      shadowDiv.appendChild(insertionPoint);
+
+      // Append three nodes as children to use as fallback content.
+      insertionPoint.innerHTML = '<span style="background-color: orange">Hello</span> <span style="background-color: green">World</span>';
+
+      var shadowRoot = document.getElementById('outer').createShadowRoot();
+      shadowRoot.appendChild(shadowDiv);
+    }
+  </script>
+</head>
+<body onload="tweak()">
+<div id="outer"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/nested-insertion-point-1-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div>
+  <div style="border: 10px solid green"><div style="border: 10px solid orange">Hello</div></div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/nested-insertion-point-1.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script>
+    function tweak() {
+      // div with style "border: 10px solid green"
+      var outerShadow = document.createElement("div");
+      outerShadow.style.border = "10px solid green";
+
+      var outerInsertionPoint = document.createElement("content");
+      outerShadow.appendChild(outerInsertionPoint);
+
+      // div with style "border: 10px solid orange"
+      var innerShadow = document.createElement("div");
+      innerShadow.style.border = "10px solid orange";
+
+      var innerInsertionPoint = document.createElement("content");
+      innerShadow.appendChild(innerInsertionPoint);
+
+      outerShadow.createShadowRoot().appendChild(innerShadow);
+
+      var shadowRoot = document.getElementById('outer').createShadowRoot();
+      shadowRoot.appendChild(outerShadow);
+    }
+  </script>
+</head>
+<body onload="tweak()">
+<div id="outer">Hello</div>
+</body>
+</html>
--- a/layout/reftests/webcomponents/reftest.list
+++ b/layout/reftests/webcomponents/reftest.list
@@ -1,1 +1,10 @@
 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) == 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
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/remove-insertion-point-1-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<div>
+  <div style="border: 10px solid green"></div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/webcomponents/remove-insertion-point-1.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <script>
+    function tweak() {
+      // div with style "border: 10px solid green"
+      var shadowDiv = document.createElement("div");
+      shadowDiv.style.border = "10px solid green";
+
+      // Insertion point will match nothing and use fallback content.
+      var insertionPoint = document.createElement("content");
+      shadowDiv.appendChild(insertionPoint);
+
+      var shadowRoot = document.getElementById('outer').createShadowRoot();
+      shadowRoot.appendChild(shadowDiv);
+
+      // Remove the insertion point from the ShadowRoot, "Hello" should no
+      // longer be rendered.
+      shadowDiv.removeChild(insertionPoint);
+    }
+  </script>
+</head>
+<body onload="tweak()">
+<div id="outer">Hello</div>
+</body>
+</html>
--- a/parser/htmlparser/public/nsHTMLTagList.h
+++ b/parser/htmlparser/public/nsHTMLTagList.h
@@ -56,16 +56,17 @@ HTML_TAG(br, BR)
 HTML_TAG(button, Button)
 HTML_TAG(canvas, Canvas)
 HTML_TAG(caption, TableCaption)
 HTML_HTMLELEMENT_TAG(center)
 HTML_HTMLELEMENT_TAG(cite)
 HTML_HTMLELEMENT_TAG(code)
 HTML_TAG(col, TableCol)
 HTML_TAG(colgroup, TableCol)
+HTML_TAG(content, Content)
 HTML_TAG(data, Data)
 HTML_TAG(datalist, DataList)
 HTML_HTMLELEMENT_TAG(dd)
 HTML_TAG(del, Mod)
 HTML_HTMLELEMENT_TAG(dfn)
 HTML_TAG(dir, Shared)
 HTML_TAG(div, Div)
 HTML_TAG(dl, SharedList)
--- a/parser/htmlparser/src/nsElementTable.cpp
+++ b/parser/htmlparser/src/nsElementTable.cpp
@@ -119,16 +119,20 @@ const nsHTMLElement gHTMLElements[] = {
     /*tag*/         eHTMLTag_col,
     /*parent,leaf*/ kNone, true
   },
   {
     /*tag*/         eHTMLTag_colgroup,
     /*parent,leaf*/ kNone, false
   },
   {
+    /*tag*/         eHTMLTag_content,
+    /*parent,leaf*/ kNone, false
+  },
+  {
     /*tag*/         eHTMLTag_data,
     /*parent,leaf*/ kPhrase, false
   },
   {
     /*tag*/         eHTMLTag_datalist,
     /*parent,leaf*/ kSpecial, false
   },
   {
--- a/parser/htmlparser/src/nsHTMLTags.cpp
+++ b/parser/htmlparser/src/nsHTMLTags.cpp
@@ -66,16 +66,18 @@ static const PRUnichar sHTMLTagUnicodeNa
 static const PRUnichar sHTMLTagUnicodeName_cite[] =
   {'c', 'i', 't', 'e', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_code[] =
   {'c', 'o', 'd', 'e', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_col[] =
   {'c', 'o', 'l', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_colgroup[] =
   {'c', 'o', 'l', 'g', 'r', 'o', 'u', 'p', '\0'};
+static const PRUnichar sHTMLTagUnicodeName_content[] =
+  {'c', 'o', 'n', 't', 'e', 'n', 't', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_data[] =
   {'d', 'a', 't', 'a', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_datalist[] =
   {'d', 'a', 't', 'a', 'l', 'i', 's', 't', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_dd[] =
   {'d', 'd', '\0'};
 static const PRUnichar sHTMLTagUnicodeName_del[] =
   {'d', 'e', 'l', '\0'};