fune/dom/base/nsDOMTokenList.cpp
Simon Giesecke ad01a10a3b Bug 1634281 - Use nsTHashMap instead of nsDataHashtable. r=xpcom-reviewers,necko-reviewers,jgilbert,nika,valentin
Note that this patch only transforms the use of the nsDataHashtable type alias
to a directly equivalent use of nsTHashMap. It does not change the specification
of the hash key type to make use of the key class deduction that nsTHashMap
allows for in some cases. That can be done in a separate step, but requires more
attention.

Differential Revision: https://phabricator.services.mozilla.com/D106008
2021-03-10 10:47:47 +00:00

395 lines
10 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* Implementation of DOMTokenList specified by HTML5.
*/
#include "nsDOMTokenList.h"
#include "nsAttrValue.h"
#include "nsAttrValueInlines.h"
#include "nsTHashMap.h"
#include "nsError.h"
#include "nsHashKeys.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/DOMTokenListBinding.h"
#include "mozilla/BloomFilter.h"
#include "mozilla/ErrorResult.h"
using namespace mozilla;
using namespace mozilla::dom;
nsDOMTokenList::nsDOMTokenList(
Element* aElement, nsAtom* aAttrAtom,
const DOMTokenListSupportedTokenArray aSupportedTokens)
: mElement(aElement),
mAttrAtom(aAttrAtom),
mSupportedTokens(aSupportedTokens) {
// We don't add a reference to our element. If it goes away,
// we'll be told to drop our reference
}
nsDOMTokenList::~nsDOMTokenList() = default;
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMTokenList, mElement)
NS_INTERFACE_MAP_BEGIN(nsDOMTokenList)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMTokenList)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMTokenList)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMTokenList)
const nsAttrValue* nsDOMTokenList::GetParsedAttr() {
if (!mElement) {
return nullptr;
}
return mElement->GetAttrInfo(kNameSpaceID_None, mAttrAtom).mValue;
}
static void RemoveDuplicatesInternal(AtomArray* aArray, uint32_t aStart) {
nsTHashMap<nsPtrHashKey<nsAtom>, bool> tokens;
for (uint32_t i = 0; i < aArray->Length(); i++) {
nsAtom* atom = aArray->ElementAt(i);
// No need to check the hashtable below aStart
if (i >= aStart && tokens.Get(atom)) {
aArray->RemoveElementAt(i);
i--;
} else {
tokens.InsertOrUpdate(atom, true);
}
}
}
void nsDOMTokenList::RemoveDuplicates(const nsAttrValue* aAttr) {
if (!aAttr || aAttr->Type() != nsAttrValue::eAtomArray) {
return;
}
BitBloomFilter<8, nsAtom> filter;
AtomArray* array = aAttr->GetAtomArrayValue();
for (uint32_t i = 0; i < array->Length(); i++) {
nsAtom* atom = array->ElementAt(i);
if (filter.mightContain(atom)) {
// Start again, with a hashtable
RemoveDuplicatesInternal(array, i);
return;
} else {
filter.add(atom);
}
}
}
uint32_t nsDOMTokenList::Length() {
const nsAttrValue* attr = GetParsedAttr();
if (!attr) {
return 0;
}
RemoveDuplicates(attr);
return attr->GetAtomCount();
}
void nsDOMTokenList::IndexedGetter(uint32_t aIndex, bool& aFound,
nsAString& aResult) {
const nsAttrValue* attr = GetParsedAttr();
if (!attr || aIndex >= static_cast<uint32_t>(attr->GetAtomCount())) {
aFound = false;
return;
}
RemoveDuplicates(attr);
if (attr && aIndex < static_cast<uint32_t>(attr->GetAtomCount())) {
aFound = true;
attr->AtomAt(aIndex)->ToString(aResult);
} else {
aFound = false;
}
}
void nsDOMTokenList::GetValue(nsAString& aResult) {
if (!mElement) {
aResult.Truncate();
return;
}
mElement->GetAttr(kNameSpaceID_None, mAttrAtom, aResult);
}
void nsDOMTokenList::SetValue(const nsAString& aValue, ErrorResult& rv) {
if (!mElement) {
return;
}
rv = mElement->SetAttr(kNameSpaceID_None, mAttrAtom, aValue, true);
}
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;
}
nsresult nsDOMTokenList::CheckTokens(const nsTArray<nsString>& aTokens) {
for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) {
nsresult rv = CheckToken(aTokens[i]);
if (NS_FAILED(rv)) {
return rv;
}
}
return NS_OK;
}
bool nsDOMTokenList::Contains(const nsAString& aToken) {
const nsAttrValue* attr = GetParsedAttr();
return attr && attr->Contains(aToken);
}
void nsDOMTokenList::AddInternal(const nsAttrValue* aAttr,
const nsTArray<nsString>& aTokens) {
if (!mElement) {
return;
}
nsAutoString resultStr;
if (aAttr) {
RemoveDuplicates(aAttr);
for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
if (i != 0) {
resultStr.AppendLiteral(" ");
}
resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
}
}
AutoTArray<nsString, 10> addedClasses;
for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) {
const nsString& aToken = aTokens[i];
if ((aAttr && aAttr->Contains(aToken)) || addedClasses.Contains(aToken)) {
continue;
}
if (!resultStr.IsEmpty()) {
resultStr.Append(' ');
}
resultStr.Append(aToken);
addedClasses.AppendElement(aToken);
}
mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
}
void nsDOMTokenList::Add(const nsTArray<nsString>& aTokens,
ErrorResult& aError) {
aError = CheckTokens(aTokens);
if (aError.Failed()) {
return;
}
const nsAttrValue* attr = GetParsedAttr();
AddInternal(attr, aTokens);
}
void nsDOMTokenList::Add(const nsAString& aToken, ErrorResult& aError) {
AutoTArray<nsString, 1> tokens;
tokens.AppendElement(aToken);
Add(tokens, aError);
}
void nsDOMTokenList::RemoveInternal(const nsAttrValue* aAttr,
const nsTArray<nsString>& aTokens) {
MOZ_ASSERT(aAttr, "Need an attribute");
RemoveDuplicates(aAttr);
nsAutoString resultStr;
for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
if (aTokens.Contains(nsDependentAtomString(aAttr->AtomAt(i)))) {
continue;
}
if (!resultStr.IsEmpty()) {
resultStr.AppendLiteral(" ");
}
resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
}
mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
}
void nsDOMTokenList::Remove(const nsTArray<nsString>& aTokens,
ErrorResult& aError) {
aError = CheckTokens(aTokens);
if (aError.Failed()) {
return;
}
const nsAttrValue* attr = GetParsedAttr();
if (!attr) {
return;
}
RemoveInternal(attr, aTokens);
}
void nsDOMTokenList::Remove(const nsAString& aToken, ErrorResult& aError) {
AutoTArray<nsString, 1> tokens;
tokens.AppendElement(aToken);
Remove(tokens, aError);
}
bool nsDOMTokenList::Toggle(const nsAString& aToken,
const Optional<bool>& aForce, ErrorResult& aError) {
aError = CheckToken(aToken);
if (aError.Failed()) {
return false;
}
const nsAttrValue* attr = GetParsedAttr();
const bool forceOn = aForce.WasPassed() && aForce.Value();
const bool forceOff = aForce.WasPassed() && !aForce.Value();
bool isPresent = attr && attr->Contains(aToken);
AutoTArray<nsString, 1> tokens;
(*tokens.AppendElement()).Rebind(aToken.Data(), aToken.Length());
if (isPresent) {
if (!forceOn) {
RemoveInternal(attr, tokens);
isPresent = false;
}
} else {
if (!forceOff) {
AddInternal(attr, tokens);
isPresent = true;
}
}
return isPresent;
}
bool nsDOMTokenList::Replace(const nsAString& aToken,
const nsAString& aNewToken, ErrorResult& aError) {
// Doing this here instead of using `CheckToken` because if aToken had invalid
// characters, and aNewToken is empty, the returned error should be a
// SyntaxError, not an InvalidCharacterError.
if (aNewToken.IsEmpty()) {
aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return false;
}
aError = CheckToken(aToken);
if (aError.Failed()) {
return false;
}
aError = CheckToken(aNewToken);
if (aError.Failed()) {
return false;
}
const nsAttrValue* attr = GetParsedAttr();
if (!attr) {
return false;
}
return ReplaceInternal(attr, aToken, aNewToken);
}
bool nsDOMTokenList::ReplaceInternal(const nsAttrValue* aAttr,
const nsAString& aToken,
const nsAString& aNewToken) {
RemoveDuplicates(aAttr);
// Trying to do a single pass here leads to really complicated code. Just do
// the simple thing.
bool haveOld = false;
for (uint32_t i = 0; i < aAttr->GetAtomCount(); ++i) {
if (aAttr->AtomAt(i)->Equals(aToken)) {
haveOld = true;
break;
}
}
if (!haveOld) {
// Make sure to not touch the attribute value in this case.
return false;
}
bool sawIt = false;
nsAutoString resultStr;
for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
if (aAttr->AtomAt(i)->Equals(aToken) ||
aAttr->AtomAt(i)->Equals(aNewToken)) {
if (sawIt) {
// We keep only the first
continue;
}
sawIt = true;
if (!resultStr.IsEmpty()) {
resultStr.AppendLiteral(" ");
}
resultStr.Append(aNewToken);
continue;
}
if (!resultStr.IsEmpty()) {
resultStr.AppendLiteral(" ");
}
resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
}
MOZ_ASSERT(sawIt, "How could we not have found our token this time?");
mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
return true;
}
bool nsDOMTokenList::Supports(const nsAString& aToken, ErrorResult& aError) {
if (!mSupportedTokens) {
aError.ThrowTypeError<MSG_TOKENLIST_NO_SUPPORTED_TOKENS>(
NS_ConvertUTF16toUTF8(mElement->LocalName()),
NS_ConvertUTF16toUTF8(nsDependentAtomString(mAttrAtom)));
return false;
}
for (DOMTokenListSupportedToken* supportedToken = mSupportedTokens;
*supportedToken; ++supportedToken) {
if (aToken.LowerCaseEqualsASCII(*supportedToken)) {
return true;
}
}
return false;
}
DocGroup* nsDOMTokenList::GetDocGroup() const {
return mElement ? mElement->OwnerDoc()->GetDocGroup() : nullptr;
}
JSObject* nsDOMTokenList::WrapObject(JSContext* cx,
JS::Handle<JSObject*> aGivenProto) {
return DOMTokenList_Binding::Wrap(cx, this, aGivenProto);
}