Title: [156925] trunk/Source/WebCore
Revision
156925
Author
[email protected]
Date
2013-10-04 16:33:10 -0700 (Fri, 04 Oct 2013)

Log Message

FocusController::advanceFocus spends a lot of time in HTMLMapElement::imageElement
https://bugs.webkit.org/show_bug.cgi?id=122313

Reviewed by Andreas Kling.

The bug was caused by HTMLMapElement::imageElement traversing the entire document to look for
the image element associated with a given map element. Because HTMLCollection used to find the
image element is not cached, it traversed the entire document on every area element we visit,
resulting in O(n^2) behavior.

Fixed the bug by adding the name-to-image-element map on document to avoid the traversal in
HTMLMapElement::imageElement.

* dom/Document.cpp:
(WebCore::Document::addImageElementByLowercasedUsemap): Added.
(WebCore::Document::removeImageElementByLowercasedUsemap): Added.
(WebCore::Document::imageElementByLowercasedUsemap): Added.

* dom/Document.h: Added m_imagesByUsemap.

* dom/DocumentOrderedMap.cpp:
(WebCore::keyMatchesLowercasedUsemap): Added.
(WebCore::DocumentOrderedMap::getElementByLowercasedUsemap): Added.

* dom/DocumentOrderedMap.h:
* html/HTMLImageElement.cpp:
(WebCore::HTMLImageElement::parseAttribute): Update the name-to-usemap map. The code to parse
the usemap attribute and strip # was moved from HTMLMapElement::imageElement.
(WebCore::HTMLImageElement::insertedInto): Ditto.
(WebCore::HTMLImageElement::removedFrom): Ditto.
(WebCore::HTMLImageElement::matchesLowercasedUsemap): Added; called by DocumentOrderedMap.

* html/HTMLImageElement.h:

* html/HTMLMapElement.cpp:
(WebCore::HTMLMapElement::imageElement): Call Document::imageElementByUsemap instead of
looking through all image elements in the document.

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (156924 => 156925)


--- trunk/Source/WebCore/ChangeLog	2013-10-04 23:25:55 UTC (rev 156924)
+++ trunk/Source/WebCore/ChangeLog	2013-10-04 23:33:10 UTC (rev 156925)
@@ -1,3 +1,43 @@
+2013-10-03  Ryosuke Niwa  <[email protected]>
+
+        FocusController::advanceFocus spends a lot of time in HTMLMapElement::imageElement
+        https://bugs.webkit.org/show_bug.cgi?id=122313
+
+        Reviewed by Andreas Kling.
+
+        The bug was caused by HTMLMapElement::imageElement traversing the entire document to look for
+        the image element associated with a given map element. Because HTMLCollection used to find the
+        image element is not cached, it traversed the entire document on every area element we visit,
+        resulting in O(n^2) behavior.
+
+        Fixed the bug by adding the name-to-image-element map on document to avoid the traversal in
+        HTMLMapElement::imageElement.
+
+        * dom/Document.cpp:
+        (WebCore::Document::addImageElementByLowercasedUsemap): Added.
+        (WebCore::Document::removeImageElementByLowercasedUsemap): Added.
+        (WebCore::Document::imageElementByLowercasedUsemap): Added.
+
+        * dom/Document.h: Added m_imagesByUsemap.
+
+        * dom/DocumentOrderedMap.cpp:
+        (WebCore::keyMatchesLowercasedUsemap): Added.
+        (WebCore::DocumentOrderedMap::getElementByLowercasedUsemap): Added.
+
+        * dom/DocumentOrderedMap.h:
+        * html/HTMLImageElement.cpp:
+        (WebCore::HTMLImageElement::parseAttribute): Update the name-to-usemap map. The code to parse
+        the usemap attribute and strip # was moved from HTMLMapElement::imageElement.
+        (WebCore::HTMLImageElement::insertedInto): Ditto.
+        (WebCore::HTMLImageElement::removedFrom): Ditto.
+        (WebCore::HTMLImageElement::matchesLowercasedUsemap): Added; called by DocumentOrderedMap.
+
+        * html/HTMLImageElement.h:
+
+        * html/HTMLMapElement.cpp:
+        (WebCore::HTMLMapElement::imageElement): Call Document::imageElementByUsemap instead of
+        looking through all image elements in the document.
+
 2013-10-04  Sam Weinig  <[email protected]>
 
         Unify rubberband control

