Bug 999271 - Implement web components getDestinationInsertionPoints() extension to Element interface. r=mrbkap

This commit is contained in:
William Chen 2014-05-21 23:11:53 -07:00
parent ca8a3778af
commit 5c4b43981d
17 changed files with 561 additions and 22 deletions

View File

@ -114,11 +114,12 @@ class Link;
class UndoManager;
class DOMRect;
class DOMRectList;
class DestinationInsertionPointList;
// IID for the dom::Element interface
#define NS_ELEMENT_IID \
{ 0xf7c18f0f, 0xa8fd, 0x4a95, \
{ 0x91, 0x72, 0xd3, 0xa7, 0x4a, 0xb8, 0xc4, 0xbe } }
{ 0xd123f791, 0x124a, 0x43f3, \
{ 0x84, 0xe3, 0x55, 0x81, 0x0b, 0x6c, 0xf3, 0x08 } }
class Element : public FragmentOrElement
{
@ -693,6 +694,7 @@ public:
already_AddRefed<DOMRect> GetBoundingClientRect();
already_AddRefed<ShadowRoot> CreateShadowRoot(ErrorResult& aError);
already_AddRefed<DestinationInsertionPointList> GetDestinationInsertionPoints();
void ScrollIntoView()
{
@ -1185,6 +1187,29 @@ private:
EventStates mState;
};
class DestinationInsertionPointList : public nsINodeList
{
public:
DestinationInsertionPointList(Element* aElement);
virtual ~DestinationInsertionPointList();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(DestinationInsertionPointList)
// 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) MOZ_OVERRIDE;
protected:
nsRefPtr<Element> mParent;
nsCOMArray<nsIContent> mDestinationPoints;
};
NS_DEFINE_STATIC_IID_ACCESSOR(Element, NS_ELEMENT_IID)
inline bool

View File

@ -209,6 +209,8 @@ public:
nsBindingManager* aOldBindingManager = nullptr) MOZ_OVERRIDE;
virtual ShadowRoot *GetShadowRoot() const MOZ_OVERRIDE;
virtual ShadowRoot *GetContainingShadow() const MOZ_OVERRIDE;
virtual nsTArray<nsIContent*> &DestInsertionPoints() MOZ_OVERRIDE;
virtual nsTArray<nsIContent*> *GetExistingDestInsertionPoints() const MOZ_OVERRIDE;
virtual void SetShadowRoot(ShadowRoot* aBinding) MOZ_OVERRIDE;
virtual nsIContent *GetXBLInsertionParent() const MOZ_OVERRIDE;
virtual void SetXBLInsertionParent(nsIContent* aContent) MOZ_OVERRIDE;
@ -375,6 +377,12 @@ public:
*/
nsRefPtr<ShadowRoot> mContainingShadow;
/**
* An array of web component insertion points to which this element
* is distributed.
*/
nsTArray<nsIContent*> mDestInsertionPoints;
/**
* XBL binding installed on the element.
*/

View File

