/* -*- 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/. */
/* * nsBaseContentList is a basic list of content nodes; nsContentList * is a commonly used NodeList implementation (used for * getElementsByTagName, some properties on HTMLDocument/Document, etc).
*/
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsBaseContentList) if (nsCCUncollectableMarker::sGeneration && tmp->HasKnownLiveWrapper()) { for (uint32_t i = 0; i < tmp->mElements.Length(); ++i) {
nsIContent* c = tmp->mElements[i]; if (c->IsPurple()) {
c->RemovePurple();
}
Element::MarkNodeChildren(c);
} returntrue;
}
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
class nsContentList::HashEntry : public PLDHashEntryHdr { public: using KeyType = const nsContentListKey*; using KeyTypePointer = KeyType;
// Note that this is creating a blank entry, so you'll have to manually // initialize it after it has been inserted into the hash table. explicit HashEntry(KeyTypePointer aKey) : mContentList(nullptr) {}
private:
nsContentList* MOZ_UNSAFE_REF( "This entry will be removed in nsContentList::RemoveFromHashtable " "before mContentList is destroyed") mContentList;
};
// Hashtable for storing nsContentLists static StaticAutoPtr<nsTHashtable<nsContentList::HashEntry>>
gContentListHashTable;
already_AddRefed<nsContentList> NS_GetContentList(nsINode* aRootNode,
int32_t aMatchNameSpaceId, const nsAString& aTagname) {
NS_ASSERTION(aRootNode, "content list has to have a root");
RefPtr<nsContentList> list;
nsContentListKey hashKey(aRootNode, aMatchNameSpaceId, aTagname,
aRootNode->OwnerDoc()->IsHTMLDocument()); auto p = sRecentlyUsedContentLists.Lookup(hashKey); if (p) {
list = p.Data(); return list.forget();
}
// Initialize the hashtable if needed. if (!gContentListHashTable) {
gContentListHashTable = new nsTHashtable<nsContentList::HashEntry>();
}
// First we look in our hashtable. Then we create a content list if needed auto entry = gContentListHashTable->PutEntry(&hashKey, fallible); if (entry) {
list = entry->GetContentList();
}
if (!list) { // We need to create a ContentList and add it to our new entry, if // we have an entry
RefPtr<nsAtom> xmlAtom = NS_Atomize(aTagname);
RefPtr<nsAtom> htmlAtom; if (aMatchNameSpaceId == kNameSpaceID_Unknown) {
nsAutoString lowercaseName;
nsContentUtils::ASCIIToLower(aTagname, lowercaseName);
htmlAtom = NS_Atomize(lowercaseName);
} else {
htmlAtom = xmlAtom;
}
list = new nsContentList(aRootNode, aMatchNameSpaceId, htmlAtom, xmlAtom); if (entry) {
entry->SetContentList(list);
}
}
class nsCacheableFuncStringContentList::HashEntry : public PLDHashEntryHdr { public: using KeyType = const nsFuncStringCacheKey*; using KeyTypePointer = KeyType;
// Note that this is creating a blank entry, so you'll have to manually // initialize it after it has been inserted into the hash table. explicit HashEntry(KeyTypePointer aKey) : mContentList(nullptr) {}
private:
nsCacheableFuncStringContentList* MOZ_UNSAFE_REF( "This entry will be removed in " "nsCacheableFuncStringContentList::RemoveFromFuncStringHashtable " "before mContentList is destroyed") mContentList;
};
// Hashtable for storing nsCacheableFuncStringContentList static StaticAutoPtr<nsTHashtable<nsCacheableFuncStringContentList::HashEntry>>
gFuncStringContentListHashTable;
template <class ListType>
already_AddRefed<nsContentList> GetFuncStringContentList(
nsINode* aRootNode, nsContentListMatchFunc aFunc,
nsContentListDestroyFunc aDestroyFunc,
nsFuncStringContentListDataAllocator aDataAllocator, const nsAString& aString) {
NS_ASSERTION(aRootNode, "content list has to have a root");
RefPtr<nsCacheableFuncStringContentList> list;
// Initialize the hashtable if needed. if (!gFuncStringContentListHashTable) {
gFuncStringContentListHashTable = new nsTHashtable<nsCacheableFuncStringContentList::HashEntry>();
}
nsCacheableFuncStringContentList::HashEntry* entry = nullptr; // First we look in our hashtable. Then we create a content list if needed if (gFuncStringContentListHashTable) {
nsFuncStringCacheKey hashKey(aRootNode, aFunc, aString);
entry = gFuncStringContentListHashTable->PutEntry(&hashKey, fallible); if (entry) {
list = entry->GetContentList(); #ifdef DEBUG
MOZ_ASSERT_IF(list, list->mType == ListType::sType); #endif
}
}
if (!list) { // We need to create a ContentList and add it to our new entry, if // we have an entry
list = new ListType(aRootNode, aFunc, aDestroyFunc, aDataAllocator, aString); if (entry) {
entry->SetContentList(list);
}
}
nsContentList::nsContentList(nsINode* aRootNode, int32_t aMatchNameSpaceId,
nsAtom* aHTMLMatchAtom, nsAtom* aXMLMatchAtom, bool aDeep, bool aLiveList)
: nsBaseContentList(),
mRootNode(aRootNode),
mMatchNameSpaceId(aMatchNameSpaceId),
mHTMLMatchAtom(aHTMLMatchAtom),
mXMLMatchAtom(aXMLMatchAtom),
mState(State::Dirty),
mDeep(aDeep),
mFuncMayDependOnAttr(false),
mIsHTMLDocument(aRootNode->OwnerDoc()->IsHTMLDocument()),
mNamedItemsCacheValid(false),
mIsLiveList(aLiveList),
mInHashtable(false) {
NS_ASSERTION(mRootNode, "Must have root"); if (nsGkAtoms::_asterisk == mHTMLMatchAtom) {
NS_ASSERTION(mXMLMatchAtom == nsGkAtoms::_asterisk, "HTML atom and XML atom are not both asterisk?");
mMatchAll = true;
} else {
mMatchAll = false;
} // This is aLiveList instead of mIsLiveList to avoid Valgrind errors. if (aLiveList) {
SetEnabledCallbacks(nsIMutationObserver::kNodeWillBeDestroyed);
mRootNode->AddMutationObserver(this);
}
// We only need to flush if we're in an non-HTML document, since the // HTML5 parser doesn't need flushing. Further, if we're not in a // document at all right now (in the GetUncomposedDoc() sense), we're // not parser-created and don't need to be flushing stuff under us // to get our kids right.
Document* doc = mRootNode->GetUncomposedDoc();
mFlushesNeeded = doc && !doc->IsHTMLDocument();
}
nsContentList::nsContentList(nsINode* aRootNode, nsContentListMatchFunc aFunc,
nsContentListDestroyFunc aDestroyFunc, void* aData, bool aDeep, nsAtom* aMatchAtom,
int32_t aMatchNameSpaceId, bool aFuncMayDependOnAttr, bool aLiveList)
: nsBaseContentList(),
mRootNode(aRootNode),
mMatchNameSpaceId(aMatchNameSpaceId),
mHTMLMatchAtom(aMatchAtom),
mXMLMatchAtom(aMatchAtom),
mFunc(aFunc),
mDestroyFunc(aDestroyFunc),
mData(aData),
mState(State::Dirty),
mMatchAll(false),
mDeep(aDeep),
mFuncMayDependOnAttr(aFuncMayDependOnAttr),
mIsHTMLDocument(false),
mNamedItemsCacheValid(false),
mIsLiveList(aLiveList),
mInHashtable(false) {
NS_ASSERTION(mRootNode, "Must have root"); // This is aLiveList instead of mIsLiveList to avoid Valgrind errors. if (aLiveList) {
SetEnabledCallbacks(nsIMutationObserver::kNodeWillBeDestroyed);
mRootNode->AddMutationObserver(this);
}
// We only need to flush if we're in an non-HTML document, since the // HTML5 parser doesn't need flushing. Further, if we're not in a // document at all right now (in the GetUncomposedDoc() sense), we're // not parser-created and don't need to be flushing stuff under us // to get our kids right.
Document* doc = mRootNode->GetUncomposedDoc();
mFlushesNeeded = doc && !doc->IsHTMLDocument();
}
nsContentList::~nsContentList() {
RemoveFromHashtable(); if (mIsLiveList && mRootNode) {
mRootNode->RemoveMutationObserver(this);
}
if (mDestroyFunc) { // Clean up mData
(*mDestroyFunc)(mData);
}
}
Element* el = aContent.AsElement();
MOZ_ASSERT_IF(hasName, el->IsHTMLElement());
uint32_t i = 0; while (BorrowedAttrInfo info = el->GetAttrInfoAt(i++)) { constbool valid = (info.mName->Equals(nsGkAtoms::name) && hasName) ||
(info.mName->Equals(nsGkAtoms::id) && hasId); if (!valid) { continue;
}
if (!mNamedItemsCache) {
mNamedItemsCache = MakeUnique<NamedItemsCache>();
}
nsAtom* name = info.mValue->GetAtomValue(); // NOTE: LookupOrInsert makes sure we keep the first element we find for a // given name.
mNamedItemsCache->LookupOrInsert(name, el);
}
}
AutoTArray<nsAtom*, 8> atoms; for (uint32_t i = 0; i < mElements.Length(); ++i) {
nsIContent* content = mElements.ElementAt(i); if (content->HasID()) {
nsAtom* id = content->GetID();
MOZ_ASSERT(id != nsGkAtoms::_empty, "Empty ids don't get atomized"); if (!atoms.Contains(id)) {
atoms.AppendElement(id);
}
}
nsGenericHTMLElement* el = nsGenericHTMLElement::FromNode(content); if (el) { // XXXbz should we be checking for particular tags here? How // stable is this part of the spec? // Note: nsINode::HasName means the name is exposed on the document, // which is false for options, so we don't check it here. const nsAttrValue* val = el->GetParsedAttr(nsGkAtoms::name); if (val && val->Type() == nsAttrValue::eAtom) {
nsAtom* name = val->GetAtomValue();
MOZ_ASSERT(name != nsGkAtoms::_empty, "Empty names don't get atomized"); if (!atoms.Contains(name)) {
atoms.AppendElement(name);
}
}
}
}
uint32_t atomsLen = atoms.Length();
nsString* names = aNames.AppendElements(atomsLen); for (uint32_t i = 0; i < atomsLen; ++i) {
atoms[i]->ToString(names[i]);
}
}
void nsContentList::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
nsAtom* aAttribute, int32_t aModType, const nsAttrValue* aOldValue) {
MOZ_ASSERT(aElement, "Must have a content node to work with");
if (mState == State::Dirty ||
!MayContainRelevantNodes(aElement->GetParentNode()) ||
!nsContentUtils::IsInSameAnonymousTree(mRootNode, aElement)) { // Either we're already dirty or aElement will never match us. return;
}
if (!mFunc || !mFuncMayDependOnAttr) { // aElement might be relevant but the attribute change doesn't affect // whether we match it. return;
}
if (Match(aElement)) { if (mElements.IndexOf(aElement) == mElements.NoIndex) { // We match aElement now, and it's not in our list already. Just dirty // ourselves; this is simpler than trying to figure out where to insert // aElement.
SetDirty();
}
} else { // We no longer match aElement. Remove it from our list. If it's // already not there, this is a no-op (though a potentially // expensive one). Either way, no change of mState is required // here. if (mElements.RemoveElement(aElement)) {
InvalidateNamedItemsCacheForDeletion(*aElement);
}
}
}
void nsContentList::ContentAppended(nsIContent* aFirstNewContent) {
nsIContent* container = aFirstNewContent->GetParent();
MOZ_ASSERT(container, "Can't get at the new content if no container!");
/* * If the state is State::Dirty then we have no useful information in our list * and we want to put off doing work as much as possible. * * Also, if container is anonymous from our point of view, we know that we * can't possibly be matching any of the kids. * * Optimize out also the common case when just one new node is appended and * it doesn't match us.
*/ if (mState == State::Dirty ||
!nsContentUtils::IsInSameAnonymousTree(mRootNode, container) ||
!MayContainRelevantNodes(container) ||
(!aFirstNewContent->HasChildren() &&
!aFirstNewContent->GetNextSibling() && !MatchSelf(aFirstNewContent))) {
MaybeMarkDirty(); return;
}
/* * We want to handle the case of ContentAppended by sometimes * appending the content to our list, not just setting state to * State::Dirty, since most of our ContentAppended notifications * should come during pageload and be at the end of the document. * Do a bit of work to see whether we could just append to what we * already have.
*/
if (!appendingToList) { // The new stuff is somewhere in the middle of our list; check // whether we need to invalidate for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { if (MatchSelf(cur)) { // Uh-oh. We're gonna have to add elements into the middle // of our list. That's not worth the effort.
SetDirty(); break;
}
}
ASSERT_IN_SYNC; return;
}
/* * At this point we know we could append. If we're not up to * date, however, that would be a bad idea -- it could miss some * content that we never picked up due to being lazy. Further, we * may never get asked for this content... so don't grab it yet.
*/ if (mState == State::Lazy) { return;
}
/* * We're up to date. That means someone's actively using us; we * may as well grab this content....
*/ if (mDeep) { for (nsIContent* cur = aFirstNewContent; cur;
cur = cur->GetNextNode(container)) { if (cur->IsElement() && Match(cur->AsElement())) {
mElements.AppendElement(cur);
InvalidateNamedItemsCacheForInsertion(*cur->AsElement());
}
}
} else { for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { if (cur->IsElement() && Match(cur->AsElement())) {
mElements.AppendElement(cur);
InvalidateNamedItemsCacheForInsertion(*cur->AsElement());
}
}
}
ASSERT_IN_SYNC;
}
void nsContentList::ContentInserted(nsIContent* aChild) { // Note that aChild->GetParentNode() can be null here if we are inserting into // the document itself; any attempted optimizations to this method should deal // with that. if (mState != State::Dirty &&
MayContainRelevantNodes(aChild->GetParentNode()) &&
nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild) &&
MatchSelf(aChild)) {
SetDirty();
}
bool nsContentList::MatchSelf(nsIContent* aContent) {
MOZ_ASSERT(aContent, "Can't match null stuff, you know");
MOZ_ASSERT(mDeep || aContent->GetParentNode() == mRootNode, "MatchSelf called on a node that we can't possibly match");
if (!aContent->IsElement()) { returnfalse;
}
if (Match(aContent->AsElement())) returntrue;
if (!mDeep) returnfalse;
for (nsIContent* cur = aContent->GetFirstChild(); cur;
cur = cur->GetNextNode(aContent)) { if (cur->IsElement() && Match(cur->AsElement())) { returntrue;
}
}
uint32_t count = mElements.Length();
NS_ASSERTION(mState != State::Dirty || count == aExpectedElementsIfDirty, "Reset() not called when setting state to State::Dirty?");
if (count >= aNeededLength) // We're all set return;
if (mDeep) { // If we already have nodes start searching at the last one, otherwise // start searching at the root.
nsINode* cur = count ? mElements[count - 1].get() : mRootNode; do {
cur = cur->GetNextNode(mRootNode); if (!cur) { break;
} if (cur->IsElement() && Match(cur->AsElement())) { // Append AsElement() to get nsIContent instead of nsINode
mElements.AppendElement(cur->AsElement());
--elementsToAppend;
}
} while (elementsToAppend);
} else {
nsIContent* cur = count ? mElements[count - 1]->GetNextSibling()
: mRootNode->GetFirstChild(); for (; cur && elementsToAppend; cur = cur->GetNextSibling()) { if (cur->IsElement() && Match(cur->AsElement())) {
mElements.AppendElement(cur);
--elementsToAppend;
}
}
}
NS_ASSERTION(elementsToAppend + mElements.Length() == invariant, "Something is awry!");
void nsContentList::RemoveFromHashtable() { if (mFunc) { // nsCacheableFuncStringContentList can be in a hash table without being // in gContentListHashTable, but it will have been removed from the hash // table in its dtor before it runs the nsContentList dtor.
MOZ_RELEASE_ASSERT(!mInHashtable);
// This can't be in gContentListHashTable. return;
}
// XXX This code will need to change if nsContentLists can ever match // elements that are outside of the document element.
nsIContent* root = mRootNode->IsDocument()
? mRootNode->AsDocument()->GetRootElement()
: mRootNode->AsContent();
PreContentIterator preOrderIter; if (mDeep) {
preOrderIter.Init(root);
preOrderIter.First();
}
uint32_t cnt = 0, index = 0; while (true) { if (cnt == mElements.Length() && mState == State::Lazy) { break;
}
nsIContent* cur =
mDeep ? preOrderIter.GetCurrentNode() : mRootNode->GetChildAt(index++); if (!cur) { break;
}
if (cur->IsElement() && Match(cur->AsElement())) {
NS_ASSERTION(cnt < mElements.Length() && mElements[cnt] == cur, "Elements is out of sync");
++cnt;
}
if (mDeep) {
preOrderIter.Next();
}
}
NS_ASSERTION(cnt == mElements.Length(), "Too few elements");
} #endif
void nsCachableElementsByNameNodeList::AttributeChanged(
Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
int32_t aModType, const nsAttrValue* aOldValue) { // No need to rebuild the list if the changed attribute is not the name // attribute. if (aAttribute != nsGkAtoms::name) {
InvalidateNamedItemsCacheForAttributeChange(aNameSpaceID, aAttribute); 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(nsIContent* aFirstNewContent) {
nsIContent* container = aFirstNewContent->GetParent(); // 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 != State::Dirty &&
nsContentUtils::IsInSameAnonymousTree(mRootNode, container)) {
SetDirty(); return;
}
}
void nsLabelsNodeList::ContentInserted(nsIContent* aChild) { // 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 != State::Dirty &&
nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild)) {
SetDirty(); return;
}
}
void nsLabelsNodeList::ContentWillBeRemoved(nsIContent* aChild, const BatchRemovalState* aState) { // If a labelable element is removed, we're gonna have to clean // the content list. if (mState != State::Dirty &&
nsContentUtils::IsInSameAnonymousTree(mRootNode, aChild)) {
SetDirty(); return;
}
}
void nsLabelsNodeList::MaybeResetRoot(nsINode* aRootNode) {
MOZ_ASSERT(aRootNode, "Must have root"); if (mRootNode == aRootNode) { return;
}
MOZ_ASSERT(mIsLiveList, "nsLabelsNodeList is always a live list"); if (mRootNode) {
mRootNode->RemoveMutationObserver(this);
}
mRootNode = aRootNode;
mRootNode->AddMutationObserver(this);
SetDirty();
}
// Start searching at the root.
nsINode* cur = mRootNode; if (mElements.IsEmpty() && cur->IsElement() && Match(cur->AsElement())) {
mElements.AppendElement(cur->AsElement());
++aExpectedElementsIfDirty;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.