1 /* 2 * Copyright (C) 2010 Google, Inc. All Rights Reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "core/html/parser/HTMLDocumentParser.h" 28 29 #include "core/HTMLNames.h" 30 #include "core/css/MediaValuesCached.h" 31 #include "core/dom/DocumentFragment.h" 32 #include "core/dom/Element.h" 33 #include "core/frame/LocalFrame.h" 34 #include "core/html/HTMLDocument.h" 35 #include "core/html/parser/AtomicHTMLToken.h" 36 #include "core/html/parser/BackgroundHTMLParser.h" 37 #include "core/html/parser/HTMLParserScheduler.h" 38 #include "core/html/parser/HTMLParserThread.h" 39 #include "core/html/parser/HTMLScriptRunner.h" 40 #include "core/html/parser/HTMLTreeBuilder.h" 41 #include "core/inspector/InspectorInstrumentation.h" 42 #include "core/inspector/InspectorTraceEvents.h" 43 #include "core/loader/DocumentLoader.h" 44 #include "platform/SharedBuffer.h" 45 #include "platform/TraceEvent.h" 46 #include "public/platform/WebThreadedDataReceiver.h" 47 #include "wtf/Functional.h" 48 49 namespace blink { 50 51 using namespace HTMLNames; 52 53 // This is a direct transcription of step 4 from: 54 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#fragment-case 55 static HTMLTokenizer::State tokenizerStateForContextElement(Element* contextElement, bool reportErrors, const HTMLParserOptions& options) 56 { 57 if (!contextElement) 58 return HTMLTokenizer::DataState; 59 60 const QualifiedName& contextTag = contextElement->tagQName(); 61 62 if (contextTag.matches(titleTag) || contextTag.matches(textareaTag)) 63 return HTMLTokenizer::RCDATAState; 64 if (contextTag.matches(styleTag) 65 || contextTag.matches(xmpTag) 66 || contextTag.matches(iframeTag) 67 || (contextTag.matches(noembedTag) && options.pluginsEnabled) 68 || (contextTag.matches(noscriptTag) && options.scriptEnabled) 69 || contextTag.matches(noframesTag)) 70 return reportErrors ? HTMLTokenizer::RAWTEXTState : HTMLTokenizer::PLAINTEXTState; 71 if (contextTag.matches(scriptTag)) 72 return reportErrors ? HTMLTokenizer::ScriptDataState : HTMLTokenizer::PLAINTEXTState; 73 if (contextTag.matches(plaintextTag)) 74 return HTMLTokenizer::PLAINTEXTState; 75 return HTMLTokenizer::DataState; 76 } 77 78 class ParserDataReceiver : public blink::WebThreadedDataReceiver { 79 public: 80 explicit ParserDataReceiver(WeakPtr<BackgroundHTMLParser> backgroundParser) 81 : m_backgroundParser(backgroundParser) 82 { 83 } 84 85 // WebThreadedDataReceiver 86 virtual void acceptData(const char* data, int dataLength) OVERRIDE FINAL 87 { 88 ASSERT(backgroundThread() && backgroundThread()->isCurrentThread()); 89 if (m_backgroundParser.get()) 90 m_backgroundParser.get()->appendRawBytesFromParserThread(data, dataLength); 91 } 92 93 virtual blink::WebThread* backgroundThread() OVERRIDE FINAL 94 { 95 if (HTMLParserThread::shared()) 96 return &HTMLParserThread::shared()->platformThread(); 97 98 return 0; 99 } 100 101 private: 102 WeakPtr<BackgroundHTMLParser> m_backgroundParser; 103 }; 104 105 HTMLDocumentParser::HTMLDocumentParser(HTMLDocument& document, bool reportErrors) 106 : ScriptableDocumentParser(document) 107 , m_options(&document) 108 , m_token(m_options.useThreading ? nullptr : adoptPtr(new HTMLToken)) 109 , m_tokenizer(m_options.useThreading ? nullptr : HTMLTokenizer::create(m_options)) 110 , m_scriptRunner(HTMLScriptRunner::create(&document, this)) 111 , m_treeBuilder(HTMLTreeBuilder::create(this, &document, parserContentPolicy(), reportErrors, m_options)) 112 , m_parserScheduler(HTMLParserScheduler::create(this)) 113 , m_xssAuditorDelegate(&document) 114 , m_weakFactory(this) 115 , m_preloader(HTMLResourcePreloader::create(document)) 116 , m_isPinnedToMainThread(false) 117 , m_endWasDelayed(false) 118 , m_haveBackgroundParser(false) 119 , m_pumpSessionNestingLevel(0) 120 { 121 ASSERT(shouldUseThreading() || (m_token && m_tokenizer)); 122 } 123 124 // FIXME: Member variables should be grouped into self-initializing structs to 125 // minimize code duplication between these constructors. 126 HTMLDocumentParser::HTMLDocumentParser(DocumentFragment* fragment, Element* contextElement, ParserContentPolicy parserContentPolicy) 127 : ScriptableDocumentParser(fragment->document(), parserContentPolicy) 128 , m_options(&fragment->document()) 129 , m_token(adoptPtr(new HTMLToken)) 130 , m_tokenizer(HTMLTokenizer::create(m_options)) 131 , m_treeBuilder(HTMLTreeBuilder::create(this, fragment, contextElement, this->parserContentPolicy(), m_options)) 132 , m_xssAuditorDelegate(&fragment->document()) 133 , m_weakFactory(this) 134 , m_isPinnedToMainThread(true) 135 , m_endWasDelayed(false) 136 , m_haveBackgroundParser(false) 137 , m_pumpSessionNestingLevel(0) 138 { 139 ASSERT(!shouldUseThreading()); 140 bool reportErrors = false; // For now document fragment parsing never reports errors. 141 m_tokenizer->setState(tokenizerStateForContextElement(contextElement, reportErrors, m_options)); 142 m_xssAuditor.initForFragment(); 143 } 144 145 HTMLDocumentParser::~HTMLDocumentParser() 146 { 147 #if ENABLE(OILPAN) 148 if (m_haveBackgroundParser) 149 stopBackgroundParser(); 150 // In Oilpan, HTMLDocumentParser can die together with Document, and 151 // detach() is not called in this case. 152 #else 153 ASSERT(!m_parserScheduler); 154 ASSERT(!m_pumpSessionNestingLevel); 155 ASSERT(!m_preloadScanner); 156 ASSERT(!m_insertionPreloadScanner); 157 ASSERT(!m_haveBackgroundParser); 158 // FIXME: We should be able to ASSERT(m_speculations.isEmpty()), 159 // but there are cases where that's not true currently. For example, 160 // we we're told to stop parsing before we've consumed all the input. 161 #endif 162 } 163 164 void HTMLDocumentParser::trace(Visitor* visitor) 165 { 166 visitor->trace(m_treeBuilder); 167 visitor->trace(m_xssAuditorDelegate); 168 visitor->trace(m_scriptRunner); 169 visitor->trace(m_preloader); 170 ScriptableDocumentParser::trace(visitor); 171 HTMLScriptRunnerHost::trace(visitor); 172 } 173 174 void HTMLDocumentParser::pinToMainThread() 175 { 176 ASSERT(!m_haveBackgroundParser); 177 ASSERT(!m_isPinnedToMainThread); 178 m_isPinnedToMainThread = true; 179 if (!m_tokenizer) { 180 ASSERT(!m_token); 181 m_token = adoptPtr(new HTMLToken); 182 m_tokenizer = HTMLTokenizer::create(m_options); 183 } 184 } 185 186 void HTMLDocumentParser::detach() 187 { 188 if (m_haveBackgroundParser) 189 stopBackgroundParser(); 190 DocumentParser::detach(); 191 if (m_scriptRunner) 192 m_scriptRunner->detach(); 193 m_treeBuilder->detach(); 194 // FIXME: It seems wrong that we would have a preload scanner here. 195 // Yet during fast/dom/HTMLScriptElement/script-load-events.html we do. 196 m_preloadScanner.clear(); 197 m_insertionPreloadScanner.clear(); 198 m_parserScheduler.clear(); // Deleting the scheduler will clear any timers. 199 } 200 201 void HTMLDocumentParser::stopParsing() 202 { 203 DocumentParser::stopParsing(); 204 m_parserScheduler.clear(); // Deleting the scheduler will clear any timers. 205 if (m_haveBackgroundParser) 206 stopBackgroundParser(); 207 } 208 209 // This kicks off "Once the user agent stops parsing" as described by: 210 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#the-end 211 void HTMLDocumentParser::prepareToStopParsing() 212 { 213 // FIXME: It may not be correct to disable this for the background parser. 214 // That means hasInsertionPoint() may not be correct in some cases. 215 ASSERT(!hasInsertionPoint() || m_haveBackgroundParser); 216 217 // pumpTokenizer can cause this parser to be detached from the Document, 218 // but we need to ensure it isn't deleted yet. 219 RefPtrWillBeRawPtr<HTMLDocumentParser> protect(this); 220 221 // NOTE: This pump should only ever emit buffered character tokens, 222 // so ForceSynchronous vs. AllowYield should be meaningless. 223 if (m_tokenizer) { 224 ASSERT(!m_haveBackgroundParser); 225 pumpTokenizerIfPossible(ForceSynchronous); 226 } 227 228 if (isStopped()) 229 return; 230 231 DocumentParser::prepareToStopParsing(); 232 233 // We will not have a scriptRunner when parsing a DocumentFragment. 234 if (m_scriptRunner) 235 document()->setReadyState(Document::Interactive); 236 237 // Setting the ready state above can fire mutation event and detach us 238 // from underneath. In that case, just bail out. 239 if (isDetached()) 240 return; 241 242 attemptToRunDeferredScriptsAndEnd(); 243 } 244 245 bool HTMLDocumentParser::isParsingFragment() const 246 { 247 return m_treeBuilder->isParsingFragment(); 248 } 249 250 bool HTMLDocumentParser::processingData() const 251 { 252 return isScheduledForResume() || inPumpSession() || m_haveBackgroundParser; 253 } 254 255 void HTMLDocumentParser::pumpTokenizerIfPossible(SynchronousMode mode) 256 { 257 if (isStopped()) 258 return; 259 if (isWaitingForScripts()) 260 return; 261 262 // Once a resume is scheduled, HTMLParserScheduler controls when we next pump. 263 if (isScheduledForResume()) { 264 ASSERT(mode == AllowYield); 265 return; 266 } 267 268 pumpTokenizer(mode); 269 } 270 271 bool HTMLDocumentParser::isScheduledForResume() const 272 { 273 return m_parserScheduler && m_parserScheduler->isScheduledForResume(); 274 } 275 276 // Used by HTMLParserScheduler 277 void HTMLDocumentParser::resumeParsingAfterYield() 278 { 279 ASSERT(!m_isPinnedToMainThread); 280 // pumpTokenizer can cause this parser to be detached from the Document, 281 // but we need to ensure it isn't deleted yet. 282 RefPtrWillBeRawPtr<HTMLDocumentParser> protect(this); 283 284 if (m_haveBackgroundParser) { 285 pumpPendingSpeculations(); 286 return; 287 } 288 289 // We should never be here unless we can pump immediately. Call pumpTokenizer() 290 // directly so that ASSERTS will fire if we're wrong. 291 pumpTokenizer(AllowYield); 292 endIfDelayed(); 293 } 294 295 void HTMLDocumentParser::runScriptsForPausedTreeBuilder() 296 { 297 ASSERT(scriptingContentIsAllowed(parserContentPolicy())); 298 299 TextPosition scriptStartPosition = TextPosition::belowRangePosition(); 300 RefPtrWillBeRawPtr<Element> scriptElement = m_treeBuilder->takeScriptToProcess(scriptStartPosition); 301 // We will not have a scriptRunner when parsing a DocumentFragment. 302 if (m_scriptRunner) 303 m_scriptRunner->execute(scriptElement.release(), scriptStartPosition); 304 } 305 306 bool HTMLDocumentParser::canTakeNextToken(SynchronousMode mode, PumpSession& session) 307 { 308 if (isStopped()) 309 return false; 310 311 ASSERT(!m_haveBackgroundParser || mode == ForceSynchronous); 312 313 if (isWaitingForScripts()) { 314 if (mode == AllowYield) 315 session.didSeeScript = true; 316 317 // If we don't run the script, we cannot allow the next token to be taken. 318 if (session.needsYield) 319 return false; 320 321 // If we're paused waiting for a script, we try to execute scripts before continuing. 322 runScriptsForPausedTreeBuilder(); 323 if (isStopped()) 324 return false; 325 if (isWaitingForScripts()) 326 return false; 327 } 328 329 // FIXME: It's wrong for the HTMLDocumentParser to reach back to the 330 // LocalFrame, but this approach is how the old parser handled 331 // stopping when the page assigns window.location. What really 332 // should happen is that assigning window.location causes the 333 // parser to stop parsing cleanly. The problem is we're not 334 // perpared to do that at every point where we run JavaScript. 335 if (!isParsingFragment() 336 && document()->frame() && document()->frame()->navigationScheduler().locationChangePending()) 337 return false; 338 339 if (mode == AllowYield) 340 m_parserScheduler->checkForYieldBeforeToken(session); 341 342 return true; 343 } 344 345 void HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser(PassOwnPtr<ParsedChunk> chunk) 346 { 347 TRACE_EVENT0("blink", "HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser"); 348 349 // alert(), runModalDialog, and the JavaScript Debugger all run nested event loops 350 // which can cause this method to be re-entered. We detect re-entry using 351 // hasActiveParser(), save the chunk as a speculation, and return. 352 if (isWaitingForScripts() || !m_speculations.isEmpty() || document()->activeParserCount() > 0) { 353 m_preloader->takeAndPreload(chunk->preloads); 354 m_speculations.append(chunk); 355 return; 356 } 357 358 // processParsedChunkFromBackgroundParser can cause this parser to be detached from the Document, 359 // but we need to ensure it isn't deleted yet. 360 RefPtrWillBeRawPtr<HTMLDocumentParser> protect(this); 361 362 ASSERT(m_speculations.isEmpty()); 363 chunk->preloads.clear(); // We don't need to preload because we're going to parse immediately. 364 m_speculations.append(chunk); 365 pumpPendingSpeculations(); 366 } 367 368 void HTMLDocumentParser::didReceiveEncodingDataFromBackgroundParser(const DocumentEncodingData& data) 369 { 370 document()->setEncodingData(data); 371 } 372 373 void HTMLDocumentParser::validateSpeculations(PassOwnPtr<ParsedChunk> chunk) 374 { 375 ASSERT(chunk); 376 if (isWaitingForScripts()) { 377 // We're waiting on a network script, just save the chunk, we'll get 378 // a second validateSpeculations call after the script completes. 379 // This call should have been made immediately after runScriptsForPausedTreeBuilder 380 // which may have started a network load and left us waiting. 381 ASSERT(!m_lastChunkBeforeScript); 382 m_lastChunkBeforeScript = chunk; 383 return; 384 } 385 386 ASSERT(!m_lastChunkBeforeScript); 387 OwnPtr<HTMLTokenizer> tokenizer = m_tokenizer.release(); 388 OwnPtr<HTMLToken> token = m_token.release(); 389 390 if (!tokenizer) { 391 // There must not have been any changes to the HTMLTokenizer state on 392 // the main thread, which means the speculation buffer is correct. 393 return; 394 } 395 396 // Currently we're only smart enough to reuse the speculation buffer if the tokenizer 397 // both starts and ends in the DataState. That state is simplest because the HTMLToken 398 // is always in the Uninitialized state. We should consider whether we can reuse the 399 // speculation buffer in other states, but we'd likely need to do something more 400 // sophisticated with the HTMLToken. 401 if (chunk->tokenizerState == HTMLTokenizer::DataState 402 && tokenizer->state() == HTMLTokenizer::DataState 403 && m_input.current().isEmpty() 404 && chunk->treeBuilderState == HTMLTreeBuilderSimulator::stateFor(m_treeBuilder.get())) { 405 ASSERT(token->isUninitialized()); 406 return; 407 } 408 409 discardSpeculationsAndResumeFrom(chunk, token.release(), tokenizer.release()); 410 } 411 412 void HTMLDocumentParser::discardSpeculationsAndResumeFrom(PassOwnPtr<ParsedChunk> lastChunkBeforeScript, PassOwnPtr<HTMLToken> token, PassOwnPtr<HTMLTokenizer> tokenizer) 413 { 414 m_weakFactory.revokeAll(); 415 m_speculations.clear(); 416 417 OwnPtr<BackgroundHTMLParser::Checkpoint> checkpoint = adoptPtr(new BackgroundHTMLParser::Checkpoint); 418 checkpoint->parser = m_weakFactory.createWeakPtr(); 419 checkpoint->token = token; 420 checkpoint->tokenizer = tokenizer; 421 checkpoint->treeBuilderState = HTMLTreeBuilderSimulator::stateFor(m_treeBuilder.get()); 422 checkpoint->inputCheckpoint = lastChunkBeforeScript->inputCheckpoint; 423 checkpoint->preloadScannerCheckpoint = lastChunkBeforeScript->preloadScannerCheckpoint; 424 checkpoint->unparsedInput = m_input.current().toString().isolatedCopy(); 425 m_input.current().clear(); // FIXME: This should be passed in instead of cleared. 426 427 ASSERT(checkpoint->unparsedInput.isSafeToSendToAnotherThread()); 428 HTMLParserThread::shared()->postTask(bind(&BackgroundHTMLParser::resumeFrom, m_backgroundParser, checkpoint.release())); 429 } 430 431 void HTMLDocumentParser::processParsedChunkFromBackgroundParser(PassOwnPtr<ParsedChunk> popChunk) 432 { 433 TRACE_EVENT0("blink", "HTMLDocumentParser::processParsedChunkFromBackgroundParser"); 434 435 ASSERT_WITH_SECURITY_IMPLICATION(!document()->activeParserCount()); 436 ASSERT(!isParsingFragment()); 437 ASSERT(!isWaitingForScripts()); 438 ASSERT(!isStopped()); 439 #if !ENABLE(OILPAN) 440 // ASSERT that this object is both attached to the Document and protected. 441 ASSERT(refCount() >= 2); 442 #endif 443 ASSERT(shouldUseThreading()); 444 ASSERT(!m_tokenizer); 445 ASSERT(!m_token); 446 ASSERT(!m_lastChunkBeforeScript); 447 448 ActiveParserSession session(contextForParsingSession()); 449 450 OwnPtr<ParsedChunk> chunk(popChunk); 451 OwnPtr<CompactHTMLTokenStream> tokens = chunk->tokens.release(); 452 453 HTMLParserThread::shared()->postTask(bind(&BackgroundHTMLParser::startedChunkWithCheckpoint, m_backgroundParser, chunk->inputCheckpoint)); 454 455 for (XSSInfoStream::const_iterator it = chunk->xssInfos.begin(); it != chunk->xssInfos.end(); ++it) { 456 m_textPosition = (*it)->m_textPosition; 457 m_xssAuditorDelegate.didBlockScript(**it); 458 if (isStopped()) 459 break; 460 } 461 462 for (Vector<CompactHTMLToken>::const_iterator it = tokens->begin(); it != tokens->end(); ++it) { 463 ASSERT(!isWaitingForScripts()); 464 465 if (document()->frame() && document()->frame()->navigationScheduler().locationChangePending()) { 466 467 // To match main-thread parser behavior (which never checks locationChangePending on the EOF path) 468 // we peek to see if this chunk has an EOF and process it anyway. 469 if (tokens->last().type() == HTMLToken::EndOfFile) { 470 ASSERT(m_speculations.isEmpty()); // There should never be any chunks after the EOF. 471 prepareToStopParsing(); 472 } 473 break; 474 } 475 476 m_textPosition = it->textPosition(); 477 478 constructTreeFromCompactHTMLToken(*it); 479 480 if (isStopped()) 481 break; 482 483 if (isWaitingForScripts()) { 484 ASSERT(it + 1 == tokens->end()); // The </script> is assumed to be the last token of this bunch. 485 runScriptsForPausedTreeBuilder(); 486 validateSpeculations(chunk.release()); 487 break; 488 } 489 490 if (it->type() == HTMLToken::EndOfFile) { 491 ASSERT(it + 1 == tokens->end()); // The EOF is assumed to be the last token of this bunch. 492 ASSERT(m_speculations.isEmpty()); // There should never be any chunks after the EOF. 493 prepareToStopParsing(); 494 break; 495 } 496 497 ASSERT(!m_tokenizer); 498 ASSERT(!m_token); 499 } 500 501 // Make sure all required pending text nodes are emitted before returning. 502 // This leaves "script", "style" and "svg" nodes text nodes intact. 503 if (!isStopped()) 504 m_treeBuilder->flush(FlushIfAtTextLimit); 505 } 506 507 void HTMLDocumentParser::pumpPendingSpeculations() 508 { 509 // FIXME: Share this constant with the parser scheduler. 510 const double parserTimeLimit = 0.500; 511 512 #if !ENABLE(OILPAN) 513 // ASSERT that this object is both attached to the Document and protected. 514 ASSERT(refCount() >= 2); 515 #endif 516 // If this assert fails, you need to call validateSpeculations to make sure 517 // m_tokenizer and m_token don't have state that invalidates m_speculations. 518 ASSERT(!m_tokenizer); 519 ASSERT(!m_token); 520 ASSERT(!m_lastChunkBeforeScript); 521 ASSERT(!isWaitingForScripts()); 522 ASSERT(!isStopped()); 523 524 // FIXME: Pass in current input length. 525 TRACE_EVENT_BEGIN1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "ParseHTML", "beginData", InspectorParseHtmlEvent::beginData(document(), lineNumber().zeroBasedInt())); 526 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"), "CallStack", "stack", InspectorCallStackEvent::currentCallStack()); 527 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing. 528 InspectorInstrumentationCookie cookie = InspectorInstrumentation::willWriteHTML(document(), lineNumber().zeroBasedInt()); 529 530 double startTime = currentTime(); 531 532 while (!m_speculations.isEmpty()) { 533 processParsedChunkFromBackgroundParser(m_speculations.takeFirst()); 534 535 // Always check isStopped first as m_document may be null. 536 if (isStopped() || isWaitingForScripts()) 537 break; 538 539 if (currentTime() - startTime > parserTimeLimit && !m_speculations.isEmpty()) { 540 m_parserScheduler->scheduleForResume(); 541 break; 542 } 543 } 544 545 TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "ParseHTML", "endLine", lineNumber().zeroBasedInt()); 546 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing. 547 InspectorInstrumentation::didWriteHTML(cookie, lineNumber().zeroBasedInt()); 548 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "UpdateCounters", "data", InspectorUpdateCountersEvent::data()); 549 } 550 551 void HTMLDocumentParser::forcePlaintextForTextDocument() 552 { 553 if (shouldUseThreading()) { 554 // This method is called before any data is appended, so we have to start 555 // the background parser ourselves. 556 if (!m_haveBackgroundParser) 557 startBackgroundParser(); 558 559 HTMLParserThread::shared()->postTask(bind(&BackgroundHTMLParser::forcePlaintextForTextDocument, m_backgroundParser)); 560 } else 561 m_tokenizer->setState(HTMLTokenizer::PLAINTEXTState); 562 } 563 564 Document* HTMLDocumentParser::contextForParsingSession() 565 { 566 // The parsing session should interact with the document only when parsing 567 // non-fragments. Otherwise, we might delay the load event mistakenly. 568 if (isParsingFragment()) 569 return 0; 570 return document(); 571 } 572 573 static PassRefPtr<MediaValues> createMediaValues(Document* document) 574 { 575 ASSERT(document); 576 RefPtr<MediaValues> mediaValues = MediaValuesCached::create(*document); 577 ASSERT(mediaValues->isSafeToSendToAnotherThread()); 578 return mediaValues; 579 } 580 581 void HTMLDocumentParser::pumpTokenizer(SynchronousMode mode) 582 { 583 ASSERT(!isStopped()); 584 ASSERT(!isScheduledForResume()); 585 #if !ENABLE(OILPAN) 586 // ASSERT that this object is both attached to the Document and protected. 587 ASSERT(refCount() >= 2); 588 #endif 589 ASSERT(m_tokenizer); 590 ASSERT(m_token); 591 ASSERT(!m_haveBackgroundParser || mode == ForceSynchronous); 592 593 PumpSession session(m_pumpSessionNestingLevel, contextForParsingSession()); 594 595 // We tell the InspectorInstrumentation about every pump, even if we 596 // end up pumping nothing. It can filter out empty pumps itself. 597 // FIXME: m_input.current().length() is only accurate if we 598 // end up parsing the whole buffer in this pump. We should pass how 599 // much we parsed as part of didWriteHTML instead of willWriteHTML. 600 TRACE_EVENT_BEGIN1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "ParseHTML", "beginData", InspectorParseHtmlEvent::beginData(document(), m_input.current().currentLine().zeroBasedInt())); 601 TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline.stack"), "CallStack", "stack", InspectorCallStackEvent::currentCallStack()); 602 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing. 603 InspectorInstrumentationCookie cookie = InspectorInstrumentation::willWriteHTML(document(), m_input.current().currentLine().zeroBasedInt()); 604 605 m_xssAuditor.init(document(), &m_xssAuditorDelegate); 606 607 while (canTakeNextToken(mode, session) && !session.needsYield) { 608 if (!isParsingFragment()) 609 m_sourceTracker.start(m_input.current(), m_tokenizer.get(), token()); 610 611 if (!m_tokenizer->nextToken(m_input.current(), token())) 612 break; 613 614 if (!isParsingFragment()) { 615 m_sourceTracker.end(m_input.current(), m_tokenizer.get(), token()); 616 617 // We do not XSS filter innerHTML, which means we (intentionally) fail 618 // http/tests/security/xssAuditor/dom-write-innerHTML.html 619 if (OwnPtr<XSSInfo> xssInfo = m_xssAuditor.filterToken(FilterTokenRequest(token(), m_sourceTracker, m_tokenizer->shouldAllowCDATA()))) 620 m_xssAuditorDelegate.didBlockScript(*xssInfo); 621 } 622 623 constructTreeFromHTMLToken(token()); 624 ASSERT(token().isUninitialized()); 625 } 626 627 #if !ENABLE(OILPAN) 628 // Ensure we haven't been totally deref'ed after pumping. Any caller of this 629 // function should be holding a RefPtr to this to ensure we weren't deleted. 630 ASSERT(refCount() >= 1); 631 #endif 632 633 if (isStopped()) 634 return; 635 636 // There should only be PendingText left since the tree-builder always flushes 637 // the task queue before returning. In case that ever changes, crash. 638 if (mode == ForceSynchronous) 639 m_treeBuilder->flush(FlushAlways); 640 RELEASE_ASSERT(!isStopped()); 641 642 if (session.needsYield) 643 m_parserScheduler->scheduleForResume(); 644 645 if (isWaitingForScripts()) { 646 ASSERT(m_tokenizer->state() == HTMLTokenizer::DataState); 647 if (!m_preloadScanner) { 648 m_preloadScanner = adoptPtr(new HTMLPreloadScanner(m_options, document()->url(), createMediaValues(document()))); 649 m_preloadScanner->appendToEnd(m_input.current()); 650 } 651 m_preloadScanner->scan(m_preloader.get(), document()->baseElementURL()); 652 } 653 654 TRACE_EVENT_END1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "ParseHTML", "endLine", m_input.current().currentLine().zeroBasedInt()); 655 // FIXME(361045): remove InspectorInstrumentation calls once DevTools Timeline migrates to tracing. 656 InspectorInstrumentation::didWriteHTML(cookie, m_input.current().currentLine().zeroBasedInt()); 657 } 658 659 void HTMLDocumentParser::constructTreeFromHTMLToken(HTMLToken& rawToken) 660 { 661 AtomicHTMLToken token(rawToken); 662 663 // We clear the rawToken in case constructTreeFromAtomicToken 664 // synchronously re-enters the parser. We don't clear the token immedately 665 // for Character tokens because the AtomicHTMLToken avoids copying the 666 // characters by keeping a pointer to the underlying buffer in the 667 // HTMLToken. Fortunately, Character tokens can't cause us to re-enter 668 // the parser. 669 // 670 // FIXME: Stop clearing the rawToken once we start running the parser off 671 // the main thread or once we stop allowing synchronous JavaScript 672 // execution from parseAttribute. 673 if (rawToken.type() != HTMLToken::Character) 674 rawToken.clear(); 675 676 m_treeBuilder->constructTree(&token); 677 678 if (!rawToken.isUninitialized()) { 679 ASSERT(rawToken.type() == HTMLToken::Character); 680 rawToken.clear(); 681 } 682 } 683 684 void HTMLDocumentParser::constructTreeFromCompactHTMLToken(const CompactHTMLToken& compactToken) 685 { 686 AtomicHTMLToken token(compactToken); 687 m_treeBuilder->constructTree(&token); 688 } 689 690 bool HTMLDocumentParser::hasInsertionPoint() 691 { 692 // FIXME: The wasCreatedByScript() branch here might not be fully correct. 693 // Our model of the EOF character differs slightly from the one in 694 // the spec because our treatment is uniform between network-sourced 695 // and script-sourced input streams whereas the spec treats them 696 // differently. 697 return m_input.hasInsertionPoint() || (wasCreatedByScript() && !m_input.haveSeenEndOfFile()); 698 } 699 700 void HTMLDocumentParser::insert(const SegmentedString& source) 701 { 702 if (isStopped()) 703 return; 704 705 TRACE_EVENT1("blink", "HTMLDocumentParser::insert", "source_length", source.length()); 706 707 // pumpTokenizer can cause this parser to be detached from the Document, 708 // but we need to ensure it isn't deleted yet. 709 RefPtrWillBeRawPtr<HTMLDocumentParser> protect(this); 710 711 if (!m_tokenizer) { 712 ASSERT(!inPumpSession()); 713 ASSERT(m_haveBackgroundParser || wasCreatedByScript()); 714 m_token = adoptPtr(new HTMLToken); 715 m_tokenizer = HTMLTokenizer::create(m_options); 716 } 717 718 SegmentedString excludedLineNumberSource(source); 719 excludedLineNumberSource.setExcludeLineNumbers(); 720 m_input.insertAtCurrentInsertionPoint(excludedLineNumberSource); 721 pumpTokenizerIfPossible(ForceSynchronous); 722 723 if (isWaitingForScripts()) { 724 // Check the document.write() output with a separate preload scanner as 725 // the main scanner can't deal with insertions. 726 if (!m_insertionPreloadScanner) 727 m_insertionPreloadScanner = adoptPtr(new HTMLPreloadScanner(m_options, document()->url(), createMediaValues(document()))); 728 729 m_insertionPreloadScanner->appendToEnd(source); 730 m_insertionPreloadScanner->scan(m_preloader.get(), document()->baseElementURL()); 731 } 732 733 endIfDelayed(); 734 } 735 736 void HTMLDocumentParser::startBackgroundParser() 737 { 738 ASSERT(!isStopped()); 739 ASSERT(shouldUseThreading()); 740 ASSERT(!m_haveBackgroundParser); 741 m_haveBackgroundParser = true; 742 743 RefPtr<WeakReference<BackgroundHTMLParser> > reference = WeakReference<BackgroundHTMLParser>::createUnbound(); 744 m_backgroundParser = WeakPtr<BackgroundHTMLParser>(reference); 745 746 // TODO(oysteine): Disabled due to crbug.com/398076 until a full fix can be implemented. 747 if (RuntimeEnabledFeatures::threadedParserDataReceiverEnabled()) { 748 if (DocumentLoader* loader = document()->loader()) 749 loader->attachThreadedDataReceiver(adoptPtr(new ParserDataReceiver(m_backgroundParser))); 750 } 751 752 OwnPtr<BackgroundHTMLParser::Configuration> config = adoptPtr(new BackgroundHTMLParser::Configuration); 753 config->options = m_options; 754 config->parser = m_weakFactory.createWeakPtr(); 755 config->xssAuditor = adoptPtr(new XSSAuditor); 756 config->xssAuditor->init(document(), &m_xssAuditorDelegate); 757 config->preloadScanner = adoptPtr(new TokenPreloadScanner(document()->url().copy(), createMediaValues(document()))); 758 config->decoder = takeDecoder(); 759 760 ASSERT(config->xssAuditor->isSafeToSendToAnotherThread()); 761 ASSERT(config->preloadScanner->isSafeToSendToAnotherThread()); 762 HTMLParserThread::shared()->postTask(bind(&BackgroundHTMLParser::start, reference.release(), config.release())); 763 } 764 765 void HTMLDocumentParser::stopBackgroundParser() 766 { 767 ASSERT(shouldUseThreading()); 768 ASSERT(m_haveBackgroundParser); 769 m_haveBackgroundParser = false; 770 771 HTMLParserThread::shared()->postTask(bind(&BackgroundHTMLParser::stop, m_backgroundParser)); 772 m_weakFactory.revokeAll(); 773 } 774 775 void HTMLDocumentParser::append(PassRefPtr<StringImpl> inputSource) 776 { 777 if (isStopped()) 778 return; 779 780 // We should never reach this point if we're using a parser thread, 781 // as appendBytes() will directly ship the data to the thread. 782 ASSERT(!shouldUseThreading()); 783 784 // pumpTokenizer can cause this parser to be detached from the Document, 785 // but we need to ensure it isn't deleted yet. 786 RefPtrWillBeRawPtr<HTMLDocumentParser> protect(this); 787 TRACE_EVENT1("net", "HTMLDocumentParser::append", "size", inputSource->length()); 788 String source(inputSource); 789 790 if (m_preloadScanner) { 791 if (m_input.current().isEmpty() && !isWaitingForScripts()) { 792 // We have parsed until the end of the current input and so are now moving ahead of the preload scanner. 793 // Clear the scanner so we know to scan starting from the current input point if we block again. 794 m_preloadScanner.clear(); 795 } else { 796 m_preloadScanner->appendToEnd(source); 797 if (isWaitingForScripts()) 798 m_preloadScanner->scan(m_preloader.get(), document()->baseElementURL()); 799 } 800 } 801 802 m_input.appendToEnd(source); 803 804 if (inPumpSession()) { 805 // We've gotten data off the network in a nested write. 806 // We don't want to consume any more of the input stream now. Do 807 // not worry. We'll consume this data in a less-nested write(). 808 return; 809 } 810 811 // A couple pinToMainThread() callers require synchronous parsing, but can't 812 // easily use the insert() method, so we hack append() for them to be synchronous. 813 // javascript: url handling is one such caller. 814 // FIXME: This is gross, and we should separate the concept of synchronous parsing 815 // from insert() so that only document.write() uses insert. 816 if (m_isPinnedToMainThread) 817 pumpTokenizerIfPossible(ForceSynchronous); 818 else 819 pumpTokenizerIfPossible(AllowYield); 820 821 endIfDelayed(); 822 } 823 824 void HTMLDocumentParser::end() 825 { 826 ASSERT(!isDetached()); 827 ASSERT(!isScheduledForResume()); 828 829 if (m_haveBackgroundParser) 830 stopBackgroundParser(); 831 832 // Informs the the rest of WebCore that parsing is really finished (and deletes this). 833 m_treeBuilder->finished(); 834 835 DocumentParser::stopParsing(); 836 } 837 838 void HTMLDocumentParser::attemptToRunDeferredScriptsAndEnd() 839 { 840 ASSERT(isStopping()); 841 // FIXME: It may not be correct to disable this for the background parser. 842 // That means hasInsertionPoint() may not be correct in some cases. 843 ASSERT(!hasInsertionPoint() || m_haveBackgroundParser); 844 if (m_scriptRunner && !m_scriptRunner->executeScriptsWaitingForParsing()) 845 return; 846 end(); 847 } 848 849 void HTMLDocumentParser::attemptToEnd() 850 { 851 // finish() indicates we will not receive any more data. If we are waiting on 852 // an external script to load, we can't finish parsing quite yet. 853 854 if (shouldDelayEnd()) { 855 m_endWasDelayed = true; 856 return; 857 } 858 prepareToStopParsing(); 859 } 860 861 void HTMLDocumentParser::endIfDelayed() 862 { 863 // If we've already been detached, don't bother ending. 864 if (isDetached()) 865 return; 866 867 if (!m_endWasDelayed || shouldDelayEnd()) 868 return; 869 870 m_endWasDelayed = false; 871 prepareToStopParsing(); 872 } 873 874 void HTMLDocumentParser::finish() 875 { 876 // FIXME: We should ASSERT(!m_parserStopped) here, since it does not 877 // makes sense to call any methods on DocumentParser once it's been stopped. 878 // However, FrameLoader::stop calls DocumentParser::finish unconditionally. 879 880 // flush may ending up executing arbitrary script, and possibly detach the parser. 881 RefPtrWillBeRawPtr<HTMLDocumentParser> protect(this); 882 flush(); 883 if (isDetached()) 884 return; 885 886 // Empty documents never got an append() call, and thus have never started 887 // a background parser. In those cases, we ignore shouldUseThreading() 888 // and fall through to the non-threading case. 889 if (m_haveBackgroundParser) { 890 if (!m_input.haveSeenEndOfFile()) 891 m_input.closeWithoutMarkingEndOfFile(); 892 HTMLParserThread::shared()->postTask(bind(&BackgroundHTMLParser::finish, m_backgroundParser)); 893 return; 894 } 895 896 if (!m_tokenizer) { 897 ASSERT(!m_token); 898 // We're finishing before receiving any data. Rather than booting up 899 // the background parser just to spin it down, we finish parsing 900 // synchronously. 901 m_token = adoptPtr(new HTMLToken); 902 m_tokenizer = HTMLTokenizer::create(m_options); 903 } 904 905 // We're not going to get any more data off the network, so we tell the 906 // input stream we've reached the end of file. finish() can be called more 907 // than once, if the first time does not call end(). 908 if (!m_input.haveSeenEndOfFile()) 909 m_input.markEndOfFile(); 910 911 attemptToEnd(); 912 } 913 914 bool HTMLDocumentParser::isExecutingScript() const 915 { 916 if (!m_scriptRunner) 917 return false; 918 return m_scriptRunner->isExecutingScript(); 919 } 920 921 OrdinalNumber HTMLDocumentParser::lineNumber() const 922 { 923 if (m_haveBackgroundParser) 924 return m_textPosition.m_line; 925 926 return m_input.current().currentLine(); 927 } 928 929 TextPosition HTMLDocumentParser::textPosition() const 930 { 931 if (m_haveBackgroundParser) 932 return m_textPosition; 933 934 const SegmentedString& currentString = m_input.current(); 935 OrdinalNumber line = currentString.currentLine(); 936 OrdinalNumber column = currentString.currentColumn(); 937 938 return TextPosition(line, column); 939 } 940 941 bool HTMLDocumentParser::isWaitingForScripts() const 942 { 943 // When the TreeBuilder encounters a </script> tag, it returns to the HTMLDocumentParser 944 // where the script is transfered from the treebuilder to the script runner. 945 // The script runner will hold the script until its loaded and run. During 946 // any of this time, we want to count ourselves as "waiting for a script" and thus 947 // run the preload scanner, as well as delay completion of parsing. 948 bool treeBuilderHasBlockingScript = m_treeBuilder->hasParserBlockingScript(); 949 bool scriptRunnerHasBlockingScript = m_scriptRunner && m_scriptRunner->hasParserBlockingScript(); 950 // Since the parser is paused while a script runner has a blocking script, it should 951 // never be possible to end up with both objects holding a blocking script. 952 ASSERT(!(treeBuilderHasBlockingScript && scriptRunnerHasBlockingScript)); 953 // If either object has a blocking script, the parser should be paused. 954 return treeBuilderHasBlockingScript || scriptRunnerHasBlockingScript; 955 } 956 957 void HTMLDocumentParser::resumeParsingAfterScriptExecution() 958 { 959 ASSERT(!isExecutingScript()); 960 ASSERT(!isWaitingForScripts()); 961 962 if (m_haveBackgroundParser) { 963 validateSpeculations(m_lastChunkBeforeScript.release()); 964 ASSERT(!m_lastChunkBeforeScript); 965 // processParsedChunkFromBackgroundParser can cause this parser to be detached from the Document, 966 // but we need to ensure it isn't deleted yet. 967 RefPtrWillBeRawPtr<HTMLDocumentParser> protect(this); 968 pumpPendingSpeculations(); 969 return; 970 } 971 972 m_insertionPreloadScanner.clear(); 973 pumpTokenizerIfPossible(AllowYield); 974 endIfDelayed(); 975 } 976 977 void HTMLDocumentParser::appendCurrentInputStreamToPreloadScannerAndScan() 978 { 979 ASSERT(m_preloadScanner); 980 m_preloadScanner->appendToEnd(m_input.current()); 981 m_preloadScanner->scan(m_preloader.get(), document()->baseElementURL()); 982 } 983 984 void HTMLDocumentParser::notifyScriptLoaded(Resource* cachedResource) 985 { 986 // pumpTokenizer can cause this parser to be detached from the Document, 987 // but we need to ensure it isn't deleted yet. 988 RefPtrWillBeRawPtr<HTMLDocumentParser> protect(this); 989 990 ASSERT(m_scriptRunner); 991 ASSERT(!isExecutingScript()); 992 if (isStopping()) { 993 attemptToRunDeferredScriptsAndEnd(); 994 return; 995 } 996 997 m_scriptRunner->executeScriptsWaitingForLoad(cachedResource); 998 if (!isWaitingForScripts()) 999 resumeParsingAfterScriptExecution(); 1000 } 1001 1002 void HTMLDocumentParser::executeScriptsWaitingForResources() 1003 { 1004 // Document only calls this when the Document owns the DocumentParser 1005 // so this will not be called in the DocumentFragment case. 1006 ASSERT(m_scriptRunner); 1007 // Ignore calls unless we have a script blocking the parser waiting on a 1008 // stylesheet load. Otherwise we are currently parsing and this 1009 // is a re-entrant call from encountering a </ style> tag. 1010 if (!m_scriptRunner->hasScriptsWaitingForResources()) 1011 return; 1012 1013 // pumpTokenizer can cause this parser to be detached from the Document, 1014 // but we need to ensure it isn't deleted yet. 1015 RefPtrWillBeRawPtr<HTMLDocumentParser> protect(this); 1016 m_scriptRunner->executeScriptsWaitingForResources(); 1017 if (!isWaitingForScripts()) 1018 resumeParsingAfterScriptExecution(); 1019 } 1020 1021 void HTMLDocumentParser::parseDocumentFragment(const String& source, DocumentFragment* fragment, Element* contextElement, ParserContentPolicy parserContentPolicy) 1022 { 1023 RefPtrWillBeRawPtr<HTMLDocumentParser> parser = HTMLDocumentParser::create(fragment, contextElement, parserContentPolicy); 1024 parser->insert(source); // Use insert() so that the parser will not yield. 1025 parser->finish(); 1026 ASSERT(!parser->processingData()); // Make sure we're done. <rdar://problem/3963151> 1027 parser->detach(); // Allows ~DocumentParser to assert it was detached before destruction. 1028 } 1029 1030 void HTMLDocumentParser::suspendScheduledTasks() 1031 { 1032 if (m_parserScheduler) 1033 m_parserScheduler->suspend(); 1034 } 1035 1036 void HTMLDocumentParser::resumeScheduledTasks() 1037 { 1038 if (m_parserScheduler) 1039 m_parserScheduler->resume(); 1040 } 1041 1042 void HTMLDocumentParser::appendBytes(const char* data, size_t length) 1043 { 1044 if (!length || isStopped()) 1045 return; 1046 1047 if (shouldUseThreading()) { 1048 if (!m_haveBackgroundParser) 1049 startBackgroundParser(); 1050 1051 OwnPtr<Vector<char> > buffer = adoptPtr(new Vector<char>(length)); 1052 memcpy(buffer->data(), data, length); 1053 TRACE_EVENT1("net", "HTMLDocumentParser::appendBytes", "size", (unsigned)length); 1054 1055 HTMLParserThread::shared()->postTask(bind(&BackgroundHTMLParser::appendRawBytesFromMainThread, m_backgroundParser, buffer.release())); 1056 return; 1057 } 1058 1059 DecodedDataDocumentParser::appendBytes(data, length); 1060 } 1061 1062 void HTMLDocumentParser::flush() 1063 { 1064 // If we've got no decoder, we never received any data. 1065 if (isDetached() || needsDecoder()) 1066 return; 1067 1068 if (m_haveBackgroundParser) 1069 HTMLParserThread::shared()->postTask(bind(&BackgroundHTMLParser::flush, m_backgroundParser)); 1070 else 1071 DecodedDataDocumentParser::flush(); 1072 } 1073 1074 void HTMLDocumentParser::setDecoder(PassOwnPtr<TextResourceDecoder> decoder) 1075 { 1076 ASSERT(decoder); 1077 DecodedDataDocumentParser::setDecoder(decoder); 1078 1079 if (m_haveBackgroundParser) 1080 HTMLParserThread::shared()->postTask(bind(&BackgroundHTMLParser::setDecoder, m_backgroundParser, takeDecoder())); 1081 } 1082 1083 } 1084