Modified: trunk/Source/WebCore/dom/Document.cpp (156924 => 156925)


--- trunk/Source/WebCore/dom/Document.cpp	2013-10-04 23:25:55 UTC (rev 156924)
+++ trunk/Source/WebCore/dom/Document.cpp	2013-10-04 23:33:10 UTC (rev 156925)
@@ -84,6 +84,7 @@
 #include "HTMLFrameOwnerElement.h"
 #include "HTMLHeadElement.h"
 #include "HTMLIFrameElement.h"
+#include "HTMLImageElement.h"
 #include "HTMLLinkElement.h"
 #include "HTMLMediaElement.h"
 #include "HTMLNameCollection.h"
@@ -706,6 +707,21 @@
     m_elementsByAccessKey.clear();
 }
 
+void Document::addImageElementByLowercasedUsemap(const AtomicString& name, HTMLImageElement& element)
+{
+    return m_imagesByUsemap.add(name.impl(), &element);
+}
+
+void Document::removeImageElementByLowercasedUsemap(const AtomicString& name, HTMLImageElement& element)
+{
+    return m_imagesByUsemap.remove(name.impl(), &element);
+}
+
+HTMLImageElement* Document::imageElementByLowercasedUsemap(const AtomicString& name) const
+{
+    return m_imagesByUsemap.getElementByLowercasedUsemap(name.impl(), this);
+}
+
 SelectorQueryCache& Document::selectorQueryCache()
 {
     if (!m_selectorQueryCache)

Modified: trunk/Source/WebCore/dom/Document.h (156924 => 156925)


--- trunk/Source/WebCore/dom/Document.h	2013-10-04 23:25:55 UTC (rev 156924)
+++ trunk/Source/WebCore/dom/Document.h	2013-10-04 23:33:10 UTC (rev 156925)
@@ -103,6 +103,7 @@
 class HTMLFrameOwnerElement;
 class HTMLHeadElement;
 class HTMLIFrameElement;
+class HTMLImageElement;
 class HTMLMapElement;
 class HTMLNameCollection;
 class HTMLScriptElement;
@@ -257,6 +258,10 @@
     Element* getElementByAccessKey(const String& key);
     void invalidateAccessKeyMap();
 
+    void addImageElementByLowercasedUsemap(const AtomicString&, HTMLImageElement&);
+    void removeImageElementByLowercasedUsemap(const AtomicString&, HTMLImageElement&);
+    HTMLImageElement* imageElementByLowercasedUsemap(const AtomicString&) const;
+
     SelectorQueryCache& selectorQueryCache();
 
     // DOM methods & attributes for Document
@@ -1450,6 +1455,8 @@
     HashMap<StringImpl*, Element*, CaseFoldingHash> m_elementsByAccessKey;
     bool m_accessKeyMapValid;
 
+    DocumentOrderedMap m_imagesByUsemap;
+
     OwnPtr<SelectorQueryCache> m_selectorQueryCache;
 
     DocumentClassFlags m_documentClasses;

Modified: trunk/Source/WebCore/dom/DocumentOrderedMap.cpp (156924 => 156925)


--- trunk/Source/WebCore/dom/DocumentOrderedMap.cpp	2013-10-04 23:25:55 UTC (rev 156924)
+++ trunk/Source/WebCore/dom/DocumentOrderedMap.cpp	2013-10-04 23:33:10 UTC (rev 156925)
@@ -33,6 +33,7 @@
 
 #include "Element.h"
 #include "ElementTraversal.h"
+#include "HTMLImageElement.h"
 #include "HTMLLabelElement.h"
 #include "HTMLMapElement.h"
 #include "HTMLNameCollection.h"
@@ -63,6 +64,12 @@
     return isHTMLMapElement(element) && toHTMLMapElement(element)->getName().lower().impl() == key;
 }
 
