HTMLDocumentParser.cpp [plain text]
#include "config.h"
#include "HTMLDocumentParser.h"
#include "ContentSecurityPolicy.h"
#include "DocumentFragment.h"
#include "Element.h"
#include "Frame.h"
#include "HTMLNames.h"
#include "HTMLParserScheduler.h"
#include "HTMLTokenizer.h"
#include "HTMLPreloadScanner.h"
#include "HTMLScriptRunner.h"
#include "HTMLTreeBuilder.h"
#include "HTMLDocument.h"
#include "InspectorInstrumentation.h"
#include "NestingLevelIncrementer.h"
#include "Settings.h"
namespace WebCore {
using namespace HTMLNames;
namespace {
HTMLTokenizer::State tokenizerStateForContextElement(Element* contextElement, bool reportErrors)
{
if (!contextElement)
return HTMLTokenizer::DataState;
const QualifiedName& contextTag = contextElement->tagQName();
if (contextTag.matches(titleTag) || contextTag.matches(textareaTag))
return HTMLTokenizer::RCDATAState;
if (contextTag.matches(styleTag)
|| contextTag.matches(xmpTag)
|| contextTag.matches(iframeTag)
|| (contextTag.matches(noembedTag) && HTMLTreeBuilder::pluginsEnabled(contextElement->document()->frame()))
|| (contextTag.matches(noscriptTag) && HTMLTreeBuilder::scriptEnabled(contextElement->document()->frame()))
|| contextTag.matches(noframesTag))
return reportErrors ? HTMLTokenizer::RAWTEXTState : HTMLTokenizer::PLAINTEXTState;
if (contextTag.matches(scriptTag))
return reportErrors ? HTMLTokenizer::ScriptDataState : HTMLTokenizer::PLAINTEXTState;
if (contextTag.matches(plaintextTag))
return HTMLTokenizer::PLAINTEXTState;
return HTMLTokenizer::DataState;
}
}
HTMLDocumentParser::HTMLDocumentParser(HTMLDocument* document, bool reportErrors)
: ScriptableDocumentParser(document)
, m_tokenizer(HTMLTokenizer::create(usePreHTML5ParserQuirks(document)))
, m_scriptRunner(HTMLScriptRunner::create(document, this))
, m_treeBuilder(HTMLTreeBuilder::create(this, document, reportErrors, usePreHTML5ParserQuirks(document)))
, m_parserScheduler(HTMLParserScheduler::create(this))
, m_xssFilter(this)
, m_endWasDelayed(false)
, m_pumpSessionNestingLevel(0)
{
}
HTMLDocumentParser::HTMLDocumentParser(DocumentFragment* fragment, Element* contextElement, FragmentScriptingPermission scriptingPermission)
: ScriptableDocumentParser(fragment->document())
, m_tokenizer(HTMLTokenizer::create(usePreHTML5ParserQuirks(fragment->document())))
, m_treeBuilder(HTMLTreeBuilder::create(this, fragment, contextElement, scriptingPermission, usePreHTML5ParserQuirks(fragment->document())))
, m_xssFilter(this)
, m_endWasDelayed(false)
, m_pumpSessionNestingLevel(0)
{
bool reportErrors = false; m_tokenizer->setState(tokenizerStateForContextElement(contextElement, reportErrors));
}
HTMLDocumentParser::~HTMLDocumentParser()
{
ASSERT(!m_parserScheduler);
ASSERT(!m_pumpSessionNestingLevel);
ASSERT(!m_preloadScanner);
}
void HTMLDocumentParser::detach()
{
DocumentParser::detach();
if (m_scriptRunner)
m_scriptRunner->detach();
m_treeBuilder->detach();
m_preloadScanner.clear();
m_parserScheduler.clear(); }
void HTMLDocumentParser::stopParsing()
{
DocumentParser::stopParsing();
m_parserScheduler.clear(); }
void HTMLDocumentParser::prepareToStopParsing()
{
ASSERT(!hasInsertionPoint());
RefPtr<HTMLDocumentParser> protect(this);
pumpTokenizerIfPossible(ForceSynchronous);
if (isStopped())
return;
DocumentParser::prepareToStopParsing();
if (m_scriptRunner)
document()->setReadyState(Document::Interactive);
attemptToRunDeferredScriptsAndEnd();
}
bool HTMLDocumentParser::isParsingFragment() const
{
return m_treeBuilder->isParsingFragment();
}
bool HTMLDocumentParser::processingData() const
{
return isScheduledForResume() || inPumpSession();
}
void HTMLDocumentParser::pumpTokenizerIfPossible(SynchronousMode mode)
{
if (isStopped() || m_treeBuilder->isPaused())
return;
if (isScheduledForResume()) {
ASSERT(mode == AllowYield);
return;
}
pumpTokenizer(mode);
}
bool HTMLDocumentParser::isScheduledForResume() const
{
return m_parserScheduler && m_parserScheduler->isScheduledForResume();
}
void HTMLDocumentParser::resumeParsingAfterYield()
{
RefPtr<HTMLDocumentParser> protect(this);
pumpTokenizer(AllowYield);
endIfDelayed();
}
bool HTMLDocumentParser::runScriptsForPausedTreeBuilder()
{
ASSERT(m_treeBuilder->isPaused());
TextPosition1 scriptStartPosition = TextPosition1::belowRangePosition();
RefPtr<Element> scriptElement = m_treeBuilder->takeScriptToProcess(scriptStartPosition);
if (!m_scriptRunner)
return true;
return m_scriptRunner->execute(scriptElement.release(), scriptStartPosition);
}
bool HTMLDocumentParser::canTakeNextToken(SynchronousMode mode, PumpSession& session)
{
if (isStopped())
return false;
if (m_treeBuilder->isPaused()) {
if (mode == AllowYield)
m_parserScheduler->checkForYieldBeforeScript(session);
if (session.needsYield)
return false;
bool shouldContinueParsing = runScriptsForPausedTreeBuilder();
m_treeBuilder->setPaused(!shouldContinueParsing);
if (!shouldContinueParsing || isStopped())
return false;
}
if (!isParsingFragment()
&& document()->frame() && document()->frame()->navigationScheduler()->locationChangePending())
return false;
if (mode == AllowYield)
m_parserScheduler->checkForYieldBeforeToken(session);
return true;
}
void HTMLDocumentParser::pumpTokenizer(SynchronousMode mode)
{
ASSERT(!isStopped());
ASSERT(!isScheduledForResume());
ASSERT(refCount() >= 2);
PumpSession session(m_pumpSessionNestingLevel);
InspectorInstrumentationCookie cookie = InspectorInstrumentation::willWriteHTML(document(), m_input.current().length(), m_tokenizer->lineNumber());
while (canTakeNextToken(mode, session) && !session.needsYield) {
if (!isParsingFragment())
m_sourceTracker.start(m_input, m_token);
if (!m_tokenizer->nextToken(m_input.current(), m_token))
break;
if (!isParsingFragment()) {
m_sourceTracker.end(m_input, m_token);
m_xssFilter.filterToken(m_token);
}
m_treeBuilder->constructTreeFromToken(m_token);
m_token.clear();
}
ASSERT(refCount() >= 1);
if (isStopped())
return;
if (session.needsYield)
m_parserScheduler->scheduleForResume();
if (isWaitingForScripts()) {
ASSERT(m_tokenizer->state() == HTMLTokenizer::DataState);
if (!m_preloadScanner) {
m_preloadScanner = adoptPtr(new HTMLPreloadScanner(document()));
m_preloadScanner->appendToEnd(m_input.current());
}
m_preloadScanner->scan();
}
InspectorInstrumentation::didWriteHTML(cookie, m_tokenizer->lineNumber());
}
bool HTMLDocumentParser::hasInsertionPoint()
{
return m_input.hasInsertionPoint() || (wasCreatedByScript() && !m_input.haveSeenEndOfFile());
}
void HTMLDocumentParser::insert(const SegmentedString& source)
{
if (isStopped())
return;
RefPtr<HTMLDocumentParser> protect(this);
SegmentedString excludedLineNumberSource(source);
excludedLineNumberSource.setExcludeLineNumbers();
m_input.insertAtCurrentInsertionPoint(excludedLineNumberSource);
pumpTokenizerIfPossible(ForceSynchronous);
if (isWaitingForScripts()) {
HTMLPreloadScanner preloadScanner(document());
preloadScanner.appendToEnd(source);
preloadScanner.scan();
}
endIfDelayed();
}
void HTMLDocumentParser::append(const SegmentedString& source)
{
if (isStopped())
return;
RefPtr<HTMLDocumentParser> protect(this);
if (m_preloadScanner) {
if (m_input.current().isEmpty() && !isWaitingForScripts()) {
m_preloadScanner.clear();
} else {
m_preloadScanner->appendToEnd(source);
if (isWaitingForScripts())
m_preloadScanner->scan();
}
}
m_input.appendToEnd(source);
if (inPumpSession()) {
return;
}
pumpTokenizerIfPossible(AllowYield);
endIfDelayed();
}
void HTMLDocumentParser::end()
{
ASSERT(!isDetached());
ASSERT(!isScheduledForResume());
m_treeBuilder->finished();
}
void HTMLDocumentParser::attemptToRunDeferredScriptsAndEnd()
{
ASSERT(isStopping());
ASSERT(!hasInsertionPoint());
if (m_scriptRunner && !m_scriptRunner->executeScriptsWaitingForParsing())
return;
end();
}
void HTMLDocumentParser::attemptToEnd()
{
if (shouldDelayEnd()) {
m_endWasDelayed = true;
return;
}
prepareToStopParsing();
}
void HTMLDocumentParser::endIfDelayed()
{
if (isDetached())
return;
if (!m_endWasDelayed || shouldDelayEnd())
return;
m_endWasDelayed = false;
prepareToStopParsing();
}
void HTMLDocumentParser::finish()
{
if (!m_input.haveSeenEndOfFile())
m_input.markEndOfFile();
attemptToEnd();
}
bool HTMLDocumentParser::finishWasCalled()
{
return m_input.haveSeenEndOfFile();
}
bool HTMLDocumentParser::isExecutingScript() const
{
return inScriptExecution();
}
bool HTMLDocumentParser::inScriptExecution() const
{
if (!m_scriptRunner)
return false;
return m_scriptRunner->isExecutingScript();
}
String HTMLDocumentParser::sourceForToken(const HTMLToken& token)
{
return m_sourceTracker.sourceForToken(token);
}
int HTMLDocumentParser::lineNumber() const
{
return m_tokenizer->lineNumber();
}
TextPosition0 HTMLDocumentParser::textPosition() const
{
const SegmentedString& currentString = m_input.current();
WTF::ZeroBasedNumber line = currentString.currentLine();
WTF::ZeroBasedNumber column = currentString.currentColumn();
ASSERT(m_tokenizer->lineNumber() == line.zeroBasedInt());
return TextPosition0(line, column);
}
bool HTMLDocumentParser::isWaitingForScripts() const
{
return m_treeBuilder->isPaused();
}
void HTMLDocumentParser::resumeParsingAfterScriptExecution()
{
ASSERT(!inScriptExecution());
ASSERT(!m_treeBuilder->isPaused());
pumpTokenizerIfPossible(AllowYield);
endIfDelayed();
}
void HTMLDocumentParser::watchForLoad(CachedResource* cachedScript)
{
ASSERT(!cachedScript->isLoaded());
cachedScript->addClient(this);
}
void HTMLDocumentParser::stopWatchingForLoad(CachedResource* cachedScript)
{
cachedScript->removeClient(this);
}
void HTMLDocumentParser::appendCurrentInputStreamToPreloadScannerAndScan()
{
ASSERT(m_preloadScanner);
m_preloadScanner->appendToEnd(m_input.current());
m_preloadScanner->scan();
}
void HTMLDocumentParser::notifyFinished(CachedResource* cachedResource)
{
RefPtr<HTMLDocumentParser> protect(this);
ASSERT(m_scriptRunner);
ASSERT(!inScriptExecution());
if (isStopping()) {
attemptToRunDeferredScriptsAndEnd();
return;
}
ASSERT(m_treeBuilder->isPaused());
m_treeBuilder->setPaused(false);
bool shouldContinueParsing = m_scriptRunner->executeScriptsWaitingForLoad(cachedResource);
m_treeBuilder->setPaused(!shouldContinueParsing);
if (shouldContinueParsing)
resumeParsingAfterScriptExecution();
}
void HTMLDocumentParser::executeScriptsWaitingForStylesheets()
{
ASSERT(m_scriptRunner);
if (!m_scriptRunner->hasScriptsWaitingForStylesheets())
return;
RefPtr<HTMLDocumentParser> protect(this);
ASSERT(!m_scriptRunner->isExecutingScript());
ASSERT(m_treeBuilder->isPaused());
m_treeBuilder->setPaused(false);
bool shouldContinueParsing = m_scriptRunner->executeScriptsWaitingForStylesheets();
m_treeBuilder->setPaused(!shouldContinueParsing);
if (shouldContinueParsing)
resumeParsingAfterScriptExecution();
}
ScriptController* HTMLDocumentParser::script() const
{
return document()->frame() ? document()->frame()->script() : 0;
}
void HTMLDocumentParser::parseDocumentFragment(const String& source, DocumentFragment* fragment, Element* contextElement, FragmentScriptingPermission scriptingPermission)
{
RefPtr<HTMLDocumentParser> parser = HTMLDocumentParser::create(fragment, contextElement, scriptingPermission);
parser->insert(source); parser->finish();
ASSERT(!parser->processingData()); parser->detach(); }
bool HTMLDocumentParser::usePreHTML5ParserQuirks(Document* document)
{
ASSERT(document);
return document->settings() && document->settings()->usePreHTML5ParserQuirks();
}
void HTMLDocumentParser::suspendScheduledTasks()
{
if (m_parserScheduler)
m_parserScheduler->suspend();
}
void HTMLDocumentParser::resumeScheduledTasks()
{
if (m_parserScheduler)
m_parserScheduler->resume();
}
}