summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--dom/base/FragmentOrElement.cpp4
-rw-r--r--dom/base/FragmentOrElement.h6
-rw-r--r--dom/base/nsContentList.cpp143
-rw-r--r--dom/base/nsContentList.h40
-rw-r--r--dom/base/nsGkAtomList.h1
-rw-r--r--dom/base/test/test_bug1375050.html33
-rw-r--r--dom/html/HTMLInputElement.cpp10
-rw-r--r--dom/html/HTMLInputElement.h2
-rw-r--r--dom/html/HTMLLabelElement.cpp23
-rw-r--r--dom/html/nsGenericHTMLElement.cpp40
-rw-r--r--dom/html/nsGenericHTMLElement.h6
-rw-r--r--dom/html/test/forms/test_button_attributes_reflection.html9
-rw-r--r--dom/webidl/HTMLButtonElement.webidl3
-rw-r--r--dom/webidl/HTMLInputElement.webidl2
-rw-r--r--dom/webidl/HTMLMeterElement.webidl5
-rw-r--r--dom/webidl/HTMLOutputElement.webidl3
-rw-r--r--dom/webidl/HTMLProgressElement.webidl6
-rw-r--r--dom/webidl/HTMLSelectElement.webidl2
-rw-r--r--dom/webidl/HTMLTextAreaElement.webidl2
-rw-r--r--testing/web-platform/meta/html/semantics/forms/the-label-element/label-attributes.html.ini17
-rw-r--r--testing/web-platform/meta/html/semantics/forms/the-label-element/labelable-elements.html.ini24
-rw-r--r--testing/web-platform/meta/old-tests/submission/Infraware/Forms/contents/Forms/button_labels.html.ini5
-rw-r--r--testing/web-platform/meta/old-tests/submission/Infraware/Forms/contents/Forms/input_labels.html.ini5
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-label-element/iframe-label-attributes.html8
-rw-r--r--testing/web-platform/tests/html/semantics/forms/the-label-element/label-attributes.html212
25 files changed, 516 insertions, 95 deletions
diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp
index 293177ce7..3112515ff 100644
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -574,6 +574,9 @@ FragmentOrElement::nsDOMSlots::Traverse(nsCycleCollectionTraversalCallback &cb,
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mChildrenList");
cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIDOMNodeList*, mChildrenList));
+ NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mLabelsList");
+ cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIDOMNodeList*, mLabelsList));
+
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mClassList");
cb.NoteXPCOMChild(mClassList.get());
@@ -602,6 +605,7 @@ FragmentOrElement::nsDOMSlots::Unlink(bool aIsXUL)
mShadowRoot = nullptr;
mContainingShadow = nullptr;
mChildrenList = nullptr;
+ mLabelsList = nullptr;
mCustomElementData = nullptr;
mClassList = nullptr;
}
diff --git a/dom/base/FragmentOrElement.h b/dom/base/FragmentOrElement.h
index 3cb5575fe..1cd8033bb 100644
--- a/dom/base/FragmentOrElement.h
+++ b/dom/base/FragmentOrElement.h
@@ -24,6 +24,7 @@
class ContentUnbinder;
class nsContentList;
+class nsLabelsNodeList;
class nsDOMAttributeMap;
class nsDOMTokenList;
class nsIControllers;
@@ -313,6 +314,11 @@ public:
*/
RefPtr<nsDOMTokenList> mClassList;
+ /*
+ * An object implementing the .labels property for this element.
+ */
+ RefPtr<nsLabelsNodeList> mLabelsList;
+
/**
* ShadowRoot bound to the element.
*/
diff --git a/dom/base/nsContentList.cpp b/dom/base/nsContentList.cpp
index 09e949009..43e65777d 100644
--- a/dom/base/nsContentList.cpp
+++ b/dom/base/nsContentList.cpp
@@ -254,19 +254,6 @@ const nsCacheableFuncStringContentList::ContentListType
nsCacheableFuncStringHTMLCollection::sType = nsCacheableFuncStringContentList::eHTMLCollection;
#endif
-JSObject*
-nsCacheableFuncStringNodeList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
-{
- return NodeListBinding::Wrap(cx, this, aGivenProto);
-}
-
-
-JSObject*
-nsCacheableFuncStringHTMLCollection::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
-{
- return HTMLCollectionBinding::Wrap(cx, this, aGivenProto);
-}
-
// Hashtable for storing nsCacheableFuncStringContentList
static PLDHashTable* gFuncStringContentListHashTable;
@@ -379,6 +366,7 @@ NS_GetFuncStringHTMLCollection(nsINode* aRootNode,
aString);
}
+//-----------------------------------------------------
// nsContentList implementation
nsContentList::nsContentList(nsINode* aRootNode,
@@ -660,7 +648,7 @@ nsContentList::AttributeChanged(nsIDocument *aDocument, Element* aElement,
const nsAttrValue* aOldValue)
{
NS_PRECONDITION(aElement, "Must have a content node to work with");
-
+
if (!mFunc || !mFuncMayDependOnAttr || mState == LIST_DIRTY ||
!MayContainRelevantNodes(aElement->GetParentNode()) ||
!nsContentUtils::IsInSameAnonymousTree(mRootNode, aElement)) {
@@ -806,7 +794,7 @@ nsContentList::ContentInserted(nsIDocument *aDocument,
ASSERT_IN_SYNC;
}
-
+
void
nsContentList::ContentRemoved(nsIDocument *aDocument,
nsIContent* aContainer,
@@ -1075,3 +1063,128 @@ nsContentList::AssertInSync()
NS_ASSERTION(cnt == mElements.Length(), "Too few elements");
}
#endif
+
+//-----------------------------------------------------
+// nsCacheableFuncStringNodeList
+
+JSObject*
+nsCacheableFuncStringNodeList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return NodeListBinding::Wrap(cx, this, aGivenProto);
+}
+
+//-----------------------------------------------------
+// nsCacheableFuncStringHTMLCollection
+
+JSObject*
+nsCacheableFuncStringHTMLCollection::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return HTMLCollectionBinding::Wrap(cx, this, aGivenProto);
+}
+
+//-----------------------------------------------------
+// nsLabelsNodeList
+
+JSObject*
+nsLabelsNodeList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
+{
+ return NodeListBinding::Wrap(cx, this, aGivenProto);
+}
+
+void
+nsLabelsNodeList::AttributeChanged(nsIDocument* aDocument, Element* aElement,
+ int32_t aNameSpaceID, nsIAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue)
+{
+ MOZ_ASSERT(aElement, "Must have a content node to work with");
+ if (mState == LIST_DIRTY ||
+ !nsContentUtils::IsInSameAnonymousTree(mRootNode, aElement)) {
+ return;
+ }
+
+ // We need to handle input type changes to or from "hidden".
+ if (aElement->IsHTMLElement(nsGkAtoms::input) &&
+ aAttribute == nsGkAtoms::type && aNameSpaceID == kNameSpaceID_None) {
+ SetDirty();
+ return;
+ }
+}
+
+void
+nsLabelsNodeList::ContentAppended(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aFirstNewContent,
+ int32_t aNewIndexInContainer)
+{
+ // If a labelable element is moved to outside or inside of
+ // nested associated labels, we're gonna have to modify
+ // the content list.
+ if (mState != LIST_DIRTY ||
+ nsContentUtils::IsInSameAnonymousTree(mRootNode, aContainer)) {
+ SetDirty();
+ return;
+ }
+}
+
+void
+nsLabelsNodeList::ContentInserted(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer)
+{
+ // If a labelable element is moved to outside or inside of
+ // nested associated labels, we're gonna have to modify
+ // the content list.
+ if (mState != LIST_DIRTY ||
+ nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild)) {
+ SetDirty();
+ return;
+ }
+}
+
+void
+nsLabelsNodeList::ContentRemoved(nsIDocument* aDocument,
+ nsIContent* aContainer,
+ nsIContent* aChild,
+ int32_t aIndexInContainer,
+ nsIContent* aPreviousSibling)
+{
+ // If a labelable element is removed, we're gonna have to clean
+ // the content list.
+ if (mState != LIST_DIRTY ||
+ nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild)) {
+ SetDirty();
+ return;
+ }
+}
+
+void
+nsLabelsNodeList::MaybeResetRoot(nsINode* aRootNode)
+{
+ MOZ_ASSERT(aRootNode, "Must have root");
+ if (mRootNode == aRootNode) {
+ return;
+ }
+
+ if (mRootNode) {
+ mRootNode->RemoveMutationObserver(this);
+ }
+ mRootNode = aRootNode;
+ mRootNode->AddMutationObserver(this);
+ SetDirty();
+}
+
+void
+nsLabelsNodeList::PopulateSelf(uint32_t aNeededLength)
+{
+ MOZ_ASSERT(mRootNode, "Must have root");
+
+ // Start searching at the root.
+ nsINode* cur = mRootNode;
+ if (mElements.IsEmpty() && cur->IsElement() && Match(cur->AsElement())) {
+ mElements.AppendElement(cur->AsElement());
+ }
+
+ nsContentList::PopulateSelf(aNeededLength);
+}
diff --git a/dom/base/nsContentList.h b/dom/base/nsContentList.h
index 3878074b2..83d27da95 100644
--- a/dom/base/nsContentList.h
+++ b/dom/base/nsContentList.h
@@ -371,9 +371,9 @@ protected:
* traversed the whole document (or both).
*
* @param aNeededLength the length the list should have when we are
- * done (unless it exhausts the document)
+ * done (unless it exhausts the document)
*/
- void PopulateSelf(uint32_t aNeededLength);
+ virtual void PopulateSelf(uint32_t aNeededLength);
/**
* @param aContainer a content node which must be a descendant of
@@ -584,4 +584,40 @@ public:
#endif
};
+class nsLabelsNodeList final : public nsContentList
+{
+public:
+ nsLabelsNodeList(nsINode* aRootNode,
+ nsContentListMatchFunc aFunc,
+ nsContentListDestroyFunc aDestroyFunc,
+ void* aData)
+ : nsContentList(aRootNode, aFunc, aDestroyFunc, aData)
+ {
+ }
+
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ virtual JSObject* WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) override;
+
+ /**
+ * Reset root, mutation observer, and clear content list
+ * if the root has been changed.
+ *
+ * @param aRootNode The node under which to limit our search.
+ */
+ void MaybeResetRoot(nsINode* aRootNode);
+
+private:
+ /**
+ * Start searching at the last one if we already have nodes, otherwise
+ * start searching at the root.
+ *
+ * @param aNeededLength The list of length should have when we are
+ * done (unless it exhausts the document).
+ */
+ void PopulateSelf(uint32_t aNeededLength) override;
+};
#endif // nsContentList_h___
diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h
index 91427fabb..e4ae7ede8 100644
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -526,6 +526,7 @@ GK_ATOM(keytext, "keytext")
GK_ATOM(keyup, "keyup")
GK_ATOM(kind, "kind")
GK_ATOM(label, "label")
+GK_ATOM(labels, "labels")
GK_ATOM(lang, "lang")
GK_ATOM(language, "language")
GK_ATOM(last, "last")
diff --git a/dom/base/test/test_bug1375050.html b/dom/base/test/test_bug1375050.html
new file mode 100644
index 000000000..b91b859d0
--- /dev/null
+++ b/dom/base/test/test_bug1375050.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1375050
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1375050</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ try { o1 = document.createElement('input'); } catch(e) { console.log(e); };
+ try { o2 = document.createElement('col'); } catch(e) { console.log(e); };
+ try { o4 = document.createRange(); } catch(e) { console.log(e); };
+ try { document.documentElement.appendChild(o1); } catch(e) { console.log(e); };
+ try { for (let p in o1) { let x = o1[p] }; } catch(e) { console.log(e); };
+ try { o4.selectNode(o1); } catch(e) { console.log(e); };
+ try { o6 = document.createComment(" x"); } catch(e) { console.log(e); }
+ try { o4.surroundContents(o6); } catch(e) { console.log(e); }
+ try { o7 = document.implementation.createDocument('', '', null).adoptNode(o1); } catch(e) { console.log(e);};
+ try { o2.appendChild(o1); } catch(e) { console.log(e); };
+ ok(true, "Didn't crash.");
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1375050">Mozilla Bug 1375050</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp
index 78f74ae0c..d46eccdbc 100644
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -8800,6 +8800,16 @@ HTMLInputElement::GetWebkitEntries(nsTArray<RefPtr<FileSystemEntry>>& aSequence)
aSequence.AppendElements(mEntries);
}
+already_AddRefed<nsINodeList>
+HTMLInputElement::GetLabels()
+{
+ if (!IsLabelable()) {
+ return nullptr;
+ }
+
+ return nsGenericHTMLElement::Labels();
+}
+
} // namespace dom
} // namespace mozilla
diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h
index e5d670e08..9ca876aee 100644
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -704,6 +704,8 @@ public:
// XPCOM GetCustomVisibility() is OK
+ already_AddRefed<nsINodeList> GetLabels();
+
// XPCOM Select() is OK
Nullable<int32_t> GetSelectionStart(ErrorResult& aRv);
diff --git a/dom/html/HTMLLabelElement.cpp b/dom/html/HTMLLabelElement.cpp
index c1d22b0a6..d1c037336 100644
--- a/dom/html/HTMLLabelElement.cpp
+++ b/dom/html/HTMLLabelElement.cpp
@@ -14,6 +14,7 @@
#include "nsFocusManager.h"
#include "nsIDOMMouseEvent.h"
#include "nsQueryObject.h"
+#include "mozilla/dom/ShadowRoot.h"
// construction, destruction
@@ -268,17 +269,23 @@ HTMLLabelElement::GetLabeledElement() const
return GetFirstLabelableDescendant();
}
- // We have a @for. The id has to be linked to an element in the same document
+ // We have a @for. The id has to be linked to an element in the same tree
// and this element should be a labelable form control.
- //XXXsmaug It is unclear how this should work in case the element is in
- // Shadow DOM.
- // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=26365.
- nsIDocument* doc = GetUncomposedDoc();
- if (!doc) {
- return nullptr;
+ nsINode* root = SubtreeRoot();
+ ShadowRoot* shadow = ShadowRoot::FromNode(root);
+ Element* element = nullptr;
+
+ if (shadow) {
+ element = shadow->GetElementById(elementId);
+ } else {
+ nsIDocument* doc = GetUncomposedDoc();
+ if (doc) {
+ element = doc->GetElementById(elementId);
+ } else {
+ element = nsContentUtils::MatchElementId(root->AsContent(), elementId);
+ }
}
- Element* element = doc->GetElementById(elementId);
if (element && element->IsLabelable()) {
return static_cast<nsGenericHTMLElement*>(element);
}
diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp
index d75001a83..2f890325a 100644
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -108,6 +108,7 @@
#include "mozilla/StyleSetHandle.h"
#include "mozilla/StyleSetHandleInlines.h"
#include "ReferrerPolicy.h"
+#include "mozilla/dom/HTMLLabelElement.h"
using namespace mozilla;
using namespace mozilla::dom;
@@ -493,6 +494,14 @@ nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
}
}
+ // We need to consider a labels element is moved to another subtree
+ // with different root, it needs to update labels list and its root
+ // as well.
+ nsDOMSlots* slots = GetExistingDOMSlots();
+ if (slots && slots->mLabelsList) {
+ slots->mLabelsList->MaybeResetRoot(SubtreeRoot());
+ }
+
return rv;
}
@@ -513,6 +522,13 @@ nsGenericHTMLElement::UnbindFromTree(bool aDeep, bool aNullParent)
}
}
+ // We need to consider a labels element is removed from tree,
+ // it needs to update labels list and its root as well.
+ nsDOMSlots* slots = GetExistingDOMSlots();
+ if (slots && slots->mLabelsList) {
+ slots->mLabelsList->MaybeResetRoot(SubtreeRoot());
+ }
+
nsStyledElement::UnbindFromTree(aDeep, aNullParent);
}
@@ -1701,6 +1717,30 @@ nsGenericHTMLElement::IsLabelable() const
return IsAnyOfHTMLElements(nsGkAtoms::progress, nsGkAtoms::meter);
}
+/* static */ bool
+nsGenericHTMLElement::MatchLabelsElement(Element* aElement, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData)
+{
+ HTMLLabelElement* element = HTMLLabelElement::FromContent(aElement);
+ return element && element->GetControl() == aData;
+}
+
+already_AddRefed<nsINodeList>
+nsGenericHTMLElement::Labels()
+{
+ MOZ_ASSERT(IsLabelable(),
+ "Labels() only allow labelable elements to use it.");
+ nsDOMSlots* slots = DOMSlots();
+
+ if (!slots->mLabelsList) {
+ slots->mLabelsList = new nsLabelsNodeList(SubtreeRoot(), MatchLabelsElement,
+ nullptr, this);
+ }
+
+ RefPtr<nsLabelsNodeList> labels = slots->mLabelsList;
+ return labels.forget();
+}
+
bool
nsGenericHTMLElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const
{
diff --git a/dom/html/nsGenericHTMLElement.h b/dom/html/nsGenericHTMLElement.h
index 3cca41c3d..0635c27e1 100644
--- a/dom/html/nsGenericHTMLElement.h
+++ b/dom/html/nsGenericHTMLElement.h
@@ -834,6 +834,12 @@ public:
}
virtual bool IsLabelable() const override;
+
+ static bool MatchLabelsElement(Element* aElement, int32_t aNamespaceID,
+ nsIAtom* aAtom, void* aData);
+
+ already_AddRefed<nsINodeList> Labels();
+
virtual bool IsInteractiveHTMLContent(bool aIgnoreTabindex) const override;
static bool TouchEventsEnabled(JSContext* /* unused */, JSObject* /* unused */);
diff --git a/dom/html/test/forms/test_button_attributes_reflection.html b/dom/html/test/forms/test_button_attributes_reflection.html
index 26858e939..4e702b3ac 100644
--- a/dom/html/test/forms/test_button_attributes_reflection.html
+++ b/dom/html/test/forms/test_button_attributes_reflection.html
@@ -128,9 +128,12 @@ is(typeof(document.createElement("button").setCustomValidity), "function",
"button.setCustomValidity should be a function");
// .labels
-todo("labels" in document.createElement("button"),
- "button.labels isn't implemented yet");
-
+ok("labels" in document.createElement("button"),
+ "button.labels should be an IDL attribute of the button element");
+is(typeof(document.createElement("button").labels), "object",
+ "button.labels should be an object");
+ok(document.createElement("button").labels instanceof NodeList,
+ "button.labels sohuld be an instance of NodeList");
</script>
</pre>
</body>
diff --git a/dom/webidl/HTMLButtonElement.webidl b/dom/webidl/HTMLButtonElement.webidl
index c50e09ae0..579efa39c 100644
--- a/dom/webidl/HTMLButtonElement.webidl
+++ b/dom/webidl/HTMLButtonElement.webidl
@@ -44,6 +44,5 @@ interface HTMLButtonElement : HTMLElement {
boolean reportValidity();
void setCustomValidity(DOMString error);
-// Not yet implemented:
-// readonly attribute NodeList labels;
+ readonly attribute NodeList labels;
};
diff --git a/dom/webidl/HTMLInputElement.webidl b/dom/webidl/HTMLInputElement.webidl
index d3d537f84..050d19510 100644
--- a/dom/webidl/HTMLInputElement.webidl
+++ b/dom/webidl/HTMLInputElement.webidl
@@ -111,7 +111,7 @@ interface HTMLInputElement : HTMLElement {
boolean reportValidity();
void setCustomValidity(DOMString error);
- // Bug 850365 readonly attribute NodeList labels;
+ readonly attribute NodeList? labels;
void select();
diff --git a/dom/webidl/HTMLMeterElement.webidl b/dom/webidl/HTMLMeterElement.webidl
index 1f80764e9..104e00353 100644
--- a/dom/webidl/HTMLMeterElement.webidl
+++ b/dom/webidl/HTMLMeterElement.webidl
@@ -26,8 +26,5 @@ interface HTMLMeterElement : HTMLElement {
[SetterThrows]
attribute double optimum;
- /**
- * The labels attribute will be done with bug 556743.
- */
- //readonly attribute NodeList labels;
+ readonly attribute NodeList labels;
};
diff --git a/dom/webidl/HTMLOutputElement.webidl b/dom/webidl/HTMLOutputElement.webidl
index 05dcf1800..d0e4ecbe6 100644
--- a/dom/webidl/HTMLOutputElement.webidl
+++ b/dom/webidl/HTMLOutputElement.webidl
@@ -33,6 +33,5 @@ interface HTMLOutputElement : HTMLElement {
boolean reportValidity();
void setCustomValidity(DOMString error);
-// Not yet implemented (bug 556743).
-// readonly attribute NodeList labels;
+ readonly attribute NodeList labels;
};
diff --git a/dom/webidl/HTMLProgressElement.webidl b/dom/webidl/HTMLProgressElement.webidl
index 3d1000d22..028728e22 100644
--- a/dom/webidl/HTMLProgressElement.webidl
+++ b/dom/webidl/HTMLProgressElement.webidl
@@ -17,9 +17,5 @@ interface HTMLProgressElement : HTMLElement {
[SetterThrows]
attribute double max;
readonly attribute double position;
-
- /**
- * The labels attribute will be done with bug 567740.
- */
- //readonly attribute NodeList labels;
+ readonly attribute NodeList labels;
};
diff --git a/dom/webidl/HTMLSelectElement.webidl b/dom/webidl/HTMLSelectElement.webidl
index 74fc7b2cf..b18ca3634 100644
--- a/dom/webidl/HTMLSelectElement.webidl
+++ b/dom/webidl/HTMLSelectElement.webidl
@@ -53,7 +53,7 @@ interface HTMLSelectElement : HTMLElement {
boolean reportValidity();
void setCustomValidity(DOMString error);
-// NYI: readonly attribute NodeList labels;
+ readonly attribute NodeList labels;
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=20720
void remove();
diff --git a/dom/webidl/HTMLTextAreaElement.webidl b/dom/webidl/HTMLTextAreaElement.webidl
index b1005ed42..4df687a0b 100644
--- a/dom/webidl/HTMLTextAreaElement.webidl
+++ b/dom/webidl/HTMLTextAreaElement.webidl
@@ -57,7 +57,7 @@ interface HTMLTextAreaElement : HTMLElement {
boolean reportValidity();
void setCustomValidity(DOMString error);
- // readonly attribute NodeList labels;
+ readonly attribute NodeList labels;
void select();
[Throws]
diff --git a/testing/web-platform/meta/html/semantics/forms/the-label-element/label-attributes.html.ini b/testing/web-platform/meta/html/semantics/forms/the-label-element/label-attributes.html.ini
deleted file mode 100644
index e1a2e38b5..000000000
--- a/testing/web-platform/meta/html/semantics/forms/the-label-element/label-attributes.html.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-[label-attributes.html]
- type: testharness
- [A non-control follows by a control with same ID.]
- expected: FAIL
-
- [A form control has multiple labels.]
- expected: FAIL
-
- [A form control has no label 1.]
- expected: FAIL
-
- [A form control has no label 2.]
- expected: FAIL
-
- [A form control has an implicit label.]
- expected: FAIL
-
diff --git a/testing/web-platform/meta/html/semantics/forms/the-label-element/labelable-elements.html.ini b/testing/web-platform/meta/html/semantics/forms/the-label-element/labelable-elements.html.ini
index bfa61edb7..363324f2e 100644
--- a/testing/web-platform/meta/html/semantics/forms/the-label-element/labelable-elements.html.ini
+++ b/testing/web-platform/meta/html/semantics/forms/the-label-element/labelable-elements.html.ini
@@ -1,29 +1,7 @@
[labelable-elements.html]
type: testharness
- [Check if the output element can access 'labels']
- expected: FAIL
-
- [Check if the progress element can access 'labels']
- expected: FAIL
-
- [Check if the select element can access 'labels']
- expected: FAIL
-
- [Check if the textarea element can access 'labels']
- expected: FAIL
-
- [Check if the button element can access 'labels']
- expected: FAIL
-
- [Check if the hidden input element can access 'labels']
- expected: FAIL
-
- [Check if the input element in radio state can access 'labels']
+ [Check if the keygen element is not a labelable element]
expected: FAIL
[Check if the keygen element can access 'labels']
expected: FAIL
-
- [Check if the meter element can access 'labels']
- expected: FAIL
-
diff --git a/testing/web-platform/meta/old-tests/submission/Infraware/Forms/contents/Forms/button_labels.html.ini b/testing/web-platform/meta/old-tests/submission/Infraware/Forms/contents/Forms/button_labels.html.ini
deleted file mode 100644
index e4a30320e..000000000
--- a/testing/web-platform/meta/old-tests/submission/Infraware/Forms/contents/Forms/button_labels.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[button_labels.html]
- type: testharness
- [Forms]
- expected: FAIL
-
diff --git a/testing/web-platform/meta/old-tests/submission/Infraware/Forms/contents/Forms/input_labels.html.ini b/testing/web-platform/meta/old-tests/submission/Infraware/Forms/contents/Forms/input_labels.html.ini
deleted file mode 100644
index da0fb96d7..000000000
--- a/testing/web-platform/meta/old-tests/submission/Infraware/Forms/contents/Forms/input_labels.html.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[input_labels.html]
- type: testharness
- [Forms]
- expected: FAIL
-
diff --git a/testing/web-platform/tests/html/semantics/forms/the-label-element/iframe-label-attributes.html b/testing/web-platform/tests/html/semantics/forms/the-label-element/iframe-label-attributes.html
new file mode 100644
index 000000000..56b52c951
--- /dev/null
+++ b/testing/web-platform/tests/html/semantics/forms/the-label-element/iframe-label-attributes.html
@@ -0,0 +1,8 @@
+<html>
+ <body>
+ <label>
+ <div id="div1"></div>
+ </label>
+ <label for="test13"></label>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/html/semantics/forms/the-label-element/label-attributes.html b/testing/web-platform/tests/html/semantics/forms/the-label-element/label-attributes.html
index 826533e0c..2910f2c01 100644
--- a/testing/web-platform/tests/html/semantics/forms/the-label-element/label-attributes.html
+++ b/testing/web-platform/tests/html/semantics/forms/the-label-element/label-attributes.html
@@ -32,10 +32,53 @@
<label id="lbl5" for="test7"></label>
<input id="test7">
+
+ <label id="lbl7">
+ <label id="lbl8">
+ <div id="div1">
+ <input id="test8">
+ </div>
+ </label>
+ </label>
+ <div id="div2"></div>
+
+ <label id="lbl9">
+ <label id="lbl10" for="test10">
+ <div id="div3">
+ <input id="test9">
+ </div>
+ </label>
+ </label>
+ <div id="div4"><input id="test10"></div>
+
+ <label id="lbl11">
+ <object id="obj">
+ <input id="test11">
+ <input id="test12">
+ </object>
+ </label>
+ <label id="lbl12" for="test12"><div id="div5"></div></label>
+
+ <label id="lbl13">
+ <p id="p1">
+ <input id="test13">
+ </p>
+ </label>
+
+ <div id="div6">
+ <div id="div7">
+ <label id="lbl14">
+ <label id="lbl15" for="test15">
+ <input id="test14">
+ </label>
+ </label>
+ </div>
+ </div>
+ <input id="test15">
</form>
<label id="lbl6" for="test7"></label>
-
+<div id="content" style="display: none">
<script>
//control attribute
@@ -57,6 +100,7 @@
}, "A label element not in a document can not label any element in the document.");
test(function () {
+ var labels = document.getElementById("test3").labels;
assert_equals(document.getElementById("lbl1").control, document.getElementById("test3"),
"The first labelable descendant of a label element should be its labeled control.");
@@ -64,6 +108,10 @@
document.getElementById("lbl1").insertBefore(input, document.getElementById("test2"));
assert_equals(document.getElementById("lbl1").control, input,
"The first labelable descendant of a label element in tree order should be its labeled control.");
+ assert_equals(input.labels.length, 1,
+ "The form control has an ancestor with no explicit associated label, and is the first labelable descendant.");
+ assert_equals(labels.length, 0,
+ "The number of labels should be 0 if it's not the first labelable descendant of a label element.");
input.remove();
}, "The labeled control for a label element that has no 'for' attribute is the first labelable element which is a descendant of that label element.");
@@ -101,6 +149,168 @@
}, "A form control has multiple labels.");
test(function () {
+ var labels = document.getElementById("test8").labels;
+ assert_true(labels instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(labels.length, 2,
+ "The form control has two ancestors with no explicit associated label, and is the first labelable descendant.");
+ assert_array_equals(labels, [document.getElementById("lbl7"), document.getElementById("lbl8")],
+ "The labels for a form control should be returned in tree order.");
+
+ document.getElementById('div2').insertBefore(document.getElementById('div1'), document.getElementById('div2').firstChild);
+ assert_equals(labels.length, 0,
+ "The number of labels should be 0 after the labelable element is moved to outside of nested associated labels.");
+ }, "A labelable element is moved to outside of nested associated labels.");
+
+ test(function () {
+ var labels1 = document.getElementById("test9").labels;
+ var labels2 = document.getElementById("test10").labels;
+ assert_true(labels1 instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_true(labels2 instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(labels1.length, 1,
+ "The form control has an ancestor with no explicit associated label, and is the first labelable descendant.");
+ assert_equals(labels2.length, 1,
+ "The number of labels associated with a form control should be the number of label elements for which it is a labeled control.");
+ assert_array_equals(labels1, [document.getElementById("lbl9")],
+ "The labels for a form control should be returned in tree order.");
+ assert_array_equals(labels2, [document.getElementById("lbl10")],
+ "The labels for a form control should be returned in tree order.");
+ document.getElementById('div3').insertBefore(document.getElementById('div4'), document.getElementById('div3').firstChild);
+ assert_equals(labels1.length, 0,
+ "The number of labels should be 0 if it's not the first labelable descendant of a label element.");
+ assert_equals(labels2.length, 2,
+ "The form control has an ancestor with an explicit associated label, and is the first labelable descendant.");
+ }, "A labelable element is moved to inside of nested associated labels.");
+
+ test(function () {
+ var labels1 = document.getElementById("test11").labels;
+ var labels2 = document.getElementById("test12").labels;
+ assert_true(labels1 instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_true(labels2 instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(labels1.length, 1,
+ "The form control has an ancestor with no explicit associated label, and it is the first labelable descendant.");
+ assert_equals(labels2.length, 1,
+ "The number of labels should be 1 since there is a label with a 'for' attribute associated with this labelable element.");
+ assert_array_equals(labels1, [document.getElementById("lbl11")],
+ "The labels for a form control should be returned in tree order.");
+ assert_array_equals(labels2, [document.getElementById("lbl12")],
+ "The labels for a form control should be returned in tree order.");
+ document.getElementById('div5').appendChild(document.getElementById('obj'));
+ assert_equals(labels1.length, 0,
+ "The number of labels should be 0 after the labelable element is moved to outside of associated label.");
+ assert_equals(labels2.length, 1,
+ "The number of labels should be 1 after the labelable element is moved to outside of associated label.");
+ }, "A labelable element which is a descendant of non-labelable element is moved to outside of associated label.");
+
+ async_test(function () {
+ var labels = document.getElementById("test13").labels;
+ assert_true(labels instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(labels.length, 1,
+ "The form control has an ancestor with no explicit associated label, and is the first labelable descendant.");
+ assert_array_equals(labels, [document.getElementById("lbl13")],
+ "The labels for a form control should be returned in tree order.");
+ let iframe = document.createElement('iframe');
+
+ iframe.onload = this.step_func_done(() => {
+ iframe.contentWindow.document.getElementById("div1").appendChild(document.getElementById("p1"));
+ assert_equals(labels.length, 2,
+ "The number of labels should be 2 after the labelable element is moved to iframe.");
+ });
+
+ iframe.setAttribute('src', 'http://web-platform.test:8000/html/semantics/forms/the-label-element/iframe-label-attributes.html');
+ document.body.appendChild(iframe);
+ }, "A labelable element is moved to iframe.");
+
+ test(function () {
+ var labels1 = document.getElementById("test14").labels;
+ var labels2 = document.getElementById("test15").labels;
+ assert_true(labels1 instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(labels1.length, 1,
+ "The form control has an ancestor with no explicit associated label, and is the first labelable descendant.");
+ assert_equals(labels2.length, 1,
+ "The number of labels associated with a form control should be the number of label elements for which it is a labeled control.");
+ assert_array_equals(labels1, [document.getElementById("lbl14")],
+ "The labels for a form control should be returned in tree order.");
+
+ document.getElementById('div6').removeChild(document.getElementById('div7'));
+ assert_equals(labels1.length, 0,
+ "The number of labels should be 0 after the labelable element is removed.");
+ assert_equals(labels2.length, 0,
+ "The number of labels should be 0 since there is no label with a 'for' attribute associated with this labelable element.");
+ }, "A div element which contains labelable element is removed.");
+
+ test(function () {
+ // <label><input id="test16"><label for="test16"></label></label>
+ var label1 = document.createElement('label');
+ label1.innerHTML = "<input id='test16'>";
+ var label2 = document.createElement('label');
+ label2.htmlFor = "test16";
+ label1.appendChild(label2);
+
+ var input = label1.firstChild;
+ var labels = input.labels;
+
+ assert_equals(labels.length, 2,
+ "The number of labels associated with a form control should be the number of label elements for which it is a labeled control.");
+ assert_true(labels instanceof NodeList,
+ "A form control's 'labels' property should be an instance of a NodeList.");
+ assert_equals(label1.control, input, "The first labelable descendant of a label element should be its labeled control.");
+ assert_equals(label2.control, input, "The labeled cotrol should be associated with the control whose ID is equal to the value of the 'for' attribute.");
+ }, "A labelable element not in a document can label element in the same tree.");
+
+ test(function () {
+ var isShadowDOMV0;
+ if ("createShadowRoot" in document.getElementById('content')) {
+ isShadowDOMV0 = true;
+ }
+ var root1;
+ if (isShadowDOMV0) {
+ root1 = document.getElementById('content').createShadowRoot();
+ } else {
+ root1 = document.getElementById('content').attachShadow({mode: 'open'});
+ }
+ assert_true(root1 instanceof DocumentFragment,
+ "ShadowRoot should be an instance of DocumentFragment.");
+ // <label><input id="shadow1"/></label><div id="div1"></div>
+ var label1 = document.createElement('label');
+ var input1 = document.createElement('input');
+ input1.setAttribute("id", "shadow1");
+ label1.appendChild(input1);
+ root1.appendChild(label1);
+
+ var div1 = document.createElement('div');
+ label1.appendChild(div1);
+ // <label for="shadow2"></label><input id="shadow2"/>
+ var root2;
+ if (isShadowDOMV0) {
+ root2 = div1.createShadowRoot();
+ } else {
+ root2 = div1.attachShadow({mode: 'open'});
+ }
+
+ assert_true(root2 instanceof DocumentFragment,
+ "ShadowRoot should be an instance of DocumentFragment.");
+ var label2 = document.createElement('label');
+ label2.setAttribute("for", "shadow2");
+
+ var input2 = document.createElement('input');
+ input2.setAttribute("id", "shadow2");
+ root2.appendChild(label2);
+ root2.appendChild(input2);
+
+ assert_equals(root1.getElementById("shadow1").labels.length, 1,
+ "The form control has an ancestor with no explicit associated label, and it is the first labelable descendant.");
+ assert_equals(root2.getElementById("shadow2").labels.length, 1,
+ "The number of labels should be 1 since there is a label with a 'for' attribute associated with this labelable element.");
+ }, "A labelable element inside the shadow DOM.");
+
+ test(function () {
var labels = document.getElementById("test3").labels;
assert_true(labels instanceof NodeList, "A form control's 'labels' property should be an instance of a NodeList.");
assert_equals(labels.length, 1, "The form control has an ancestor with no explicit associated label, and is the first labelable descendant.");