Bug 501257 - Implement HTML 5's HTMLElement.classList property, p=sylvain.pasche@gmail.com, r=smaug, sr=sicking

--HG--
extra : rebase_source : cbbd5a5679af57f97122082213f44491431d87e3
This commit is contained in:
Olli Pettay 2009-08-12 11:55:14 +03:00
parent 630f98121a
commit 5575b12b86
21 changed files with 959 additions and 18 deletions

View File

@ -130,6 +130,7 @@ CPPSRCS = \
nsDOMLists.cpp \
nsDOMParser.cpp \
nsDOMSerializer.cpp \
nsDOMTokenList.cpp \
nsDocument.cpp \
nsDocumentEncoder.cpp \
nsDocumentFragment.cpp \

View File

@ -0,0 +1,300 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Sylvain Pasche <sylvain.pasche@gmail.com>
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/*
* Implementation of nsIDOMDOMTokenList specified by HTML5.
*/
#include "nsDOMTokenList.h"
#include "nsAttrValue.h"
#include "nsContentUtils.h"
#include "nsDOMError.h"
#include "nsGenericElement.h"
#include "nsHashSets.h"
nsDOMTokenList::nsDOMTokenList(nsGenericElement *aElement, nsIAtom* aAttrAtom)
: mElement(aElement),
mAttrAtom(aAttrAtom)
{
// We don't add a reference to our element. If it goes away,
// we'll be told to drop our reference
}
nsDOMTokenList::~nsDOMTokenList() { }
NS_INTERFACE_TABLE_HEAD(nsDOMTokenList)
NS_INTERFACE_TABLE1(nsDOMTokenList,
nsIDOMDOMTokenList)
NS_INTERFACE_TABLE_TO_MAP_SEGUE
NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(DOMTokenList)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(nsDOMTokenList)
NS_IMPL_RELEASE(nsDOMTokenList)
void
nsDOMTokenList::DropReference()
{
mElement = nsnull;
}
NS_IMETHODIMP
nsDOMTokenList::GetLength(PRUint32 *aLength)
{
const nsAttrValue* attr = GetParsedAttr();
if (!attr) {
*aLength = 0;
return NS_OK;
}
*aLength = attr->GetAtomCount();
return NS_OK;
}
NS_IMETHODIMP
nsDOMTokenList::Item(PRUint32 aIndex, nsAString& aResult)
{
const nsAttrValue* attr = GetParsedAttr();
if (!attr || aIndex >= static_cast<PRUint32>(attr->GetAtomCount())) {
SetDOMStringToNull(aResult);
return NS_OK;
}
attr->AtomAt(aIndex)->ToString(aResult);
return NS_OK;
}
nsresult
nsDOMTokenList::CheckToken(const nsAString& aStr)
{
if (aStr.IsEmpty()) {
return NS_ERROR_DOM_SYNTAX_ERR;
}
nsAString::const_iterator iter, end;
aStr.BeginReading(iter);
aStr.EndReading(end);
while (iter != end) {
if (nsContentUtils::IsHTMLWhitespace(*iter))
return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
++iter;
}
return NS_OK;
}
PRBool
nsDOMTokenList::ContainsInternal(const nsAttrValue* aAttr,
const nsAString& aToken)
{
NS_ABORT_IF_FALSE(aAttr, "Need an attribute");
nsCOMPtr<nsIAtom> atom = do_GetAtom(aToken);
return aAttr->Contains(atom, eCaseMatters);
}
NS_IMETHODIMP
nsDOMTokenList::Contains(const nsAString& aToken, PRBool* aResult)
{
nsresult rv = CheckToken(aToken);
NS_ENSURE_SUCCESS(rv, rv);
const nsAttrValue* attr = GetParsedAttr();
if (!attr) {
*aResult = PR_FALSE;
return NS_OK;
}
*aResult = ContainsInternal(attr, aToken);
return NS_OK;
}
void
nsDOMTokenList::AddInternal(const nsAttrValue* aAttr,
const nsAString& aToken)
{
nsAutoString resultStr;
if (aAttr) {
aAttr->ToString(resultStr);
}
if (!resultStr.IsEmpty() &&
!nsContentUtils::IsHTMLWhitespace(
resultStr.CharAt(resultStr.Length() - 1))) {
resultStr.Append(NS_LITERAL_STRING(" ") + aToken);
} else {
resultStr.Append(aToken);
}
mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, PR_TRUE);
}
NS_IMETHODIMP
nsDOMTokenList::Add(const nsAString& aToken)
{
nsresult rv = CheckToken(aToken);
NS_ENSURE_SUCCESS(rv, rv);
const nsAttrValue* attr = GetParsedAttr();
if (attr && ContainsInternal(attr, aToken)) {
return NS_OK;
}
AddInternal(attr, aToken);
return NS_OK;
}
void
nsDOMTokenList::RemoveInternal(const nsAttrValue* aAttr,
const nsAString& aToken)
{
NS_ABORT_IF_FALSE(aAttr, "Need an attribute");
nsAutoString input;
aAttr->ToString(input);
nsAString::const_iterator copyStart, tokenStart, iter, end;
input.BeginReading(iter);
input.EndReading(end);
copyStart = iter;
nsAutoString output;
PRBool lastTokenRemoved = PR_FALSE;
while (iter != end) {
// skip whitespace.
while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
++iter;
}
if (iter == end) {
// At this point we're sure the last seen token (if any) wasn't to be
// removed. So the trailing spaces will need to be kept.
NS_ABORT_IF_FALSE(!lastTokenRemoved, "How did this happen?");
output.Append(Substring(copyStart, end));
break;
}
tokenStart = iter;
do {
++iter;
} while (iter != end && !nsContentUtils::IsHTMLWhitespace(*iter));
if (Substring(tokenStart, iter).Equals(aToken)) {
// Skip whitespace after the token, it will be collapsed.
while (iter != end && nsContentUtils::IsHTMLWhitespace(*iter)) {
++iter;
}
copyStart = iter;
lastTokenRemoved = PR_TRUE;
} else {
if (lastTokenRemoved && !output.IsEmpty()) {
NS_ABORT_IF_FALSE(!nsContentUtils::IsHTMLWhitespace(
output.CharAt(output.Length() - 1)), "Invalid last output token");
output.Append(PRUnichar(' '));
}
lastTokenRemoved = PR_FALSE;
output.Append(Substring(copyStart, iter));
copyStart = iter;
}
}
mElement->SetAttr(kNameSpaceID_None, mAttrAtom, output, PR_TRUE);
}
NS_IMETHODIMP
nsDOMTokenList::Remove(const nsAString& aToken)
{
nsresult rv = CheckToken(aToken);
NS_ENSURE_SUCCESS(rv, rv);
const nsAttrValue* attr = GetParsedAttr();
if (!attr) {
return NS_OK;
}
if (!ContainsInternal(attr, aToken)) {
return NS_OK;
}
RemoveInternal(attr, aToken);
return NS_OK;
}
NS_IMETHODIMP
nsDOMTokenList::Toggle(const nsAString& aToken, PRBool* aResult)
{
nsresult rv = CheckToken(aToken);
NS_ENSURE_SUCCESS(rv, rv);
const nsAttrValue* attr = GetParsedAttr();
if (attr && ContainsInternal(attr, aToken)) {
RemoveInternal(attr, aToken);
*aResult = PR_FALSE;
} else {
AddInternal(attr, aToken);
*aResult = PR_TRUE;
}
return NS_OK;
}
NS_IMETHODIMP
nsDOMTokenList::ToString(nsAString& aResult)
{
if (!mElement) {
aResult.Truncate();
return NS_OK;
}
mElement->GetAttr(kNameSpaceID_None, mAttrAtom, aResult);
return NS_OK;
}