@ -39,8 +39,8 @@ enum nsLinkState {
// IID for the nsIContent interface
#define NS_ICONTENT_IID \
{ 0x1329e5b7, 0x4bcd, 0x450c, \
{ 0xa2, 0x3a, 0x98, 0xc5, 0x85, 0xcd, 0x73, 0xf9 } }
{ 0xc534a378, 0x7b5f, 0x43a4, \
{ 0xaf, 0x65, 0x5f, 0xfe, 0xea, 0xd6, 0x00, 0xfb } }
/**
* A node of content in a document's content model. This interface
@ -668,6 +668,20 @@ public:
*/
virtual mozilla::dom::ShadowRoot *GetContainingShadow() const = 0;
/**
* Gets an array of destination insertion points where this content
* is distributed by web component distribution algorithms.
* The array is created if it does not already exist.
*/
virtual nsTArray<nsIContent*> &DestInsertionPoints() = 0;
/**
* Same as DestInsertionPoints except that this method will return
* null if the array of destination insertion points does not already
* exist.
*/
virtual nsTArray<nsIContent*> *GetExistingDestInsertionPoints() const = 0;
/**
* Gets the insertion parent element of the XBL binding.
* The insertion parent is our one true parent in the transformed DOM.

View File

@ -125,6 +125,7 @@
#include "mozilla/CORSMode.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/NodeListBinding.h"
#include "nsStyledElement.h"
#include "nsXBLService.h"
@ -826,6 +827,83 @@ Element::CreateShadowRoot(ErrorResult& aError)
return shadowRoot.forget();
}
NS_IMPL_CYCLE_COLLECTION(DestinationInsertionPointList, mParent, mDestinationPoints)
NS_INTERFACE_TABLE_HEAD(DestinationInsertionPointList)
NS_INTERFACE_TABLE(DestinationInsertionPointList, nsINodeList)
NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(DestinationInsertionPointList)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(DestinationInsertionPointList)
NS_IMPL_CYCLE_COLLECTING_RELEASE(DestinationInsertionPointList)
DestinationInsertionPointList::DestinationInsertionPointList(Element* aElement)
: mParent(aElement)
{
SetIsDOMBinding();
nsTArray<nsIContent*>* destPoints = aElement->GetExistingDestInsertionPoints();
if (destPoints) {
for (uint32_t i = 0; i < destPoints->Length(); i++) {
mDestinationPoints.AppendElement(destPoints->ElementAt(i));
}
}
}
DestinationInsertionPointList::~DestinationInsertionPointList()
{
}
nsIContent*
DestinationInsertionPointList::Item(uint32_t aIndex)
{
return mDestinationPoints.SafeElementAt(aIndex);
}
NS_IMETHODIMP
DestinationInsertionPointList::Item(uint32_t aIndex, nsIDOMNode** aReturn)
{
nsIContent* item = Item(aIndex);
if (!item) {
return NS_ERROR_FAILURE;
}
return CallQueryInterface(item, aReturn);
}
uint32_t
DestinationInsertionPointList::Length() const
{
return mDestinationPoints.Length();
}
NS_IMETHODIMP
DestinationInsertionPointList::GetLength(uint32_t* aLength)
{
*aLength = Length();
return NS_OK;
}
int32_t
DestinationInsertionPointList::IndexOf(nsIContent* aContent)
{
return mDestinationPoints.IndexOf(aContent);
}
JSObject*
DestinationInsertionPointList::WrapObject(JSContext* aCx)
{
return NodeListBinding::Wrap(aCx, this);
}
already_AddRefed<DestinationInsertionPointList>
Element::GetDestinationInsertionPoints()
{
nsRefPtr<DestinationInsertionPointList> list =
new DestinationInsertionPointList(this);
return list.forget();
}
void
Element::GetAttribute(const nsAString& aName, DOMString& aReturn)
{

View File

@ -1011,6 +1011,23 @@ FragmentOrElement::SetShadowRoot(ShadowRoot* aShadowRoot)
slots->mShadowRoot = aShadowRoot;
}
nsTArray<nsIContent*>&
FragmentOrElement::DestInsertionPoints()
{
nsDOMSlots *slots = DOMSlots();
return slots->mDestInsertionPoints;
}
nsTArray<nsIContent*>*
FragmentOrElement::GetExistingDestInsertionPoints() const
{
nsDOMSlots *slots = GetExistingDOMSlots();
if (slots) {
return &slots->mDestInsertionPoints;
}
return nullptr;
}
void
FragmentOrElement::SetXBLInsertionParent(nsIContent* aContent)
{

View File

@ -256,6 +256,22 @@ ShadowRoot::SetYoungerShadow(ShadowRoot* aYoungerShadow)
ChangePoolHost(mYoungerShadow->GetShadowElement());
}
void
ShadowRoot::RemoveDestInsertionPoint(nsIContent* aInsertionPoint,
nsTArray<nsIContent*>& aDestInsertionPoints)
{
// Remove the insertion point from the destination insertion points.
// Also remove all succeeding insertion points because it is no longer
// possible for the content to be distributed into deeper node trees.
int32_t index = aDestInsertionPoints.IndexOf(aInsertionPoint);
// It's possible that we already removed the insertion point while processing
// other insertion point removals.
if (index >= 0) {
aDestInsertionPoints.SetLength(index);
}
}
void
ShadowRoot::DistributeSingleNode(nsIContent* aContent)
{
@ -295,7 +311,7 @@ ShadowRoot::DistributeSingleNode(nsIContent* aContent)
// 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);
insertionPoint->InsertMatchedNode(i, aContent);
isIndexFound = true;
break;
}
@ -306,7 +322,7 @@ ShadowRoot::DistributeSingleNode(nsIContent* aContent)
// 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);
insertionPoint->AppendMatchedNode(aContent);
}
// Handle the case where the parent of the insertion point is a ShadowRoot
@ -354,7 +370,7 @@ ShadowRoot::RemoveDistributedNode(nsIContent* aContent)
return;
}
mInsertionPoints[i]->MatchedNodes().RemoveElement(aContent);
mInsertionPoints[i]->RemoveMatchedNode(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.
@ -412,8 +428,7 @@ ShadowRoot::DistributeAllNodes()
// 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]);
mInsertionPoints[i]->AppendMatchedNode(nodePool[j]);
nodePool.RemoveElementAt(j--);
}
}
@ -425,7 +440,7 @@ ShadowRoot::DistributeAllNodes()
"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
// If the parent of the insertion point has a ShadowRoot, the nodes distributed
// to the insertion point must be reprojected to the insertion points of the
// parent's ShadowRoot.
ShadowRoot* parentShadow = insertionParent->GetShadowRoot();
@ -562,6 +577,11 @@ ShadowRoot::IsPooledNode(nsIContent* aContent, nsIContent* aContainer,
return false;
}
if (aContainer == aHost) {
// Any other child nodes of the host will end up in the pool.
return true;
}
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);
@ -569,11 +589,6 @@ ShadowRoot::IsPooledNode(nsIContent* aContent, nsIContent* aContainer,
aContainer->GetParentNode() == aHost;
}
if (aContainer == aHost) {
// Any other child nodes of the host will end up in the pool.
return true;
}
return false;
}
@ -609,9 +624,18 @@ ShadowRoot::ContentAppended(nsIDocument* aDocument,
// may need to be added to an insertion point.
nsIContent* currentChild = aFirstNewContent;
while (currentChild) {
// Add insertion point to destination insertion points of fallback content.
if (nsContentUtils::IsContentInsertionPoint(aContainer)) {
HTMLContentElement* content = static_cast<HTMLContentElement*>(aContainer);
if (content->MatchedNodes().IsEmpty()) {
currentChild->DestInsertionPoints().AppendElement(aContainer);
}
}
if (IsPooledNode(currentChild, aContainer, mPoolHost)) {
DistributeSingleNode(currentChild);
}
currentChild = currentChild->GetNextSibling();
}
}
@ -631,6 +655,14 @@ ShadowRoot::ContentInserted(nsIDocument* aDocument,
// Watch for new nodes added to the pool because the node
// may need to be added to an insertion point.
if (IsPooledNode(aChild, aContainer, mPoolHost)) {
// Add insertion point to destination insertion points of fallback content.
if (nsContentUtils::IsContentInsertionPoint(aContainer)) {
HTMLContentElement* content = static_cast<HTMLContentElement*>(aContainer);
if (content->MatchedNodes().IsEmpty()) {
aChild->DestInsertionPoints().AppendElement(aContainer);
}
}
DistributeSingleNode(aChild);
}
}
@ -648,6 +680,15 @@ ShadowRoot::ContentRemoved(nsIDocument* aDocument,
return;
}
// Clear destination insertion points for removed
// fallback content.
if (nsContentUtils::IsContentInsertionPoint(aContainer)) {
HTMLContentElement* content = static_cast<HTMLContentElement*>(aContainer);
if (content->MatchedNodes().IsEmpty()) {
aChild->DestInsertionPoints().Clear();
}
}
// Watch for node that is removed from the pool because
// it may need to be removed from an insertion point.
if (IsPooledNode(aChild, aContainer, mPoolHost)) {

View File

@ -113,6 +113,9 @@ public:
static ShadowRoot* FromNode(nsINode* aNode);
static bool IsShadowInsertionPoint(nsIContent* aContent);
static void RemoveDestInsertionPoint(nsIContent* aInsertionPoint,
nsTArray<nsIContent*>& aDestInsertionPoints);
// WebIDL methods.
Element* GetElementById(const nsAString& aElementId);
already_AddRefed<nsContentList>

View File

@ -685,6 +685,23 @@ nsGenericDOMDataNode::SetShadowRoot(ShadowRoot* aShadowRoot)
{
}
nsTArray<nsIContent*>&
nsGenericDOMDataNode::DestInsertionPoints()
{
nsDataSlots *slots = DataSlots();
return slots->mDestInsertionPoints;
}
nsTArray<nsIContent*>*
nsGenericDOMDataNode::GetExistingDestInsertionPoints() const
{
nsDataSlots *slots = GetExistingDataSlots();
if (slots) {
return &slots->mDestInsertionPoints;
}
return nullptr;
}
nsXBLBinding *
nsGenericDOMDataNode::GetXBLBinding() const
{

View File

@ -159,6 +159,8 @@ public:
nsBindingManager* aOldBindingManager = nullptr) MOZ_OVERRIDE;
virtual mozilla::dom::ShadowRoot *GetContainingShadow() const MOZ_OVERRIDE;
virtual mozilla::dom::ShadowRoot *GetShadowRoot() const MOZ_OVERRIDE;
virtual nsTArray<nsIContent*> &DestInsertionPoints() MOZ_OVERRIDE;
virtual nsTArray<nsIContent*> *GetExistingDestInsertionPoints() const MOZ_OVERRIDE;
virtual void SetShadowRoot(mozilla::dom::ShadowRoot* aShadowRoot) MOZ_OVERRIDE;
virtual nsIContent *GetXBLInsertionParent() const MOZ_OVERRIDE;
virtual void SetXBLInsertionParent(nsIContent* aContent) MOZ_OVERRIDE;
@ -266,6 +268,11 @@ protected:
* @see nsIContent::GetContainingShadow
*/
nsRefPtr<mozilla::dom::ShadowRoot> mContainingShadow;
/**
* @see nsIContent::GetDestInsertionPoints
*/
nsTArray<nsIContent*> mDestInsertionPoints;
};
// Override from nsINode

