| /* |
| * Copyright (C) 2010 Google, 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 APPLE 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 APPLE 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 "core/html/parser/HTMLDocumentParser.h" |
| |
| #include "bindings/core/v8/DocumentWriteEvaluator.h" |
| #include "core/HTMLNames.h" |
| #include "core/css/MediaValuesCached.h" |
| #include "core/css/resolver/StyleResolver.h" |
| #include "core/dom/DocumentFragment.h" |
| #include "core/dom/Element.h" |
| #include "core/dom/TaskRunnerHelper.h" |
| #include "core/fetch/ResourceFetcher.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/Settings.h" |
| #include "core/html/HTMLDocument.h" |
| #include "core/html/parser/AtomicHTMLToken.h" |
| #include "core/html/parser/BackgroundHTMLParser.h" |
| #include "core/html/parser/HTMLParserScheduler.h" |
| #include "core/html/parser/HTMLParserThread.h" |
| #include "core/html/parser/HTMLResourcePreloader.h" |
| #include "core/html/parser/HTMLScriptRunner.h" |
| #include "core/html/parser/HTMLTreeBuilder.h" |
| #include "core/inspector/InspectorInstrumentation.h" |
| #include "core/inspector/InspectorTraceEvents.h" |
| #include "core/loader/DocumentLoader.h" |
| #include "core/loader/LinkLoader.h" |
| #include "core/loader/NavigationScheduler.h" |
| #include "platform/CrossThreadFunctional.h" |
| #include "platform/Histogram.h" |
| #include "platform/SharedBuffer.h" |
| #include "platform/TraceEvent.h" |
| #include "platform/heap/Handle.h" |
| #include "public/platform/Platform.h" |
| #include "public/platform/WebFrameScheduler.h" |
| #include "public/platform/WebLoadingBehaviorFlag.h" |
| #include "public/platform/WebScheduler.h" |
| #include "public/platform/WebThread.h" |
| #include "wtf/AutoReset.h" |
| #include "wtf/PtrUtil.h" |
| #include <memory> |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| |
| // This is a direct transcription of step 4 from: |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#fragment-case |
| 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, |
| ParserSynchronizationPolicy syncPolicy) |
| : HTMLDocumentParser(document, AllowScriptingContent, syncPolicy) { |
| m_scriptRunner = HTMLScriptRunner::create(reentryPermit(), &document, this); |
| m_treeBuilder = |
| HTMLTreeBuilder::create(this, document, AllowScriptingContent, m_options); |
| } |
| |
| HTMLDocumentParser::HTMLDocumentParser(DocumentFragment* fragment, |
| Element* contextElement, |
| ParserContentPolicy parserContentPolicy) |
| : HTMLDocumentParser(fragment->document(), |
| parserContentPolicy, |
| ForceSynchronousParsing) { |
| // No m_scriptRunner in fragment parser. |
| m_treeBuilder = HTMLTreeBuilder::create(this, fragment, contextElement, |
| parserContentPolicy, m_options); |
| |
| // For now document fragment parsing never reports errors. |
| bool reportErrors = false; |
| m_tokenizer->setState( |
| tokenizerStateForContextElement(contextElement, reportErrors, m_options)); |
| m_xssAuditor.initForFragment(); |
| } |
| |
| HTMLDocumentParser::HTMLDocumentParser(Document& document, |
| ParserContentPolicy contentPolicy, |
| ParserSynchronizationPolicy syncPolicy) |
| : ScriptableDocumentParser(document, contentPolicy), |
| m_options(&document), |
| m_reentryPermit(HTMLParserReentryPermit::create()), |
| m_token(syncPolicy == ForceSynchronousParsing ? wrapUnique(new HTMLToken) |
| : nullptr), |
| m_tokenizer(syncPolicy == ForceSynchronousParsing |
| ? HTMLTokenizer::create(m_options) |
| : nullptr), |
| m_loadingTaskRunner( |
| TaskRunnerHelper::get(TaskType::Networking, &document)->clone()), |
| m_parserScheduler( |
| syncPolicy == AllowAsynchronousParsing |
| ? HTMLParserScheduler::create(this, m_loadingTaskRunner.get()) |
| : nullptr), |
| m_xssAuditorDelegate(&document), |
| m_weakFactory(this), |
| m_preloader(HTMLResourcePreloader::create(document)), |
| m_tokenizedChunkQueue(TokenizedChunkQueue::create()), |
| m_evaluator(DocumentWriteEvaluator::create(document)), |
| m_pendingCSPMetaToken(nullptr), |
| m_shouldUseThreading(syncPolicy == AllowAsynchronousParsing), |
| m_endWasDelayed(false), |
| m_haveBackgroundParser(false), |
| m_tasksWereSuspended(false), |
| m_pumpSessionNestingLevel(0), |
| m_pumpSpeculationsSessionNestingLevel(0), |
| m_isParsingAtLineNumber(false), |
| m_triedLoadingLinkHeaders(false) { |
| ASSERT(shouldUseThreading() || (m_token && m_tokenizer)); |
| // Threading is not allowed in prefetch mode. |
| DCHECK(!document.isPrefetchOnly() || !shouldUseThreading()); |
| |
| ThreadState::current()->registerPreFinalizer(this); |
| } |
| |
| HTMLDocumentParser::~HTMLDocumentParser() {} |
| |
| void HTMLDocumentParser::dispose() { |
| // In Oilpan, HTMLDocumentParser can die together with Document, and detach() |
| // is not called in this case. |
| if (m_haveBackgroundParser) |
| stopBackgroundParser(); |
| } |
| |
| DEFINE_TRACE(HTMLDocumentParser) { |
| visitor->trace(m_treeBuilder); |
| visitor->trace(m_parserScheduler); |
| visitor->trace(m_xssAuditorDelegate); |
| visitor->trace(m_scriptRunner); |
| visitor->trace(m_preloader); |
| ScriptableDocumentParser::trace(visitor); |
| HTMLScriptRunnerHost::trace(visitor); |
| } |
| |
| void HTMLDocumentParser::detach() { |
| if (!isParsingFragment() && m_tokenizedChunkQueue.get() && |
| m_tokenizedChunkQueue->peakPendingChunkCount()) { |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, peakPendingChunkHistogram, |
| ("Parser.PeakPendingChunkCount", 1, 1000, 50)); |
| peakPendingChunkHistogram.count( |
| m_tokenizedChunkQueue->peakPendingChunkCount()); |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, peakPendingTokenHistogram, |
| ("Parser.PeakPendingTokenCount", 1, 100000, 50)); |
| peakPendingTokenHistogram.count( |
| m_tokenizedChunkQueue->peakPendingTokenCount()); |
| } |
| |
| if (m_haveBackgroundParser) |
| stopBackgroundParser(); |
| DocumentParser::detach(); |
| if (m_scriptRunner) |
| m_scriptRunner->detach(); |
| m_treeBuilder->detach(); |
| // FIXME: It seems wrong that we would have a preload scanner here. Yet during |
| // fast/dom/HTMLScriptElement/script-load-events.html we do. |
| m_preloadScanner.reset(); |
| m_insertionPreloadScanner.reset(); |
| if (m_parserScheduler) { |
| m_parserScheduler->detach(); |
| m_parserScheduler.clear(); |
| } |
| // Oilpan: It is important to clear m_token to deallocate backing memory of |
| // HTMLToken::m_data and let the allocator reuse the memory for |
| // HTMLToken::m_data of a next HTMLDocumentParser. We need to clear |
| // m_tokenizer first because m_tokenizer has a raw pointer to m_token. |
| m_tokenizer.reset(); |
| m_token.reset(); |
| } |
| |
| void HTMLDocumentParser::stopParsing() { |
| DocumentParser::stopParsing(); |
| if (m_parserScheduler) { |
| m_parserScheduler->detach(); |
| m_parserScheduler.clear(); |
| } |
| if (m_haveBackgroundParser) |
| stopBackgroundParser(); |
| } |
| |
| // This kicks off "Once the user agent stops parsing" as described by: |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#the-end |
| void HTMLDocumentParser::prepareToStopParsing() { |
| // FIXME: It may not be correct to disable this for the background parser. |
| // That means hasInsertionPoint() may not be correct in some cases. |
| ASSERT(!hasInsertionPoint() || m_haveBackgroundParser); |
| |
| // NOTE: This pump should only ever emit buffered character tokens. |
| if (m_tokenizer) { |
| ASSERT(!m_haveBackgroundParser); |
| pumpTokenizerIfPossible(); |
| } |
| |
| if (isStopped()) |
| return; |
| |
| DocumentParser::prepareToStopParsing(); |
| |
| // We will not have a scriptRunner when parsing a DocumentFragment. |
| if (m_scriptRunner) |
| document()->setReadyState(Document::Interactive); |
| |
| // Setting the ready state above can fire mutation event and detach us from |
| // underneath. In that case, just bail out. |
| if (isDetached()) |
| return; |
| |
| attemptToRunDeferredScriptsAndEnd(); |
| } |
| |
| bool HTMLDocumentParser::isParsingFragment() const { |
| return m_treeBuilder->isParsingFragment(); |
| } |
| |
| void HTMLDocumentParser::pumpTokenizerIfPossible() { |
| if (isStopped() || isWaitingForScripts()) |
| return; |
| |
| pumpTokenizer(); |
| } |
| |
| bool HTMLDocumentParser::isScheduledForResume() const { |
| return m_parserScheduler && m_parserScheduler->isScheduledForResume(); |
| } |
| |
| // Used by HTMLParserScheduler |
| void HTMLDocumentParser::resumeParsingAfterYield() { |
| ASSERT(shouldUseThreading()); |
| ASSERT(m_haveBackgroundParser); |
| |
| pumpPendingSpeculations(); |
| } |
| |
| void HTMLDocumentParser::runScriptsForPausedTreeBuilder() { |
| ASSERT(scriptingContentIsAllowed(getParserContentPolicy())); |
| |
| TextPosition scriptStartPosition = TextPosition::belowRangePosition(); |
| Element* scriptElement = |
| m_treeBuilder->takeScriptToProcess(scriptStartPosition); |
| // We will not have a scriptRunner when parsing a DocumentFragment. |
| if (m_scriptRunner) |
| m_scriptRunner->execute(scriptElement, scriptStartPosition); |
| } |
| |
| bool HTMLDocumentParser::canTakeNextToken() { |
| if (isStopped()) |
| return false; |
| |
| // If we're paused waiting for a script, we try to execute scripts before |
| // continuing. |
| if (m_treeBuilder->hasParserBlockingScript()) |
| runScriptsForPausedTreeBuilder(); |
| if (isStopped() || isWaitingForScripts()) |
| return false; |
| |
| // FIXME: It's wrong for the HTMLDocumentParser to reach back to the |
| // LocalFrame, but this approach is how the old parser handled stopping when |
| // the page assigns window.location. What really should happen is that |
| // assigning window.location causes the parser to stop parsing cleanly. The |
| // problem is we're not perpared to do that at every point where we run |
| // JavaScript. |
| if (!isParsingFragment() && document()->frame() && |
| document()->frame()->navigationScheduler().locationChangePending()) |
| return false; |
| |
| return true; |
| } |
| |
| void HTMLDocumentParser::notifyPendingTokenizedChunks() { |
| TRACE_EVENT0("blink", "HTMLDocumentParser::notifyPendingTokenizedChunks"); |
| DCHECK(m_tokenizedChunkQueue); |
| |
| Vector<std::unique_ptr<TokenizedChunk>> pendingChunks; |
| m_tokenizedChunkQueue->takeAll(pendingChunks); |
| |
| if (!isParsing()) |
| return; |
| |
| // ApplicationCache needs to be initialized before issuing preloads. We |
| // suspend preload until HTMLHTMLElement is inserted and ApplicationCache is |
| // initialized. Note: link rel preloads don't follow this policy per the spec. |
| // These directives should initiate a fetch as fast as possible. |
| if (!m_triedLoadingLinkHeaders && document()->loader() && |
| !pendingChunks.isEmpty()) { |
| // Note that on commit, the loader dispatched preloads for all the non-media |
| // links. |
| document()->loader()->dispatchLinkHeaderPreloads( |
| &pendingChunks.first()->viewport, LinkLoader::OnlyLoadMedia); |
| m_triedLoadingLinkHeaders = true; |
| } |
| |
| // Defer preloads if any of the chunks contains a <meta> csp tag. |
| for (auto& chunk : pendingChunks) { |
| if (chunk->pendingCSPMetaTokenIndex != TokenizedChunk::noPendingToken) { |
| m_pendingCSPMetaToken = |
| &chunk->tokens->at(chunk->pendingCSPMetaTokenIndex); |
| } |
| } |
| |
| if (m_pendingCSPMetaToken || !document()->documentElement()) { |
| PreloadRequestStream linkRelPreloads; |
| for (auto& chunk : pendingChunks) { |
| for (auto& request : chunk->preloads) { |
| // Link rel preloads don't need to wait for AppCache but they |
| // should probably wait for CSP. |
| if (!m_pendingCSPMetaToken && request->isLinkRelPreload()) |
| linkRelPreloads.append(std::move(request)); |
| else |
| m_queuedPreloads.append(std::move(request)); |
| } |
| for (auto& index : chunk->likelyDocumentWriteScriptIndices) { |
| const CompactHTMLToken& token = chunk->tokens->at(index); |
| ASSERT(token.type() == HTMLToken::TokenType::Character); |
| m_queuedDocumentWriteScripts.append(token.data()); |
| } |
| } |
| m_preloader->takeAndPreload(linkRelPreloads); |
| } else { |
| // We can safely assume that there are no queued preloads request after the |
| // document element is available, as we empty the queue immediately after |
| // the document element is created in documentElementAvailable(). |
| ASSERT(m_queuedPreloads.isEmpty()); |
| ASSERT(m_queuedDocumentWriteScripts.isEmpty()); |
| // Loop through the chunks to generate preloads before any document.write |
| // script evaluation takes place. Preloading these scripts is valuable and |
| // comparably cheap, while evaluating JS can be expensive. |
| for (auto& chunk : pendingChunks) { |
| m_preloader->takeAndPreload(chunk->preloads); |
| } |
| for (auto& chunk : pendingChunks) { |
| for (auto& index : chunk->likelyDocumentWriteScriptIndices) { |
| const CompactHTMLToken& token = chunk->tokens->at(index); |
| ASSERT(token.type() == HTMLToken::TokenType::Character); |
| evaluateAndPreloadScriptForDocumentWrite(token.data()); |
| } |
| } |
| } |
| |
| for (auto& chunk : pendingChunks) |
| m_speculations.append(std::move(chunk)); |
| |
| if (!isWaitingForScripts() && !isScheduledForResume()) { |
| if (m_tasksWereSuspended) |
| m_parserScheduler->forceResumeAfterYield(); |
| else |
| m_parserScheduler->scheduleForResume(); |
| } |
| } |
| |
| void HTMLDocumentParser::didReceiveEncodingDataFromBackgroundParser( |
| const DocumentEncodingData& data) { |
| document()->setEncodingData(data); |
| } |
| |
| void HTMLDocumentParser::validateSpeculations( |
| std::unique_ptr<TokenizedChunk> chunk) { |
| ASSERT(chunk); |
| 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); |
| return; |
| } |
| |
| ASSERT(!m_lastChunkBeforeScript); |
| std::unique_ptr<HTMLTokenizer> tokenizer = std::move(m_tokenizer); |
| std::unique_ptr<HTMLToken> token = std::move(m_token); |
| |
| if (!tokenizer) { |
| // There must not have been any changes to the HTMLTokenizer state on the |
| // main thread, which means the speculation buffer is correct. |
| return; |
| } |
| |
| // Currently we're only smart enough to reuse the speculation buffer if the |
| // tokenizer both starts and ends in the DataState. That state is simplest |
| // because the HTMLToken is always in the Uninitialized state. We should |
| // consider whether we can reuse the speculation buffer in other states, but |
| // we'd likely need to do something more sophisticated with the HTMLToken. |
| if (chunk->tokenizerState == HTMLTokenizer::DataState && |
| tokenizer->getState() == HTMLTokenizer::DataState && |
| m_input.current().isEmpty() && |
| chunk->treeBuilderState == |
| HTMLTreeBuilderSimulator::stateFor(m_treeBuilder.get())) { |
| ASSERT(token->isUninitialized()); |
| return; |
| } |
| |
| discardSpeculationsAndResumeFrom(std::move(chunk), std::move(token), |
| std::move(tokenizer)); |
| } |
| |
| void HTMLDocumentParser::discardSpeculationsAndResumeFrom( |
| std::unique_ptr<TokenizedChunk> lastChunkBeforeScript, |
| std::unique_ptr<HTMLToken> token, |
| std::unique_ptr<HTMLTokenizer> tokenizer) { |
| m_weakFactory.revokeAll(); |
| |
| size_t discardedTokenCount = 0; |
| for (const auto& speculation : m_speculations) { |
| discardedTokenCount += speculation->tokens->size(); |
| } |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, discardedTokenCountHistogram, |
| ("Parser.DiscardedTokenCount", 1, 100000, 50)); |
| discardedTokenCountHistogram.count(discardedTokenCount); |
| |
| m_speculations.clear(); |
| m_pendingCSPMetaToken = nullptr; |
| m_queuedPreloads.clear(); |
| |
| std::unique_ptr<BackgroundHTMLParser::Checkpoint> checkpoint = |
| wrapUnique(new BackgroundHTMLParser::Checkpoint); |
| checkpoint->parser = m_weakFactory.createWeakPtr(); |
| checkpoint->token = std::move(token); |
| checkpoint->tokenizer = std::move(tokenizer); |
| checkpoint->treeBuilderState = |
| HTMLTreeBuilderSimulator::stateFor(m_treeBuilder.get()); |
| checkpoint->inputCheckpoint = lastChunkBeforeScript->inputCheckpoint; |
| checkpoint->preloadScannerCheckpoint = |
| lastChunkBeforeScript->preloadScannerCheckpoint; |
| checkpoint->unparsedInput = m_input.current().toString().isolatedCopy(); |
| // FIXME: This should be passed in instead of cleared. |
| m_input.current().clear(); |
| |
| ASSERT(checkpoint->unparsedInput.isSafeToSendToAnotherThread()); |
| postTaskToLookaheadParser(Asynchronous, &BackgroundHTMLParser::resumeFrom, |
| m_backgroundParser, passed(std::move(checkpoint))); |
| } |
| |
| size_t HTMLDocumentParser::processTokenizedChunkFromBackgroundParser( |
| std::unique_ptr<TokenizedChunk> popChunk) { |
| TRACE_EVENT_WITH_FLOW0( |
| "blink,loading", |
| "HTMLDocumentParser::processTokenizedChunkFromBackgroundParser", |
| popChunk.get(), TRACE_EVENT_FLAG_FLOW_IN); |
| AutoReset<bool> hasLineNumber(&m_isParsingAtLineNumber, true); |
| |
| ASSERT_WITH_SECURITY_IMPLICATION(m_pumpSpeculationsSessionNestingLevel == 1); |
| ASSERT_WITH_SECURITY_IMPLICATION(!inPumpSession()); |
| ASSERT(!isParsingFragment()); |
| ASSERT(!isWaitingForScripts()); |
| ASSERT(!isStopped()); |
| ASSERT(shouldUseThreading()); |
| ASSERT(!m_tokenizer); |
| ASSERT(!m_token); |
| ASSERT(!m_lastChunkBeforeScript); |
| |
| std::unique_ptr<TokenizedChunk> chunk(std::move(popChunk)); |
| std::unique_ptr<CompactHTMLTokenStream> tokens = std::move(chunk->tokens); |
| size_t elementTokenCount = 0; |
| |
| postTaskToLookaheadParser(Asynchronous, |
| &BackgroundHTMLParser::startedChunkWithCheckpoint, |
| m_backgroundParser, chunk->inputCheckpoint); |
| |
| for (const auto& xssInfo : chunk->xssInfos) { |
| m_textPosition = xssInfo->m_textPosition; |
| m_xssAuditorDelegate.didBlockScript(*xssInfo); |
| if (isStopped()) |
| break; |
| } |
| // XSSAuditorDelegate can detach the parser if it decides to block the entire |
| // current document. |
| if (isDetached()) |
| return elementTokenCount; |
| |
| for (Vector<CompactHTMLToken>::const_iterator it = tokens->begin(); |
| it != tokens->end(); ++it) { |
| ASSERT(!isWaitingForScripts()); |
| |
| if (!chunk->startingScript && |
| (it->type() == HTMLToken::StartTag || it->type() == HTMLToken::EndTag)) |
| elementTokenCount++; |
| |
| if (document()->frame() && |
| document()->frame()->navigationScheduler().locationChangePending()) { |
| // To match main-thread parser behavior (which never checks |
| // locationChangePending on the EOF path) we peek to see if this chunk has |
| // an EOF and process it anyway. |
| if (tokens->last().type() == HTMLToken::EndOfFile) { |
| ASSERT( |
| m_speculations |
| .isEmpty()); // There should never be any chunks after the EOF. |
| prepareToStopParsing(); |
| } |
| break; |
| } |
| |
| m_textPosition = it->textPosition(); |
| |
| constructTreeFromCompactHTMLToken(*it); |
| |
| if (isStopped()) |
| break; |
| |
| // Preloads were queued if there was a <meta> csp token in a tokenized |
| // chunk. |
| if (m_pendingCSPMetaToken && it == m_pendingCSPMetaToken) { |
| m_pendingCSPMetaToken = nullptr; |
| fetchQueuedPreloads(); |
| } |
| |
| if (isWaitingForScripts()) { |
| // The </script> is assumed to be the last token of this bunch. |
| ASSERT(it + 1 == tokens->end()); |
| runScriptsForPausedTreeBuilder(); |
| 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()); |
| // There should never be any chunks after the EOF. |
| ASSERT(m_speculations.isEmpty()); |
| prepareToStopParsing(); |
| break; |
| } |
| |
| ASSERT(!m_tokenizer); |
| ASSERT(!m_token); |
| } |
| |
| // Make sure all required pending text nodes are emitted before returning. |
| // This leaves "script", "style" and "svg" nodes text nodes intact. |
| if (!isStopped()) |
| m_treeBuilder->flush(FlushIfAtTextLimit); |
| |
| m_isParsingAtLineNumber = false; |
| |
| return elementTokenCount; |
| } |
| |
| void HTMLDocumentParser::pumpPendingSpeculations() { |
| // If this assert fails, you need to call validateSpeculations to make sure |
| // m_tokenizer and m_token don't have state that invalidates m_speculations. |
| ASSERT(!m_tokenizer); |
| ASSERT(!m_token); |
| ASSERT(!m_lastChunkBeforeScript); |
| ASSERT(!isWaitingForScripts()); |
| ASSERT(!isStopped()); |
| ASSERT(!isScheduledForResume()); |
| ASSERT(!inPumpSession()); |
| |
| // FIXME: Here should never be reached when there is a blocking script, |
| // but it happens in unknown scenarios. See https://crbug.com/440901 |
| if (isWaitingForScripts()) { |
| m_parserScheduler->scheduleForResume(); |
| return; |
| } |
| |
| // Do not allow pumping speculations in nested event loops. |
| if (m_pumpSpeculationsSessionNestingLevel) { |
| m_parserScheduler->scheduleForResume(); |
| return; |
| } |
| |
| // FIXME: Pass in current input length. |
| TRACE_EVENT_BEGIN1("devtools.timeline", "ParseHTML", "beginData", |
| InspectorParseHtmlEvent::beginData( |
| document(), lineNumber().zeroBasedInt())); |
| |
| SpeculationsPumpSession session(m_pumpSpeculationsSessionNestingLevel); |
| while (!m_speculations.isEmpty()) { |
| ASSERT(!isScheduledForResume()); |
| size_t elementTokenCount = |
| processTokenizedChunkFromBackgroundParser(m_speculations.takeFirst()); |
| session.addedElementTokens(elementTokenCount); |
| |
| // Always check isParsing first as m_document may be null. Surprisingly, |
| // 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()) |
| break; |
| |
| if (m_speculations.isEmpty() || |
| m_parserScheduler->yieldIfNeeded( |
| session, m_speculations.first()->startingScript)) |
| break; |
| } |
| |
| TRACE_EVENT_END1( |
| "devtools.timeline", "ParseHTML", "endData", |
| InspectorParseHtmlEvent::endData(lineNumber().zeroBasedInt() - 1)); |
| TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), |
| "UpdateCounters", TRACE_EVENT_SCOPE_THREAD, "data", |
| InspectorUpdateCountersEvent::data()); |
| } |
| |
| void HTMLDocumentParser::forcePlaintextForTextDocument() { |
| if (shouldUseThreading()) { |
| // This method is called before any data is appended, so we have to start |
| // the background parser ourselves. |
| if (!m_haveBackgroundParser) |
| startBackgroundParser(); |
| |
| // This task should be synchronous, because otherwise synchronous |
| // tokenizing can happen before plaintext is forced. |
| postTaskToLookaheadParser( |
| Synchronous, &BackgroundHTMLParser::forcePlaintextForTextDocument, |
| m_backgroundParser); |
| } else |
| m_tokenizer->setState(HTMLTokenizer::PLAINTEXTState); |
| } |
| |
| void HTMLDocumentParser::pumpTokenizer() { |
| ASSERT(!isStopped()); |
| ASSERT(m_tokenizer); |
| ASSERT(m_token); |
| |
| PumpSession session(m_pumpSessionNestingLevel); |
| |
| // We tell the InspectorInstrumentation about every pump, even if we end up |
| // pumping nothing. It can filter out empty pumps itself. |
| // FIXME: m_input.current().length() is only accurate if we end up parsing the |
| // whole buffer in this pump. We should pass how much we parsed as part of |
| // didWriteHTML instead of willWriteHTML. |
| TRACE_EVENT_BEGIN1( |
| "devtools.timeline", "ParseHTML", "beginData", |
| InspectorParseHtmlEvent::beginData( |
| document(), m_input.current().currentLine().zeroBasedInt())); |
| |
| if (!isParsingFragment()) |
| m_xssAuditor.init(document(), &m_xssAuditorDelegate); |
| |
| while (canTakeNextToken()) { |
| if (m_xssAuditor.isEnabled()) |
| m_sourceTracker.start(m_input.current(), m_tokenizer.get(), token()); |
| |
| if (!m_tokenizer->nextToken(m_input.current(), token())) |
| break; |
| |
| if (m_xssAuditor.isEnabled()) { |
| m_sourceTracker.end(m_input.current(), m_tokenizer.get(), token()); |
| |
| // We do not XSS filter innerHTML, which means we (intentionally) fail |
| // http/tests/security/xssAuditor/dom-write-innerHTML.html |
| if (std::unique_ptr<XSSInfo> xssInfo = |
| m_xssAuditor.filterToken(FilterTokenRequest( |
| token(), m_sourceTracker, m_tokenizer->shouldAllowCDATA()))) |
| m_xssAuditorDelegate.didBlockScript(*xssInfo); |
| } |
| |
| constructTreeFromHTMLToken(); |
| ASSERT(isStopped() || token().isUninitialized()); |
| } |
| |
| if (isStopped()) |
| return; |
| |
| // There should only be PendingText left since the tree-builder always flushes |
| // the task queue before returning. In case that ever changes, crash. |
| m_treeBuilder->flush(FlushAlways); |
| RELEASE_ASSERT(!isStopped()); |
| |
| if (isWaitingForScripts()) { |
| ASSERT(m_tokenizer->getState() == HTMLTokenizer::DataState); |
| |
| ASSERT(m_preloader); |
| // TODO(kouhei): m_preloader should be always available for synchronous |
| // parsing case, adding paranoia if for speculative crash fix for |
| // crbug.com/465478 |
| if (m_preloader) { |
| if (!m_preloadScanner) { |
| m_preloadScanner = createPreloadScanner(); |
| m_preloadScanner->appendToEnd(m_input.current()); |
| } |
| m_preloadScanner->scanAndPreload( |
| m_preloader.get(), document()->validBaseElementURL(), nullptr); |
| } |
| } |
| |
| TRACE_EVENT_END1("devtools.timeline", "ParseHTML", "endData", |
| InspectorParseHtmlEvent::endData( |
| m_input.current().currentLine().zeroBasedInt() - 1)); |
| } |
| |
| void HTMLDocumentParser::constructTreeFromHTMLToken() { |
| AtomicHTMLToken atomicToken(token()); |
| |
| // We clear the m_token in case constructTreeFromAtomicToken |
| // synchronously re-enters the parser. We don't clear the token immedately |
| // for Character tokens because the AtomicHTMLToken avoids copying the |
| // characters by keeping a pointer to the underlying buffer in the |
| // HTMLToken. Fortunately, Character tokens can't cause us to re-enter |
| // the parser. |
| // |
| // FIXME: Stop clearing the m_token once we start running the parser off |
| // the main thread or once we stop allowing synchronous JavaScript |
| // execution from parseAttribute. |
| if (token().type() != HTMLToken::Character) |
| token().clear(); |
| |
| m_treeBuilder->constructTree(&atomicToken); |
| |
| // FIXME: constructTree may synchronously cause Document to be detached. |
| if (!m_token) |
| return; |
| |
| if (!token().isUninitialized()) { |
| ASSERT(token().type() == HTMLToken::Character); |
| token().clear(); |
| } |
| } |
| |
| void HTMLDocumentParser::constructTreeFromCompactHTMLToken( |
| const CompactHTMLToken& compactToken) { |
| AtomicHTMLToken token(compactToken); |
| m_treeBuilder->constructTree(&token); |
| } |
| |
| bool HTMLDocumentParser::hasInsertionPoint() { |
| // FIXME: The wasCreatedByScript() branch here might not be fully correct. Our |
| // model of the EOF character differs slightly from the one in the spec |
| // because our treatment is uniform between network-sourced and script-sourced |
| // input streams whereas the spec treats them differently. |
| return m_input.hasInsertionPoint() || |
| (wasCreatedByScript() && !m_input.haveSeenEndOfFile()); |
| } |
| |
| void HTMLDocumentParser::insert(const SegmentedString& source) { |
| if (isStopped()) |
| return; |
| |
| TRACE_EVENT1("blink", "HTMLDocumentParser::insert", "source_length", |
| source.length()); |
| |
| if (!m_tokenizer) { |
| ASSERT(!inPumpSession()); |
| ASSERT(m_haveBackgroundParser || wasCreatedByScript()); |
| m_token = wrapUnique(new HTMLToken); |
| m_tokenizer = HTMLTokenizer::create(m_options); |
| } |
| |
| SegmentedString excludedLineNumberSource(source); |
| excludedLineNumberSource.setExcludeLineNumbers(); |
| m_input.insertAtCurrentInsertionPoint(excludedLineNumberSource); |
| pumpTokenizerIfPossible(); |
| |
| if (isWaitingForScripts()) { |
| // Check the document.write() output with a separate preload scanner as |
| // the main scanner can't deal with insertions. |
| if (!m_insertionPreloadScanner) |
| m_insertionPreloadScanner = createPreloadScanner(); |
| m_insertionPreloadScanner->appendToEnd(source); |
| m_insertionPreloadScanner->scanAndPreload( |
| m_preloader.get(), document()->validBaseElementURL(), nullptr); |
| } |
| |
| endIfDelayed(); |
| } |
| |
| void HTMLDocumentParser::startBackgroundParser() { |
| ASSERT(!isStopped()); |
| ASSERT(shouldUseThreading()); |
| ASSERT(!m_haveBackgroundParser); |
| ASSERT(document()); |
| m_haveBackgroundParser = true; |
| |
| // TODO(alexclarke): Remove WebFrameScheduler::setDocumentParsingInBackground |
| // when background parser goes away. |
| if (document()->frame() && document()->frame()->frameScheduler()) |
| document()->frame()->frameScheduler()->setDocumentParsingInBackground(true); |
| |
| // Make sure that a resolver is set up, so that the correct viewport |
| // dimensions will be fed to the background parser and preload scanner. |
| if (document()->loader()) |
| document()->ensureStyleResolver(); |
| |
| std::unique_ptr<BackgroundHTMLParser::Configuration> config = |
| wrapUnique(new BackgroundHTMLParser::Configuration); |
| config->options = m_options; |
| config->parser = m_weakFactory.createWeakPtr(); |
| config->xssAuditor = wrapUnique(new XSSAuditor); |
| config->xssAuditor->init(document(), &m_xssAuditorDelegate); |
| |
| config->decoder = takeDecoder(); |
| config->tokenizedChunkQueue = m_tokenizedChunkQueue.get(); |
| if (document()->settings()) { |
| if (document()->settings()->backgroundHtmlParserOutstandingTokenLimit()) |
| config->outstandingTokenLimit = |
| document()->settings()->backgroundHtmlParserOutstandingTokenLimit(); |
| if (document()->settings()->backgroundHtmlParserPendingTokenLimit()) |
| config->pendingTokenLimit = |
| document()->settings()->backgroundHtmlParserPendingTokenLimit(); |
| config->shouldCoalesceChunks = |
| document()->settings()->parseHTMLOnMainThreadCoalesceChunks(); |
| } |
| |
| ASSERT(config->xssAuditor->isSafeToSendToAnotherThread()); |
| |
| // The background parser is created on the main thread, but may otherwise |
| // only be used from the parser thread. |
| m_backgroundParser = BackgroundHTMLParser::create( |
| std::move(config), m_loadingTaskRunner->clone()); |
| // TODO(csharrison): This is a hack to initialize MediaValuesCached on the |
| // correct thread. We should get rid of it. |
| postTaskToLookaheadParser( |
| Synchronous, &BackgroundHTMLParser::init, m_backgroundParser, |
| document()->url(), passed(CachedDocumentParameters::create(document())), |
| MediaValuesCached::MediaValuesCachedData(*document())); |
| } |
| |
| void HTMLDocumentParser::stopBackgroundParser() { |
| ASSERT(shouldUseThreading()); |
| ASSERT(m_haveBackgroundParser); |
| |
| if (m_haveBackgroundParser && document()->frame() && |
| document()->frame()->frameScheduler()) |
| document()->frame()->frameScheduler()->setDocumentParsingInBackground( |
| false); |
| |
| m_haveBackgroundParser = false; |
| |
| // Make this sync, as lsan triggers on some unittests if the task runner is |
| // used. Note that these lifetimes will be much more concrete if |
| // ParseHTMLOnMainThread lands (the lookahead parser will be a member). |
| postTaskToLookaheadParser(Synchronous, &BackgroundHTMLParser::stop, |
| m_backgroundParser); |
| m_weakFactory.revokeAll(); |
| } |
| |
| void HTMLDocumentParser::append(const String& inputSource) { |
| if (isStopped()) |
| return; |
| |
| // We should never reach this point if we're using a parser thread, as |
| // appendBytes() will directly ship the data to the thread. |
| ASSERT(!shouldUseThreading()); |
| |
| TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("blink.debug"), |
| "HTMLDocumentParser::append", "size", inputSource.length()); |
| const SegmentedString source(inputSource); |
| |
| if (document()->isPrefetchOnly()) { |
| if (!m_preloadScanner) |
| m_preloadScanner = createPreloadScanner(); |
| |
| m_preloadScanner->appendToEnd(source); |
| m_preloadScanner->scanAndPreload( |
| m_preloader.get(), document()->validBaseElementURL(), nullptr); |
| |
| // Return after the preload scanner, do not actually parse the document. |
| return; |
| } |
| |
| if (m_preloadScanner) { |
| if (m_input.current().isEmpty() && !isWaitingForScripts()) { |
| // 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()) |
| m_preloadScanner->scanAndPreload( |
| m_preloader.get(), document()->validBaseElementURL(), nullptr); |
| } |
| } |
| |
| m_input.appendToEnd(source); |
| |
| if (inPumpSession()) { |
| // We've gotten data off the network in a nested write. We don't want to |
| // consume any more of the input stream now. Do not worry. We'll consume |
| // this data in a less-nested write(). |
| return; |
| } |
| |
| pumpTokenizerIfPossible(); |
| |
| endIfDelayed(); |
| } |
| |
| void HTMLDocumentParser::end() { |
| ASSERT(!isDetached()); |
| ASSERT(!isScheduledForResume()); |
| |
| if (m_haveBackgroundParser) |
| stopBackgroundParser(); |
| |
| // Informs the the rest of WebCore that parsing is really finished (and |
| // deletes this). |
| m_treeBuilder->finished(); |
| |
| DocumentParser::stopParsing(); |
| } |
| |
| void HTMLDocumentParser::attemptToRunDeferredScriptsAndEnd() { |
| ASSERT(isStopping()); |
| // FIXME: It may not be correct to disable this for the background parser. |
| // That means hasInsertionPoint() may not be correct in some cases. |
| ASSERT(!hasInsertionPoint() || m_haveBackgroundParser); |
| if (m_scriptRunner && !m_scriptRunner->executeScriptsWaitingForParsing()) |
| return; |
| end(); |
| } |
| |
| void HTMLDocumentParser::attemptToEnd() { |
| // finish() indicates we will not receive any more data. If we are waiting on |
| // an external script to load, we can't finish parsing quite yet. |
| |
| if (shouldDelayEnd()) { |
| m_endWasDelayed = true; |
| return; |
| } |
| prepareToStopParsing(); |
| } |
| |
| void HTMLDocumentParser::endIfDelayed() { |
| // If we've already been detached, don't bother ending. |
| if (isDetached()) |
| return; |
| |
| if (!m_endWasDelayed || shouldDelayEnd()) |
| return; |
| |
| m_endWasDelayed = false; |
| prepareToStopParsing(); |
| } |
| |
| void HTMLDocumentParser::finish() { |
| // FIXME: We should ASSERT(!m_parserStopped) here, since it does not makes |
| // sense to call any methods on DocumentParser once it's been stopped. |
| // However, FrameLoader::stop calls DocumentParser::finish unconditionally. |
| |
| flush(); |
| if (isDetached()) |
| return; |
| |
| // Empty documents never got an append() call, and thus have never started a |
| // background parser. In those cases, we ignore shouldUseThreading() and fall |
| // through to the non-threading case. |
| if (m_haveBackgroundParser) { |
| if (!m_input.haveSeenEndOfFile()) |
| m_input.closeWithoutMarkingEndOfFile(); |
| postTaskToLookaheadParser(Asynchronous, &BackgroundHTMLParser::finish, |
| m_backgroundParser); |
| return; |
| } |
| |
| if (!m_tokenizer) { |
| ASSERT(!m_token); |
| // We're finishing before receiving any data. Rather than booting up the |
| // background parser just to spin it down, we finish parsing synchronously. |
| m_token = wrapUnique(new HTMLToken); |
| m_tokenizer = HTMLTokenizer::create(m_options); |
| } |
| |
| // We're not going to get any more data off the network, so we tell the input |
| // stream we've reached the end of file. finish() can be called more than |
| // once, if the first time does not call end(). |
| if (!m_input.haveSeenEndOfFile()) |
| m_input.markEndOfFile(); |
| |
| attemptToEnd(); |
| } |
| |
| bool HTMLDocumentParser::isExecutingScript() const { |
| if (!m_scriptRunner) |
| return false; |
| return m_scriptRunner->isExecutingScript(); |
| } |
| |
| bool HTMLDocumentParser::isParsingAtLineNumber() const { |
| return m_isParsingAtLineNumber && |
| ScriptableDocumentParser::isParsingAtLineNumber(); |
| } |
| |
| OrdinalNumber HTMLDocumentParser::lineNumber() const { |
| if (m_haveBackgroundParser) |
| return m_textPosition.m_line; |
| |
| return m_input.current().currentLine(); |
| } |
| |
| TextPosition HTMLDocumentParser::textPosition() const { |
| if (m_haveBackgroundParser) |
| return m_textPosition; |
| |
| const SegmentedString& currentString = m_input.current(); |
| OrdinalNumber line = currentString.currentLine(); |
| OrdinalNumber column = currentString.currentColumn(); |
| |
| return TextPosition(line, column); |
| } |
| |
| bool HTMLDocumentParser::isWaitingForScripts() const { |
| // When the TreeBuilder encounters a </script> tag, it returns to the |
| // HTMLDocumentParser where the script is transfered from the treebuilder to |
| // the script runner. The script runner will hold the script until its loaded |
| // and run. During any of this time, we want to count ourselves as "waiting |
| // for a script" and thus run the preload scanner, as well as delay completion |
| // of parsing. |
| bool treeBuilderHasBlockingScript = m_treeBuilder->hasParserBlockingScript(); |
| bool scriptRunnerHasBlockingScript = |
| m_scriptRunner && m_scriptRunner->hasParserBlockingScript(); |
| // Since the parser is paused while a script runner has a blocking script, it |
| // should never be possible to end up with both objects holding a blocking |
| // script. |
| ASSERT(!(treeBuilderHasBlockingScript && scriptRunnerHasBlockingScript)); |
| // If either object has a blocking script, the parser should be paused. |
| return treeBuilderHasBlockingScript || scriptRunnerHasBlockingScript || |
| m_reentryPermit->parserPauseFlag(); |
| } |
| |
| void HTMLDocumentParser::resumeParsingAfterScriptExecution() { |
| ASSERT(!isExecutingScript()); |
| ASSERT(!isWaitingForScripts()); |
| |
| if (m_haveBackgroundParser) { |
| validateSpeculations(std::move(m_lastChunkBeforeScript)); |
| ASSERT(!m_lastChunkBeforeScript); |
| pumpPendingSpeculations(); |
| return; |
| } |
| |
| m_insertionPreloadScanner.reset(); |
| pumpTokenizerIfPossible(); |
| endIfDelayed(); |
| } |
| |
| void HTMLDocumentParser::appendCurrentInputStreamToPreloadScannerAndScan() { |
| ASSERT(m_preloadScanner); |
| m_preloadScanner->appendToEnd(m_input.current()); |
| m_preloadScanner->scanAndPreload(m_preloader.get(), |
| document()->validBaseElementURL(), nullptr); |
| } |
| |
| void HTMLDocumentParser::notifyScriptLoaded(Resource* cachedResource) { |
| ASSERT(m_scriptRunner); |
| ASSERT(!isExecutingScript()); |
| |
| if (isStopped()) { |
| return; |
| } |
| |
| if (isStopping()) { |
| attemptToRunDeferredScriptsAndEnd(); |
| return; |
| } |
| |
| m_scriptRunner->executeScriptsWaitingForLoad(cachedResource); |
| if (!isWaitingForScripts()) |
| resumeParsingAfterScriptExecution(); |
| } |
| |
| void HTMLDocumentParser::executeScriptsWaitingForResources() { |
| // Document only calls this when the Document owns the DocumentParser so this |
| // will not be called in the DocumentFragment case. |
| ASSERT(m_scriptRunner); |
| // Ignore calls unless we have a script blocking the parser waiting on a |
| // stylesheet load. Otherwise we are currently parsing and this is a |
| // re-entrant call from encountering a </ style> tag. |
| if (!m_scriptRunner->hasScriptsWaitingForResources()) |
| return; |
| m_scriptRunner->executeScriptsWaitingForResources(); |
| if (!isWaitingForScripts()) |
| resumeParsingAfterScriptExecution(); |
| } |
| |
| void HTMLDocumentParser::parseDocumentFragment( |
| const String& source, |
| DocumentFragment* fragment, |
| Element* contextElement, |
| ParserContentPolicy parserContentPolicy) { |
| HTMLDocumentParser* parser = |
| HTMLDocumentParser::create(fragment, contextElement, parserContentPolicy); |
| parser->append(source); |
| parser->finish(); |
| // Allows ~DocumentParser to assert it was detached before destruction. |
| parser->detach(); |
| } |
| |
| void HTMLDocumentParser::suspendScheduledTasks() { |
| ASSERT(!m_tasksWereSuspended); |
| m_tasksWereSuspended = true; |
| if (m_parserScheduler) |
| m_parserScheduler->suspend(); |
| } |
| |
| void HTMLDocumentParser::resumeScheduledTasks() { |
| ASSERT(m_tasksWereSuspended); |
| m_tasksWereSuspended = false; |
| if (m_parserScheduler) |
| m_parserScheduler->resume(); |
| } |
| |
| void HTMLDocumentParser::appendBytes(const char* data, size_t length) { |
| if (!length || isStopped()) |
| return; |
| |
| if (shouldUseThreading()) { |
| double bytesReceivedTime = monotonicallyIncreasingTimeMS(); |
| if (!m_haveBackgroundParser) |
| startBackgroundParser(); |
| |
| std::unique_ptr<Vector<char>> buffer = wrapUnique(new Vector<char>(length)); |
| memcpy(buffer->data(), data, length); |
| TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("blink.debug"), |
| "HTMLDocumentParser::appendBytes", "size", (unsigned)length); |
| |
| LookaheadParserTaskSynchrony policy = |
| document()->settings() && |
| document()->settings()->parseHTMLOnMainThreadSyncTokenize() |
| ? Synchronous |
| : Asynchronous; |
| postTaskToLookaheadParser( |
| policy, &BackgroundHTMLParser::appendRawBytesFromMainThread, |
| m_backgroundParser, passed(std::move(buffer)), bytesReceivedTime); |
| return; |
| } |
| |
| DecodedDataDocumentParser::appendBytes(data, length); |
| } |
| |
| void HTMLDocumentParser::flush() { |
| // If we've got no decoder, we never received any data. |
| if (isDetached() || needsDecoder()) |
| return; |
| |
| if (shouldUseThreading()) { |
| // In some cases, flush() is called without any invocation of appendBytes. |
| // Fallback to synchronous parsing in that case. |
| if (!m_haveBackgroundParser) { |
| m_shouldUseThreading = false; |
| m_token = wrapUnique(new HTMLToken); |
| m_tokenizer = HTMLTokenizer::create(m_options); |
| DecodedDataDocumentParser::flush(); |
| return; |
| } |
| |
| postTaskToLookaheadParser(Asynchronous, &BackgroundHTMLParser::flush, |
| m_backgroundParser); |
| } else { |
| DecodedDataDocumentParser::flush(); |
| } |
| } |
| |
| void HTMLDocumentParser::setDecoder( |
| std::unique_ptr<TextResourceDecoder> decoder) { |
| ASSERT(decoder); |
| DecodedDataDocumentParser::setDecoder(std::move(decoder)); |
| |
| if (m_haveBackgroundParser) |
| postTaskToLookaheadParser(Asynchronous, &BackgroundHTMLParser::setDecoder, |
| m_backgroundParser, passed(takeDecoder())); |
| } |
| |
| void HTMLDocumentParser::documentElementAvailable() { |
| TRACE_EVENT0("blink,loader", "HTMLDocumentParser::documentElementAvailable"); |
| DCHECK(document()->documentElement()); |
| fetchQueuedPreloads(); |
| } |
| |
| std::unique_ptr<HTMLPreloadScanner> HTMLDocumentParser::createPreloadScanner() { |
| return HTMLPreloadScanner::create( |
| m_options, document()->url(), |
| CachedDocumentParameters::create(document()), |
| MediaValuesCached::MediaValuesCachedData(*document())); |
| } |
| |
| void HTMLDocumentParser::fetchQueuedPreloads() { |
| if (m_pendingCSPMetaToken || !document()->documentElement()) |
| return; |
| |
| if (!m_queuedPreloads.isEmpty()) |
| m_preloader->takeAndPreload(m_queuedPreloads); |
| |
| for (const String& scriptSource : m_queuedDocumentWriteScripts) { |
| evaluateAndPreloadScriptForDocumentWrite(scriptSource); |
| } |
| |
| m_queuedDocumentWriteScripts.clear(); |
| } |
| |
| void HTMLDocumentParser::evaluateAndPreloadScriptForDocumentWrite( |
| const String& source) { |
| if (!m_evaluator->shouldEvaluate(source)) |
| return; |
| document()->loader()->didObserveLoadingBehavior( |
| WebLoadingBehaviorFlag::WebLoadingBehaviorDocumentWriteEvaluator); |
| if (!RuntimeEnabledFeatures::documentWriteEvaluatorEnabled()) |
| return; |
| TRACE_EVENT0("blink", |
| "HTMLDocumentParser::evaluateAndPreloadScriptForDocumentWrite"); |
| |
| double initializeStartTime = monotonicallyIncreasingTimeMS(); |
| bool neededInitialization = m_evaluator->ensureEvaluationContext(); |
| double initializationDuration = |
| monotonicallyIncreasingTimeMS() - initializeStartTime; |
| |
| double startTime = monotonicallyIncreasingTimeMS(); |
| String writtenSource = m_evaluator->evaluateAndEmitWrittenSource(source); |
| double duration = monotonicallyIncreasingTimeMS() - startTime; |
| |
| int currentPreloadCount = document()->loader()->fetcher()->countPreloads(); |
| std::unique_ptr<HTMLPreloadScanner> scanner = createPreloadScanner(); |
| scanner->appendToEnd(SegmentedString(writtenSource)); |
| scanner->scanAndPreload(m_preloader.get(), document()->validBaseElementURL(), |
| nullptr); |
| int numPreloads = |
| document()->loader()->fetcher()->countPreloads() - currentPreloadCount; |
| |
| TRACE_EVENT_INSTANT2( |
| "blink", |
| "HTMLDocumentParser::evaluateAndPreloadScriptForDocumentWrite.data", |
| TRACE_EVENT_SCOPE_THREAD, "numPreloads", numPreloads, "scriptLength", |
| source.length()); |
| |
| if (neededInitialization) { |
| DEFINE_STATIC_LOCAL( |
| CustomCountHistogram, initializeHistograms, |
| ("PreloadScanner.DocumentWrite.InitializationTime", 1, 10000, 50)); |
| initializeHistograms.count(initializationDuration); |
| } |
| |
| if (numPreloads) { |
| DEFINE_STATIC_LOCAL( |
| CustomCountHistogram, successHistogram, |
| ("PreloadScanner.DocumentWrite.ExecutionTime.Success", 1, 10000, 50)); |
| successHistogram.count(duration); |
| } else { |
| DEFINE_STATIC_LOCAL( |
| CustomCountHistogram, failureHistogram, |
| ("PreloadScanner.DocumentWrite.ExecutionTime.Failure", 1, 10000, 50)); |
| failureHistogram.count(duration); |
| } |
| } |
| |
| template <typename FunctionType, typename... Ps> |
| void HTMLDocumentParser::postTaskToLookaheadParser( |
| LookaheadParserTaskSynchrony synchronyPolicy, |
| FunctionType function, |
| Ps&&... parameters) { |
| if (!RuntimeEnabledFeatures::parseHTMLOnMainThreadEnabled()) { |
| HTMLParserThread::shared()->postTask( |
| crossThreadBind(function, std::forward<Ps>(parameters)...)); |
| return; |
| } |
| |
| // Some messages to the lookahead parser should be synchronous. Otherwise, |
| // just post to the loading task runner. |
| switch (synchronyPolicy) { |
| case Synchronous: |
| (*WTF::bind(function, std::forward<Ps>(parameters)...))(); |
| return; |
| case Asynchronous: |
| m_loadingTaskRunner->postTask( |
| BLINK_FROM_HERE, |
| WTF::bind(function, std::forward<Ps>(parameters)...)); |
| return; |
| } |
| NOTREACHED(); |
| } |
| |
| } // namespace blink |