diff -r 000000000000 -r 4f2f89ce4247 WebCore/html/HTMLConstructionSite.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebCore/html/HTMLConstructionSite.cpp Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2010 Google, Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "HTMLTreeBuilder.h" + +#include "Comment.h" +#include "DocumentFragment.h" +#include "DocumentType.h" +#include "Element.h" +#include "Frame.h" +#include "HTMLDocument.h" +#include "HTMLElementFactory.h" +#include "HTMLFormElement.h" +#include "HTMLHtmlElement.h" +#include "HTMLNames.h" +#include "HTMLScriptElement.h" +#include "HTMLToken.h" +#include "HTMLTokenizer.h" +#include "LegacyHTMLDocumentParser.h" +#include "LegacyHTMLTreeBuilder.h" +#include "LocalizedStrings.h" +#if ENABLE(MATHML) +#include "MathMLNames.h" +#endif +#include "NotImplemented.h" +#if ENABLE(SVG) +#include "SVGNames.h" +#endif +#include "ScriptController.h" +#include "Settings.h" +#include "Text.h" +#include + +namespace WebCore { + +using namespace HTMLNames; + +namespace { + +bool hasImpliedEndTag(Element* element) +{ + return element->hasTagName(ddTag) + || element->hasTagName(dtTag) + || element->hasTagName(liTag) + || element->hasTagName(optionTag) + || element->hasTagName(optgroupTag) + || element->hasTagName(pTag) + || element->hasTagName(rpTag) + || element->hasTagName(rtTag); +} + +bool causesFosterParenting(const QualifiedName& tagName) +{ + return tagName == tableTag + || tagName == tbodyTag + || tagName == tfootTag + || tagName == theadTag + || tagName == trTag; +} + +} // namespace + +template +PassRefPtr HTMLConstructionSite::attach(Node* parent, PassRefPtr prpChild) +{ + RefPtr child = prpChild; + + // 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 (shouldFosterParent()) { + fosterParent(child.get()); + ASSERT(child->attached()); + return child.release(); + } + + parent->parserAddChild(child); + // 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 + // |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. + if (parent->attached()) { + ASSERT(!child->attached()); + child->attach(); + } + return child.release(); +} + +void HTMLConstructionSite::attachAtSite(const AttachmentSite& site, PassRefPtr prpChild) +{ + RefPtr 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); + ASSERT(!ec); + if (site.parent->attached() && !child->attached()) + child->attach(); + return; + } + site.parent->parserAddChild(child); + // 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, bool isParsingFragment) + : m_document(document) + , m_fragmentScriptingPermission(scriptingPermission) + , m_isParsingFragment(isParsingFragment) + , m_redirectAttachToFosterParent(false) +{ +} + +HTMLConstructionSite::~HTMLConstructionSite() +{ +} + +PassRefPtr HTMLConstructionSite::takeForm() +{ + return m_form.release(); +} + +void HTMLConstructionSite::dispatchDocumentElementAvailableIfNeeded() +{ + if (m_document->frame() && !m_isParsingFragment) + m_document->frame()->loader()->dispatchDocumentElementAvailable(); +} + +void HTMLConstructionSite::insertHTMLHtmlStartTagBeforeHTML(AtomicHTMLToken& token) +{ + RefPtr 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) +{ + if (!token.attributes()) + return; + + NamedNodeMap* attributes = element->attributes(false); + for (unsigned i = 0; i < token.attributes()->length(); ++i) { + Attribute* attribute = token.attributes()->attributeItem(i); + if (!attributes->getAttributeItem(attribute->name())) + element->setAttribute(attribute->name(), attribute->value()); + } +} + +void HTMLConstructionSite::insertHTMLHtmlStartTagInBody(AtomicHTMLToken& token) +{ + // FIXME: parse error + mergeAttributesFromTokenIntoElement(token, m_openElements.htmlElement()); +} + +void HTMLConstructionSite::insertHTMLBodyStartTagInBody(AtomicHTMLToken& token) +{ + // FIXME: parse error + notImplemented(); // fragment case + mergeAttributesFromTokenIntoElement(token, m_openElements.bodyElement()); +} + +void HTMLConstructionSite::insertDoctype(AtomicHTMLToken& token) +{ + ASSERT(token.type() == HTMLToken::DOCTYPE); + attach(m_document, DocumentType::create(m_document, token.name(), String::adopt(token.publicIdentifier()), String::adopt(token.systemIdentifier()))); + // FIXME: Move quirks mode detection from DocumentType element to here. + notImplemented(); + if (token.forceQuirks()) + m_document->setParseMode(Document::Compat); +} + +void HTMLConstructionSite::insertComment(AtomicHTMLToken& token) +{ + ASSERT(token.type() == HTMLToken::Comment); + attach(currentElement(), Comment::create(m_document, token.comment())); +} + +void HTMLConstructionSite::insertCommentOnDocument(AtomicHTMLToken& token) +{ + ASSERT(token.type() == HTMLToken::Comment); + attach(m_document, Comment::create(m_document, token.comment())); +} + +void HTMLConstructionSite::insertCommentOnHTMLHtmlElement(AtomicHTMLToken& token) +{ + ASSERT(token.type() == HTMLToken::Comment); + attach(m_openElements.htmlElement(), Comment::create(m_document, token.comment())); +} + +PassRefPtr HTMLConstructionSite::attachToCurrent(PassRefPtr child) +{ + return attach(currentElement(), child); +} + +void HTMLConstructionSite::insertHTMLHtmlElement(AtomicHTMLToken& token) +{ + ASSERT(!shouldFosterParent()); + m_openElements.pushHTMLHtmlElement(attachToCurrent(createHTMLElement(token))); + dispatchDocumentElementAvailableIfNeeded(); +} + +void HTMLConstructionSite::insertHTMLHeadElement(AtomicHTMLToken& token) +{ + ASSERT(!shouldFosterParent()); + m_head = attachToCurrent(createHTMLElement(token)); + m_openElements.pushHTMLHeadElement(m_head); +} + +void HTMLConstructionSite::insertHTMLBodyElement(AtomicHTMLToken& token) +{ + ASSERT(!shouldFosterParent()); + m_openElements.pushHTMLBodyElement(attachToCurrent(createHTMLElement(token))); +} + +void HTMLConstructionSite::insertHTMLFormElement(AtomicHTMLToken& token) +{ + insertHTMLElement(token); + ASSERT(currentElement()->isHTMLElement()); + ASSERT(currentElement()->hasTagName(formTag)); + m_form = static_cast(currentElement()); +} + +void HTMLConstructionSite::insertHTMLElement(AtomicHTMLToken& token) +{ + m_openElements.push(attachToCurrent(createHTMLElement(token))); +} + +void HTMLConstructionSite::insertSelfClosingHTMLElement(AtomicHTMLToken& token) +{ + ASSERT(token.type() == HTMLToken::StartTag); + RefPtr 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 +} + +void HTMLConstructionSite::insertFormattingElement(AtomicHTMLToken& token) +{ + // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#the-stack-of-open-elements + // Possible active formatting elements include: + // a, b, big, code, em, font, i, nobr, s, small, strike, strong, tt, and u. + insertHTMLElement(token); + m_activeFormattingElements.append(currentElement()); +} + +void HTMLConstructionSite::insertScriptElement(AtomicHTMLToken& token) +{ + RefPtr element = HTMLScriptElement::create(scriptTag, m_document, true); + element->setAttributeMap(token.takeAtributes(), m_fragmentScriptingPermission); + m_openElements.push(attachToCurrent(element.release())); +} + +void HTMLConstructionSite::insertForeignElement(AtomicHTMLToken& token, const AtomicString& namespaceURI) +{ + ASSERT(token.type() == HTMLToken::StartTag); + notImplemented(); // parseError when xmlns or xmlns:xlink are wrong. + + RefPtr element = attachToCurrent(createElement(token, namespaceURI)); + if (!token.selfClosing()) + m_openElements.push(element); +} + +void HTMLConstructionSite::insertTextNode(const String& characters) +{ + AttachmentSite site; + site.parent = currentElement(); + site.nextChild = 0; + if (shouldFosterParent()) + findFosterSite(site); + + Node* previousChild = site.nextChild ? site.nextChild->previousSibling() : site.parent->lastChild(); + if (previousChild && previousChild->isTextNode()) { + // FIXME: We're only supposed to append to this text node if it + // was the last text node inserted by the parser. + CharacterData* textNode = static_cast(previousChild); + textNode->parserAppendData(characters); + return; + } + + attachAtSite(site, Text::create(m_document, characters)); +} + +PassRefPtr HTMLConstructionSite::createElement(AtomicHTMLToken& token, const AtomicString& namespaceURI) +{ + QualifiedName tagName(nullAtom, token.name(), namespaceURI); + RefPtr element = m_document->createElement(tagName, true); + element->setAttributeMap(token.takeAtributes(), m_fragmentScriptingPermission); + return element.release(); +} + +PassRefPtr HTMLConstructionSite::createHTMLElement(AtomicHTMLToken& token) +{ + QualifiedName tagName(nullAtom, token.name(), xhtmlNamespaceURI); + // FIXME: This can't use HTMLConstructionSite::createElement because we + // have to pass the current form element. We should rework form association + // to occur after construction to allow better code sharing here. + RefPtr element = HTMLElementFactory::createHTMLElement(tagName, m_document, form(), true); + element->setAttributeMap(token.takeAtributes(), m_fragmentScriptingPermission); + ASSERT(element->isHTMLElement()); + return element.release(); +} + +PassRefPtr HTMLConstructionSite::createHTMLElementFromElementRecord(HTMLElementStack::ElementRecord* record) +{ + return createHTMLElementFromSavedElement(record->element()); +} + +namespace { + +PassRefPtr cloneAttributes(Element* element) +{ + NamedNodeMap* attributes = element->attributes(true); + if (!attributes) + return 0; + + RefPtr newAttributes = NamedNodeMap::create(); + for (size_t i = 0; i < attributes->length(); ++i) { + Attribute* attribute = attributes->attributeItem(i); + RefPtr clone = Attribute::createMapped(attribute->name(), attribute->value()); + newAttributes->addAttribute(clone); + } + return newAttributes.release(); +} + +} + +PassRefPtr 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: + //