View File

@ -0,0 +1,78 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Sylvain Pasche <sylvain.pasche@gmail.com>
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/*
* Implementation of nsIDOMDOMTokenList specified by HTML5.
*/
#ifndef nsDOMTokenList_h___
#define nsDOMTokenList_h___
#include "nsGenericElement.h"
#include "nsIDOMDOMTokenList.h"
class nsAttrValue;
class nsDOMTokenList : public nsIDOMDOMTokenList
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIDOMDOMTOKENLIST
nsDOMTokenList(nsGenericElement* aElement, nsIAtom* aAttrAtom);
void DropReference();
private:
~nsDOMTokenList();
const nsAttrValue* GetParsedAttr() {
if (!mElement) {
return nsnull;
}
return mElement->GetParsedAttr(mAttrAtom);
}
nsresult CheckToken(const nsAString& aStr);
PRBool ContainsInternal(const nsAttrValue* aAttr, const nsAString& aToken);
void AddInternal(const nsAttrValue* aAttr, const nsAString& aToken);
void RemoveInternal(const nsAttrValue* aAttr, const nsAString& aToken);
nsGenericElement* mElement;
nsCOMPtr<nsIAtom> mAttrAtom;
};
#endif // nsDOMTokenList_h___

View File

@ -74,6 +74,7 @@
#include "nsDOMCSSDeclaration.h"
#include "nsINameSpaceManager.h"
#include "nsContentList.h"
#include "nsDOMTokenList.h"
#include "nsDOMError.h"
#include "nsDOMString.h"
#include "nsIScriptSecurityManager.h"
@ -1064,6 +1065,24 @@ nsNSElementTearoff::GetChildren(nsIDOMNodeList** aResult)
return NS_OK;
}
NS_IMETHODIMP
nsNSElementTearoff::GetClassList(nsIDOMDOMTokenList** aResult)
{
nsGenericElement::nsDOMSlots *slots = mContent->GetDOMSlots();
NS_ENSURE_TRUE(slots, nsnull);
if (!slots->mClassList) {
nsCOMPtr<nsIAtom> classAttr = mContent->GetClassAttributeName();
NS_ENSURE_TRUE(classAttr, NS_OK);
slots->mClassList = new nsDOMTokenList(mContent, classAttr);
NS_ENSURE_TRUE(slots->mClassList, NS_ERROR_OUT_OF_MEMORY);
}
NS_ADDREF(*aResult = slots->mClassList);
return NS_OK;
}
//----------------------------------------------------------------------
@ -1757,6 +1776,10 @@ nsGenericElement::nsDOMSlots::~nsDOMSlots()
if (mAttributeMap) {
mAttributeMap->DropReference();
}
if (mClassList) {
mClassList->DropReference();
}
}
nsGenericElement::nsGenericElement(nsINodeInfo *aNodeInfo)

