Fix <rdar://problem/5609579> (DOMParser().parseFromString() freezes Safari when parsi...
authormrowe@apple.com <mrowe@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 21 Nov 2007 07:29:50 +0000 (07:29 +0000)
committermrowe@apple.com <mrowe@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 21 Nov 2007 07:29:50 +0000 (07:29 +0000)
http://bugs.webkit.org/show_bug.cgi?id=16076

Reviewed by Maciej Stachowiak.

XMLTokenizer was calling CharacterData::appendData twice per entity in the fragment of XML being
parsed (once for text before the entity, once for the entity itself).  This triggered O(n^2) copying
of the CharacterData's string due to resizing.  We now prevent this happening by buffering all the
content for a given Text node in the XMLTokenizer before sending it out to the node in a single go.

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

WebCore/ChangeLog
WebCore/dom/XMLTokenizer.cpp
WebCore/dom/XMLTokenizer.h

index 22e2f17..b4c56f9 100644 (file)
@@ -1,3 +1,26 @@
+2007-11-20  Mark Rowe  <mrowe@apple.com>
+
+        Reviewed by Maciej Stachowiak.
+
+        Fix <rdar://problem/5609579> (DOMParser().parseFromString() freezes Safari when parsing large nodes with XML entities)
+        http://bugs.webkit.org/show_bug.cgi?id=16076
+
+        XMLTokenizer was calling CharacterData::appendData twice per entity in the fragment of XML being
+        parsed (once for text before the entity, once for the entity itself).  This triggered O(n^2) copying
+        of the CharacterData's string due to resizing.  We now prevent this happening by buffering all the
+        content for a given Text node in the XMLTokenizer before sending it out to the node in a single go.
+
+        * dom/XMLTokenizer.cpp:
+        (WebCore::XMLTokenizer::characters): Append the characters to the buffer.
+        (WebCore::XMLTokenizer::endDocument): Ensure the buffer is flushed when the document has ended.
+        (WebCore::endDocumentHandler):
+        (WebCore::XMLTokenizer::enterText):
+        (WebCore::XMLTokenizer::exitText): Append the contents of the buffer to the node.
+        (WebCore::XMLTokenizer::initializeParserContext): Add the endDocument handler.
+        (WebCore::parseXMLDocumentFragment): Force endDocument to be called when parsing a fragment to ensure
+        that the buffer gets flushed to the node.
+        * dom/XMLTokenizer.h:
+
 2007-11-20  Timothy Hatcher  <timothy@apple.com>
 
         Reviewed by Mark Rowe.
index bb48dfc..65fe4a6 100644 (file)
@@ -864,10 +864,8 @@ void XMLTokenizer::characters(const xmlChar* s, int len)
         return;
     }
     
-    if (m_currentNode->isTextNode() || enterText()) {
-        ExceptionCode ec = 0;
-        static_cast<Text*>(m_currentNode)->appendData(toString(s, len), ec);
-    }
+    if (m_currentNode->isTextNode() || enterText())
+        m_bufferedText.append(s, len);
 }
 
 void XMLTokenizer::error(ErrorType type, const char* message, va_list args)
@@ -979,6 +977,11 @@ void XMLTokenizer::startDocument(const xmlChar* version, const xmlChar* encoding
         m_doc->setXMLEncoding(toString(encoding));
 }
 
+void XMLTokenizer::endDocument()
+{
+    exitText();
+}
+
 void XMLTokenizer::internalSubset(const xmlChar* name, const xmlChar* externalID, const xmlChar* systemID)
 {
     if (m_parserStopped)
@@ -1137,6 +1140,12 @@ static void startDocumentHandler(void* closure)
     xmlSAX2StartDocument(closure);
 }
 
+static void endDocumentHandler(void* closure)
+{
+    getTokenizer(closure)->endDocument();
+    xmlSAX2EndDocument(closure);
+}
+
 static void internalSubsetHandler(void* closure, const xmlChar* name, const xmlChar* externalID, const xmlChar* systemID)
 {
     getTokenizer(closure)->internalSubset(name, externalID, systemID);
@@ -1191,6 +1200,9 @@ void XMLTokenizer::handleError(ErrorType type, const char* m, int lineNumber, in
 
 bool XMLTokenizer::enterText()
 {
+#ifndef USE_QXMLSTREAM
+    ASSERT(m_bufferedText.size() == 0);
+#endif
     RefPtr<Node> newNode = new Text(m_doc, "");
     if (!m_currentNode->addChild(newNode.get()))
         return false;
@@ -1206,6 +1218,13 @@ void XMLTokenizer::exitText()
     if (!m_currentNode || !m_currentNode->isTextNode())
         return;
 
+#ifndef USE_QXMLSTREAM
+    ExceptionCode ec = 0;
+    static_cast<Text*>(m_currentNode)->appendData(toString(m_bufferedText.data(), m_bufferedText.size()), ec);
+    Vector<xmlChar> empty;
+    m_bufferedText.swap(empty);
+#endif
+
     if (m_view && m_currentNode && !m_currentNode->attached())
         m_currentNode->attach();
 
@@ -1231,6 +1250,7 @@ void XMLTokenizer::initializeParserContext()
     sax.endElementNs = endElementNsHandler;
     sax.getEntity = getEntityHandler;
     sax.startDocument = startDocumentHandler;
+    sax.endDocument = endDocumentHandler;
     sax.internalSubset = internalSubsetHandler;
     sax.externalSubset = externalSubsetHandler;
     sax.ignorableWhitespace = ignorableWhitespaceHandler;
@@ -1560,6 +1580,7 @@ bool parseXMLDocumentFragment(const String& string, DocumentFragment* fragment,
     sax.initialized = XML_SAX2_MAGIC;
     
     int result = xmlParseBalancedChunkMemory(0, &sax, &tokenizer, 0, (const xmlChar*)string.utf8().data(), 0);
+    tokenizer.endDocument();
     return result == 0;
 #else
     tokenizer.write(String("<qxmlstreamdummyelement>"), false);
index a595d51..26f40cb 100644 (file)
@@ -94,6 +94,7 @@ namespace WebCore {
         void comment(const xmlChar* s);
         void startDocument(const xmlChar* version, const xmlChar* encoding, int standalone);
         void internalSubset(const xmlChar* name, const xmlChar* externalID, const xmlChar* systemID);
+        void endDocument();
 #else
         void parse();
         void startDocument();
@@ -163,6 +164,7 @@ namespace WebCore {
         PrefixForNamespaceMap m_prefixToNamespaceMap;
 #ifndef USE_QXMLSTREAM
         OwnPtr<PendingCallbacks> m_pendingCallbacks;
+        Vector<xmlChar> m_bufferedText;
 #endif
         SegmentedString m_pendingSrc;
     };