TEXT
+ // When reconstructTheActiveFormattingElements calls this method to open + // a second 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()) + return false; + unsigned index = m_activeFormattingElements.size(); + do { + --index; + const HTMLFormattingElementList::Entry& entry = m_activeFormattingElements.at(index); + if (entry.isMarker() || m_openElements.contains(entry.element())) { + firstUnopenElementIndex = index + 1; + return firstUnopenElementIndex < m_activeFormattingElements.size(); + } + } while (index); + firstUnopenElementIndex = index; + return true; +} + +void HTMLConstructionSite::reconstructTheActiveFormattingElements() +{ + unsigned firstUnopenElementIndex; + if (!indexOfFirstUnopenFormattingElement(firstUnopenElementIndex)) + return; + + unsigned unopenEntryIndex = firstUnopenElementIndex; + ASSERT(unopenEntryIndex < m_activeFormattingElements.size()); + for (; unopenEntryIndex < m_activeFormattingElements.size(); ++unopenEntryIndex) { + HTMLFormattingElementList::Entry& unopenedEntry = m_activeFormattingElements.at(unopenEntryIndex); + RefPtr reconstructed = createHTMLElementFromSavedElement(unopenedEntry.element()); + m_openElements.push(attachToCurrent(reconstructed.release())); + unopenedEntry.replaceElement(currentElement()); + } +} + +void HTMLConstructionSite::generateImpliedEndTagsWithExclusion(const AtomicString& tagName) +{ + while (hasImpliedEndTag(currentElement()) && !currentElement()->hasLocalName(tagName)) + m_openElements.pop(); +} + +void HTMLConstructionSite::generateImpliedEndTags() +{ + while (hasImpliedEndTag(currentElement())) + m_openElements.pop(); +} + +void HTMLConstructionSite::findFosterSite(AttachmentSite& site) +{ + HTMLElementStack::ElementRecord* lastTableElementRecord = m_openElements.topmost(tableTag.localName()); + if (lastTableElementRecord) { + Element* lastTableElement = lastTableElementRecord->element(); + if (Node* parent = lastTableElement->parent()) { + site.parent = parent; + site.nextChild = lastTableElement; + return; + } + site.parent = lastTableElementRecord->next()->element(); + site.nextChild = 0; + return; + } + // Fragment case + site.parent = m_openElements.bottom(); // element + site.nextChild = 0; +} + +bool HTMLConstructionSite::shouldFosterParent() const +{ + return m_redirectAttachToFosterParent + && causesFosterParenting(currentElement()->tagQName()); +} + +void HTMLConstructionSite::fosterParent(Node* node) +{ + AttachmentSite site; + findFosterSite(site); + attachAtSite(site, node); +} + +}