View File

@ -83,6 +83,7 @@ class nsIDOMNSFeatureFactory;
class nsIEventListenerManager;
class nsIScrollableView;
class nsContentList;
class nsDOMTokenList;
struct nsRect;
typedef PRUptrdiff PtrBits;
@ -110,7 +111,7 @@ public:
// nsINodeList interface
virtual nsIContent* GetNodeAt(PRUint32 aIndex);
virtual PRInt32 IndexOf(nsIContent* aContent);
void DropReference()
{
mNode = nsnull;
@ -454,7 +455,7 @@ public:
NS_IMETHOD SetInlineStyleRule(nsICSSStyleRule* aStyleRule, PRBool aNotify);
NS_IMETHOD_(PRBool)
IsAttributeMapped(const nsIAtom* aAttribute) const;
virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
virtual nsChangeHint GetAttributeChangeHint(const nsIAtom* aAttribute,
PRInt32 aModType) const;
/*
* Attribute Mapping Helpers
@ -462,7 +463,7 @@ public:
struct MappedAttributeEntry {
nsIAtom** attribute;
};
/**
* A common method where you can just pass in a list of maps to check
* for attribute dependence. Most implementations of
@ -540,7 +541,7 @@ public:
/**
* Add a script event listener with the given event handler name
* (like onclick) and with the value as JS
* (like onclick) and with the value as JS
* @param aEventName the event listener name
* @param aValue the JS to attach
* @param aDefer indicates if deferred execution is allowed
@ -579,7 +580,7 @@ public:
const nsAString& aFeature,
const nsAString& aVersion,
nsISupports** aReturn);
static already_AddRefed<nsIDOMNSFeatureFactory>
GetDOMFeatureFactory(const nsAString& aFeature, const nsAString& aVersion);
@ -684,7 +685,7 @@ public:
nsIContent* aTarget,
PRBool aFullDispatch,
nsEventStatus* aStatus);
/**
* Method to dispatch aEvent to aTarget. If aFullDispatch is true, the event
* will be dispatched through the full dispatching of the presshell of the
@ -702,7 +703,7 @@ public:
* Get the primary frame for this content without flushing (see
* GetPrimaryFrameFor)
*
* @return the primary frame
* @return the primary frame
*/
nsIFrame* GetPrimaryFrame();
@ -735,11 +736,16 @@ public:
mName(aName), mValue(aValue) {}
nsAttrInfo(const nsAttrInfo& aOther) :
mName(aOther.mName), mValue(aOther.mValue) {}
const nsAttrName* mName;
const nsAttrValue* mValue;
};
const nsAttrValue* GetParsedAttr(nsIAtom* aAttr) const
{
return mAttrsAndChildren.GetAttr(aAttr);
}
/**
* Returns the attribute map, if there is one.
*
@ -941,7 +947,7 @@ public:
*/
nsIControllers* mControllers; // [OWNER]
};
/**
* Weak reference to this node
*/
@ -951,6 +957,11 @@ public:
* An object implementing the .children property for this element.
*/
nsRefPtr<nsContentList> mChildrenList;
/**
* An object implementing the .classList property for this element.
*/
nsRefPtr<nsDOMTokenList> mClassList;
};
protected:
@ -1096,7 +1107,7 @@ public:
nsNSElementTearoff(nsGenericElement *aContent) : mContent(aContent)
{
}
private:
nsContentList* GetChildrenList();

