HTML - implement the labels attribute

This commit is contained in:
janekptacijarabaci 2017-08-18 06:04:15 +02:00 • committed by Roy Tam
commit 5b24f05ce7
25 changed files with 516 additions and 95 deletions

View file

@ -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;
}

View file

@ -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.
*/

View file

@ -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);
}

View file

@ -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___

View file

@ -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")

View file

@ -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>

View file

@ -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

View file

@ -704,6 +704,8 @@ public:
// XPCOM GetCustomVisibility() is OK
already_AddRefed<nsINodeList> GetLabels();
// XPCOM Select() is OK
Nullable<int32_t> GetSelectionStart(ErrorResult& aRv);

View file

@ -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);
}

View file

@ -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
{

View file

@ -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 */);

View file

@ -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>

View file

@ -44,6 +44,5 @@ interface HTMLButtonElement : HTMLElement {
boolean reportValidity();
void setCustomValidity(DOMString error);
// Not yet implemented:
// readonly attribute NodeList labels;
readonly attribute NodeList labels;
};

View file

@ -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();

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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();

View file

@ -57,7 +57,7 @@ interface HTMLTextAreaElement : HTMLElement {
boolean reportValidity();
void setCustomValidity(DOMString error);
// readonly attribute NodeList labels;
readonly attribute NodeList labels;
void select();
[Throws]