View File

@ -92,9 +92,8 @@ HTMLContentElement::UnbindFromTree(bool aDeep, bool aNullParent)
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.
// Remove all the matched nodes now that the
// insertion point is no longer an insertion point.
ClearMatchedNodes();
containingShadow->SetInsertionPointChanged();
}
@ -105,13 +104,71 @@ HTMLContentElement::UnbindFromTree(bool aDeep, bool aNullParent)
nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
}
void
HTMLContentElement::AppendMatchedNode(nsIContent* aContent)
{
mMatchedNodes.AppendElement(aContent);
nsTArray<nsIContent*>& destInsertionPoint = aContent->DestInsertionPoints();
destInsertionPoint.AppendElement(this);
if (mMatchedNodes.Length() == 1) {
// Fallback content gets dropped so we need to updated fallback
// content distribution.
UpdateFallbackDistribution();
}
}
void
HTMLContentElement::UpdateFallbackDistribution()
{
for (nsIContent* child = nsINode::GetFirstChild();
child;
child = child->GetNextSibling()) {
nsTArray<nsIContent*>& destInsertionPoint = child->DestInsertionPoints();
destInsertionPoint.Clear();
if (mMatchedNodes.IsEmpty()) {
destInsertionPoint.AppendElement(this);
}
}
}
void
HTMLContentElement::RemoveMatchedNode(nsIContent* aContent)
{
mMatchedNodes.RemoveElement(aContent);
ShadowRoot::RemoveDestInsertionPoint(this, aContent->DestInsertionPoints());
if (mMatchedNodes.IsEmpty()) {
// Fallback content is activated so we need to update fallback
// content distribution.
UpdateFallbackDistribution();
}
}
void
HTMLContentElement::InsertMatchedNode(uint32_t aIndex, nsIContent* aContent)
{
mMatchedNodes.InsertElementAt(aIndex, aContent);
nsTArray<nsIContent*>& destInsertionPoint = aContent->DestInsertionPoints();
destInsertionPoint.AppendElement(this);
if (mMatchedNodes.Length() == 1) {
// Fallback content gets dropped so we need to updated fallback
// content distribution.
UpdateFallbackDistribution();
}
}
void
HTMLContentElement::ClearMatchedNodes()
{
for (uint32_t i = 0; i < mMatchedNodes.Length(); i++) {
mMatchedNodes[i]->SetXBLInsertionParent(nullptr);
ShadowRoot::RemoveDestInsertionPoint(this, mMatchedNodes[i]->DestInsertionPoints());
}
mMatchedNodes.Clear();
UpdateFallbackDistribution();
}
static bool