+inline bool keyMatchesLowercasedUsemap(const AtomicStringImpl* key, Element* element)
+{
+    // FIXME: HTML5 specification says we should match both image and object elements.
+    return isHTMLImageElement(element) && toHTMLImageElement(element)->matchesLowercasedUsemap(*key);
+}
+
 inline bool keyMatchesLabelForAttribute(const AtomicStringImpl* key, Element* element)
 {
     return isHTMLLabelElement(element) && element->getAttribute(forAttr).impl() == key;
@@ -169,6 +176,11 @@
     return get<keyMatchesLowercasedMapName>(key, scope);
 }
 
+HTMLImageElement* DocumentOrderedMap::getElementByLowercasedUsemap(const AtomicStringImpl* key, const TreeScope* scope) const
+{
+    return toHTMLImageElement(get<keyMatchesLowercasedUsemap>(key, scope));
+}
+
 Element* DocumentOrderedMap::getElementByLabelForAttribute(const AtomicStringImpl* key, const TreeScope* scope) const
 {
     return get<keyMatchesLabelForAttribute>(key, scope);

Modified: trunk/Source/WebCore/dom/DocumentOrderedMap.h (156924 => 156925)


--- trunk/Source/WebCore/dom/DocumentOrderedMap.h	2013-10-04 23:25:55 UTC (rev 156924)
+++ trunk/Source/WebCore/dom/DocumentOrderedMap.h	2013-10-04 23:33:10 UTC (rev 156925)
@@ -39,6 +39,7 @@
 namespace WebCore {
 
 class Element;
+class HTMLImageElement;
 class TreeScope;
 
 class DocumentOrderedMap {
@@ -56,6 +57,7 @@
     Element* getElementByName(const AtomicStringImpl*, const TreeScope*) const;
     Element* getElementByMapName(const AtomicStringImpl*, const TreeScope*) const;
     Element* getElementByLowercasedMapName(const AtomicStringImpl*, const TreeScope*) const;
+    HTMLImageElement* getElementByLowercasedUsemap(const AtomicStringImpl*, const TreeScope*) const;
     Element* getElementByLabelForAttribute(const AtomicStringImpl*, const TreeScope*) const;
     Element* getElementByWindowNamedItem(const AtomicStringImpl*, const TreeScope*) const;
     Element* getElementByDocumentNamedItem(const AtomicStringImpl*, const TreeScope*) const;

Modified: trunk/Source/WebCore/html/HTMLImageElement.cpp (156924 => 156925)


--- trunk/Source/WebCore/html/HTMLImageElement.cpp	2013-10-04 23:25:55 UTC (rev 156924)
+++ trunk/Source/WebCore/html/HTMLImageElement.cpp	2013-10-04 23:33:10 UTC (rev 156925)
@@ -123,9 +123,27 @@
     } else if (name == srcAttr || name == srcsetAttr) {
         m_bestFitImageURL = bestFitSourceForImageAttributes(document().deviceScaleFactor(), fastGetAttribute(srcAttr), fastGetAttribute(srcsetAttr));
         m_imageLoader.updateFromElementIgnoringPreviousError();
-    } else if (name == usemapAttr)
+    } else if (name == usemapAttr) {
         setIsLink(!value.isNull() && !shouldProhibitLinks(this));
-    else if (name == onbeforeloadAttr)
+
+        if (m_lowercasedUsemap == value)
+            return;
+
+        if (!m_lowercasedUsemap.isNull())
+            document().removeImageElementByLowercasedUsemap(m_lowercasedUsemap, *this);
+
+        // The HTMLImageElement's useMap() value includes the '#' symbol at the beginning, which has to be stripped off.
+        // FIXME: We should check that the first character is '#'.
+        // FIXME: HTML5 specification says we should strip any leading string before '#'.
+        // FIXME: HTML5 specification says we should ignore usemap attributes without #.
+        if (value.length() > 1)
+            m_lowercasedUsemap = value.string().substring(1).lower();
+        else
+            m_lowercasedUsemap = nullAtom;
+
+        if (!m_lowercasedUsemap.isNull())
+            document().addImageElementByLowercasedUsemap(m_lowercasedUsemap, *this);
+    } else if (name == onbeforeloadAttr)
         setAttributeEventListener(eventNames().beforeloadEvent, name, value);
     else if (name == compositeAttr) {
         // FIXME: images don't support blend modes in their compositing attribute.
@@ -206,6 +224,9 @@
             m_form->registerImgElement(this);
     }
 
+    if (insertionPoint->inDocument() && !m_lowercasedUsemap.isNull())
+        document().addImageElementByLowercasedUsemap(m_lowercasedUsemap, *this);
+
     // If we have been inserted from a renderer-less document,
     // our loader may have not fetched the image, so do it now.
     if (insertionPoint.inDocument() && !m_imageLoader.image())
@@ -218,6 +239,10 @@
 {
     if (m_form)
         m_form->removeImgElement(this);
+
+    if (insertionPoint->inDocument() && !m_lowercasedUsemap.isNull())
+        document().removeImageElementByLowercasedUsemap(m_lowercasedUsemap, *this);
+
     m_form = 0;
     HTMLElement::removedFrom(insertionPoint);
 }
@@ -293,6 +318,12 @@
         || HTMLElement::isURLAttribute(attribute);
 }
 
