Microdata: Implement cache mechanism for HTMLPropertiesCollection.
authorarko@motorola.com <arko@motorola.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 11 Apr 2012 15:20:04 +0000 (15:20 +0000)
committerarko@motorola.com <arko@motorola.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 11 Apr 2012 15:20:04 +0000 (15:20 +0000)
https://bugs.webkit.org/show_bug.cgi?id=80490

Reviewed by Ryosuke Niwa.

Implemented caching mechanism for HTMLPropertiesCollection.
propertyCache - contains microdata item properties.
itemRefElements - contains sorted microdata item and itemref elements.
propertyNames - contains microdata property names of the elements in the collection.
itemRefElementPosition - store the current position of itemRefElements.
hasItemRefElements - set to ture once we have sorted microdata item and itemref elements list i.e, itemRefElements.
Cache is invalidated only when dom tree modified. Whenever any query is made on HTMLPropertiesCollection,
result is returned from the cache. Earliar we used to calculate properties node list every time a query is made.

* html/HTMLPropertiesCollection.cpp:
(WebCore):
(WebCore::HTMLPropertiesCollection::HTMLPropertiesCollection):
(WebCore::HTMLPropertiesCollection::invalidateCacheIfNeeded):
(WebCore::HTMLPropertiesCollection::updateRefElements): Appends microdata item element and elements which
are added through itemref attribute in itemRefElements (in tree order).
(WebCore::nextNodeWithProperty):
(WebCore::HTMLPropertiesCollection::itemAfter): Takes parent base and previous item property as argument.
Finds the next item property in base.
(WebCore::HTMLPropertiesCollection::calcLength): Calculates the length of properties collection if length
is not available in the cache.
(WebCore::HTMLPropertiesCollection::length):
(WebCore::HTMLPropertiesCollection::firstProperty): Returns the first property in the collection.
(WebCore::HTMLPropertiesCollection::item):
(WebCore::HTMLPropertiesCollection::findProperties): Finds microdata item properties in the base element.
Appends the properties in propertyCache and property names in propertyNames.
(WebCore::HTMLPropertiesCollection::updateNameCache):  It updates the propertyCache and propertyNames if hasNameCache is false.
(WebCore::HTMLPropertiesCollection::names):
(WebCore::HTMLPropertiesCollection::namedItem):
(WebCore::HTMLPropertiesCollection::hasNamedItem):
* html/HTMLPropertiesCollection.h:
(HTMLPropertiesCollection):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@113862 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/html/HTMLPropertiesCollection.cpp
Source/WebCore/html/HTMLPropertiesCollection.h

index c4b1f6a..0d218bc 100644 (file)
@@ -1,3 +1,42 @@
+2012-04-11   Arko Saha  <arko@motorola.com>
+
+        Microdata: Implement cache mechanism for HTMLPropertiesCollection.
+        https://bugs.webkit.org/show_bug.cgi?id=80490
+
+        Reviewed by Ryosuke Niwa.
+
+        Implemented caching mechanism for HTMLPropertiesCollection.
+        propertyCache - contains microdata item properties.
+        itemRefElements - contains sorted microdata item and itemref elements.
+        propertyNames - contains microdata property names of the elements in the collection.
+        itemRefElementPosition - store the current position of itemRefElements.
+        hasItemRefElements - set to ture once we have sorted microdata item and itemref elements list i.e, itemRefElements.
+        Cache is invalidated only when dom tree modified. Whenever any query is made on HTMLPropertiesCollection,
+        result is returned from the cache. Earliar we used to calculate properties node list every time a query is made.
+
+        * html/HTMLPropertiesCollection.cpp:
+        (WebCore):
+        (WebCore::HTMLPropertiesCollection::HTMLPropertiesCollection):
+        (WebCore::HTMLPropertiesCollection::invalidateCacheIfNeeded):
+        (WebCore::HTMLPropertiesCollection::updateRefElements): Appends microdata item element and elements which
+        are added through itemref attribute in itemRefElements (in tree order).
+        (WebCore::nextNodeWithProperty):
+        (WebCore::HTMLPropertiesCollection::itemAfter): Takes parent base and previous item property as argument.
+        Finds the next item property in base.
+        (WebCore::HTMLPropertiesCollection::calcLength): Calculates the length of properties collection if length
+        is not available in the cache.
+        (WebCore::HTMLPropertiesCollection::length):
+        (WebCore::HTMLPropertiesCollection::firstProperty): Returns the first property in the collection.
+        (WebCore::HTMLPropertiesCollection::item):
+        (WebCore::HTMLPropertiesCollection::findProperties): Finds microdata item properties in the base element.
+        Appends the properties in propertyCache and property names in propertyNames.
+        (WebCore::HTMLPropertiesCollection::updateNameCache):  It updates the propertyCache and propertyNames if hasNameCache is false.
+        (WebCore::HTMLPropertiesCollection::names):
+        (WebCore::HTMLPropertiesCollection::namedItem):
+        (WebCore::HTMLPropertiesCollection::hasNamedItem):
+        * html/HTMLPropertiesCollection.h:
+        (HTMLPropertiesCollection):
+
 2012-04-10  Jocelyn Turcotte  <jocelyn.turcotte@nokia.com>
 
         [Qt] Fix issues when using the WebView as ShaderEffectSource
