/* * 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() && !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); } }