View File

@ -46,6 +46,9 @@ public:
bool Match(nsIContent* aContent);
bool IsInsertionPoint() const { return mIsInsertionPoint; }
nsCOMArray<nsIContent>& MatchedNodes() { return mMatchedNodes; }
void AppendMatchedNode(nsIContent* aContent);
void RemoveMatchedNode(nsIContent* aContent);
void InsertMatchedNode(uint32_t aIndex, nsIContent* aContent);
void ClearMatchedNodes();
virtual nsresult SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
@ -69,6 +72,15 @@ public:
protected:
virtual JSObject* WrapNode(JSContext *aCx) MOZ_OVERRIDE;
/**
* Updates the destination insertion points of the fallback
* content of this insertion point. If there are nodes matched
* to this insertion point, then destination insertion points
* of fallback are cleared, otherwise, this insertion point
* is a destination insertion point.
*/
void UpdateFallbackDistribution();
/**
* An array of nodes from the ShadowRoot host that match the
* content insertion selector.

View File

@ -5,6 +5,7 @@
#include "mozilla/dom/ShadowRoot.h"
#include "ChildIterator.h"
#include "nsContentUtils.h"
#include "mozilla/dom/HTMLShadowElement.h"
#include "mozilla/dom/HTMLShadowElementBinding.h"
@ -60,10 +61,28 @@ HTMLShadowElement::SetProjectedShadow(ShadowRoot* aProjectedShadow)
{
if (mProjectedShadow) {
mProjectedShadow->RemoveMutationObserver(this);
// The currently projected ShadowRoot is going away,
// thus the destination insertion points need to be updated.
ExplicitChildIterator childIterator(mProjectedShadow);
for (nsIContent* content = childIterator.GetNextChild();
content;
content = childIterator.GetNextChild()) {
ShadowRoot::RemoveDestInsertionPoint(this, content->DestInsertionPoints());
}
}
mProjectedShadow = aProjectedShadow;
if (mProjectedShadow) {
// A new ShadowRoot is being projected, thus its explcit
// children will be distributed to this shadow insertion point.
ExplicitChildIterator childIterator(mProjectedShadow);
for (nsIContent* content = childIterator.GetNextChild();
content;
content = childIterator.GetNextChild()) {
content->DestInsertionPoints().AppendElement(this);
}
// Watch for mutations on the projected shadow because
// it affects the nodes that are distributed to this shadow
// insertion point.
@ -156,6 +175,14 @@ HTMLShadowElement::UnbindFromTree(bool aDeep, bool aNullParent)
void
HTMLShadowElement::DistributeSingleNode(nsIContent* aContent)
{
if (aContent->DestInsertionPoints().Contains(this)) {
// Node has already been distrbuted this this node,
// we are done.
return;
}
aContent->DestInsertionPoints().AppendElement(this);
// 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
@ -181,6 +208,8 @@ HTMLShadowElement::DistributeSingleNode(nsIContent* aContent)
void
HTMLShadowElement::RemoveDistributedNode(nsIContent* aContent)
{
ShadowRoot::RemoveDestInsertionPoint(this, aContent->DestInsertionPoints());
// 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
@ -206,6 +235,21 @@ HTMLShadowElement::RemoveDistributedNode(nsIContent* aContent)
void
HTMLShadowElement::DistributeAllNodes()
{
// All the explicit children of the projected ShadowRoot are distributed
// into this shadow insertion point so update the destination insertion
// points.
ShadowRoot* containingShadow = GetContainingShadow();
ShadowRoot* olderShadow = containingShadow->GetOlderShadow();
if (olderShadow) {
ExplicitChildIterator childIterator(olderShadow);
for (nsIContent* content = childIterator.GetNextChild();
content;
content = childIterator.GetNextChild()) {
ShadowRoot::RemoveDestInsertionPoint(this, content->DestInsertionPoints());
content->DestInsertionPoints().AppendElement(this);
}
}
// 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
@ -218,7 +262,6 @@ HTMLShadowElement::DistributeAllNodes()
// 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();
@ -269,7 +312,7 @@ HTMLShadowElement::ContentRemoved(nsIDocument* aDocument,
int32_t aIndexInContainer,
nsIContent* aPreviousSibling)
{
// Watch for content removed to the projected shadow (the ShadowRoot that
// Watch for content removed from 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)) {

View File

@ -5,6 +5,9 @@ support-files =
[test_bug900724.html]
[test_content_element.html]
[test_nested_content_element.html]
[test_dest_insertion_points.html]
[test_dest_insertion_points_shadow.html]
[test_fallback_dest_insertion_points.html]
[test_dynamic_content_element_matching.html]
[test_document_register.html]
[test_document_register_base_queue.html]

View File

@ -0,0 +1,73 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=999999
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 999999</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=999999">Mozilla Bug 999999</a>
<p id="display"></p>
<div id="content">
<div id="shadowhost">
</div>
</div>
<pre id="test">
</pre>
<script type="application/javascript">
/** Test for Bug 999999 **/
var host = document.getElementById("shadowhost");
// Test destination insertion points of node distributed to content element.
var shadowRoot = host.createShadowRoot();
shadowRoot.innerHTML = '<div id="innerhost"><content id="innercontent" select=".red"></content></div>';
var innerContent = shadowRoot.getElementById("innercontent");
var span = document.createElement("span");
span.setAttribute("class", "red blue");
is(host.getDestinationInsertionPoints().length, 0, "Destination insertion points should be empty when not being distributed.");
host.appendChild(span);
is(span.getDestinationInsertionPoints().length, 1, "Destination insertion points should only contain a single content insertion point.");
is(span.getDestinationInsertionPoints()[0], innerContent, "Content element should contain destination insertion point.");
// Test destination insertion points of redistributed node.
var innerHost = shadowRoot.getElementById("innerhost");
var innerShadowRoot = innerHost.createShadowRoot();
innerShadowRoot.innerHTML = '<content id="innerinnercontent" select=".blue"></content>';
var innerInnerContent = innerShadowRoot.getElementById("innerinnercontent");
is(span.getDestinationInsertionPoints().length, 2, "Redistributed node should have 2 destination insertion points.");
is(span.getDestinationInsertionPoints()[1], innerInnerContent, "Nested content insertion point should be in list of destination insertion points.");
// Test destination insertion points after removing reprojection onto second content element.
span.setAttribute("class", "red");
is(span.getDestinationInsertionPoints().length, 1, "Destination insertion points should only contain 1 insertion point after removing reprojection.");
is(span.getDestinationInsertionPoints()[0], innerContent, "First content element should be only insertion point after removing reprojection.");
// Test destination insertion points after removing the projected content from the host.
host.removeChild(span);
is(span.getDestinationInsertionPoints().length, 0, "Destination insertion points should be empty after being removed from the shadow host.");
// Test destination insertion points of distributed content after removing insertion point.
var div = document.createElement("div");
div.setAttribute("class", "red blue");
host.appendChild(div);
is(div.getDestinationInsertionPoints().length, 2, "Div should be distributed into 2 insertion points.");
innerShadowRoot.removeChild(innerInnerContent);
is(div.getDestinationInsertionPoints().length, 1, "Div should be distributed into insertion point in one ShadowRoot.");
is(div.getDestinationInsertionPoints()[0], innerContent, "Destination insertion points should only contain content insertion point in first ShadowRoot.");
</script>
</body>
</html>

