2010-07-20 Adam Barth <abarth@webkit.org>
[WebKit-https.git] / WebCore / html / HTMLConstructionSite.cpp
index de73a79..9d6cdd8 100644 (file)
@@ -32,7 +32,6 @@
 #include "Element.h"
 #include "Frame.h"
 #include "HTMLDocument.h"
-#include "HTMLElementFactory.h"
 #include "HTMLHtmlElement.h"
 #include "HTMLNames.h"
 #include "HTMLScriptElement.h"
@@ -71,6 +70,15 @@ bool hasImpliedEndTag(Element* element)
         || element->hasTagName(rtTag);
 }
 
+bool causesFosterParenting(const QualifiedName& tagName)
+{
+    return tagName == tableTag
+        || tagName == tbodyTag
+        || tagName == tfootTag
+        || tagName == theadTag
+        || tagName == trTag;
+}
+
 } // namespace
 
 template<typename ChildType>
@@ -81,8 +89,9 @@ PassRefPtr<ChildType> HTMLConstructionSite::attach(Node* parent, PassRefPtr<Chil
     // FIXME: It's confusing that HTMLConstructionSite::attach does the magic
     // redirection to the foster parent but HTMLConstructionSite::attachAtSite
     // doesn't.  It feels like we're missing a concept somehow.
-    if (m_redirectAttachToFosterParent) {
+    if (shouldFosterParent()) {
         fosterParent(child.get());
+        ASSERT(child->attached());
         return child.release();
     }
 
@@ -92,27 +101,38 @@ PassRefPtr<ChildType> HTMLConstructionSite::attach(Node* parent, PassRefPtr<Chil
     // |parent| to hold a ref at this point.  In the common case (at least
     // for elements), however, we'll get to use this ref in the stack of
     // open elements.
-    child->attach();
+    if (parent->attached()) {
+        ASSERT(!child->attached());
+        child->attach();
+    }
     return child.release();
 }
 
-void HTMLConstructionSite::attachAtSite(const AttachmentSite& site, PassRefPtr<Node> child)
+void HTMLConstructionSite::attachAtSite(const AttachmentSite& site, PassRefPtr<Node> prpChild)
 {
+    RefPtr<Node> child = prpChild;
+
     if (site.nextChild) {
         // FIXME: We need an insertElement which does not send mutation events.
         ExceptionCode ec = 0;
         site.parent->insertBefore(child, site.nextChild, ec);
-        // FIXME: Do we need to call attach()?
         ASSERT(!ec);
+        if (site.parent->attached() && !child->attached())
+            child->attach();
         return;
     }
     site.parent->parserAddChild(child);
-    // FIXME: Do we need to call attach()?
+    // It's slightly unfortunate that we need to hold a reference to child
+    // here to call attach().  We should investigate whether we can rely on
+    // |site.parent| to hold a ref at this point.
+    if (site.parent->attached() && !child->attached())
+        child->attach();
 }
 
-HTMLConstructionSite::HTMLConstructionSite(Document* document, FragmentScriptingPermission scriptingPermission)
+HTMLConstructionSite::HTMLConstructionSite(Document* document, FragmentScriptingPermission scriptingPermission, bool isParsingFragment)
     : m_document(document)
     , m_fragmentScriptingPermission(scriptingPermission)
+    , m_isParsingFragment(isParsingFragment)
     , m_redirectAttachToFosterParent(false)
 {
 }
@@ -121,11 +141,18 @@ HTMLConstructionSite::~HTMLConstructionSite()
 {
 }
 
+void HTMLConstructionSite::dispatchDocumentElementAvailableIfNeeded()
+{
+    if (m_document->frame() && !m_isParsingFragment)
+        m_document->frame()->loader()->dispatchDocumentElementAvailable();
+}
+
 void HTMLConstructionSite::insertHTMLHtmlStartTagBeforeHTML(AtomicHTMLToken& token)
 {
     RefPtr<Element> element = HTMLHtmlElement::create(m_document);
     element->setAttributeMap(token.takeAtributes(), m_fragmentScriptingPermission);
     m_openElements.pushHTMLHtmlElement(attach(m_document, element.release()));
+    dispatchDocumentElementAvailableIfNeeded();
 }
 
 void HTMLConstructionSite::mergeAttributesFromTokenIntoElement(AtomicHTMLToken& token, Element* element)
@@ -182,40 +209,44 @@ void HTMLConstructionSite::insertCommentOnHTMLHtmlElement(AtomicHTMLToken& token
     attach(m_openElements.htmlElement(), Comment::create(m_document, token.comment()));
 }
 
-PassRefPtr<Element> HTMLConstructionSite::createHTMLElementAndAttachToCurrent(AtomicHTMLToken& token)
+PassRefPtr<Element> HTMLConstructionSite::attachToCurrent(PassRefPtr<Element> child)
 {
-    ASSERT(token.type() == HTMLToken::StartTag);
-    return attach(currentElement(), createHTMLElement(token));
+    return attach(currentElement(), child);
 }
 
 void HTMLConstructionSite::insertHTMLHtmlElement(AtomicHTMLToken& token)
 {
-    ASSERT(!m_redirectAttachToFosterParent);
-    m_openElements.pushHTMLHtmlElement(createHTMLElementAndAttachToCurrent(token));
+    ASSERT(!shouldFosterParent());
+    m_openElements.pushHTMLHtmlElement(attachToCurrent(createHTMLElement(token)));
+    dispatchDocumentElementAvailableIfNeeded();
 }
 
 void HTMLConstructionSite::insertHTMLHeadElement(AtomicHTMLToken& token)
 {
-    ASSERT(!m_redirectAttachToFosterParent);
-    m_head = createHTMLElementAndAttachToCurrent(token);
+    ASSERT(!shouldFosterParent());
+    m_head = attachToCurrent(createHTMLElement(token));
     m_openElements.pushHTMLHeadElement(m_head);
 }
 
 void HTMLConstructionSite::insertHTMLBodyElement(AtomicHTMLToken& token)
 {
-    ASSERT(!m_redirectAttachToFosterParent);
-    m_openElements.pushHTMLBodyElement(createHTMLElementAndAttachToCurrent(token));
+    ASSERT(!shouldFosterParent());
+    m_openElements.pushHTMLBodyElement(attachToCurrent(createHTMLElement(token)));
 }
 
 void HTMLConstructionSite::insertHTMLElement(AtomicHTMLToken& token)
 {
-    m_openElements.push(createHTMLElementAndAttachToCurrent(token));
+    m_openElements.push(attachToCurrent(createHTMLElement(token)));
 }
 
 void HTMLConstructionSite::insertSelfClosingHTMLElement(AtomicHTMLToken& token)
 {
     ASSERT(token.type() == HTMLToken::StartTag);
-    attach(currentElement(), createHTMLElement(token));
+    RefPtr<Element> element = attachToCurrent(createHTMLElement(token));
+    // Normally HTMLElementStack is responsible for calling finishParsingChildren,
+    // but self-closing elements are never in the element stack so the stack
+    // doesn't get a chance to tell them that we're done parsing their children.
+    element->finishParsingChildren();
     // FIXME: Do we want to acknowledge the token's self-closing flag?
     // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#acknowledge-self-closing-flag
 }
@@ -233,7 +264,7 @@ void HTMLConstructionSite::insertScriptElement(AtomicHTMLToken& token)
 {
     RefPtr<HTMLScriptElement> element = HTMLScriptElement::create(scriptTag, m_document, true);
     element->setAttributeMap(token.takeAtributes(), m_fragmentScriptingPermission);
-    m_openElements.push(attach(currentElement(), element.release()));
+    m_openElements.push(attachToCurrent(element.release()));
 }
 
 void HTMLConstructionSite::insertForeignElement(AtomicHTMLToken& token, const AtomicString& namespaceURI)
@@ -241,7 +272,7 @@ void HTMLConstructionSite::insertForeignElement(AtomicHTMLToken& token, const At
     ASSERT(token.type() == HTMLToken::StartTag);
     notImplemented(); // parseError when xmlns or xmlns:xlink are wrong.
 
-    RefPtr<Element> element = attach(currentElement(), createElement(token, namespaceURI));
+    RefPtr<Element> element = attachToCurrent(createElement(token, namespaceURI));
     if (!token.selfClosing())
         m_openElements.push(element);
 }
@@ -251,7 +282,7 @@ void HTMLConstructionSite::insertTextNode(const String& characters)
     AttachmentSite site;
     site.parent = currentElement();
     site.nextChild = 0;
-    if (m_redirectAttachToFosterParent)
+    if (shouldFosterParent())
         findFosterSite(site);
 
     Node* previousChild = site.nextChild ? site.nextChild->previousSibling() : site.parent->lastChild();
@@ -281,6 +312,48 @@ PassRefPtr<Element> HTMLConstructionSite::createHTMLElement(AtomicHTMLToken& tok
     return element.release();
 }
 
+PassRefPtr<Element> HTMLConstructionSite::createHTMLElementFromElementRecord(HTMLElementStack::ElementRecord* record)
+{
+    // FIXME: This will change to use
+    // return createHTMLElementFromSavedElement(record->element());
+    // in a later patch once tested.
+    AtomicHTMLToken fakeToken(HTMLToken::StartTag, record->element()->localName());
+    return createHTMLElement(fakeToken);
+}
+
+namespace {
+
+PassRefPtr<NamedNodeMap> cloneAttributes(Element* element)
+{
+    NamedNodeMap* attributes = element->attributes(true);
+    if (!attributes)
+        return 0;
+
+    RefPtr<NamedNodeMap> newAttributes = NamedNodeMap::create();
+    for (size_t i = 0; i < attributes->length(); ++i) {
+        Attribute* attribute = attributes->attributeItem(i);
+        RefPtr<Attribute> clone = Attribute::createMapped(attribute->name(), attribute->value());
+        newAttributes->addAttribute(clone);
+    }
+    return newAttributes.release();
+}
+
+}
+
+PassRefPtr<Element> HTMLConstructionSite::createHTMLElementFromSavedElement(Element* element)
+{
+    // FIXME: This method is wrong.  We should be using the original token.
+    // Using an Element* causes us to fail examples like this:
+    // <b id="1"><p><script>document.getElementById("1").id = "2"</script></p>TEXT</b>
+    // When reconstructActiveFormattingElements calls this method to open
+    // a second <b> tag to wrap TEXT, it will have id "2", even though the HTML5
+    // spec implies it should be "1".  Minefield matches the HTML5 spec here.
+
+    ASSERT(element->isHTMLElement()); // otherwise localName() might be wrong.
+    AtomicHTMLToken fakeToken(HTMLToken::StartTag, element->localName(), cloneAttributes(element));
+    return createHTMLElement(fakeToken);
+}
+
 bool HTMLConstructionSite::indexOfFirstUnopenFormattingElement(unsigned& firstUnopenElementIndex) const
 {
     if (m_activeFormattingElements.isEmpty())
@@ -308,9 +381,8 @@ void HTMLConstructionSite::reconstructTheActiveFormattingElements()
     ASSERT(unopenEntryIndex < m_activeFormattingElements.size());
     for (; unopenEntryIndex < m_activeFormattingElements.size(); ++unopenEntryIndex) {
         HTMLFormattingElementList::Entry& unopenedEntry = m_activeFormattingElements.at(unopenEntryIndex);
-        // FIXME: We're supposed to save the original token in the entry.
-        AtomicHTMLToken fakeToken(HTMLToken::StartTag, unopenedEntry.element()->localName());
-        insertHTMLElement(fakeToken);
+        RefPtr<Element> reconstructed = createHTMLElementFromSavedElement(unopenedEntry.element());
+        m_openElements.push(attachToCurrent(reconstructed.release()));
         unopenedEntry.replaceElement(currentElement());
     }
 }
@@ -346,6 +418,12 @@ void HTMLConstructionSite::findFosterSite(AttachmentSite& site)
     site.nextChild = 0;
 }
 
+bool HTMLConstructionSite::shouldFosterParent() const
+{
+    return m_redirectAttachToFosterParent
+        && causesFosterParenting(currentElement()->tagQName());
+}
+
 void HTMLConstructionSite::fosterParent(Node* node)
 {
     AttachmentSite site;