View File

@ -322,6 +322,7 @@ _TEST_FILES = test_bug5141.html \
bug466409-page.html \
bug466409-empty.css \
test_bug466409.html \
test_classList.html \
$(NULL)
# Disabled; see bug 492181
# test_plugin_freezing.html

View File

@ -0,0 +1,351 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=501257
-->
<head>
<title>Test for the classList element attribute</title>
<script type="application/javascript" src="/MochiKit/packed.js"></script>
<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="http://www.whatwg.org/specs/web-apps/current-work/#dom-classlist">classList DOM attribute</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 501257 **/
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const SVG_NS = "http://www.w3.org/2000/svg";
const XHTML_NS = "http://www.w3.org/1999/xhtml"
const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
var gMutationEvents = [];
function onAttrModified(event) {
is(event.attrName, "class", "mutation on unexpected attribute");
gMutationEvents.push({
attrChange: event.attrChange,
prevValue: event.prevValue,
newValue: event.newValue,
});
}
function checkModification(e, funcName, argument, expectedRes, before, after) {
var shouldThrow = typeof(after) === "number";
if (shouldThrow) {
var expectedException = after;
// If an exception is thrown, the class attribute shouldn't change.
after = before;
}
if (before === null)
e.removeAttribute("class");
else
e.setAttribute("class", before);
var contextMsg = "(checkModification: funcName=" + funcName + ",argument=" +
argument + ",expectedRes=" + expectedRes + ",before=" +
before + ",after=" + after + ")";
gMutationEvents = [];
e.addEventListener("DOMAttrModified", onAttrModified, false);
try {
var res = e.classList[funcName](argument);
if (shouldThrow)
ok(false, "classList modification didn't throw " + contextMsg);
} catch (e) {
if (!shouldThrow)
ok(false, "classList modification threw an exception " + contextMsg);
is(e.code, expectedException, "wrong exception thrown " + contextMsg);
}
e.removeEventListener("DOMAttrModified", onAttrModified, false);
if (expectedRes !== null)
is(res, expectedRes, "wrong return value from " + funcName +
" " + contextMsg);
var expectedAfter = after;
// XUL returns an empty string when getting a non-existing class attribute.
if (e.namespaceURI == XUL_NS && expectedAfter === null)
expectedAfter = "";
is(e.getAttribute("class"), expectedAfter, "wrong class after modification " +
contextMsg);
var expectedMutation = before != after;
is(gMutationEvents.length, expectedMutation ? 1 : 0,
"unexpected mutation event count " + contextMsg);
if (expectedMutation && gMutationEvents.length) {
is(gMutationEvents[0].attrChange,
before == null ? MutationEvent.ADDITION : MutationEvent.MODIFICATION,
"wrong type of attribute change " + contextMsg);
// If there wasn't any previous attribute, prevValue will return an empty
// string.
var expectedPrevValue = before === null ? "" : before;
is(gMutationEvents[0].prevValue, expectedPrevValue,
"wrong previous value " + contextMsg);
is(gMutationEvents[0].newValue, after, "wrong new value " + contextMsg);
}
}
function testClassList(e) {
// basic tests
isnot(e.classList, undefined, "no classList attribute");
is(typeof(e.classList.contains), "function",
"no classList.contains function");
is(typeof(e.classList.add), "function", "no classList.add function");
is(typeof(e.classList.remove), "function", "no classList.remove function");
is(typeof(e.classList.toggle), "function", "no classList.toggle function");
try {
e.classList = "foo";
ok(false, "assigning to classList didn't throw");
} catch (e) { }
// length attribute
is(e.classList.length, 0, "wrong classList.length value");
e.setAttribute("class", "");
is(e.classList.length, 0, "wrong classList.length value");
e.setAttribute("class", " \t \f");
is(e.classList.length, 0, "wrong classList.length value");
e.setAttribute("class", "a");
is(e.classList.length, 1, "wrong classList.length value");
e.setAttribute("class", "a A");
is(e.classList.length, 2, "wrong classList.length value");
e.setAttribute("class", "\r\na\t\f");
is(e.classList.length, 1, "wrong classList.length value");
e.setAttribute("class", "a a");
is(e.classList.length, 2, "wrong classList.length value");
e.setAttribute("class", "a a a a a a");
is(e.classList.length, 6, "wrong classList.length value");
e.setAttribute("class", "a a b b");
is(e.classList.length, 4, "wrong classList.length value");
e.setAttribute("class", "a A B b");
is(e.classList.length, 4, "wrong classList.length value");
e.setAttribute("class", "a b c c b a a b c c");
is(e.classList.length, 10, "wrong classList.length value");
// [Stringifies]
e.removeAttribute("class");
is(e.classList.toString(), "", "wrong classList.toString() value");
is(e.classList + "", "", "wrong classList string conversion value");
e.setAttribute("class", "foo");
is(e.classList.toString(), "foo", "wrong classList.toString() value");
is(e.classList + "", "foo", "wrong classList string conversion value");
// item() method
// TODO out of bounds array indexing should return undefined according to the
// WebIDL spec. They are returning an empty string at the moment.
var OOB_VALUE = "";
todo_is(OOB_VALUE, undefined, "Wrong out of bounds value");
e.setAttribute("class", "a");
is(e.classList.item(-1), null, "wrong classList.item() result");
is(e.classList[-1], OOB_VALUE, "wrong classList[] result");
is(e.classList.item(0), "a", "wrong classList.item() result");
is(e.classList[0], "a", "wrong classList.item() result");
is(e.classList.item(1), null, "wrong classList.item() result");
is(e.classList[1], OOB_VALUE, "wrong classList.item() result");
e.setAttribute("class", "aa AA aa");
is(e.classList.item(-1), null, "wrong classList.item() result");
is(e.classList[-1], OOB_VALUE, "wrong classList[] result");
is(e.classList.item(0), "aa", "wrong classList.item() result");
is(e.classList[0], "aa", "wrong classList[] result");
is(e.classList.item(1), "AA", "wrong classList.item() result");
is(e.classList[1], "AA", "wrong classList[] result");
is(e.classList.item(2), "aa", "wrong classList.item() result");
is(e.classList[2], "aa", "wrong classList[] result");
is(e.classList.item(3), null, "wrong classList.item() result");
is(e.classList[3], OOB_VALUE, "wrong classList[] result");
is(e.classList.item(0xffffffff), null, "wrong classList.item() result");
// XXX returns undefined for index >= 0xffffffff
todo_is(e.classList[0xffffffff], OOB_VALUE, "wrong classList[] result");
is(e.classList.item(0xfffffffe), null, "wrong classList.item() result");
is(e.classList[0xffffffe], OOB_VALUE, "wrong classList[] result");
e.setAttribute("class", "a b");
is(e.classList.item(-1), null, "wrong classList.item() result");
is(e.classList[-1], OOB_VALUE, "wrong classList[] result");
is(e.classList.item(0), "a", "wrong classList.item() result");
is(e.classList[0], "a", "wrong classList[] result");
is(e.classList.item(1), "b", "wrong classList.item() result");
is(e.classList[1], "b", "wrong classList[] result");
is(e.classList.item(2), null, "wrong classList.item() result");
is(e.classList[2], OOB_VALUE, "wrong classList[] result");
// contains() method
e.removeAttribute("class");
is(e.classList.contains("a"), false, "wrong classList.contains() result");
try {
e.classList.contains("");
ok(false, "classList.contains() didn't throw");
} catch (e) {
is(e.code, DOMException.SYNTAX_ERR, "wrong exception thrown");
}
try {
e.classList.contains(" ");
ok(false, "classList.contains() didn't throw");
} catch (e) {
is(e.code, DOMException.INVALID_CHARACTER_ERR, "wrong exception thrown");
}
try {
e.classList.contains("aa ");
ok(false, "classList.contains() didn't throw");
} catch (e) {
is(e.code, DOMException.INVALID_CHARACTER_ERR, "wrong exception thrown");
}
e.setAttribute("class", "");
is(e.classList.contains("a"), false, "wrong classList.contains() result");
e.setAttribute("class", "a");
is(e.classList.contains("a"), true, "wrong classList.contains() result");
is(e.classList.contains("aa"), false, "wrong classList.contains() result");
is(e.classList.contains("b"), false, "wrong classList.contains() result");
e.setAttribute("class", "aa AA");
is(e.classList.contains("aa"), true, "wrong classList.contains() result");
is(e.classList.contains("AA"), true, "wrong classList.contains() result");
is(e.classList.contains("aA"), false, "wrong classList.contains() result");
e.setAttribute("class", "a a a");
is(e.classList.contains("a"), true, "wrong classList.contains() result");
is(e.classList.contains("aa"), false, "wrong classList.contains() result");
is(e.classList.contains("b"), false, "wrong classList.contains() result");
e.setAttribute("class", "a b c");
is(e.classList.contains("a"), true, "wrong classList.contains() result");
is(e.classList.contains("b"), true, "wrong classList.contains() result");
// add() method
function checkAdd(before, argument, after) {
checkModification(e, "add", argument, null, before, after);
}
checkAdd(null, "", DOMException.SYNTAX_ERR);
checkAdd(null, " ", DOMException.INVALID_CHARACTER_ERR);
checkAdd(null, "aa ", DOMException.INVALID_CHARACTER_ERR);
checkAdd("a", "a", "a");
checkAdd("aa", "AA", "aa AA");
checkAdd("a b c", "a", "a b c");
checkAdd("a a a b", "a", "a a a b");
checkAdd(null, "a", "a");
checkAdd("", "a", "a");
checkAdd(" ", "a", " a");
checkAdd(" \f", "a", " \fa");
checkAdd("a", "b", "a b");
checkAdd("a b c", "d", "a b c d");
checkAdd("a b c ", "d", "a b c d");
// remove() method
function checkRemove(before, argument, after) {
checkModification(e, "remove", argument, null, before, after);
}
checkRemove(null, "", DOMException.SYNTAX_ERR);
checkRemove(null, " ", DOMException.INVALID_CHARACTER_ERR);
checkRemove(null, "aa ", DOMException.INVALID_CHARACTER_ERR);
checkRemove(null, "a", null);
checkRemove("", "a", "");
checkRemove("a b c", "d", "a b c");
checkRemove("a b c", "A", "a b c");
checkRemove(" a a a ", "a", "");
checkRemove("a b", "a", "b");
checkRemove("a b ", "a", "b ");
checkRemove("a a b", "a", "b");
checkRemove("aa aa bb", "aa", "bb");
checkRemove("a a b a a c a a", "a", "b c");
checkRemove("a b c", "b", "a c");
checkRemove("aaa bbb ccc", "bbb", "aaa ccc");
checkRemove(" a b c ", "b", " a c ");
checkRemove("a b b b c", "b", "a c");
checkRemove("a b c", "c", "a b");
checkRemove(" a b c ", "c", " a b");
checkRemove("a b c c c", "c", "a b");
checkRemove("a b a c a d a", "a", "b c d");
checkRemove("AA BB aa CC AA dd aa", "AA", "BB aa CC dd aa");
checkRemove("\ra\na\ta\f", "a", "");
// toggle() method
function checkToggle(before, argument, expectedRes, after) {
checkModification(e, "toggle", argument, expectedRes, before, after);
}
checkToggle(null, "", null, DOMException.SYNTAX_ERR);
checkToggle(null, "aa ", null, DOMException.INVALID_CHARACTER_ERR);
checkToggle(null, "a", true, "a");
checkToggle("", "a", true, "a");
checkToggle(" ", "a", true, " a");
checkToggle(" \f", "a", true, " \fa");
checkToggle("a", "b", true, "a b");
checkToggle("a", "A", true, "a A");
checkToggle("a b c", "d", true, "a b c d");
checkToggle("a b c", "d", true, "a b c d");
checkToggle("a", "a", false, "");
checkToggle(" a a a ", "a", false, "");
checkToggle(" A A A ", "a", true, " A A A a");
checkToggle(" a b c ", "b", false, " a c ");
checkToggle(" a b c b b", "b", false, " a c");
checkToggle(" a b c ", "c", false, " a b");
checkToggle(" a b c ", "a", false, "b c ");
}
var content = document.getElementById("content");
var htmlNode = document.createElement("div");
content.appendChild(htmlNode);
testClassList(htmlNode);
var xhtmlNode = document.createElementNS(XHTML_NS, "div");
content.appendChild(xhtmlNode);
testClassList(xhtmlNode);
var xulNode = document.createElementNS(XUL_NS, "box");
content.appendChild(xulNode);
testClassList(xulNode);
var mathMLNode = document.createElementNS(MATHML_NS, "math");
content.appendChild(mathMLNode);
testClassList(mathMLNode);
// Nodes not meant to be styled have a null classList property.
var xmlNode = document.createElementNS(null, "foo");
content.appendChild(xmlNode);
is(xmlNode.classList, null, "classList is not null for plain XML nodes");
var fooNode = document.createElementNS("http://example.org/foo", "foo");
content.appendChild(fooNode);
is(fooNode.classList, null, "classList is not null for nodes in " +
" http://example.org/foo namespace");
</script>
</pre>
</body>
</html>

