 Chromium Code Reviews
 Chromium Code Reviews Issue 2614663004:
  Pause HTML parser for external stylesheets in the body  (Closed)
    
  
    Issue 2614663004:
  Pause HTML parser for external stylesheets in the body  (Closed) 
  | Index: third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp | 
| diff --git a/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp b/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp | 
| index d3d8d1ed36c91d0f28aba5ba2016f8c71dde1c23..bbbab42d5624b4970af129f4a7e4f993f62f15c4 100644 | 
| --- a/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp | 
| +++ b/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp | 
| @@ -151,7 +151,9 @@ HTMLDocumentParser::HTMLDocumentParser(Document& document, | 
| m_pumpSessionNestingLevel(0), | 
| m_pumpSpeculationsSessionNestingLevel(0), | 
| m_isParsingAtLineNumber(false), | 
| - m_triedLoadingLinkHeaders(false) { | 
| + m_triedLoadingLinkHeaders(false), | 
| + m_addedPendingStylesheetInBody(false), | 
| + m_isWaitingForStylesheets(false) { | 
| ASSERT(shouldUseThreading() || (m_token && m_tokenizer)); | 
| // Threading is not allowed in prefetch mode. | 
| DCHECK(!document.isPrefetchOnly() || !shouldUseThreading()); | 
| @@ -256,7 +258,7 @@ bool HTMLDocumentParser::isParsingFragment() const { | 
| } | 
| void HTMLDocumentParser::pumpTokenizerIfPossible() { | 
| - if (isStopped() || isWaitingForScripts()) | 
| + if (isStopped() || isPaused()) | 
| return; | 
| pumpTokenizer(); | 
| @@ -293,7 +295,7 @@ bool HTMLDocumentParser::canTakeNextToken() { | 
| // continuing. | 
| if (m_treeBuilder->hasParserBlockingScript()) | 
| runScriptsForPausedTreeBuilder(); | 
| - if (isStopped() || isWaitingForScripts()) | 
| + if (isStopped() || isPaused()) | 
| return false; | 
| // FIXME: It's wrong for the HTMLDocumentParser to reach back to the | 
| @@ -381,7 +383,7 @@ void HTMLDocumentParser::notifyPendingTokenizedChunks() { | 
| for (auto& chunk : pendingChunks) | 
| m_speculations.append(std::move(chunk)); | 
| - if (!isWaitingForScripts() && !isScheduledForResume()) { | 
| + if (!isPaused() && !isScheduledForResume()) { | 
| if (m_tasksWereSuspended) | 
| m_parserScheduler->forceResumeAfterYield(); | 
| else | 
| @@ -399,19 +401,20 @@ void HTMLDocumentParser::validateSpeculations( | 
| ASSERT(chunk); | 
| // TODO(kouhei): We should simplify codepath here by disallowing | 
| // validateSpeculations | 
| - // while isWaitingForScripts, and m_lastChunkBeforeScript can simply be | 
| + // while isPaused, and m_lastChunkBeforePause can simply be | 
| // pushed to m_speculations. | 
| - if (isWaitingForScripts()) { | 
| - // We're waiting on a network script, just save the chunk, we'll get a | 
| - // second validateSpeculations call after the script completes. This call | 
| - // should have been made immediately after runScriptsForPausedTreeBuilder | 
| - // which may have started a network load and left us waiting. | 
| - ASSERT(!m_lastChunkBeforeScript); | 
| - m_lastChunkBeforeScript = std::move(chunk); | 
| + if (isPaused()) { | 
| + // We're waiting on a network script or stylesheet, just save the chunk, | 
| + // we'll get a second validateSpeculations call after the script or | 
| + // stylesheet completes. This call should have been made immediately after | 
| + // runScriptsForPausedTreeBuilder in the script case which may have started | 
| + // a network load and left us waiting. | 
| + DCHECK(!m_lastChunkBeforePause); | 
| + m_lastChunkBeforePause = std::move(chunk); | 
| return; | 
| } | 
| - ASSERT(!m_lastChunkBeforeScript); | 
| + DCHECK(!m_lastChunkBeforePause); | 
| std::unique_ptr<HTMLTokenizer> tokenizer = std::move(m_tokenizer); | 
| std::unique_ptr<HTMLToken> token = std::move(m_token); | 
| @@ -488,12 +491,12 @@ size_t HTMLDocumentParser::processTokenizedChunkFromBackgroundParser( | 
| SECURITY_DCHECK(m_pumpSpeculationsSessionNestingLevel == 1); | 
| SECURITY_DCHECK(!inPumpSession()); | 
| ASSERT(!isParsingFragment()); | 
| - ASSERT(!isWaitingForScripts()); | 
| + DCHECK(!isPaused()); | 
| ASSERT(!isStopped()); | 
| ASSERT(shouldUseThreading()); | 
| ASSERT(!m_tokenizer); | 
| ASSERT(!m_token); | 
| - ASSERT(!m_lastChunkBeforeScript); | 
| + DCHECK(!m_lastChunkBeforePause); | 
| std::unique_ptr<TokenizedChunk> chunk(std::move(popChunk)); | 
| std::unique_ptr<CompactHTMLTokenStream> tokens = std::move(chunk->tokens); | 
| @@ -558,6 +561,11 @@ size_t HTMLDocumentParser::processTokenizedChunkFromBackgroundParser( | 
| break; | 
| } | 
| + if (m_isWaitingForStylesheets && it + 1 == tokens->end()) { | 
| 
kouhei (in TOK)
2017/01/12 11:58:13
Can we merge this to the if condition above?
It is
 
Pat Meenan
2017/01/12 13:26:46
I'm not 100% sure that there aren't any conditions
 | 
| + validateSpeculations(std::move(chunk)); | 
| + break; | 
| + } | 
| + | 
| if (it->type() == HTMLToken::EndOfFile) { | 
| // The EOF is assumed to be the last token of this bunch. | 
| ASSERT(it + 1 == tokens->end()); | 
| @@ -586,8 +594,8 @@ void HTMLDocumentParser::pumpPendingSpeculations() { | 
| // m_tokenizer and m_token don't have state that invalidates m_speculations. | 
| ASSERT(!m_tokenizer); | 
| ASSERT(!m_token); | 
| - ASSERT(!m_lastChunkBeforeScript); | 
| - ASSERT(!isWaitingForScripts()); | 
| + DCHECK(!m_lastChunkBeforePause); | 
| + DCHECK(!isPaused()); | 
| ASSERT(!isStopped()); | 
| ASSERT(!isScheduledForResume()); | 
| ASSERT(!inPumpSession()); | 
| @@ -621,7 +629,7 @@ void HTMLDocumentParser::pumpPendingSpeculations() { | 
| // isScheduledForResume() may be set here as a result of | 
| // processTokenizedChunkFromBackgroundParser running arbitrary javascript | 
| // which invokes nested event loops. (e.g. inspector breakpoints) | 
| - if (!isParsing() || isWaitingForScripts() || isScheduledForResume()) | 
| + if (!isParsing() || isPaused() || isScheduledForResume()) | 
| break; | 
| if (m_speculations.isEmpty() || | 
| @@ -709,7 +717,7 @@ void HTMLDocumentParser::pumpTokenizer() { | 
| m_treeBuilder->flush(FlushAlways); | 
| RELEASE_ASSERT(!isStopped()); | 
| - if (isWaitingForScripts()) { | 
| + if (isPaused()) { | 
| ASSERT(m_tokenizer->getState() == HTMLTokenizer::DataState); | 
| ASSERT(m_preloader); | 
| @@ -748,6 +756,9 @@ void HTMLDocumentParser::constructTreeFromHTMLToken() { | 
| m_treeBuilder->constructTree(&atomicToken); | 
| + if (m_addedPendingStylesheetInBody) | 
| + m_isWaitingForStylesheets = true; | 
| + | 
| // FIXME: constructTree may synchronously cause Document to be detached. | 
| if (!m_token) | 
| return; | 
| @@ -762,6 +773,8 @@ void HTMLDocumentParser::constructTreeFromCompactHTMLToken( | 
| const CompactHTMLToken& compactToken) { | 
| AtomicHTMLToken token(compactToken); | 
| m_treeBuilder->constructTree(&token); | 
| + if (m_addedPendingStylesheetInBody) | 
| + m_isWaitingForStylesheets = true; | 
| } | 
| bool HTMLDocumentParser::hasInsertionPoint() { | 
| @@ -792,7 +805,7 @@ void HTMLDocumentParser::insert(const SegmentedString& source) { | 
| m_input.insertAtCurrentInsertionPoint(excludedLineNumberSource); | 
| pumpTokenizerIfPossible(); | 
| - if (isWaitingForScripts()) { | 
| + if (isPaused()) { | 
| // Check the document.write() output with a separate preload scanner as | 
| // the main scanner can't deal with insertions. | 
| if (!m_insertionPreloadScanner) | 
| @@ -905,14 +918,14 @@ void HTMLDocumentParser::append(const String& inputSource) { | 
| } | 
| if (m_preloadScanner) { | 
| - if (m_input.current().isEmpty() && !isWaitingForScripts()) { | 
| + if (m_input.current().isEmpty() && !isPaused()) { | 
| // We have parsed until the end of the current input and so are now moving | 
| // ahead of the preload scanner. Clear the scanner so we know to scan | 
| // starting from the current input point if we block again. | 
| m_preloadScanner.reset(); | 
| } else { | 
| m_preloadScanner->appendToEnd(source); | 
| - if (isWaitingForScripts()) | 
| + if (isPaused()) | 
| scanAndPreload(m_preloadScanner.get()); | 
| } | 
| } | 
| @@ -1063,14 +1076,14 @@ bool HTMLDocumentParser::isWaitingForScripts() const { | 
| m_reentryPermit->parserPauseFlag(); | 
| } | 
| -void HTMLDocumentParser::resumeParsingAfterScriptExecution() { | 
| +void HTMLDocumentParser::resumeParsingAfterPause() { | 
| ASSERT(!isExecutingScript()); | 
| - ASSERT(!isWaitingForScripts()); | 
| + DCHECK(!isPaused()); | 
| if (m_haveBackgroundParser) { | 
| - if (m_lastChunkBeforeScript) { | 
| - validateSpeculations(std::move(m_lastChunkBeforeScript)); | 
| - DCHECK(!m_lastChunkBeforeScript); | 
| + if (m_lastChunkBeforePause) { | 
| + validateSpeculations(std::move(m_lastChunkBeforePause)); | 
| + DCHECK(!m_lastChunkBeforePause); | 
| pumpPendingSpeculations(); | 
| } | 
| return; | 
| @@ -1103,19 +1116,41 @@ void HTMLDocumentParser::notifyScriptLoaded(PendingScript* pendingScript) { | 
| } | 
| m_scriptRunner->executeScriptsWaitingForLoad(pendingScript); | 
| - if (!isWaitingForScripts()) | 
| - resumeParsingAfterScriptExecution(); | 
| + if (!isPaused()) | 
| + resumeParsingAfterPause(); | 
| } | 
| void HTMLDocumentParser::executeScriptsWaitingForResources() { | 
| DCHECK(document()->isScriptExecutionReady()); | 
| + if (m_isWaitingForStylesheets) | 
| + m_isWaitingForStylesheets = false; | 
| + | 
| // Document only calls this when the Document owns the DocumentParser so this | 
| // will not be called in the DocumentFragment case. | 
| DCHECK(m_scriptRunner); | 
| m_scriptRunner->executeScriptsWaitingForResources(); | 
| - if (!isWaitingForScripts()) | 
| - resumeParsingAfterScriptExecution(); | 
| + if (!isPaused()) | 
| + resumeParsingAfterPause(); | 
| +} | 
| + | 
| +void HTMLDocumentParser::didAddPendingStylesheetInBody() { | 
| + // When in-body CSS doesn't block painting, the parser needs to pause so that | 
| + // the DOM doesn't include any elements that may depend on the CSS for style. | 
| + // The stylesheet can be added and removed during the parsing of a single | 
| + // token so don't actually set the bit to block parsing here, just track | 
| + // the state of the added sheet in case it does persist beyond a single | 
| + // token. | 
| + if (RuntimeEnabledFeatures::cssInBodyDoesNotBlockPaintEnabled()) | 
| + m_addedPendingStylesheetInBody = true; | 
| +} | 
| + | 
| +void HTMLDocumentParser::didLoadAllStylesheets() { | 
| + // Just toggle the stylesheet flag here (mostly for synchronous sheets). | 
| + // The document will also call into executeScriptsWaitingForResources | 
| + // which is when the parser will re-start, otherwise it will attempt to | 
| + // resume twice which could cause state machine issues. | 
| + m_addedPendingStylesheetInBody = false; | 
| } | 
| void HTMLDocumentParser::parseDocumentFragment( |