blob: 2674bf790684544ec50ca6b69185288e74eb2741 [file] [log] [blame]
/*
* Copyright (C) 2010 Google, Inc. All Rights Reserved.
* Copyright (C) 2011 Apple 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 "core/html/parser/HTMLTreeBuilder.h"
#include "HTMLElementFactory.h"
#include "HTMLNames.h"
#include "core/dom/Comment.h"
#include "core/dom/DocumentFragment.h"
#include "core/dom/DocumentType.h"
#include "core/dom/Element.h"
#include "core/dom/ScriptLoader.h"
#include "core/dom/Text.h"
#include "core/html/HTMLFormElement.h"
#include "core/html/HTMLHtmlElement.h"
#include "core/html/HTMLOptGroupElement.h"
#include "core/html/HTMLScriptElement.h"
#include "core/html/HTMLTemplateElement.h"
#include "core/html/parser/AtomicHTMLToken.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/html/parser/HTMLStackItem.h"
#include "core/html/parser/HTMLToken.h"
#include "core/loader/FrameLoader.h"
#include "core/loader/FrameLoaderClient.h"
#include "core/frame/Frame.h"
#include "platform/NotImplemented.h"
#include "platform/text/TextBreakIterator.h"
#include <limits>
namespace WebCore {
using namespace HTMLNames;
static const unsigned maximumHTMLParserDOMTreeDepth = 512;
static inline void setAttributes(Element* element, AtomicHTMLToken* token, ParserContentPolicy parserContentPolicy)
{
if (!scriptingContentIsAllowed(parserContentPolicy))
element->stripScriptingAttributes(token->attributes());
element->parserSetAttributes(token->attributes());
}
static bool hasImpliedEndTag(const HTMLStackItem* item)
{
return item->hasTagName(ddTag)
|| item->hasTagName(dtTag)
|| item->hasTagName(liTag)
|| item->hasTagName(optionTag)
|| isHTMLOptGroupElement(item->node())
|| item->hasTagName(pTag)
|| item->hasTagName(rpTag)
|| item->hasTagName(rtTag);
}
static bool shouldUseLengthLimit(const ContainerNode* node)
{
return !node->hasTagName(scriptTag)
&& !node->hasTagName(styleTag)
&& !node->hasTagName(SVGNames::scriptTag);
}
static unsigned textLengthLimitForContainer(const ContainerNode* node)
{
return shouldUseLengthLimit(node) ? Text::defaultLengthLimit : std::numeric_limits<unsigned>::max();
}
static inline bool isAllWhitespace(const String& string)
{
return string.isAllSpecialCharacters<isHTMLSpace<UChar> >();
}
static inline void insert(HTMLConstructionSiteTask& task)
{
if (task.parent->hasTagName(templateTag))
task.parent = toHTMLTemplateElement(task.parent.get())->content();
if (ContainerNode* parent = task.child->parentNode())
parent->parserRemoveChild(task.child.get());
if (task.nextChild)
task.parent->parserInsertBefore(task.child.get(), task.nextChild.get());
else
task.parent->parserAppendChild(task.child.get());
}
static inline void executeInsertTask(HTMLConstructionSiteTask& task)
{
ASSERT(task.operation == HTMLConstructionSiteTask::Insert);
insert(task);
task.child->beginParsingChildren();
if (task.selfClosing)
task.child->finishParsingChildren();
}
static inline void executeReparentTask(HTMLConstructionSiteTask& task)
{
ASSERT(task.operation == HTMLConstructionSiteTask::Reparent);
if (ContainerNode* parent = task.child->parentNode())
parent->parserRemoveChild(task.child.get());
task.parent->parserAppendChild(task.child);
}
static inline void executeInsertAlreadyParsedChildTask(HTMLConstructionSiteTask& task)
{
ASSERT(task.operation == HTMLConstructionSiteTask::InsertAlreadyParsedChild);
insert(task);
}
static inline void executeTakeAllChildrenTask(HTMLConstructionSiteTask& task)
{
ASSERT(task.operation == HTMLConstructionSiteTask::TakeAllChildren);
task.parent->parserTakeAllChildrenFrom(task.oldParent());
}
void HTMLConstructionSite::executeTask(HTMLConstructionSiteTask& task)
{
ASSERT(m_taskQueue.isEmpty());
if (task.operation == HTMLConstructionSiteTask::Insert)
return executeInsertTask(task);
// All the cases below this point are only used by the adoption agency.
if (task.operation == HTMLConstructionSiteTask::InsertAlreadyParsedChild)
return executeInsertAlreadyParsedChildTask(task);
if (task.operation == HTMLConstructionSiteTask::Reparent)
return executeReparentTask(task);
if (task.operation == HTMLConstructionSiteTask::TakeAllChildren)
return executeTakeAllChildrenTask(task);
ASSERT_NOT_REACHED();
}
// This is only needed for TextDocuments where we might have text nodes
// approaching the default length limit (~64k) and we don't want to
// break a text node in the middle of a combining character.
static unsigned findBreakIndexBetween(const String& string, unsigned currentPosition, unsigned proposedBreakIndex)
{
ASSERT(currentPosition < proposedBreakIndex);
ASSERT(proposedBreakIndex <= string.length());
// The end of the string is always a valid break.
if (proposedBreakIndex == string.length())
return proposedBreakIndex;
// Latin-1 does not have breakable boundaries. If we ever moved to a differnet 8-bit encoding this could be wrong.
if (string.is8Bit())
return proposedBreakIndex;
const UChar* breakSearchCharacters = string.characters16() + currentPosition;
// We need at least two characters look-ahead to account for UTF-16 surrogates, but can't search off the end of the buffer!
unsigned breakSearchLength = std::min(proposedBreakIndex - currentPosition + 2, string.length() - currentPosition);
NonSharedCharacterBreakIterator it(breakSearchCharacters, breakSearchLength);
if (it.isBreak(proposedBreakIndex - currentPosition))
return proposedBreakIndex;
int adjustedBreakIndexInSubstring = it.preceding(proposedBreakIndex - currentPosition);
if (adjustedBreakIndexInSubstring > 0)
return currentPosition + adjustedBreakIndexInSubstring;
// We failed to find a breakable point, let the caller figure out what to do.
return 0;
}
static String atomizeIfAllWhitespace(const String& string, WhitespaceMode whitespaceMode)
{
// Strings composed entirely of whitespace are likely to be repeated.
// Turn them into AtomicString so we share a single string for each.
if (whitespaceMode == AllWhitespace || (whitespaceMode == WhitespaceUnknown && isAllWhitespace(string)))
return AtomicString(string).string();
return string;
}
void HTMLConstructionSite::queueTask(const HTMLConstructionSiteTask& task)
{
m_taskQueue.append(task);
}
void HTMLConstructionSite::attachLater(ContainerNode* parent, PassRefPtr<Node> prpChild, bool selfClosing)
{
ASSERT(scriptingContentIsAllowed(m_parserContentPolicy) || !prpChild.get()->isElementNode() || !toScriptLoaderIfPossible(toElement(prpChild.get())));
ASSERT(pluginContentIsAllowed(m_parserContentPolicy) || !prpChild->isPluginElement());
HTMLConstructionSiteTask task(HTMLConstructionSiteTask::Insert);
task.parent = parent;
task.child = prpChild;
task.selfClosing = selfClosing;
if (shouldFosterParent()) {
fosterParent(task.child);
return;
}
// Add as a sibling of the parent if we have reached the maximum depth allowed.
if (m_openElements.stackDepth() > maximumHTMLParserDOMTreeDepth && task.parent->parentNode())
task.parent = task.parent->parentNode();
ASSERT(task.parent);
queueTask(task);
}
void HTMLConstructionSite::executeQueuedTasks()
{
const size_t size = m_taskQueue.size();
if (!size)
return;
// Copy the task queue into a local variable in case executeTask
// re-enters the parser.
TaskQueue queue;
queue.swap(m_taskQueue);
for (size_t i = 0; i < size; ++i)
executeTask(queue[i]);
// We might be detached now.
}
HTMLConstructionSite::HTMLConstructionSite(Document* document, ParserContentPolicy parserContentPolicy)
: m_document(document)
, m_attachmentRoot(document)
, m_parserContentPolicy(parserContentPolicy)
, m_isParsingFragment(false)
, m_redirectAttachToFosterParent(false)
, m_inQuirksMode(document->inQuirksMode())
{
ASSERT(m_document->isHTMLDocument() || m_document->isXHTMLDocument());
}
HTMLConstructionSite::HTMLConstructionSite(DocumentFragment* fragment, ParserContentPolicy parserContentPolicy)
: m_document(&fragment->document())
, m_attachmentRoot(fragment)
, m_parserContentPolicy(parserContentPolicy)
, m_isParsingFragment(true)
, m_redirectAttachToFosterParent(false)
, m_inQuirksMode(fragment->document().inQuirksMode())
{
ASSERT(m_document->isHTMLDocument() || m_document->isXHTMLDocument());
}
HTMLConstructionSite::~HTMLConstructionSite()
{
// Depending on why we're being destroyed it might be OK
// to forget queued tasks, but currently we don't expect to.
ASSERT(m_taskQueue.isEmpty());
}
void HTMLConstructionSite::detach()
{
m_document = 0;
m_attachmentRoot = 0;
}
void HTMLConstructionSite::setForm(HTMLFormElement* form)
{
// This method should only be needed for HTMLTreeBuilder in the fragment case.
ASSERT(!m_form);
m_form = form;
}
PassRefPtr<HTMLFormElement> HTMLConstructionSite::takeForm()
{
return m_form.release();
}
void HTMLConstructionSite::dispatchDocumentElementAvailableIfNeeded()
{
ASSERT(m_document);
if (m_document->frame() && !m_isParsingFragment)
m_document->frame()->loader()->dispatchDocumentElementAvailable();
}
void HTMLConstructionSite::insertHTMLHtmlStartTagBeforeHTML(AtomicHTMLToken* token)
{
ASSERT(m_document);
RefPtr<HTMLHtmlElement> element = HTMLHtmlElement::create(*m_document);
setAttributes(element.get(), token, m_parserContentPolicy);
attachLater(m_attachmentRoot, element);
m_openElements.pushHTMLHtmlElement(HTMLStackItem::create(element, token));
executeQueuedTasks();
element->insertedByParser();
dispatchDocumentElementAvailableIfNeeded();
}
void HTMLConstructionSite::mergeAttributesFromTokenIntoElement(AtomicHTMLToken* token, Element* element)
{
if (token->attributes().isEmpty())
return;
for (unsigned i = 0; i < token->attributes().size(); ++i) {
const Attribute& tokenAttribute = token->attributes().at(i);
if (!element->elementData() || !element->getAttributeItem(tokenAttribute.name()))
element->setAttribute(tokenAttribute.name(), tokenAttribute.value());
}
}
void HTMLConstructionSite::insertHTMLHtmlStartTagInBody(AtomicHTMLToken* token)
{
// Fragments do not have a root HTML element, so any additional HTML elements
// encountered during fragment parsing should be ignored.
if (m_isParsingFragment)
return;
mergeAttributesFromTokenIntoElement(token, m_openElements.htmlElement());
}
void HTMLConstructionSite::insertHTMLBodyStartTagInBody(AtomicHTMLToken* token)
{
mergeAttributesFromTokenIntoElement(token, m_openElements.bodyElement());
}
void HTMLConstructionSite::setDefaultCompatibilityMode()
{
if (m_isParsingFragment)
return;
if (m_document->isSrcdocDocument())
return;
setCompatibilityMode(Document::QuirksMode);
}
void HTMLConstructionSite::setCompatibilityMode(Document::CompatibilityMode mode)
{
m_inQuirksMode = (mode == Document::QuirksMode);
m_document->setCompatibilityMode(mode);
}
void HTMLConstructionSite::setCompatibilityModeFromDoctype(const String& name, const String& publicId, const String& systemId)
{
// There are three possible compatibility modes:
// Quirks - quirks mode emulates WinIE and NS4. CSS parsing is also relaxed in this mode, e.g., unit types can
// be omitted from numbers.
// Limited Quirks - This mode is identical to no-quirks mode except for its treatment of line-height in the inline box model.
// No Quirks - no quirks apply. Web pages will obey the specifications to the letter.
// Check for Quirks Mode.
if (name != "html"
|| publicId.startsWith("+//Silmaril//dtd html Pro v0r11 19970101//", false)
|| publicId.startsWith("-//AdvaSoft Ltd//DTD HTML 3.0 asWedit + extensions//", false)
|| publicId.startsWith("-//AS//DTD HTML 3.0 asWedit + extensions//", false)
|| publicId.startsWith("-//IETF//DTD HTML 2.0 Level 1//", false)
|| publicId.startsWith("-//IETF//DTD HTML 2.0 Level 2//", false)
|| publicId.startsWith("-//IETF//DTD HTML 2.0 Strict Level 1//", false)
|| publicId.startsWith("-//IETF//DTD HTML 2.0 Strict Level 2//", false)
|| publicId.startsWith("-//IETF//DTD HTML 2.0 Strict//", false)
|| publicId.startsWith("-//IETF//DTD HTML 2.0//", false)
|| publicId.startsWith("-//IETF//DTD HTML 2.1E//", false)
|| publicId.startsWith("-//IETF//DTD HTML 3.0//", false)
|| publicId.startsWith("-//IETF//DTD HTML 3.2 Final//", false)
|| publicId.startsWith("-//IETF//DTD HTML 3.2//", false)
|| publicId.startsWith("-//IETF//DTD HTML 3//", false)
|| publicId.startsWith("-//IETF//DTD HTML Level 0//", false)
|| publicId.startsWith("-//IETF//DTD HTML Level 1//", false)
|| publicId.startsWith("-//IETF//DTD HTML Level 2//", false)
|| publicId.startsWith("-//IETF//DTD HTML Level 3//", false)
|| publicId.startsWith("-//IETF//DTD HTML Strict Level 0//", false)
|| publicId.startsWith("-//IETF//DTD HTML Strict Level 1//", false)
|| publicId.startsWith("-//IETF//DTD HTML Strict Level 2//", false)
|| publicId.startsWith("-//IETF//DTD HTML Strict Level 3//", false)
|| publicId.startsWith("-//IETF//DTD HTML Strict//", false)
|| publicId.startsWith("-//IETF//DTD HTML//", false)
|| publicId.startsWith("-//Metrius//DTD Metrius Presentational//", false)
|| publicId.startsWith("-//Microsoft//DTD Internet Explorer 2.0 HTML Strict//", false)
|| publicId.startsWith("-//Microsoft//DTD Internet Explorer 2.0 HTML//", false)
|| publicId.startsWith("-//Microsoft//DTD Internet Explorer 2.0 Tables//", false)
|| publicId.startsWith("-//Microsoft//DTD Internet Explorer 3.0 HTML Strict//", false)
|| publicId.startsWith("-//Microsoft//DTD Internet Explorer 3.0 HTML//", false)
|| publicId.startsWith("-//Microsoft//DTD Internet Explorer 3.0 Tables//", false)
|| publicId.startsWith("-//Netscape Comm. Corp.//DTD HTML//", false)
|| publicId.startsWith("-//Netscape Comm. Corp.//DTD Strict HTML//", false)
|| publicId.startsWith("-//O'Reilly and Associates//DTD HTML 2.0//", false)
|| publicId.startsWith("-//O'Reilly and Associates//DTD HTML Extended 1.0//", false)
|| publicId.startsWith("-//O'Reilly and Associates//DTD HTML Extended Relaxed 1.0//", false)
|| publicId.startsWith("-//SoftQuad Software//DTD HoTMetaL PRO 6.0::19990601::extensions to HTML 4.0//", false)
|| publicId.startsWith("-//SoftQuad//DTD HoTMetaL PRO 4.0::19971010::extensions to HTML 4.0//", false)
|| publicId.startsWith("-//Spyglass//DTD HTML 2.0 Extended//", false)
|| publicId.startsWith("-//SQ//DTD HTML 2.0 HoTMetaL + extensions//", false)
|| publicId.startsWith("-//Sun Microsystems Corp.//DTD HotJava HTML//", false)
|| publicId.startsWith("-//Sun Microsystems Corp.//DTD HotJava Strict HTML//", false)
|| publicId.startsWith("-//W3C//DTD HTML 3 1995-03-24//", false)
|| publicId.startsWith("-//W3C//DTD HTML 3.2 Draft//", false)
|| publicId.startsWith("-//W3C//DTD HTML 3.2 Final//", false)
|| publicId.startsWith("-//W3C//DTD HTML 3.2//", false)
|| publicId.startsWith("-//W3C//DTD HTML 3.2S Draft//", false)
|| publicId.startsWith("-//W3C//DTD HTML 4.0 Frameset//", false)
|| publicId.startsWith("-//W3C//DTD HTML 4.0 Transitional//", false)
|| publicId.startsWith("-//W3C//DTD HTML Experimental 19960712//", false)
|| publicId.startsWith("-//W3C//DTD HTML Experimental 970421//", false)
|| publicId.startsWith("-//W3C//DTD W3 HTML//", false)
|| publicId.startsWith("-//W3O//DTD W3 HTML 3.0//", false)
|| equalIgnoringCase(publicId, "-//W3O//DTD W3 HTML Strict 3.0//EN//")
|| publicId.startsWith("-//WebTechs//DTD Mozilla HTML 2.0//", false)
|| publicId.startsWith("-//WebTechs//DTD Mozilla HTML//", false)
|| equalIgnoringCase(publicId, "-/W3C/DTD HTML 4.0 Transitional/EN")
|| equalIgnoringCase(publicId, "HTML")
|| equalIgnoringCase(systemId, "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd")
|| (systemId.isEmpty() && publicId.startsWith("-//W3C//DTD HTML 4.01 Frameset//", false))
|| (systemId.isEmpty() && publicId.startsWith("-//W3C//DTD HTML 4.01 Transitional//", false))) {
setCompatibilityMode(Document::QuirksMode);
return;
}
// Check for Limited Quirks Mode.
if (publicId.startsWith("-//W3C//DTD XHTML 1.0 Frameset//", false)
|| publicId.startsWith("-//W3C//DTD XHTML 1.0 Transitional//", false)
|| (!systemId.isEmpty() && publicId.startsWith("-//W3C//DTD HTML 4.01 Frameset//", false))
|| (!systemId.isEmpty() && publicId.startsWith("-//W3C//DTD HTML 4.01 Transitional//", false))) {
setCompatibilityMode(Document::LimitedQuirksMode);
return;
}
// Otherwise we are No Quirks Mode.
setCompatibilityMode(Document::NoQuirksMode);
}
void HTMLConstructionSite::processEndOfFile()
{
ASSERT(currentNode());
openElements()->popAll();
}
void HTMLConstructionSite::finishedParsing()
{
ASSERT(m_taskQueue.isEmpty());
m_document->finishedParsing();
}
void HTMLConstructionSite::insertDoctype(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::DOCTYPE);
const String& publicId = StringImpl::create8BitIfPossible(token->publicIdentifier());
const String& systemId = StringImpl::create8BitIfPossible(token->systemIdentifier());
RefPtr<DocumentType> doctype = DocumentType::create(m_document, token->name(), publicId, systemId);
attachLater(m_attachmentRoot, doctype.release());
// DOCTYPE nodes are only processed when parsing fragments w/o contextElements, which
// never occurs. However, if we ever chose to support such, this code is subtly wrong,
// because context-less fragments can determine their own quirks mode, and thus change
// parsing rules (like <p> inside <table>). For now we ASSERT that we never hit this code
// in a fragment, as changing the owning document's compatibility mode would be wrong.
ASSERT(!m_isParsingFragment);
if (m_isParsingFragment)
return;
if (token->forceQuirks())
setCompatibilityMode(Document::QuirksMode);
else {
setCompatibilityModeFromDoctype(token->name(), publicId, systemId);
}
}
void HTMLConstructionSite::insertComment(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::Comment);
attachLater(currentNode(), Comment::create(ownerDocumentForCurrentNode(), token->comment()));
}
void HTMLConstructionSite::insertCommentOnDocument(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::Comment);
ASSERT(m_document);
attachLater(m_attachmentRoot, Comment::create(*m_document, token->comment()));
}
void HTMLConstructionSite::insertCommentOnHTMLHtmlElement(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::Comment);
ContainerNode* parent = m_openElements.rootNode();
attachLater(parent, Comment::create(parent->document(), token->comment()));
}
void HTMLConstructionSite::insertHTMLHeadElement(AtomicHTMLToken* token)
{
ASSERT(!shouldFosterParent());
m_head = HTMLStackItem::create(createHTMLElement(token), token);
attachLater(currentNode(), m_head->element());
m_openElements.pushHTMLHeadElement(m_head);
}
void HTMLConstructionSite::insertHTMLBodyElement(AtomicHTMLToken* token)
{
ASSERT(!shouldFosterParent());
RefPtr<Element> body = createHTMLElement(token);
attachLater(currentNode(), body);
m_openElements.pushHTMLBodyElement(HTMLStackItem::create(body.release(), token));
if (Frame* frame = m_document->frame())
frame->loader()->client()->dispatchWillInsertBody();
}
void HTMLConstructionSite::insertHTMLFormElement(AtomicHTMLToken* token, bool isDemoted)
{
RefPtr<Element> element = createHTMLElement(token);
ASSERT(element->hasTagName(formTag));
m_form = static_pointer_cast<HTMLFormElement>(element.release());
m_form->setDemoted(isDemoted);
attachLater(currentNode(), m_form);
m_openElements.push(HTMLStackItem::create(m_form, token));
}
void HTMLConstructionSite::insertHTMLElement(AtomicHTMLToken* token)
{
RefPtr<Element> element = createHTMLElement(token);
attachLater(currentNode(), element);
m_openElements.push(HTMLStackItem::create(element.release(), token));
}
void HTMLConstructionSite::insertSelfClosingHTMLElement(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::StartTag);
// 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.
attachLater(currentNode(), createHTMLElement(token), true);
// 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(currentElementRecord()->stackItem());
}
void HTMLConstructionSite::insertScriptElement(AtomicHTMLToken* token)
{
// http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#already-started
// http://html5.org/specs/dom-parsing.html#dom-range-createcontextualfragment
// For createContextualFragment, the specifications say to mark it parser-inserted and already-started and later unmark them.
// However, we short circuit that logic to avoid the subtree traversal to find script elements since scripts can never see
// those flags or effects thereof.
const bool parserInserted = m_parserContentPolicy != AllowScriptingContentAndDoNotMarkAlreadyStarted;
const bool alreadyStarted = m_isParsingFragment && parserInserted;
RefPtr<HTMLScriptElement> element = HTMLScriptElement::create(scriptTag, ownerDocumentForCurrentNode(), parserInserted, alreadyStarted);
setAttributes(element.get(), token, m_parserContentPolicy);
if (scriptingContentIsAllowed(m_parserContentPolicy))
attachLater(currentNode(), element);
m_openElements.push(HTMLStackItem::create(element.release(), token));
}
void HTMLConstructionSite::insertForeignElement(AtomicHTMLToken* token, const AtomicString& namespaceURI)
{
ASSERT(token->type() == HTMLToken::StartTag);
notImplemented(); // parseError when xmlns or xmlns:xlink are wrong.
RefPtr<Element> element = createElement(token, namespaceURI);
if (scriptingContentIsAllowed(m_parserContentPolicy) || !toScriptLoaderIfPossible(element.get()))
attachLater(currentNode(), element, token->selfClosing());
if (!token->selfClosing())
m_openElements.push(HTMLStackItem::create(element.release(), token, namespaceURI));
}
void HTMLConstructionSite::insertTextNode(const String& string, WhitespaceMode whitespaceMode)
{
HTMLConstructionSiteTask protoTask(HTMLConstructionSiteTask::Insert);
protoTask.parent = currentNode();
if (shouldFosterParent())
findFosterSite(protoTask);
// FIXME: This probably doesn't need to be done both here and in insert(Task).
if (protoTask.parent->hasTagName(templateTag))
protoTask.parent = toHTMLTemplateElement(protoTask.parent.get())->content();
// Splitting text nodes into smaller chunks contradicts HTML5 spec, but is necessary
// for performance, see: https://bugs.webkit.org/show_bug.cgi?id=55898
unsigned lengthLimit = textLengthLimitForContainer(protoTask.parent.get());
unsigned currentPosition = 0;
// Merge text nodes into previous ones if possible:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#insert-a-character
Node* previousChild = protoTask.nextChild ? protoTask.nextChild->previousSibling() : protoTask.parent->lastChild();
if (previousChild && previousChild->isTextNode()) {
Text* previousText = toText(previousChild);
unsigned appendLengthLimit = lengthLimit - previousText->length();
unsigned proposedBreakIndex = std::min(currentPosition + appendLengthLimit, string.length());
unsigned breakIndex = findBreakIndexBetween(string, currentPosition, proposedBreakIndex);
ASSERT(breakIndex <= string.length());
// If we didn't find a breable piece to append, forget it.
if (breakIndex) {
String substring = string.substring(currentPosition, breakIndex - currentPosition);
substring = atomizeIfAllWhitespace(substring, whitespaceMode);
previousText->parserAppendData(substring);
currentPosition += substring.length();
}
}
while (currentPosition < string.length()) {
unsigned proposedBreakIndex = std::min(currentPosition + lengthLimit, string.length());
unsigned breakIndex = findBreakIndexBetween(string, currentPosition, proposedBreakIndex);
// We failed to find a breakable boudary between the minimum and the proposed, just give up and break at the proposed index.
// We could go searching after the proposed index, but current callers are attempting to break after 65k chars!
// 65k of unbreakable characters isn't worth trying to handle "correctly".
if (!breakIndex)
breakIndex = proposedBreakIndex;
ASSERT(breakIndex <= string.length());
String substring = string.substring(currentPosition, breakIndex - currentPosition);
substring = atomizeIfAllWhitespace(substring, whitespaceMode);
HTMLConstructionSiteTask task(HTMLConstructionSiteTask::Insert);
task.parent = protoTask.parent;
task.nextChild = protoTask.nextChild;
task.child = Text::create(task.parent->document(), substring);
queueTask(task);
ASSERT(breakIndex > currentPosition);
ASSERT(breakIndex - currentPosition == substring.length());
ASSERT(toText(task.child.get())->length() == substring.length());
currentPosition = breakIndex;
}
}
void HTMLConstructionSite::reparent(HTMLElementStack::ElementRecord* newParent, HTMLElementStack::ElementRecord* child)
{
HTMLConstructionSiteTask task(HTMLConstructionSiteTask::Reparent);
task.parent = newParent->node();
task.child = child->node();
queueTask(task);
}
void HTMLConstructionSite::reparent(HTMLElementStack::ElementRecord* newParent, HTMLStackItem* child)
{
HTMLConstructionSiteTask task(HTMLConstructionSiteTask::Reparent);
task.parent = newParent->node();
task.child = child->node();
queueTask(task);
}
void HTMLConstructionSite::insertAlreadyParsedChild(HTMLStackItem* newParent, HTMLElementStack::ElementRecord* child)
{
if (newParent->causesFosterParenting()) {
fosterParent(child->node());
return;
}
HTMLConstructionSiteTask task(HTMLConstructionSiteTask::InsertAlreadyParsedChild);
task.parent = newParent->node();
task.child = child->node();
queueTask(task);
}
void HTMLConstructionSite::takeAllChildren(HTMLStackItem* newParent, HTMLElementStack::ElementRecord* oldParent)
{
HTMLConstructionSiteTask task(HTMLConstructionSiteTask::TakeAllChildren);
task.parent = newParent->node();
task.child = oldParent->node();
queueTask(task);
}
PassRefPtr<Element> HTMLConstructionSite::createElement(AtomicHTMLToken* token, const AtomicString& namespaceURI)
{
QualifiedName tagName(nullAtom, token->name(), namespaceURI);
RefPtr<Element> element = ownerDocumentForCurrentNode().createElement(tagName, true);
setAttributes(element.get(), token, m_parserContentPolicy);
return element.release();
}
inline Document& HTMLConstructionSite::ownerDocumentForCurrentNode()
{
if (currentNode()->hasTagName(templateTag))
return toHTMLTemplateElement(currentElement())->content()->document();
return currentNode()->document();
}
PassRefPtr<Element> HTMLConstructionSite::createHTMLElement(AtomicHTMLToken* token)
{
QualifiedName tagName(nullAtom, token->name(), xhtmlNamespaceURI);
Document& document = ownerDocumentForCurrentNode();
// Only associate the element with the current form if we're creating the new element
// in a document with a browsing context (rather than in <template> contents).
HTMLFormElement* form = document.frame() ? m_form.get() : 0;
// 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> element = HTMLElementFactory::createHTMLElement(tagName, &document, form, true);
setAttributes(element.get(), token, m_parserContentPolicy);
ASSERT(element->isHTMLElement());
return element.release();
}
PassRefPtr<HTMLStackItem> HTMLConstructionSite::createElementFromSavedToken(HTMLStackItem* item)
{
RefPtr<Element> element;
// NOTE: Moving from item -> token -> item copies the Attribute vector twice!
AtomicHTMLToken fakeToken(HTMLToken::StartTag, item->localName(), item->attributes());
if (item->namespaceURI() == HTMLNames::xhtmlNamespaceURI)
element = createHTMLElement(&fakeToken);
else
element = createElement(&fakeToken, item->namespaceURI());
return HTMLStackItem::create(element.release(), &fakeToken, item->namespaceURI());
}
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<HTMLStackItem> reconstructed = createElementFromSavedToken(unopenedEntry.stackItem().get());
attachLater(currentNode(), reconstructed->node());
m_openElements.push(reconstructed);
unopenedEntry.replaceElement(reconstructed.release());
}
}
void HTMLConstructionSite::generateImpliedEndTagsWithExclusion(const AtomicString& tagName)
{
while (hasImpliedEndTag(currentStackItem()) && !currentStackItem()->matchesHTMLTag(tagName))
m_openElements.pop();
}
void HTMLConstructionSite::generateImpliedEndTags()
{
while (hasImpliedEndTag(currentStackItem()))
m_openElements.pop();
}
bool HTMLConstructionSite::inQuirksMode()
{
return m_inQuirksMode;
}
void HTMLConstructionSite::findFosterSite(HTMLConstructionSiteTask& task)
{
// When a node is to be foster parented, the last template element with no table element is below it in the stack of open elements is the foster parent element (NOT the template's parent!)
HTMLElementStack::ElementRecord* lastTemplateElement = m_openElements.topmost(templateTag.localName());
if (lastTemplateElement && !m_openElements.inTableScope(tableTag)) {
task.parent = lastTemplateElement->element();
return;
}
HTMLElementStack::ElementRecord* lastTableElementRecord = m_openElements.topmost(tableTag.localName());
if (lastTableElementRecord) {
Element* lastTableElement = lastTableElementRecord->element();
ContainerNode* parent;
if (lastTableElementRecord->next()->stackItem()->hasTagName(templateTag))
parent = lastTableElementRecord->next()->element();
else
parent = lastTableElement->parentNode();
// When parsing HTML fragments, we skip step 4.2 ("Let root be a new html element with no attributes") for efficiency,
// and instead use the DocumentFragment as a root node. So we must treat the root node (DocumentFragment) as if it is a html element here.
if (parent && (parent->isElementNode() || (m_isParsingFragment && parent == m_openElements.rootNode()))) {
task.parent = parent;
task.nextChild = lastTableElement;
return;
}
task.parent = lastTableElementRecord->next()->element();
return;
}
// Fragment case
task.parent = m_openElements.rootNode(); // DocumentFragment
}
bool HTMLConstructionSite::shouldFosterParent() const
{
return m_redirectAttachToFosterParent
&& currentStackItem()->isElementNode()
&& currentStackItem()->causesFosterParenting();
}
void HTMLConstructionSite::fosterParent(PassRefPtr<Node> node)
{
HTMLConstructionSiteTask task(HTMLConstructionSiteTask::Insert);
findFosterSite(task);
task.child = node;
ASSERT(task.parent);
queueTask(task);
}
}