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