index 68045cb..f0a1412 100644 (file)
@@ -45,11 +45,6 @@ namespace WebCore {
 
 using namespace HTMLNames;
 
-static inline bool compareTreeOrder(Node* node1, Node* node2)
-{
-    return (node2->compareDocumentPosition(node1) & (Node::DOCUMENT_POSITION_PRECEDING | Node::DOCUMENT_POSITION_DISCONNECTED)) == Node::DOCUMENT_POSITION_PRECEDING;
-}
-
 PassOwnPtr<HTMLPropertiesCollection> HTMLPropertiesCollection::create(Node* itemNode)
 {
     return adoptPtr(new HTMLPropertiesCollection(itemNode));
@@ -57,7 +52,6 @@ PassOwnPtr<HTMLPropertiesCollection> HTMLPropertiesCollection::create(Node* item
 
 HTMLPropertiesCollection::HTMLPropertiesCollection(Node* itemNode)
     : HTMLCollection(itemNode, ItemProperties)
-    , m_propertyNames(DOMStringList::create())
 {
 }
 
@@ -65,132 +59,217 @@ HTMLPropertiesCollection::~HTMLPropertiesCollection()
 {
 }
 
-void HTMLPropertiesCollection::findPropetiesOfAnItem(Node* root) const
+void HTMLPropertiesCollection::invalidateCacheIfNeeded() const
 {
-    // 5.2.5 Associating names with items.
-    Vector<Node*> memory;
+    uint64_t docversion = base()->document()->domTreeVersion();
 
-    memory.append(root);
+    if (m_cache.version == docversion)
+        return;
 
-    Vector<Node*> pending;
-    // Add the child elements of root, if any, to pending.
-    for (Node* child = root->firstChild(); child; child = child->nextSibling())
-        if (child->isHTMLElement())
-            pending.append(child);
+    m_cache.clear();
+    m_cache.version = docversion;
+}
 
-    // If root has an itemref attribute, split the value of that itemref attribute on spaces.
-    // For each resulting token ID, if there is an element in the home subtree of root with the ID ID,
-    // then add the first such element to pending.
-    if (toHTMLElement(root)->fastHasAttribute(itemrefAttr)) {
-        DOMSettableTokenList* itemRef = root->itemRef();
+void HTMLPropertiesCollection::updateRefElements() const
+{
+    if (m_cache.hasItemRefElements)
+        return;
 
-        for (size_t i = 0; i < itemRef->length(); ++i) {
-            AtomicString id = itemRef->item(i);
+    Vector<Element*> itemRefElements;
+    HTMLElement* baseElement = toHTMLElement(base());
 
-            Element* element = root->document()->getElementById(id);
-            if (element && element->isHTMLElement())
-                pending.append(element);
-        }
+    if (!baseElement->fastHasAttribute(itemrefAttr)) {
+        itemRefElements.append(baseElement);
+        m_cache.setItemRefElements(itemRefElements);
+        return;
     }
 
-    // Loop till we have processed all pending elements
-    while (!pending.isEmpty()) {
+    DOMSettableTokenList* itemRef = baseElement->itemRef();
+    RefPtr<DOMSettableTokenList> processedItemRef = DOMSettableTokenList::create();
+    Node* rootNode = baseElement->treeScope()->rootNode();
 
-        // Remove first element from pending and let current be that element.
-        Node* current = pending[0];
-        pending.remove(0);
+    for (Node* current = rootNode->firstChild(); current; current = current->traverseNextNode(rootNode)) {
+        if (!current->isHTMLElement())
+            continue;
+        HTMLElement* element = toHTMLElement(current);
 
-        // If current is already in memory, there is a microdata error;
-        if (memory.contains(current)) {
-            // microdata error;
+        if (element == baseElement) {
+            itemRefElements.append(element);
             continue;
         }
 
-        memory.append(current);
+        const AtomicString& id = element->getIdAttribute();
+        if (!processedItemRef->tokens().contains(id) && itemRef->tokens().contains(id)) {
+            processedItemRef->setValue(id);
+            if (!element->isDescendantOf(baseElement))
+                itemRefElements.append(element);
+        }
+    }
+
+    m_cache.setItemRefElements(itemRefElements);
+}
 
-        // If current does not have an itemscope attribute, then: add all the child elements of current to pending.
+static Node* nextNodeWithProperty(Node* base, Node* node)
+{
+    // An Microdata item may contain properties which in turn are items themselves. Properties can
+    // also themselves be groups of name-value pairs, by putting the itemscope attribute on the element
+    // that declares the property. If the property has an itemscope attribute specified then we need
+    // to traverse the next sibling.
+    return node == base || (node->isHTMLElement() && !toHTMLElement(node)->fastHasAttribute(itemscopeAttr))
+            ? node->traverseNextNode(base) : node->traverseNextSibling(base);
+}
+
+Element* HTMLPropertiesCollection::itemAfter(Element* base, Element* previous) const
+{
+    Node* current;
+    current = previous ? nextNodeWithProperty(base, previous) : base;
+
+    for (; current; current = nextNodeWithProperty(base, current)) {
+        if (!current->isHTMLElement())
+            continue;
         HTMLElement* element = toHTMLElement(current);
-        if (!element->fastHasAttribute(itemscopeAttr)) {
-            for (Node* child = current->firstChild(); child; child = child->nextSibling())
-                if (child->isHTMLElement())
-                    pending.append(child);
+        if (element->fastHasAttribute(itempropAttr)) {
+            return element;
         }
+    }
 
-        // If current has an itemprop attribute specified, add it to results.
-        if (element->fastHasAttribute(itempropAttr))
-             m_properties.append(current);
+    return 0;
+}
+
+unsigned HTMLPropertiesCollection::calcLength() const
+{
+    unsigned length = 0;
+    updateRefElements();
+
+    const Vector<Element*>& itemRefElements = m_cache.getItemRefElements();
+    for (unsigned i = 0; i < itemRefElements.size(); ++i) {
+        for (Element* element = itemAfter(itemRefElements[i], 0); element; element = itemAfter(itemRefElements[i], element))
+            ++length;
     }
+
+    return length;
 }
 
 unsigned HTMLPropertiesCollection::length() const
 {
-    if (!base()->isHTMLElement() || !toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
+    if (!toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
         return 0;
 
-    m_properties.clear();
-    findPropetiesOfAnItem(base());
-    return m_properties.size();
+    invalidateCacheIfNeeded();
+
+    if (!m_cache.hasLength)
+        m_cache.updateLength(calcLength());
+
+    return m_cache.length;
+}
+
+Element* HTMLPropertiesCollection::firstProperty() const
+{
+    Element* element = 0;
+    m_cache.resetPosition();
+    const Vector<Element*>& itemRefElements = m_cache.getItemRefElements();
+    for (unsigned i = 0; i < itemRefElements.size(); ++i) {
+        element = itemAfter(itemRefElements[i], 0);
+        if (element) {
+            m_cache.itemRefElementPosition = i;
+            break;
+        }
+    }
+
+    return element;
 }
 
 Node* HTMLPropertiesCollection::item(unsigned index) const
 {
-    if (!base()->isHTMLElement() || !toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
+    if (!toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
         return 0;
 
-    m_properties.clear();
-    findPropetiesOfAnItem(base());
+    invalidateCacheIfNeeded();
+    if (m_cache.current && m_cache.position == index)
+        return m_cache.current;
 
-    if (m_properties.size() <= index)
+    if (m_cache.hasLength && m_cache.length <= index)
         return 0;
 
-    std::sort(m_properties.begin(), m_properties.end(), compareTreeOrder);
-    return m_properties[index];
+    updateRefElements();
+    if (!m_cache.current || m_cache.position > index) {
+        m_cache.current = firstProperty();
+        if (!m_cache.current)
+            return 0;
+    }
+
+    unsigned currentPosition = m_cache.position;
+    Element* element = m_cache.current;
+    unsigned itemRefElementPos = m_cache.itemRefElementPosition;
+    const Vector<Element*>& itemRefElements = m_cache.getItemRefElements();
+
+    bool found = (m_cache.position == index);
+
+    for (unsigned i = itemRefElementPos; i < itemRefElements.size() && !found; ++i) {
+        while (currentPosition < index) {
+            element = itemAfter(itemRefElements[i], element);
+            if (!element)
+                break;
+            currentPosition++;
+
+            if (currentPosition == index) {
+                found = true;
+                itemRefElementPos = i;
+                break;
+            }
+        }
+    }
+
+    m_cache.updateCurrentItem(element, index, itemRefElementPos);
+    return m_cache.current;
 }
 
-PassRefPtr<DOMStringList> HTMLPropertiesCollection::names() const
+void HTMLPropertiesCollection::findProperties(Element* base) const
 {
-    m_properties.clear();
-    m_propertyNames->clear();
+    for (Element* element = itemAfter(base, 0); element; element = itemAfter(base, element)) {
+        DOMSettableTokenList* itemProperty = element->itemProp();
+        for (unsigned i = 0; i < itemProperty->length(); ++i)
+            m_cache.updatePropertyCache(element, itemProperty->item(i));
+    }
+}
 
-    if (!base()->isHTMLElement() || !toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
-        return m_propertyNames;
+void HTMLPropertiesCollection::updateNameCache() const
+{
+    invalidateCacheIfNeeded();
+    if (m_cache.hasNameCache)
+        return;
 
-    findPropetiesOfAnItem(base());
+    updateRefElements();
 
-    std::sort(m_properties.begin(), m_properties.end(), compareTreeOrder);
+    const Vector<Element*>& itemRefElements = m_cache.getItemRefElements();
+    for (unsigned i = 0; i < itemRefElements.size(); ++i)
+        findProperties(itemRefElements[i]);
 
-    for (size_t i = 0; i < m_properties.size(); ++i) {
-        // For each item properties, split the value of that itemprop attribute on spaces.
-        // Add all tokens to property names, with the order preserved but with duplicates removed.
-        DOMSettableTokenList* itemProperty = m_properties[i]->itemProp();
-        for (size_t i = 0; i < itemProperty->length(); ++i) {
-            AtomicString propertyName = itemProperty->item(i);
-            if (m_propertyNames->isEmpty() || !m_propertyNames->contains(propertyName))
-                m_propertyNames->append(propertyName);
-        }
-    }
+    m_cache.hasNameCache = true;
+}
+
+PassRefPtr<DOMStringList> HTMLPropertiesCollection::names() const
+{
+    if (!toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
+        return DOMStringList::create();
+
+    updateNameCache();
 
-    return m_propertyNames;
+    return m_cache.propertyNames;
 }
 
 PassRefPtr<NodeList> HTMLPropertiesCollection::namedItem(const String& name) const
 {
-    if (!base()->isHTMLElement() || !toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
+    if (!toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
       return 0;
 
-    m_properties.clear();
     Vector<RefPtr<Node> > namedItems;
-    findPropetiesOfAnItem(base());
 
-    std::sort(m_properties.begin(), m_properties.end(), compareTreeOrder);
+    updateNameCache();
 
-    // For each item properties, split the value of that itemprop attribute on spaces.
-    // Add element to namedItem that contains a property named name, with the order preserved.
-    for (size_t i = 0; i < m_properties.size(); ++i) {
-        DOMSettableTokenList* itemProperty = m_properties[i]->itemProp();
-        if (itemProperty->tokens().contains(name))
-            namedItems.append(m_properties[i]);
-    }
+    Vector<Element*>* propertyResults = m_cache.propertyCache.get(AtomicString(name).impl());
+    for (unsigned i = 0; propertyResults && i < propertyResults->size(); ++i)
+        namedItems.append(propertyResults->at(i));
 
     // FIXME: HTML5 specifies that this should return PropertyNodeList.
     return namedItems.isEmpty() ? 0 : StaticNodeList::adopt(namedItems);
@@ -198,17 +277,13 @@ PassRefPtr<NodeList> HTMLPropertiesCollection::namedItem(const String& name) con
 
 bool HTMLPropertiesCollection::hasNamedItem(const AtomicString& name) const
 {
-    if (!base()->isHTMLElement() || !toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
+    if (!toHTMLElement(base())->fastHasAttribute(itemscopeAttr))
         return false;
 
-    m_properties.clear();
-    findPropetiesOfAnItem(base());
+    updateNameCache();
 
-    // For each item properties, split the value of that itemprop attribute on spaces.
-    // Return true if element contains a property named name.
-    for (size_t i = 0; i < m_properties.size(); ++i) {
-        DOMSettableTokenList* itemProperty = m_properties[i]->itemProp();
-        if (itemProperty->tokens().contains(name))
+    if (Vector<Element*>* propertyCache = m_cache.propertyCache.get(name.impl())) {
+        if (!propertyCache->isEmpty())
             return true;
     }
 
index 46434e3..9cd9175 100644 (file)
@@ -33,6 +33,7 @@
 
 #if ENABLE(MICRODATA)
 
+#include "DOMStringList.h"
 #include "HTMLCollection.h"
 
 namespace WebCore {
@@ -56,11 +57,93 @@ public:
 private:
     HTMLPropertiesCollection(Node*);
 
-    void findPropetiesOfAnItem(Node* current) const;
-    void getNamedItems(Vector<RefPtr<Node> >&, const String&) const;
+    unsigned calcLength() const;
+    void findProperties(Element* base) const;
+
+    Node* findRefElements(Node* previous) const;
+
+    Element* firstProperty() const;
+    Element* itemAfter(Element* base, Element* previous) const;
+
+    void updateNameCache() const;
+    void updateRefElements() const;
+
+    void invalidateCacheIfNeeded() const;
+
+    mutable struct {
+        uint64_t version;
+        Element* current;
+        unsigned position;
+        unsigned length;
+        bool hasLength;
+        bool hasNameCache;
+        NodeCacheMap propertyCache;
+        Vector<Element*> itemRefElements;
+        RefPtr<DOMStringList> propertyNames;
+        unsigned itemRefElementPosition;
+        bool hasItemRefElements;
+
+        void clear()
+        {
+            version = 0;
+            current = 0;
+            position = 0;
+            length = 0;
+            hasLength = false;
+            hasNameCache = false;
+            propertyCache.clear();
+            itemRefElements.clear();
+            propertyNames.clear();
+            itemRefElementPosition = 0;
+            hasItemRefElements = false;
+        }
+
+        void setItemRefElements(const Vector<Element*>& elements)
+        {
+            itemRefElements = elements;
+            hasItemRefElements = true;
+        }
+
+        const Vector<Element*>& getItemRefElements()
+        {
+            return itemRefElements;
+        }
+
+        void updateLength(unsigned len)
+        {
+            length = len;
+            hasLength = true;
+        }
+
+        void updatePropertyCache(Element* element, const AtomicString& propertyName)
+        {
+            if (!propertyNames)
+                propertyNames = DOMStringList::create();
+
+            if (!propertyNames->contains(propertyName))
+                propertyNames->append(propertyName);
+
+            Vector<Element*>* propertyResults = propertyCache.get(propertyName.impl());
+            if (!propertyResults || !propertyResults->contains(element))
+                append(propertyCache, propertyName, element);
+        }
+
+        void updateCurrentItem(Element* element, unsigned pos, unsigned itemRefElementPos)
+        {
+            current = element;
+            position = pos;
+            itemRefElementPosition = itemRefElementPos;
+        }
+
+        void resetPosition()
+        {
+            current = 0;
+            position = 0;
+            itemRefElementPosition = 0;
+        }
+
+    } m_cache;
 
-    mutable Vector<Node*> m_properties;
-    mutable RefPtr<DOMStringList> m_propertyNames;
 };
 
 } // namespace WebCore