+bool HTMLImageElement::matchesLowercasedUsemap(const AtomicStringImpl& name) const
+{
+    ASSERT(const_cast<AtomicStringImpl&>(name).lower() == &name);
+    return m_lowercasedUsemap.impl() == &name;
+}
+
 const AtomicString& HTMLImageElement::alt() const
 {
     return getAttribute(altAttr);

Modified: trunk/Source/WebCore/html/HTMLImageElement.h (156924 => 156925)


--- trunk/Source/WebCore/html/HTMLImageElement.h	2013-10-04 23:25:55 UTC (rev 156924)
+++ trunk/Source/WebCore/html/HTMLImageElement.h	2013-10-04 23:33:10 UTC (rev 156925)
@@ -59,6 +59,8 @@
 
     void setLoadManually(bool loadManually) { m_imageLoader.setLoadManually(loadManually); }
 
+    bool matchesLowercasedUsemap(const AtomicStringImpl&) const;
+
     const AtomicString& alt() const;
 
     void setHeight(int);
@@ -117,6 +119,7 @@
     HTMLFormElement* m_form;
     CompositeOperator m_compositeOperator;
     AtomicString m_bestFitImageURL;
+    AtomicString m_lowercasedUsemap;
 };
 
 ELEMENT_TYPE_CASTS(HTMLImageElement)

Modified: trunk/Source/WebCore/html/HTMLMapElement.cpp (156924 => 156925)


--- trunk/Source/WebCore/html/HTMLMapElement.cpp	2013-10-04 23:25:55 UTC (rev 156924)
+++ trunk/Source/WebCore/html/HTMLMapElement.cpp	2013-10-04 23:33:10 UTC (rev 156925)
@@ -79,20 +79,7 @@
 
 HTMLImageElement* HTMLMapElement::imageElement()
 {
-    RefPtr<HTMLCollection> images = document().images();
-    for (unsigned i = 0; Node* curr = images->item(i); i++) {
-        if (!isHTMLImageElement(curr))
-            continue;
-        
-        // The HTMLImageElement's useMap() value includes the '#' symbol at the beginning,
-        // which has to be stripped off.
-        HTMLImageElement* imageElement = toHTMLImageElement(curr);
-        String useMapName = imageElement->getAttribute(usemapAttr).string().substring(1);
-        if (equalIgnoringCase(useMapName, m_name))
-            return imageElement;
-    }
-    
-    return 0;    
+    return document().imageElementByLowercasedUsemap(m_name.lower());
 }
 
 void HTMLMapElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to