https://bugs.webkit.org/show_bug.cgi?id=129727
Reviewed by Andreas Kling.
Before this patch, invalidateNodeListAndCollectionCachesInAncestors invalidated node lists and HTML
collections on ancestors of a node whenever we're inserting or removing a child node. This patch
makes HTMLCollections and LiveNodeLists register themselves with Document only when they have valid
caches.
Each user of CollectionIndexCache now implements willValidateIndexCache member function that gets
called when CollectionIndexCache caches any state and necessitates the registration with document.
* dom/ChildNodeList.h: Added an empty willValidateIndexCache since child node lists are never
registered with document.
* dom/CollectionIndexCache.h:
(WebCore::CollectionIndexCache::hasValidCache): Added.
(WebCore::CollectionIndexCache::nodeCount): Calls willValidateIndexCache when caching node count.
(WebCore::CollectionIndexCache::nodeAfterCached): Ditto. Also assert that hasValidCache() true in
the cases where we're simply updating our caches or adding more caches.
(WebCore::CollectionIndexCache::nodeAt): Ditto. Also added a code to set the length cache when
we've reached the end of the list. This should be a slight speed up on some cases.
* dom/Document.cpp:
(WebCore::Document::Document): Initializes a variable used by assertions.
(WebCore::Document::unregisterNodeList): Added an early exit for when m_listsInvalidatedAtDocument
is empty since invalidateNodeListAndCollectionCaches swaps out the list.
(WebCore::Document::registerCollection): Removed the boolean hasIdNameMap since we now explicitly
call collectionCachedIdNameMap in HTMLCollection.
(WebCore::Document::unregisterCollection): Ditto. Exit early if m_collectionsInvalidatedAtDocument
is empty since invalidateNodeListAndCollectionCaches swaps out the list.
* dom/Document.h:
* dom/LiveNodeList.cpp:
(WebCore::LiveNodeList::invalidateCache): Unregister the node list with document if we had caches.
* dom/LiveNodeList.h:
(WebCore::LiveNodeList::LiveNodeList):
(WebCore::LiveNodeList::~LiveNodeList): Ditto.
(WebCore::LiveNodeList::invalidateCache): Pass around document. This is necessary since document()
had already moved to the new document inside NodeListsNodeData::invalidateCaches.
(WebCore::LiveNodeList::willValidateIndexCache): Added. Registers itself with document.
* dom/Node.cpp:
(WebCore::Document::invalidateNodeListAndCollectionCaches): Swap the lists since invalidateCache
tries to unregister node lists and HTML collections with document. Since this is the only case in
which node lists and HTML collections being removed may not be in the lists in unregisterNodeList
and unregisterCollection, assert this condition via m_inInvalidateNodeListAndCollectionCaches.
(WebCore::NodeListsNodeData::invalidateCaches):
* dom/NodeRareData.h:
(WebCore::NodeListsNodeData::adoptDocument): Unregister node lists and HTML collections from old
document via invalidateCache. We need to explicitly pass in oldDocument here since owner node's
document had already been changed to newDocument at this point. Since we're invalidating caches,
there is no need to register node lists and HTML collections with newDocument.
* html/HTMLCollection.cpp:
(WebCore::HTMLCollection::HTMLCollection):
(WebCore::HTMLCollection::~HTMLCollection): Unregister the node list with document if we had caches.
(WebCore::HTMLCollection::invalidateCache): Ditto.
(WebCore::HTMLCollection::invalidateNamedElementCache):
* html/HTMLCollection.h:
(WebCore::HTMLCollection::invalidateCache): Pass around document as done in LiveNodeList.
(WebCore::HTMLCollection::willValidateIndexCache): Ditto.
* html/HTMLFormControlsCollection.cpp:
(WebCore::HTMLFormControlsCollection::invalidateCache): Ditto.
* html/HTMLFormControlsCollection.h:
* html/HTMLSelectElement.cpp:
(WebCore::HTMLSelectElement::invalidateSelectedItems): Ditto.
(WebCore::HTMLSelectElement::setRecalcListItems): Ditto.
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@165103
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2014-03-05 Ryosuke Niwa <rniwa@webkit.org>
+
+ appendChild shouldn't invalidate LiveNodeLists and HTMLCollections if they don't have valid caches
+ https://bugs.webkit.org/show_bug.cgi?id=129727
+
+ Reviewed by Andreas Kling.
+
+ Before this patch, invalidateNodeListAndCollectionCachesInAncestors invalidated node lists and HTML
+ collections on ancestors of a node whenever we're inserting or removing a child node. This patch
+ makes HTMLCollections and LiveNodeLists register themselves with Document only when they have valid
+ caches.
+
+ Each user of CollectionIndexCache now implements willValidateIndexCache member function that gets
+ called when CollectionIndexCache caches any state and necessitates the registration with document.
+
+ * dom/ChildNodeList.h: Added an empty willValidateIndexCache since child node lists are never
+ registered with document.
+
+ * dom/CollectionIndexCache.h:
+ (WebCore::CollectionIndexCache::hasValidCache): Added.
+ (WebCore::CollectionIndexCache::nodeCount): Calls willValidateIndexCache when caching node count.
+ (WebCore::CollectionIndexCache::nodeAfterCached): Ditto. Also assert that hasValidCache() true in
+ the cases where we're simply updating our caches or adding more caches.
+ (WebCore::CollectionIndexCache::nodeAt): Ditto. Also added a code to set the length cache when
+ we've reached the end of the list. This should be a slight speed up on some cases.
+
+ * dom/Document.cpp:
+ (WebCore::Document::Document): Initializes a variable used by assertions.
+ (WebCore::Document::unregisterNodeList): Added an early exit for when m_listsInvalidatedAtDocument
+ is empty since invalidateNodeListAndCollectionCaches swaps out the list.
+ (WebCore::Document::registerCollection): Removed the boolean hasIdNameMap since we now explicitly
+ call collectionCachedIdNameMap in HTMLCollection.
+ (WebCore::Document::unregisterCollection): Ditto. Exit early if m_collectionsInvalidatedAtDocument
+ is empty since invalidateNodeListAndCollectionCaches swaps out the list.
+ * dom/Document.h:
+
+ * dom/LiveNodeList.cpp:
+ (WebCore::LiveNodeList::invalidateCache): Unregister the node list with document if we had caches.
+ * dom/LiveNodeList.h:
+ (WebCore::LiveNodeList::LiveNodeList):
+ (WebCore::LiveNodeList::~LiveNodeList): Ditto.
+ (WebCore::LiveNodeList::invalidateCache): Pass around document. This is necessary since document()
+ had already moved to the new document inside NodeListsNodeData::invalidateCaches.
+ (WebCore::LiveNodeList::willValidateIndexCache): Added. Registers itself with document.
+
+ * dom/Node.cpp:
+ (WebCore::Document::invalidateNodeListAndCollectionCaches): Swap the lists since invalidateCache
+ tries to unregister node lists and HTML collections with document. Since this is the only case in
+ which node lists and HTML collections being removed may not be in the lists in unregisterNodeList
+ and unregisterCollection, assert this condition via m_inInvalidateNodeListAndCollectionCaches.
+ (WebCore::NodeListsNodeData::invalidateCaches):
+
+ * dom/NodeRareData.h:
+ (WebCore::NodeListsNodeData::adoptDocument): Unregister node lists and HTML collections from old
+ document via invalidateCache. We need to explicitly pass in oldDocument here since owner node's
+ document had already been changed to newDocument at this point. Since we're invalidating caches,
+ there is no need to register node lists and HTML collections with newDocument.
+
+ * html/HTMLCollection.cpp:
+ (WebCore::HTMLCollection::HTMLCollection):
+ (WebCore::HTMLCollection::~HTMLCollection): Unregister the node list with document if we had caches.
+ (WebCore::HTMLCollection::invalidateCache): Ditto.
+ (WebCore::HTMLCollection::invalidateNamedElementCache):
+ * html/HTMLCollection.h:
+ (WebCore::HTMLCollection::invalidateCache): Pass around document as done in LiveNodeList.
+ (WebCore::HTMLCollection::willValidateIndexCache): Ditto.
+
+ * html/HTMLFormControlsCollection.cpp:
+ (WebCore::HTMLFormControlsCollection::invalidateCache): Ditto.
+ * html/HTMLFormControlsCollection.h:
+
+ * html/HTMLSelectElement.cpp:
+ (WebCore::HTMLSelectElement::invalidateSelectedItems): Ditto.
+ (WebCore::HTMLSelectElement::setRecalcListItems): Ditto.
+
2014-03-05 Jon Lee <jonlee@apple.com>
Fix linker error after r165087
Node* collectionTraverseForward(Node&, unsigned count, unsigned& traversedCount) const;
Node* collectionTraverseBackward(Node&, unsigned count) const;
bool collectionCanTraverseBackward() const { return true; }
+ void willValidateIndexCache() const { }
private:
explicit ChildNodeList(ContainerNode& parent);
unsigned nodeCount(const Collection&);
NodeType* nodeAt(const Collection&, unsigned index);
+ bool hasValidCache() const { return m_currentNode || m_nodeCountValid || m_listValid; }
void invalidate();
size_t memoryCost() { return m_cachedList.capacity() * sizeof(NodeType*); }
private:
+
unsigned computeNodeCountUpdatingListCache(const Collection&);
NodeType* nodeBeforeCached(const Collection&, unsigned);
NodeType* nodeAfterCached(const Collection&, unsigned);
inline unsigned CollectionIndexCache<Collection, NodeType>::nodeCount(const Collection& collection)
{
if (!m_nodeCountValid) {
+ if (!hasValidCache())
+ collection.willValidateIndexCache();
m_nodeCount = computeNodeCountUpdatingListCache(collection);
m_nodeCountValid = true;
}
bool lastIsCloser = m_nodeCountValid && m_nodeCount - index < index - m_currentIndex;
if (lastIsCloser && collection.collectionCanTraverseBackward()) {
+ ASSERT(hasValidCache());
m_currentNode = collection.collectionLast();
if (index < m_nodeCount - 1)
m_currentNode = collection.collectionTraverseBackward(*m_currentNode, m_nodeCount - index - 1);
return m_currentNode;
}
+ if (!hasValidCache())
+ collection.willValidateIndexCache();
+
unsigned traversedCount;
m_currentNode = collection.collectionTraverseForward(*m_currentNode, index - m_currentIndex, traversedCount);
m_currentIndex = m_currentIndex + traversedCount;
m_nodeCount = m_currentIndex + 1;
m_nodeCountValid = true;
}
+ ASSERT(hasValidCache());
return m_currentNode;
}
bool lastIsCloser = m_nodeCountValid && m_nodeCount - index < index;
if (lastIsCloser && collection.collectionCanTraverseBackward()) {
+ ASSERT(hasValidCache());
m_currentNode = collection.collectionLast();
if (index < m_nodeCount - 1)
m_currentNode = collection.collectionTraverseBackward(*m_currentNode, m_nodeCount - index - 1);
return m_currentNode;
}
+ if (!hasValidCache())
+ collection.willValidateIndexCache();
+
m_currentNode = collection.collectionFirst();
m_currentIndex = 0;
if (index && m_currentNode) {
m_currentNode = collection.collectionTraverseForward(*m_currentNode, index, m_currentIndex);
ASSERT(m_currentNode || m_currentIndex < index);
}
+ if (!m_currentNode && !m_nodeCountValid) {
+ // Failed to find the index but at least we now know the size.
+ m_nodeCount = m_currentIndex + 1;
+ m_nodeCountValid = true;
+ }
+ ASSERT(hasValidCache());
return m_currentNode;
}
, m_xmlStandalone(StandaloneUnspecified)
, m_hasXMLDeclaration(false)
, m_designMode(inherit)
+#if !ASSERT_DISABLED
+ , m_inInvalidateNodeListAndCollectionCaches(false)
+#endif
#if ENABLE(DASHBOARD_SUPPORT)
, m_hasAnnotatedRegions(false)
, m_annotatedRegionsDirty(false)
{
m_nodeListAndCollectionCounts[list.invalidationType()]--;
if (list.isRootedAtDocument()) {
+ if (!m_listsInvalidatedAtDocument.size()) {
+ ASSERT(m_inInvalidateNodeListAndCollectionCaches);
+ return;
+ }
ASSERT(m_listsInvalidatedAtDocument.contains(&list));
m_listsInvalidatedAtDocument.remove(&list);
}
}
-void Document::registerCollection(HTMLCollection& collection, bool hasIdNameMap)
+void Document::registerCollection(HTMLCollection& collection)
{
- if (hasIdNameMap)
- collectionCachedIdNameMap(collection);
m_nodeListAndCollectionCounts[collection.invalidationType()]++;
if (collection.isRootedAtDocument())
m_collectionsInvalidatedAtDocument.add(&collection);
}
-void Document::unregisterCollection(HTMLCollection& collection, bool hasIdNameMap)
+void Document::unregisterCollection(HTMLCollection& collection)
{
- if (hasIdNameMap)
- collectionWillClearIdNameMap(collection);
ASSERT(m_nodeListAndCollectionCounts[collection.invalidationType()]);
m_nodeListAndCollectionCounts[collection.invalidationType()]--;
if (collection.isRootedAtDocument()) {
+ if (!m_collectionsInvalidatedAtDocument.size()) {
+ ASSERT(m_inInvalidateNodeListAndCollectionCaches);
+ return;
+ }
ASSERT(m_collectionsInvalidatedAtDocument.contains(&collection));
m_collectionsInvalidatedAtDocument.remove(&collection);
}
void registerNodeList(LiveNodeList&);
void unregisterNodeList(LiveNodeList&);
- void registerCollection(HTMLCollection&, bool hasNamedElementCache);
- void unregisterCollection(HTMLCollection&, bool hasNamedElementCache);
+ void registerCollection(HTMLCollection&);
+ void unregisterCollection(HTMLCollection&);
void collectionCachedIdNameMap(const HTMLCollection&);
void collectionWillClearIdNameMap(const HTMLCollection&);
bool shouldInvalidateNodeListAndCollectionCaches(const QualifiedName* attrName = nullptr) const;
HashSet<LiveNodeList*> m_listsInvalidatedAtDocument;
HashSet<HTMLCollection*> m_collectionsInvalidatedAtDocument;
+#if !ASSERT_DISABLED
+ bool m_inInvalidateNodeListAndCollectionCaches;
+#endif
unsigned m_nodeListAndCollectionCounts[numNodeListInvalidationTypes];
return m_indexCache.memoryCost();
}
-void LiveNodeList::invalidateCache() const
+void LiveNodeList::invalidateCache(Document& document) const
{
+ if (!m_indexCache.hasValidCache())
+ return;
+ document.unregisterNodeList(const_cast<LiveNodeList&>(*this));
m_indexCache.invalidate();
}
ASSERT(m_rootType == static_cast<unsigned>(rootType));
ASSERT(m_invalidationType == static_cast<unsigned>(invalidationType));
ASSERT(m_type == static_cast<unsigned>(type));
-
- document().registerNodeList(*this);
}
virtual Node* namedItem(const AtomicString&) const override final;
virtual bool nodeMatches(Element*) const = 0;
virtual ~LiveNodeList()
{
- document().unregisterNodeList(*this);
+ if (m_indexCache.hasValidCache())
+ document().unregisterNodeList(*this);
}
// DOM API
ALWAYS_INLINE void invalidateCache(const QualifiedName* attrName) const
{
if (!attrName || shouldInvalidateTypeOnAttributeChange(invalidationType(), *attrName))
- invalidateCache();
+ invalidateCache(document());
}
- void invalidateCache() const;
+ void invalidateCache(Document&) const;
// For CollectionIndexCache
Element* collectionFirst() const;
Element* collectionTraverseForward(Element&, unsigned count, unsigned& traversedCount) const;
Element* collectionTraverseBackward(Element&, unsigned count) const;
bool collectionCanTraverseBackward() const { return true; }
+ void willValidateIndexCache() const { document().registerNodeList(const_cast<LiveNodeList&>(*this)); }
protected:
Document& document() const { return m_ownerNode->document(); }
void Document::invalidateNodeListAndCollectionCaches(const QualifiedName* attrName)
{
- for (HashSet<LiveNodeList*>::iterator it = m_listsInvalidatedAtDocument.begin(), end = m_listsInvalidatedAtDocument.end(); it != end; ++it)
- (*it)->invalidateCache(attrName);
- for (HashSet<HTMLCollection*>::iterator it = m_collectionsInvalidatedAtDocument.begin(), end = m_collectionsInvalidatedAtDocument.end(); it != end; ++it)
- (*it)->invalidateCache(attrName);
+#if !ASSERT_DISABLED
+ m_inInvalidateNodeListAndCollectionCaches = true;
+#endif
+ HashSet<LiveNodeList*> liveNodeLists = std::move(m_listsInvalidatedAtDocument);
+ for (auto it : liveNodeLists)
+ it->invalidateCache(attrName);
+
+ HashSet<HTMLCollection*> collectionLists = std::move(m_collectionsInvalidatedAtDocument);
+ for (auto it : collectionLists)
+ it->invalidateCache(attrName);
+#if !ASSERT_DISABLED
+ m_inInvalidateNodeListAndCollectionCaches = false;
+#endif
}
void Node::invalidateNodeListAndCollectionCachesInAncestors(const QualifiedName* attrName, Element* attributeOwnerElement)
return;
for (auto& tagNodeList : m_tagNodeListCacheNS)
- tagNodeList.value->invalidateCache();
+ tagNodeList.value->invalidateCache(nullptr);
}
void Node::getSubresourceURLs(ListHashSet<URL>& urls) const
void adoptDocument(Document* oldDocument, Document* newDocument)
{
+ ASSERT(oldDocument);
if (oldDocument == newDocument) {
invalidateCaches();
return;
}
- for (auto it : m_atomicNameCaches) {
- LiveNodeList& list = *it.value;
- oldDocument->unregisterNodeList(list);
- newDocument->registerNodeList(list);
- list.invalidateCache();
- }
+ for (auto it : m_atomicNameCaches)
+ it.value->invalidateCache(*oldDocument);
- for (auto it : m_nameCaches) {
- LiveNodeList& list = *it.value;
- oldDocument->unregisterNodeList(list);
- newDocument->registerNodeList(list);
- list.invalidateCache();
- }
+ for (auto it : m_nameCaches)
+ it.value->invalidateCache(*oldDocument);
for (auto it : m_tagNodeListCacheNS) {
LiveNodeList& list = *it.value;
ASSERT(!list.isRootedAtDocument());
- oldDocument->unregisterNodeList(list);
- newDocument->registerNodeList(list);
- list.invalidateCache();
+ list.invalidateCache(*oldDocument);
}
- for (auto it : m_cachedCollections) {
- HTMLCollection& collection = *it.value;
- oldDocument->unregisterCollection(collection, collection.hasNamedElementCache());
- newDocument->registerCollection(collection, collection.hasNamedElementCache());
- collection.invalidateCache();
- }
+ for (auto it : m_cachedCollections)
+ it.value->invalidateCache(*oldDocument);
}
private:
ASSERT(m_rootType == static_cast<unsigned>(rootTypeFromCollectionType(type)));
ASSERT(m_invalidationType == static_cast<unsigned>(invalidationTypeExcludingIdAndNameAttributes(type)));
ASSERT(m_collectionType == static_cast<unsigned>(type));
-
- document().registerCollection(*this, hasNamedElementCache());
}
PassRefPtr<HTMLCollection> HTMLCollection::create(ContainerNode& base, CollectionType type)
HTMLCollection::~HTMLCollection()
{
- document().unregisterCollection(*this, hasNamedElementCache());
+ if (m_indexCache.hasValidCache())
+ document().unregisterCollection(*this);
+ if (hasNamedElementCache())
+ document().collectionWillClearIdNameMap(*this);
// HTMLNameCollection removes cache by itself.
if (type() != WindowNamedItems && type() != DocumentNamedItems)
ownerNode().nodeLists()->removeCachedCollection(this);
return element;
}
-void HTMLCollection::invalidateCache() const
+void HTMLCollection::invalidateCache(Document& document) const
{
- m_indexCache.invalidate();
+ if (m_indexCache.hasValidCache()) {
+ document.unregisterCollection(const_cast<HTMLCollection&>(*this));
+ m_indexCache.invalidate();
+ }
if (hasNamedElementCache())
- invalidateNamedElementCache();
+ invalidateNamedElementCache(document);
}
-void HTMLCollection::invalidateNamedElementCache() const
+void HTMLCollection::invalidateNamedElementCache(Document& document) const
{
ASSERT(hasNamedElementCache());
- document().collectionWillClearIdNameMap(*this);
+ document.collectionWillClearIdNameMap(*this);
m_namedElementCache = nullptr;
}
void invalidateCache(const QualifiedName* attrName) const
{
if (!attrName || shouldInvalidateTypeOnAttributeChange(invalidationType(), *attrName))
- invalidateCache();
+ invalidateCache(document());
else if (hasNamedElementCache() && (*attrName == HTMLNames::idAttr || *attrName == HTMLNames::nameAttr))
- invalidateNamedElementCache();
+ invalidateNamedElementCache(document());
}
- virtual void invalidateCache() const;
+ virtual void invalidateCache(Document&) const;
// For CollectionIndexCache
Element* collectionFirst() const;
Element* collectionTraverseForward(Element&, unsigned count, unsigned& traversedCount) const;
Element* collectionTraverseBackward(Element&, unsigned count) const;
bool collectionCanTraverseBackward() const { return !m_usesCustomForwardOnlyTraversal; }
+ void willValidateIndexCache() const { document().registerCollection(const_cast<HTMLCollection&>(*this)); }
bool hasNamedElementCache() const { return !!m_namedElementCache; }
virtual Element* customElementAfter(Element*) const { ASSERT_NOT_REACHED(); return nullptr; }
- void invalidateNamedElementCache() const;
+ void invalidateNamedElementCache(Document&) const;
Ref<ContainerNode> m_ownerNode;
cache.didPopulate();
}
-void HTMLFormControlsCollection::invalidateCache() const
+void HTMLFormControlsCollection::invalidateCache(Document& document) const
{
- HTMLCollection::invalidateCache();
+ HTMLCollection::invalidateCache(document);
m_cachedElement = nullptr;
m_cachedElementOffsetInArray = 0;
}
private:
explicit HTMLFormControlsCollection(ContainerNode&);
- virtual void invalidateCache() const override;
+ virtual void invalidateCache(Document&) const override;
virtual void updateNamedElementCache() const override;
const Vector<FormAssociatedElement*>& formControlElements() const;
void HTMLSelectElement::invalidateSelectedItems()
{
if (HTMLCollection* collection = cachedHTMLCollection(SelectedOptions))
- collection->invalidateCache();
+ collection->invalidateCache(document());
}
void HTMLSelectElement::setRecalcListItems()
setNeedsStyleRecalc();
if (!inDocument()) {
if (HTMLCollection* collection = cachedHTMLCollection(SelectOptions))
- collection->invalidateCache();
+ collection->invalidateCache(document());
}
if (!inDocument())
invalidateSelectedItems();