HTMLDocumentParser.cpp [plain text]
#include "config.h"
#include "HTMLDocumentParser.h"
#include "CustomElementReactionQueue.h"
#include "DocumentFragment.h"
#include "DocumentLoader.h"
#include "EventLoop.h"
#include "Frame.h"
#include "HTMLDocument.h"
#include "HTMLParserScheduler.h"
#include "HTMLPreloadScanner.h"
#include "HTMLScriptRunner.h"
#include "HTMLTreeBuilder.h"
#include "HTMLUnknownElement.h"
#include "JSCustomElementInterface.h"
#include "LinkLoader.h"
#include "NavigationScheduler.h"
#include "ScriptElement.h"
#include "ThrowOnDynamicMarkupInsertionCountIncrementer.h"
#include <wtf/SystemTracing.h>
namespace WebCore {
using namespace HTMLNames;
DEFINE_ALLOCATOR_WITH_HEAP_IDENTIFIER(HTMLDocumentParser);
static bool isMainDocumentLoadingFromHTTP(const Document& document)
{
return !document.ownerElement() && document.url().protocolIsInHTTPFamily();
}
HTMLDocumentParser::HTMLDocumentParser(HTMLDocument& document)
: ScriptableDocumentParser(document)
, m_options(document)
, m_tokenizer(m_options)
, m_scriptRunner(makeUnique<HTMLScriptRunner>(document, static_cast<HTMLScriptRunnerHost&>(*this)))
, m_treeBuilder(makeUnique<HTMLTreeBuilder>(*this, document, parserContentPolicy(), m_options))
, m_parserScheduler(makeUnique<HTMLParserScheduler>(*this))
, m_xssAuditorDelegate(document)
, m_preloader(makeUnique<HTMLResourcePreloader>(document))
, m_shouldEmitTracePoints(isMainDocumentLoadingFromHTTP(document))
{
}
Ref<HTMLDocumentParser> HTMLDocumentParser::create(HTMLDocument& document)
{
return adoptRef(*new HTMLDocumentParser(document));
}
inline HTMLDocumentParser::HTMLDocumentParser(DocumentFragment& fragment, Element& contextElement, ParserContentPolicy rawPolicy)
: ScriptableDocumentParser(fragment.document(), rawPolicy)
, m_options(fragment.document())
, m_tokenizer(m_options)
, m_treeBuilder(makeUnique<HTMLTreeBuilder>(*this, fragment, contextElement, parserContentPolicy(), m_options))
, m_xssAuditorDelegate(fragment.document())
, m_shouldEmitTracePoints(false) {
if (contextElement.isHTMLElement())
m_tokenizer.updateStateFor(contextElement.tagQName().localName());
m_xssAuditor.initForFragment();
}
inline Ref<HTMLDocumentParser> HTMLDocumentParser::create(DocumentFragment& fragment, Element& contextElement, ParserContentPolicy parserContentPolicy)
{
return adoptRef(*new HTMLDocumentParser(fragment, contextElement, parserContentPolicy));
}
HTMLDocumentParser::~HTMLDocumentParser()
{
ASSERT(!m_parserScheduler);
ASSERT(!m_pumpSessionNestingLevel);
ASSERT(!m_preloadScanner);
ASSERT(!m_insertionPreloadScanner);
}
void HTMLDocumentParser::detach()
{
ScriptableDocumentParser::detach();
if (m_scriptRunner)
m_scriptRunner->detach();
m_preloadScanner = nullptr;
m_insertionPreloadScanner = nullptr;
m_parserScheduler = nullptr; }
void HTMLDocumentParser::stopParsing()
{
DocumentParser::stopParsing();
m_parserScheduler = nullptr; }
void HTMLDocumentParser::prepareToStopParsing()
{
ASSERT(!hasInsertionPoint());
Ref<HTMLDocumentParser> protectedThis(*this);
pumpTokenizerIfPossible(ForceSynchronous);
if (isStopped())
return;
DocumentParser::prepareToStopParsing();
if (m_scriptRunner)
document()->setReadyState(Document::Interactive);
if (isDetached())
return;
attemptToRunDeferredScriptsAndEnd();
}
inline bool HTMLDocumentParser::inPumpSession() const
{
return m_pumpSessionNestingLevel > 0;
}
inline bool HTMLDocumentParser::shouldDelayEnd() const
{
return inPumpSession() || isWaitingForScripts() || isScheduledForResume() || isExecutingScript();
}
void HTMLDocumentParser::didBeginYieldingParser()
{
m_parserScheduler->didBeginYieldingParser();
}
void HTMLDocumentParser::didEndYieldingParser()
{
m_parserScheduler->didEndYieldingParser();
}
bool HTMLDocumentParser::isParsingFragment() const
{
return m_treeBuilder->isParsingFragment();
}
bool HTMLDocumentParser::processingData() const
{
return isScheduledForResume() || inPumpSession();
}
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> protectedThis(*this);
pumpTokenizer(AllowYield);
endIfDelayed();
}
void HTMLDocumentParser::runScriptsForPausedTreeBuilder()
{
ASSERT(scriptingContentIsAllowed(parserContentPolicy()));
if (std::unique_ptr<CustomElementConstructionData> constructionData = m_treeBuilder->takeCustomElementConstructionData()) {
ASSERT(!m_treeBuilder->hasParserBlockingScriptWork());
{
ThrowOnDynamicMarkupInsertionCountIncrementer incrementer(*document());
document()->eventLoop().performMicrotaskCheckpoint();
CustomElementReactionStack reactionStack(document()->execState());
auto& elementInterface = constructionData->elementInterface.get();
auto newElement = elementInterface.constructElementWithFallback(*document(), constructionData->name);
m_treeBuilder->didCreateCustomOrFallbackElement(WTFMove(newElement), *constructionData);
}
return;
}
TextPosition scriptStartPosition = TextPosition::belowRangePosition();
if (auto scriptElement = m_treeBuilder->takeScriptToProcess(scriptStartPosition)) {
ASSERT(!m_treeBuilder->hasParserBlockingScriptWork());
if (m_scriptRunner)
m_scriptRunner->execute(scriptElement.releaseNonNull(), scriptStartPosition);
}
}
Document* HTMLDocumentParser::contextForParsingSession()
{
if (isParsingFragment())
return nullptr;
return document();
}
bool HTMLDocumentParser::pumpTokenizerLoop(SynchronousMode mode, bool parsingFragment, PumpSession& session)
{
do {
if (UNLIKELY(isWaitingForScripts())) {
if (mode == AllowYield && m_parserScheduler->shouldYieldBeforeExecutingScript(session))
return true;
runScriptsForPausedTreeBuilder();
if (isWaitingForScripts() || isStopped())
return false;
}
if (UNLIKELY(!parsingFragment && document()->frame() && document()->frame()->navigationScheduler().locationChangePending()))
return false;
if (UNLIKELY(mode == AllowYield && m_parserScheduler->shouldYieldBeforeToken(session)))
return true;
if (!parsingFragment)
m_sourceTracker.startToken(m_input.current(), m_tokenizer);
auto token = m_tokenizer.nextToken(m_input.current());
if (!token)
return false;
if (!parsingFragment) {
m_sourceTracker.endToken(m_input.current(), m_tokenizer);
if (auto xssInfo = m_xssAuditor.filterToken(FilterTokenRequest(*token, m_sourceTracker, m_tokenizer.shouldAllowCDATA())))
m_xssAuditorDelegate.didBlockScript(*xssInfo);
}
constructTreeFromHTMLToken(token);
} while (!isStopped());
return false;
}
void HTMLDocumentParser::pumpTokenizer(SynchronousMode mode)
{
ASSERT(!isStopped());
ASSERT(!isScheduledForResume());
ASSERT(refCount() >= 2);
PumpSession session(m_pumpSessionNestingLevel, contextForParsingSession());
m_xssAuditor.init(document(), &m_xssAuditorDelegate);
auto emitTracePoint = [this](TracePointCode code) {
if (!m_shouldEmitTracePoints)
return;
auto position = textPosition();
tracePoint(code, position.m_line.oneBasedInt(), position.m_column.oneBasedInt());
};
emitTracePoint(ParseHTMLStart);
bool shouldResume = pumpTokenizerLoop(mode, isParsingFragment(), session);
emitTracePoint(ParseHTMLEnd);
ASSERT(refCount() >= 1);
if (isStopped())
return;
if (shouldResume)
m_parserScheduler->scheduleForResume();
if (isWaitingForScripts() && !isDetached()) {
ASSERT(m_tokenizer.isInDataState());
if (!m_preloadScanner) {
m_preloadScanner = makeUnique<HTMLPreloadScanner>(m_options, document()->url(), document()->deviceScaleFactor());
m_preloadScanner->appendToEnd(m_input.current());
}
m_preloadScanner->scan(*m_preloader, *document());
}
if (document()->loader())
LinkLoader::loadLinksFromHeader(document()->loader()->response().httpHeaderField(HTTPHeaderName::Link), document()->url(), *document(), LinkLoader::MediaAttributeCheck::MediaAttributeNotEmpty);
}
void HTMLDocumentParser::constructTreeFromHTMLToken(HTMLTokenizer::TokenPtr& rawToken)
{
AtomicHTMLToken token(*rawToken);
if (rawToken->type() != HTMLToken::Character) {
rawToken.clear();
}
m_treeBuilder->constructTree(WTFMove(token));
}
bool HTMLDocumentParser::hasInsertionPoint()
{
return m_input.hasInsertionPoint() || (wasCreatedByScript() && !m_input.haveSeenEndOfFile());
}
void HTMLDocumentParser::insert(SegmentedString&& source)
{
if (isStopped())
return;
Ref<HTMLDocumentParser> protectedThis(*this);
source.setExcludeLineNumbers();
m_input.insertAtCurrentInsertionPoint(WTFMove(source));
pumpTokenizerIfPossible(ForceSynchronous);
if (isWaitingForScripts() && !isDetached()) {
if (!m_insertionPreloadScanner)
m_insertionPreloadScanner = makeUnique<HTMLPreloadScanner>(m_options, document()->url(), document()->deviceScaleFactor());
m_insertionPreloadScanner->appendToEnd(source);
m_insertionPreloadScanner->scan(*m_preloader, *document());
}
endIfDelayed();
}
void HTMLDocumentParser::append(RefPtr<StringImpl>&& inputSource)
{
if (isStopped())
return;
Ref<HTMLDocumentParser> protectedThis(*this);
String source { WTFMove(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, *document());
}
}
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::isExecutingScript() const
{
return m_scriptRunner && m_scriptRunner->isExecutingScript();
}
TextPosition HTMLDocumentParser::textPosition() const
{
auto& currentString = m_input.current();
return TextPosition(currentString.currentLine(), currentString.currentColumn());
}
bool HTMLDocumentParser::shouldAssociateConsoleMessagesWithTextPosition() const
{
return inPumpSession() && !isExecutingScript();
}
bool HTMLDocumentParser::isWaitingForScripts() const
{
bool treeBuilderHasBlockingScript = m_treeBuilder->hasParserBlockingScriptWork();
bool scriptRunnerHasBlockingScript = m_scriptRunner && m_scriptRunner->hasParserBlockingScript();
ASSERT(!(treeBuilderHasBlockingScript && scriptRunnerHasBlockingScript));
return treeBuilderHasBlockingScript || scriptRunnerHasBlockingScript;
}
void HTMLDocumentParser::resumeParsingAfterScriptExecution()
{
ASSERT(!isExecutingScript());
ASSERT(!isWaitingForScripts());
Ref<HTMLDocumentParser> protectedThis(*this);
m_insertionPreloadScanner = nullptr;
pumpTokenizerIfPossible(AllowYield);
endIfDelayed();
}
void HTMLDocumentParser::watchForLoad(PendingScript& pendingScript)
{
ASSERT(!pendingScript.isLoaded());
pendingScript.setClient(*this);
}
void HTMLDocumentParser::stopWatchingForLoad(PendingScript& pendingScript)
{
pendingScript.clearClient();
}
void HTMLDocumentParser::appendCurrentInputStreamToPreloadScannerAndScan()
{
ASSERT(m_preloadScanner);
m_preloadScanner->appendToEnd(m_input.current());
m_preloadScanner->scan(*m_preloader, *document());
}
void HTMLDocumentParser::notifyFinished(PendingScript& pendingScript)
{
Ref<HTMLDocumentParser> protectedThis(*this);
if (isStopped())
return;
ASSERT(m_scriptRunner);
ASSERT(!isExecutingScript());
if (isStopping()) {
attemptToRunDeferredScriptsAndEnd();
return;
}
m_scriptRunner->executeScriptsWaitingForLoad(pendingScript);
if (!isWaitingForScripts())
resumeParsingAfterScriptExecution();
}
bool HTMLDocumentParser::hasScriptsWaitingForStylesheets() const
{
return m_scriptRunner && m_scriptRunner->hasScriptsWaitingForStylesheets();
}
void HTMLDocumentParser::executeScriptsWaitingForStylesheets()
{
ASSERT(m_scriptRunner);
if (!m_scriptRunner->hasScriptsWaitingForStylesheets())
return;
Ref<HTMLDocumentParser> protectedThis(*this);
m_scriptRunner->executeScriptsWaitingForStylesheets();
if (!isWaitingForScripts())
resumeParsingAfterScriptExecution();
}
void HTMLDocumentParser::parseDocumentFragment(const String& source, DocumentFragment& fragment, Element& contextElement, ParserContentPolicy parserContentPolicy)
{
auto parser = 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();
}
}