HTMLDocumentParser.cpp [plain text]
#include "config.h"
#include "HTMLDocumentParser.h"
#include "ContentSecurityPolicy.h"
#include "DocumentFragment.h"
#include "DocumentLoader.h"
#include "Frame.h"
#include "HTMLParserScheduler.h"
#include "HTMLScriptRunner.h"
#include "HTMLTreeBuilder.h"
#include "HTMLDocument.h"
#include "InspectorInstrumentation.h"
#include "Settings.h"
#include <wtf/Ref.h>
namespace WebCore {
using namespace HTMLNames;
static HTMLTokenizer::State tokenizerStateForContextElement(Element* contextElement, bool reportErrors, const HTMLParserOptions& options)
{
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) && options.pluginsEnabled)
|| (contextTag.matches(noscriptTag) && options.scriptEnabled)
|| 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)
: ScriptableDocumentParser(document)
, m_options(document)
, m_token(std::make_unique<HTMLToken>())
, m_tokenizer(std::make_unique<HTMLTokenizer>(m_options))
, m_scriptRunner(std::make_unique<HTMLScriptRunner>(document, static_cast<HTMLScriptRunnerHost&>(*this)))
, m_treeBuilder(std::make_unique<HTMLTreeBuilder>(*this, document, parserContentPolicy(), m_options))
, m_parserScheduler(std::make_unique<HTMLParserScheduler>(*this))
, m_xssAuditorDelegate(document)
, m_preloader(std::make_unique<HTMLResourcePreloader>(document))
, m_endWasDelayed(false)
, m_haveBackgroundParser(false)
, m_pumpSessionNestingLevel(0)
{
ASSERT(m_token);
ASSERT(m_tokenizer);
}
HTMLDocumentParser::HTMLDocumentParser(DocumentFragment& fragment, Element* contextElement, ParserContentPolicy parserContentPolicy)
: ScriptableDocumentParser(fragment.document(), parserContentPolicy)
, m_options(fragment.document())
, m_token(std::make_unique<HTMLToken>())
, m_tokenizer(std::make_unique<HTMLTokenizer>(m_options))
, m_treeBuilder(std::make_unique<HTMLTreeBuilder>(*this, fragment, contextElement, this->parserContentPolicy(), m_options))
, m_xssAuditorDelegate(fragment.document())
, m_endWasDelayed(false)
, m_haveBackgroundParser(false)
, m_pumpSessionNestingLevel(0)
{
bool reportErrors = false; m_tokenizer->setState(tokenizerStateForContextElement(contextElement, reportErrors, m_options));
m_xssAuditor.initForFragment();
}
HTMLDocumentParser::~HTMLDocumentParser()
{
ASSERT(!m_parserScheduler);
ASSERT(!m_pumpSessionNestingLevel);
ASSERT(!m_preloadScanner);
ASSERT(!m_insertionPreloadScanner);
ASSERT(!m_haveBackgroundParser);
}
void HTMLDocumentParser::detach()
{
DocumentParser::detach();
if (m_scriptRunner)
m_scriptRunner->detach();
m_treeBuilder->detach();
m_preloadScanner = nullptr;
m_insertionPreloadScanner = nullptr;
m_parserScheduler = nullptr; }
void HTMLDocumentParser::stopParsing()
{
DocumentParser::stopParsing();
m_parserScheduler = nullptr; }
void HTMLDocumentParser::prepareToStopParsing()
{
ASSERT(!hasInsertionPoint() || m_haveBackgroundParser);
Ref<HTMLDocumentParser> protect(*this);
pumpTokenizerIfPossible(ForceSynchronous);
if (isStopped())
return;
DocumentParser::prepareToStopParsing();
if (m_scriptRunner)
document()->setReadyState(Document::Interactive);
if (isDetached())
return;
attemptToRunDeferredScriptsAndEnd();
}
bool HTMLDocumentParser::isParsingFragment() const
{
return m_treeBuilder->isParsingFragment();
}
bool HTMLDocumentParser::processingData() const
{
return isScheduledForResume() || inPumpSession() || m_haveBackgroundParser;
}
void HTMLDocumentParser::pumpTokenizerIfPossible(SynchronousMode mode)
{
if (isStopped() || isWaitingForScripts())
return;
if (isScheduledForResume()) {
ASSERT(mode == AllowYield);
return;
}
pumpTokenizer(mode);
}
bool HTMLDocumentParser::isScheduledForResume() const
{
return m_parserScheduler && m_parserScheduler->isScheduledForResume();
}
void HTMLDocumentParser::resumeParsingAfterYield()
{
Ref<HTMLDocumentParser> protect(*this);
pumpTokenizer(AllowYield);
endIfDelayed();
}
void HTMLDocumentParser::runScriptsForPausedTreeBuilder()
{
ASSERT(scriptingContentIsAllowed(parserContentPolicy()));
TextPosition scriptStartPosition = TextPosition::belowRangePosition();
RefPtr<Element> scriptElement = m_treeBuilder->takeScriptToProcess(scriptStartPosition);
if (m_scriptRunner)
m_scriptRunner->execute(scriptElement.release(), scriptStartPosition);
}
bool HTMLDocumentParser::canTakeNextToken(SynchronousMode mode, PumpSession& session)
{
if (isStopped())
return false;
ASSERT(!m_haveBackgroundParser || mode == ForceSynchronous);
if (isWaitingForScripts()) {
if (mode == AllowYield)
m_parserScheduler->checkForYieldBeforeScript(session);
if (session.needsYield)
return false;
runScriptsForPausedTreeBuilder();
if (isWaitingForScripts() || isStopped())
return false;
}
if (!isParsingFragment()
&& document()->frame() && document()->frame()->navigationScheduler().locationChangePending())
return false;
if (mode == AllowYield)
m_parserScheduler->checkForYieldBeforeToken(session);
return true;
}
void HTMLDocumentParser::forcePlaintextForTextDocument()
{
m_tokenizer->setState(HTMLTokenizer::PLAINTEXTState);
}
Document* HTMLDocumentParser::contextForParsingSession()
{
if (isParsingFragment())
return 0;
return document();
}
void HTMLDocumentParser::pumpTokenizer(SynchronousMode mode)
{
ASSERT(!isStopped());
ASSERT(!isScheduledForResume());
ASSERT(refCount() >= 2);
ASSERT(m_tokenizer);
ASSERT(m_token);
ASSERT(!m_haveBackgroundParser || mode == ForceSynchronous);
PumpSession session(m_pumpSessionNestingLevel, contextForParsingSession());
InspectorInstrumentationCookie cookie = InspectorInstrumentation::willWriteHTML(document(), m_input.current().currentLine().zeroBasedInt());
m_xssAuditor.init(document(), &m_xssAuditorDelegate);
while (canTakeNextToken(mode, session) && !session.needsYield) {
if (!isParsingFragment())
m_sourceTracker.start(m_input.current(), m_tokenizer.get(), token());
if (!m_tokenizer->nextToken(m_input.current(), token()))
break;
if (!isParsingFragment()) {
m_sourceTracker.end(m_input.current(), m_tokenizer.get(), token());
if (auto xssInfo = m_xssAuditor.filterToken(FilterTokenRequest(token(), m_sourceTracker, m_tokenizer->shouldAllowCDATA())))
m_xssAuditorDelegate.didBlockScript(*xssInfo);
}
constructTreeFromHTMLToken(token());
ASSERT(token().isUninitialized());
}
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 = std::make_unique<HTMLPreloadScanner>(m_options, document()->url(), document()->deviceScaleFactor());
m_preloadScanner->appendToEnd(m_input.current());
}
m_preloadScanner->scan(m_preloader.get(), document()->baseElementURL()
#if ENABLE(PICTURE_SIZES)
, document()->renderView(), document()->frame()
#endif
);
}
InspectorInstrumentation::didWriteHTML(cookie, m_input.current().currentLine().zeroBasedInt());
}
void HTMLDocumentParser::constructTreeFromHTMLToken(HTMLToken& rawToken)
{
AtomicHTMLToken token(rawToken);
if (rawToken.type() != HTMLToken::Character)
rawToken.clear();
m_treeBuilder->constructTree(&token);
if (!rawToken.isUninitialized()) {
ASSERT(rawToken.type() == HTMLToken::Character);
rawToken.clear();
}
}
bool HTMLDocumentParser::hasInsertionPoint()
{
return m_input.hasInsertionPoint() || (wasCreatedByScript() && !m_input.haveSeenEndOfFile());
}
void HTMLDocumentParser::insert(const SegmentedString& source)
{
if (isStopped())
return;
Ref<HTMLDocumentParser> protect(*this);
SegmentedString excludedLineNumberSource(source);
excludedLineNumberSource.setExcludeLineNumbers();
m_input.insertAtCurrentInsertionPoint(excludedLineNumberSource);
pumpTokenizerIfPossible(ForceSynchronous);
if (isWaitingForScripts()) {
if (!m_insertionPreloadScanner) {
m_insertionPreloadScanner = std::make_unique<HTMLPreloadScanner>(m_options, document()->url(), document()->deviceScaleFactor());
}
m_insertionPreloadScanner->appendToEnd(source);
m_insertionPreloadScanner->scan(m_preloader.get(), document()->baseElementURL()
#if ENABLE(PICTURE_SIZES)
, document()->renderView(), document()->frame()
#endif
);
}
endIfDelayed();
}
void HTMLDocumentParser::append(PassRefPtr<StringImpl> inputSource)
{
if (isStopped())
return;
Ref<HTMLDocumentParser> protect(*this);
String source(inputSource);
if (m_preloadScanner) {
if (m_input.current().isEmpty() && !isWaitingForScripts()) {
m_preloadScanner = nullptr;
} else {
m_preloadScanner->appendToEnd(source);
if (isWaitingForScripts())
m_preloadScanner->scan(m_preloader.get(), document()->baseElementURL()
#if ENABLE(PICTURE_SIZES)
, document()->renderView(), document()->frame()
#endif
);
}
}
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() || m_haveBackgroundParser);
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::isExecutingScript() const
{
if (!m_scriptRunner)
return false;
return m_scriptRunner->isExecutingScript();
}
TextPosition HTMLDocumentParser::textPosition() const
{
const SegmentedString& currentString = m_input.current();
OrdinalNumber line = currentString.currentLine();
OrdinalNumber column = currentString.currentColumn();
return TextPosition(line, column);
}
bool HTMLDocumentParser::isWaitingForScripts() const
{
bool treeBuilderHasBlockingScript = m_treeBuilder->hasParserBlockingScript();
bool scriptRunnerHasBlockingScript = m_scriptRunner && m_scriptRunner->hasParserBlockingScript();
ASSERT(!(treeBuilderHasBlockingScript && scriptRunnerHasBlockingScript));
return treeBuilderHasBlockingScript || scriptRunnerHasBlockingScript;
}
void HTMLDocumentParser::resumeParsingAfterScriptExecution()
{
ASSERT(!isExecutingScript());
ASSERT(!isWaitingForScripts());
m_insertionPreloadScanner = nullptr;
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(m_preloader.get(), document()->baseElementURL()
#if ENABLE(PICTURE_SIZES)
, document()->renderView(), document()->frame()
#endif
);
}
void HTMLDocumentParser::notifyFinished(CachedResource* cachedResource)
{
Ref<HTMLDocumentParser> protect(*this);
ASSERT(m_scriptRunner);
ASSERT(!isExecutingScript());
if (isStopping()) {
attemptToRunDeferredScriptsAndEnd();
return;
}
m_scriptRunner->executeScriptsWaitingForLoad(cachedResource);
if (!isWaitingForScripts())
resumeParsingAfterScriptExecution();
}
void HTMLDocumentParser::executeScriptsWaitingForStylesheets()
{
ASSERT(m_scriptRunner);
if (!m_scriptRunner->hasScriptsWaitingForStylesheets())
return;
Ref<HTMLDocumentParser> protect(*this);
m_scriptRunner->executeScriptsWaitingForStylesheets();
if (!isWaitingForScripts())
resumeParsingAfterScriptExecution();
}
void HTMLDocumentParser::parseDocumentFragment(const String& source, DocumentFragment& fragment, Element* contextElement, ParserContentPolicy parserContentPolicy)
{
RefPtr<HTMLDocumentParser> parser = HTMLDocumentParser::create(fragment, contextElement, parserContentPolicy);
parser->insert(source); parser->finish();
ASSERT(!parser->processingData()); parser->detach(); }
void HTMLDocumentParser::suspendScheduledTasks()
{
if (m_parserScheduler)
m_parserScheduler->suspend();
}
void HTMLDocumentParser::resumeScheduledTasks()
{
if (m_parserScheduler)
m_parserScheduler->resume();
}
}