View File

@ -0,0 +1,68 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=999999
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 999999</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=999999">Mozilla Bug 999999</a>
<p id="display"></p>
<div id="content">
<div id="shadowhost"></div>
</div>
<pre id="test">
</pre>
<script type="application/javascript">
/** Test for Bug 999999 **/
var host = document.getElementById("shadowhost");
// Test destination insertion points of node distributed to shadow element.
var olderShadowRoot = host.createShadowRoot();
var youngerShadowRoot = host.createShadowRoot();
var shadowElem = document.createElement("shadow");
youngerShadowRoot.appendChild(shadowElem);
var span = document.createElement("span");
olderShadowRoot.appendChild(span);
is(span.getDestinationInsertionPoints().length, 1, "Child of ShadowRoot should be distributed to shadow insertion point.");
is(span.getDestinationInsertionPoints()[0], shadowElem, "Shadow element should be in destination insertion point list.");
// Test destination insertion points of node removed from tree.
olderShadowRoot.removeChild(span);
is(span.getDestinationInsertionPoints().length, 0, "Node removed from tree should no longer be distributed.");
// Test destination insertion points of fallback content being reprojected into a shadow element.
var content = document.createElement("content");
var fallback = document.createElement("span");
content.appendChild(fallback);
olderShadowRoot.appendChild(content);
is(fallback.getDestinationInsertionPoints().length, 2, "The fallback content should have 2 destination insertion points, the parent content and the shadow element to which it is reprojected.");
is(fallback.getDestinationInsertionPoints()[0], content, "First destination of the fallback content should be the parent content element.");
is(fallback.getDestinationInsertionPoints()[1], shadowElem, "Second destination of the fallback content should be the shadow element to which the element is reprojected.");
// Test destination insertion points of fallback content being removed from tree.
content.removeChild(fallback);
is(fallback.getDestinationInsertionPoints().length, 0, "The content should no longer be distributed to any nodes because it is no longer fallback content.");
// Test destination insertion points of distributed content after removing shadow insertion point.
var div = document.createElement("div");
olderShadowRoot.appendChild(div);
is(div.getDestinationInsertionPoints().length, 1, "Children in older shadow root should be distributed to shadow insertion point.");
is(div.getDestinationInsertionPoints()[0], shadowElem, "Destination insertion point should include shadow element.");
youngerShadowRoot.removeChild(shadowElem);
is(div.getDestinationInsertionPoints().length, 0, "Destination insertion points should be empty after removing shadow element.");
</script>
</body>
</html>