View File

@ -205,10 +205,6 @@ public:
// HTML element methods
void Compact() { mAttrsAndChildren.Compact(); }
const nsAttrValue* GetParsedAttr(nsIAtom* aAttr) const
{
return mAttrsAndChildren.GetAttr(aAttr);
}
virtual void UpdateEditableState();

View File

@ -123,6 +123,7 @@
#include "nsIDOMNodeList.h"
#include "nsIDOMNamedNodeMap.h"
#include "nsIDOMDOMStringList.h"
#include "nsIDOMDOMTokenList.h"
#include "nsIDOMNameList.h"
#include "nsIDOMNSElement.h"
@ -621,6 +622,8 @@ static nsDOMClassInfoData sClassInfoData[] = {
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(DOMException, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(DOMTokenList, nsDOMTokenListSH,
ARRAY_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(DocumentFragment, nsDOMGenericSH,
DOM_DEFAULT_SCRIPTABLE_FLAGS)
NS_DEFINE_CLASSINFO_DATA(Element, nsElementSH,
@ -2062,6 +2065,10 @@ nsDOMClassInfo::Init()
DOM_CLASSINFO_MAP_ENTRY(nsIException)
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(DOMTokenList, nsIDOMDOMTokenList)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMTokenList)
DOM_CLASSINFO_MAP_END
DOM_CLASSINFO_MAP_BEGIN(DocumentFragment, nsIDOMDocumentFragment)
DOM_CLASSINFO_MAP_ENTRY(nsIDOMDocumentFragment)
DOM_CLASSINFO_MAP_ENTRY(nsIDOM3Node)
@ -7923,6 +7930,19 @@ nsStringListSH::GetStringAt(nsISupports *aNative, PRInt32 aIndex,
}
// DOMTokenList scriptable helper
nsresult
nsDOMTokenListSH::GetStringAt(nsISupports *aNative, PRInt32 aIndex,
nsAString& aResult)
{
nsCOMPtr<nsIDOMDOMTokenList> list(do_QueryInterface(aNative));
NS_ENSURE_TRUE(list, NS_ERROR_UNEXPECTED);
return list->Item(aIndex, aResult);
}
// Named Array helper
NS_IMETHODIMP

View File

@ -1331,6 +1331,31 @@ public:
};
// DOMTokenList scriptable helper
class nsDOMTokenListSH : public nsStringArraySH
{
protected:
nsDOMTokenListSH(nsDOMClassInfoData* aData) : nsStringArraySH(aData)
{
}
virtual ~nsDOMTokenListSH()
{
}
virtual nsresult GetStringAt(nsISupports *aNative, PRInt32 aIndex,
nsAString& aResult);
public:
static nsIClassInfo *doCreate(nsDOMClassInfoData* aData)
{
return new nsDOMTokenListSH(aData);
}
};
// MediaList helper
class nsMediaListSH : public nsStringArraySH

View File

@ -64,6 +64,7 @@ enum nsDOMClassInfoID {
eDOMClassInfo_DocumentType_id,
eDOMClassInfo_DOMImplementation_id,
eDOMClassInfo_DOMException_id,
eDOMClassInfo_DOMTokenList_id,
eDOMClassInfo_DocumentFragment_id,
eDOMClassInfo_Element_id,
eDOMClassInfo_Attr_id,

View File

@ -62,6 +62,7 @@ interface nsIDOMNotation;
interface nsIDOMProcessingInstruction;
interface nsIDOMText;
interface nsIDOMDOMStringList;
interface nsIDOMDOMTokenList;
interface nsIDOMNameList;
interface nsIDOMClientRect;
interface nsIDOMClientRectList;

View File

@ -80,7 +80,8 @@ XPIDLSRCS = \
nsIDOMDOMConfiguration.idl \
nsIDOMNSEditableElement.idl \
nsIDOMNSElement.idl \
nsIDOMNodeSelector.idl \
nsIDOMNodeSelector.idl \
nsIDOMDOMTokenList.idl \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,60 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Sylvain Pasche <sylvain.pasche@gmail.com>
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "domstubs.idl"
/**
* The DOMTokenList interface represents an interface to an underlying string
* that consists of a set of space-separated tokens.
*
* For more information on this interface please see
* http://www.w3.org/TR/html5/infrastructure.html#domtokenlist
*
*/
[scriptable, uuid(c6f1e160-eeeb-404a-98b0-6f1246520b6e)]
interface nsIDOMDOMTokenList : nsISupports
{
readonly attribute unsigned long length;
DOMString item(in unsigned long index);
boolean contains(in DOMString token);
void add(in DOMString token);
void remove(in DOMString token);
boolean toggle(in DOMString token);
DOMString toString();
};

View File

@ -39,9 +39,7 @@
#include "domstubs.idl"
interface nsIDOMNodeList;
[scriptable, uuid(f0aef489-18c5-4de6-99d5-58b3758b098c)]
[scriptable, uuid(df86b1a8-02c3-47be-a76b-856620f925df)]
interface nsIDOMNSElement : nsISupports
{
/*
@ -149,4 +147,9 @@ interface nsIDOMNSElement : nsISupports
* Returns a live nsIDOMNodeList of the current child elements.
*/
readonly attribute nsIDOMNodeList children;
/**
* Returns a DOMTokenList object reflecting the class attribute.
*/
readonly attribute nsIDOMDOMTokenList classList;
};

View File

@ -0,0 +1,8 @@
<!DOCTYPE html>
<html>
<body>
<div style="color: green;">
This should be green
</div>
</body>
</html>

View File

@ -0,0 +1,7 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<div style="color: green;">
This should be green
</div>
</body>
</html>

View File

@ -0,0 +1,18 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<style><![CDATA[
.green { color: green; }
.toremove { color: red !important; }
]]></style>
</head>
<body>
<div class="foo toremove">
This should be green
</div>
<script>
var div = document.getElementsByTagName("div")[0];
div.classList.toggle("toremove");
div.classList.toggle("green");
</script>
</body>
</html>

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<style>
.green { color: green; }
</style>
</head>
<body>
<div>
This should be green
</div>
<script>
document.body.firstElementChild.classList.add("green");
</script>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<style>
body > div { color: green; }
.toremove { color: red; }
</style>
</head>
<body>
<div class="toremove">
This should be green
</div>
<script>
document.body.firstElementChild.classList.remove("toremove");
</script>
</body>
</html>

View File

@ -1290,6 +1290,9 @@ fails-if(MOZ_WIDGET_TOOLKIT!="cocoa") == 488692-1.html 488692-1-ref.html # needs
== 495385-5.html 495385-5-ref.html
== 498228-1.xul 498228-1-ref.xul
== 496032-1.html 496032-1-ref.html
== 501257-1a.html 501257-1-ref.html
== 501257-1b.html 501257-1-ref.html
== 501257-1.xhtml 501257-1-ref.xhtml
== 502288-1.html 502288-1-ref.html
== 502942-1.html 502942-1-ref.html
== 502447-1.html 502447-1-ref.html