View File

@ -0,0 +1,71 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=999999
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 999999</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=999999">Mozilla Bug 999999</a>
<p id="display"></p>
<div id="content">
<div id="shadowhost"></div>
</div>
<pre id="test">
</pre>
<script type="application/javascript">
/** Test for Bug 999999 **/
var host = document.getElementById("shadowhost");
// Test destination insertion points of node distributed to content element.
var shadowRoot = host.createShadowRoot();
shadowRoot.innerHTML = '<div id="innerhost"><content id="innercontent"></content></div>';
var fallback = document.createElement("span");
var innerContent = shadowRoot.getElementById("innercontent");
innerContent.appendChild(fallback);
is(fallback.getDestinationInsertionPoints().length, 1, "Active fallback content should be distributed to insertion point.");
is(fallback.getDestinationInsertionPoints()[0], innerContent, "Insertion point should be in list of destination insertion points.");
// Test destination insertion points of reprojected fallback content.
var innerHost = shadowRoot.getElementById("innerhost");
var innerShadowRoot = innerHost.createShadowRoot();
innerShadowRoot.innerHTML = '<content id="innerinnercontent"></content>';
var innerInnerContent = innerShadowRoot.getElementById("innerinnercontent");
is(fallback.getDestinationInsertionPoints().length, 2, "Fallback content should have been distributed into parent and reprojected into another insertion point.");
is(fallback.getDestinationInsertionPoints()[1], innerInnerContent, "Destination insertion points should contain content element to which the node was reprojected.");
// Test destination insertion points of fallback content that was dropped due to content element matching a node in the host.
var span = document.createElement("span");
host.appendChild(span);
is(fallback.getDestinationInsertionPoints().length, 0, "After dropping insertion points, fallback content should not have any nodes in destination insertion points list.");
// Test destination insertion points of fallback content after reactivating by dropping matched content on host.
host.removeChild(span);
is(fallback.getDestinationInsertionPoints().length, 2, "Fallback content should have 2 destination insertion points after being reactivated.");
is(fallback.getDestinationInsertionPoints()[0], innerContent, "First destination insertion point should be the parent content");
is(fallback.getDestinationInsertionPoints()[1], innerInnerContent, "Second destination insertion point should be the content to which the node is reprojected.");
// Test destination insertion points of fallback content after removed from the tree.
innerContent.removeChild(fallback);
is(fallback.getDestinationInsertionPoints().length, 0, "Fallback content is no longer fallback content, destination insertion points should be empty.");
// Test destination insertion points of child of non-insertion point content element.
var notInsertionPointContent = document.createElement("content");
var notFallback = document.createElement("span");
notInsertionPointContent.appendChild(notFallback);
is(notFallback.getDestinationInsertionPoints().length, 0, "Child of non-insertion point content should not be distributed to any nodes.");
</script>
</body>
</html>

View File

@ -201,11 +201,13 @@ partial interface Element {
NodeList querySelectorAll(DOMString selectors);
};
// https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#shadow-root-object
// http://w3c.github.io/webcomponents/spec/shadow/#extensions-to-element-interface
partial interface Element {
[Throws,Pref="dom.webcomponents.enabled"]
ShadowRoot createShadowRoot();
[Pref="dom.webcomponents.enabled"]
NodeList getDestinationInsertionPoints();
[Pref="dom.webcomponents.enabled"]
readonly attribute ShadowRoot? shadowRoot;
};