nsHtml5Parser.cpp (28606B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set sw=2 ts=2 et tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "nsHtml5Parser.h" 8 9 #include "ErrorList.h" 10 #include "encoding_rs_statics.h" 11 #include "mozilla/AutoRestore.h" 12 #include "mozilla/UniquePtr.h" 13 #include "nsCRT.h" 14 #include "nsContentUtils.h" // for kLoadAsData 15 #include "nsHtml5AtomTable.h" 16 #include "nsHtml5DependentUTF16Buffer.h" 17 #include "nsHtml5Tokenizer.h" 18 #include "nsHtml5TreeBuilder.h" 19 #include "nsNetUtil.h" 20 21 NS_INTERFACE_TABLE_HEAD(nsHtml5Parser) 22 NS_INTERFACE_TABLE(nsHtml5Parser, nsIParser, nsISupportsWeakReference, 23 nsIStreamListener) 24 NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsHtml5Parser) 25 NS_INTERFACE_MAP_END 26 27 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHtml5Parser) 28 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHtml5Parser) 29 30 NS_IMPL_CYCLE_COLLECTION_CLASS(nsHtml5Parser) 31 32 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsHtml5Parser) 33 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExecutor) 34 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(GetStreamParser()) 35 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 36 37 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHtml5Parser) 38 NS_IMPL_CYCLE_COLLECTION_UNLINK(mExecutor) 39 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE 40 tmp->DropStreamParser(); 41 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 42 43 nsHtml5Parser::nsHtml5Parser() 44 : mAboutBlankMode(false), 45 mLastWasCR(false), 46 mDocWriteSpeculativeLastWasCR(false), 47 mBlocked(0), 48 mDocWriteSpeculatorActive(false), 49 mScriptNestingLevel(0), 50 mTerminationStarted(false), 51 mDocumentClosed(false), 52 mInDocumentWrite(false), 53 mInsertionPointPermanentlyUndefined(false), 54 mFirstBuffer(new nsHtml5OwningUTF16Buffer((void*)nullptr)), 55 mLastBuffer(mFirstBuffer), 56 mExecutor(new nsHtml5TreeOpExecutor()), 57 mTreeBuilder(new nsHtml5TreeBuilder(mExecutor, nullptr, false)), 58 mTokenizer(new nsHtml5Tokenizer(mTreeBuilder.get(), false)), 59 mRootContextLineNumber(1), 60 mReturnToStreamParserPermitted(false) { 61 mTokenizer->setInterner(&mAtomTable); 62 } 63 64 nsHtml5Parser::~nsHtml5Parser() { 65 mTokenizer->end(); 66 if (mDocWriteSpeculativeTokenizer) { 67 mDocWriteSpeculativeTokenizer->end(); 68 } 69 } 70 71 NS_IMETHODIMP_(void) 72 nsHtml5Parser::SetContentSink(nsIContentSink* aSink) { 73 NS_ASSERTION(aSink == static_cast<nsIContentSink*>(mExecutor), 74 "Attempt to set a foreign sink."); 75 } 76 77 NS_IMETHODIMP_(nsIContentSink*) 78 nsHtml5Parser::GetContentSink() { 79 return static_cast<nsIContentSink*>(mExecutor); 80 } 81 82 NS_IMETHODIMP_(void) 83 nsHtml5Parser::GetCommand(nsCString& aCommand) { 84 aCommand.AssignLiteral("view"); 85 } 86 87 NS_IMETHODIMP_(void) 88 nsHtml5Parser::SetCommand(const char* aCommand) { 89 NS_ASSERTION(!strcmp(aCommand, "view") || !strcmp(aCommand, "view-source") || 90 !strcmp(aCommand, "external-resource") || 91 !strcmp(aCommand, "import") || 92 !strcmp(aCommand, kLoadAsData), 93 "Unsupported parser command"); 94 } 95 96 NS_IMETHODIMP_(void) 97 nsHtml5Parser::SetCommand(eParserCommands aParserCommand) { 98 NS_ASSERTION(aParserCommand == eViewNormal, 99 "Parser command was not eViewNormal."); 100 } 101 102 void nsHtml5Parser::SetDocumentCharset(NotNull<const Encoding*> aEncoding, 103 int32_t aCharsetSource, 104 bool aForceAutoDetection) { 105 MOZ_ASSERT(!mExecutor->HasStarted(), "Document charset set too late."); 106 if (mAboutBlankMode) { 107 MOZ_ASSERT(aEncoding == UTF_8_ENCODING); 108 } else { 109 MOZ_ASSERT(GetStreamParser(), "Setting charset on a script-only parser."); 110 GetStreamParser()->SetDocumentCharset( 111 aEncoding, (nsCharsetSource)aCharsetSource, aForceAutoDetection); 112 } 113 mExecutor->SetDocumentCharsetAndSource(aEncoding, 114 (nsCharsetSource)aCharsetSource); 115 } 116 117 nsresult nsHtml5Parser::GetChannel(nsIChannel** aChannel) { 118 if (GetStreamParser()) { 119 return GetStreamParser()->GetChannel(aChannel); 120 } 121 return NS_ERROR_NOT_AVAILABLE; 122 } 123 124 nsIStreamListener* nsHtml5Parser::GetStreamListener() { 125 if (mAboutBlankMode) { 126 return this; 127 } 128 return mStreamListener; 129 } 130 131 NS_IMETHODIMP 132 nsHtml5Parser::ContinueInterruptedParsing() { 133 MOZ_ASSERT_UNREACHABLE("Don't call. For interface compat only."); 134 return NS_ERROR_NOT_IMPLEMENTED; 135 } 136 137 NS_IMETHODIMP_(void) 138 nsHtml5Parser::BlockParser() { 139 MOZ_ASSERT(!mAboutBlankMode, "Must not block about:blank"); 140 mBlocked++; 141 } 142 143 NS_IMETHODIMP_(void) 144 nsHtml5Parser::UnblockParser() { 145 MOZ_ASSERT(!mAboutBlankMode, "Must not unblock about:blank"); 146 MOZ_DIAGNOSTIC_ASSERT(mBlocked > 0); 147 if (MOZ_LIKELY(mBlocked > 0)) { 148 mBlocked--; 149 } 150 if (MOZ_LIKELY(mBlocked == 0) && mExecutor) { 151 mExecutor->ContinueInterruptedParsingAsync(); 152 } 153 } 154 155 NS_IMETHODIMP_(void) 156 nsHtml5Parser::ContinueInterruptedParsingAsync() { 157 if (mExecutor) { 158 mExecutor->ContinueInterruptedParsingAsync(); 159 } 160 } 161 162 NS_IMETHODIMP_(bool) 163 nsHtml5Parser::IsParserEnabled() { return !mBlocked; } 164 165 NS_IMETHODIMP_(bool) 166 nsHtml5Parser::IsParserClosed() { return mDocumentClosed; } 167 168 NS_IMETHODIMP_(bool) 169 nsHtml5Parser::IsComplete() { return mExecutor->IsComplete(); } 170 171 NS_IMETHODIMP 172 nsHtml5Parser::Parse(nsIURI* aURL) { 173 /* 174 * Do NOT cause WillBuildModel to be called synchronously from here! 175 * The document won't be ready for it until OnStartRequest! 176 */ 177 MOZ_ASSERT(!mExecutor->HasStarted(), 178 "Tried to start parse without initializing the parser."); 179 if (!mAboutBlankMode) { 180 MOZ_ASSERT(GetStreamParser(), 181 "Can't call this Parse() variant on script-created parser"); 182 183 GetStreamParser()->SetViewSourceTitle( 184 aURL); // In case we're viewing source 185 mExecutor->SetStreamParser(GetStreamParser()); 186 } 187 mExecutor->SetParser(this); 188 return NS_OK; 189 } 190 191 nsresult nsHtml5Parser::Parse(const nsAString& aSourceBuffer, void* aKey, 192 bool aLastCall) { 193 nsresult rv; 194 if (NS_FAILED(rv = mExecutor->IsBroken())) { 195 return rv; 196 } 197 if (aSourceBuffer.Length() > INT32_MAX) { 198 return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY); 199 } 200 201 // Maintain a reference to ourselves so we don't go away 202 // till we're completely done. The old parser grips itself in this method. 203 nsCOMPtr<nsIParser> kungFuDeathGrip(this); 204 205 // Gripping the other objects just in case, since the other old grip 206 // required grips to these, too. 207 RefPtr<nsHtml5StreamParser> streamKungFuDeathGrip(GetStreamParser()); 208 (void)streamKungFuDeathGrip; // Not used within function 209 RefPtr<nsHtml5TreeOpExecutor> executor(mExecutor); 210 211 MOZ_RELEASE_ASSERT(executor->HasStarted()); 212 213 // Return early if the parser has processed EOF 214 if (executor->IsComplete()) { 215 return NS_OK; 216 } 217 218 if (aLastCall && aSourceBuffer.IsEmpty() && !aKey) { 219 // document.close() 220 NS_ASSERTION(!GetStreamParser(), 221 "Had stream parser but got document.close()."); 222 if (mDocumentClosed) { 223 // already closed 224 return NS_OK; 225 } 226 mDocumentClosed = true; 227 if (!mBlocked && !mInDocumentWrite && !executor->IsFlushing()) { 228 return ParseUntilBlocked(); 229 } 230 return NS_OK; 231 } 232 233 // If we got this far, we are dealing with a document.write or 234 // document.writeln call--not document.close(). 235 236 MOZ_RELEASE_ASSERT( 237 IsInsertionPointDefined(), 238 "Doc.write reached parser with undefined insertion point."); 239 240 MOZ_RELEASE_ASSERT(!(GetStreamParser() && !aKey), 241 "Got a null key in a non-script-created parser"); 242 243 // XXX is this optimization bogus? 244 if (aSourceBuffer.IsEmpty()) { 245 return NS_OK; 246 } 247 248 // This guard is here to prevent document.close from tokenizing synchronously 249 // while a document.write (that wrote the script that called document.close!) 250 // is still on the call stack. 251 mozilla::AutoRestore<bool> guard(mInDocumentWrite); 252 mInDocumentWrite = true; 253 254 // The script is identified by aKey. If there's nothing in the buffer 255 // chain for that key, we'll insert at the head of the queue. 256 // When the script leaves something in the queue, a zero-length 257 // key-holder "buffer" is inserted in the queue. If the same script 258 // leaves something in the chain again, it will be inserted immediately 259 // before the old key holder belonging to the same script. 260 // 261 // We don't do the actual data insertion yet in the hope that the data gets 262 // tokenized and there no data or less data to copy to the heap after 263 // tokenization. Also, this way, we avoid inserting one empty data buffer 264 // per document.write, which matters for performance when the parser isn't 265 // blocked and a badly-authored script calls document.write() once per 266 // input character. (As seen in a benchmark!) 267 // 268 // The insertion into the input stream happens conceptually before anything 269 // gets tokenized. To make sure multi-level document.write works right, 270 // it's necessary to establish the location of our parser key up front 271 // in case this is the first write with this key. 272 // 273 // In a document.open() case, the first write level has a null key, so that 274 // case is handled separately, because normal buffers containing data 275 // have null keys. 276 277 // These don't need to be owning references, because they always point to 278 // the buffer queue and buffers can't be removed from the buffer queue 279 // before document.write() returns. The buffer queue clean-up happens the 280 // next time ParseUntilBlocked() is called. 281 // However, they are made owning just in case the reasoning above is flawed 282 // and a flaw would lead to worse problems with plain pointers. If this 283 // turns out to be a perf problem, it's worthwhile to consider making 284 // prevSearchbuf a plain pointer again. 285 RefPtr<nsHtml5OwningUTF16Buffer> prevSearchBuf; 286 RefPtr<nsHtml5OwningUTF16Buffer> firstLevelMarker; 287 288 if (aKey) { 289 if (mFirstBuffer == mLastBuffer) { 290 nsHtml5OwningUTF16Buffer* keyHolder = new nsHtml5OwningUTF16Buffer(aKey); 291 keyHolder->next = mLastBuffer; 292 mFirstBuffer = keyHolder; 293 } else if (mFirstBuffer->key != aKey) { 294 prevSearchBuf = mFirstBuffer; 295 for (;;) { 296 if (prevSearchBuf->next == mLastBuffer) { 297 // key was not found 298 nsHtml5OwningUTF16Buffer* keyHolder = 299 new nsHtml5OwningUTF16Buffer(aKey); 300 keyHolder->next = mFirstBuffer; 301 mFirstBuffer = keyHolder; 302 prevSearchBuf = nullptr; 303 break; 304 } 305 if (prevSearchBuf->next->key == aKey) { 306 // found a key holder 307 break; 308 } 309 prevSearchBuf = prevSearchBuf->next; 310 } 311 } // else mFirstBuffer is the keyholder 312 313 // prevSearchBuf is the previous buffer before the keyholder or null if 314 // there isn't one. 315 } else { 316 // We have a first-level write in the document.open() case. We insert before 317 // mLastBuffer, effectively, by making mLastBuffer be a new sentinel object 318 // and redesignating the previous mLastBuffer as our firstLevelMarker. We 319 // need to put a marker there, because otherwise additional document.writes 320 // from nested event loops would insert in the wrong place. Sigh. 321 mLastBuffer->next = new nsHtml5OwningUTF16Buffer((void*)nullptr); 322 firstLevelMarker = mLastBuffer; 323 mLastBuffer = mLastBuffer->next; 324 } 325 326 nsHtml5DependentUTF16Buffer stackBuffer(aSourceBuffer); 327 328 while (!mBlocked && stackBuffer.hasMore()) { 329 stackBuffer.adjust(mLastWasCR); 330 mLastWasCR = false; 331 if (stackBuffer.hasMore()) { 332 int32_t lineNumberSave; 333 bool inRootContext = (!GetStreamParser() && !aKey); 334 if (inRootContext) { 335 mTokenizer->setLineNumber(mRootContextLineNumber); 336 } else { 337 // we aren't the root context, so save the line number on the 338 // *stack* so that we can restore it. 339 lineNumberSave = mTokenizer->getLineNumber(); 340 } 341 342 if (!mTokenizer->EnsureBufferSpace(stackBuffer.getLength())) { 343 return executor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY); 344 } 345 mLastWasCR = mTokenizer->tokenizeBuffer(&stackBuffer); 346 if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) { 347 return executor->MarkAsBroken(rv); 348 } 349 350 if (inRootContext) { 351 mRootContextLineNumber = mTokenizer->getLineNumber(); 352 } else { 353 mTokenizer->setLineNumber(lineNumberSave); 354 } 355 356 if (mTreeBuilder->HasScriptThatMayDocumentWriteOrBlock()) { 357 auto r = mTreeBuilder->Flush(); // Move ops to the executor 358 if (r.isErr()) { 359 return executor->MarkAsBroken(r.unwrapErr()); 360 } 361 rv = executor->FlushDocumentWrite(); // run the ops 362 NS_ENSURE_SUCCESS(rv, rv); 363 // Flushing tree ops can cause all sorts of things. 364 // Return early if the parser got terminated. 365 if (executor->IsComplete()) { 366 return NS_OK; 367 } 368 } 369 // Ignore suspension requests 370 } 371 } 372 373 RefPtr<nsHtml5OwningUTF16Buffer> heapBuffer; 374 if (stackBuffer.hasMore()) { 375 // The buffer wasn't tokenized to completion. Create a copy of the tail 376 // on the heap. 377 heapBuffer = stackBuffer.FalliblyCopyAsOwningBuffer(); 378 if (!heapBuffer) { 379 // Allocation failed. The parser is now broken. 380 return executor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY); 381 } 382 } 383 384 if (heapBuffer) { 385 // We have something to insert before the keyholder holding in the non-null 386 // aKey case and we have something to swap into firstLevelMarker in the 387 // null aKey case. 388 if (aKey) { 389 NS_ASSERTION(mFirstBuffer != mLastBuffer, "Where's the keyholder?"); 390 // the key holder is still somewhere further down the list from 391 // prevSearchBuf (which may be null) 392 if (mFirstBuffer->key == aKey) { 393 NS_ASSERTION( 394 !prevSearchBuf, 395 "Non-null prevSearchBuf when mFirstBuffer is the key holder?"); 396 heapBuffer->next = mFirstBuffer; 397 mFirstBuffer = heapBuffer; 398 } else { 399 if (!prevSearchBuf) { 400 prevSearchBuf = mFirstBuffer; 401 } 402 // We created a key holder earlier, so we will find it without walking 403 // past the end of the list. 404 while (prevSearchBuf->next->key != aKey) { 405 prevSearchBuf = prevSearchBuf->next; 406 } 407 heapBuffer->next = prevSearchBuf->next; 408 prevSearchBuf->next = heapBuffer; 409 } 410 } else { 411 NS_ASSERTION(firstLevelMarker, "How come we don't have a marker."); 412 firstLevelMarker->Swap(heapBuffer); 413 } 414 } 415 416 if (!mBlocked) { // buffer was tokenized to completion 417 NS_ASSERTION(!stackBuffer.hasMore(), 418 "Buffer wasn't tokenized to completion?"); 419 // Scripting semantics require a forced tree builder flush here 420 auto r = mTreeBuilder->Flush(); // Move ops to the executor 421 if (r.isErr()) { 422 return executor->MarkAsBroken(r.unwrapErr()); 423 } 424 rv = executor->FlushDocumentWrite(); // run the ops 425 NS_ENSURE_SUCCESS(rv, rv); 426 } else if (stackBuffer.hasMore()) { 427 // The buffer wasn't tokenized to completion. Tokenize the untokenized 428 // content in order to preload stuff. This content will be retokenized 429 // later for normal parsing. 430 if (!mDocWriteSpeculatorActive) { 431 mDocWriteSpeculatorActive = true; 432 if (!mDocWriteSpeculativeTreeBuilder) { 433 // Lazily initialize if uninitialized 434 mDocWriteSpeculativeTreeBuilder = 435 mozilla::MakeUnique<nsHtml5TreeBuilder>(nullptr, 436 executor->GetStage(), true); 437 mDocWriteSpeculativeTreeBuilder->setScriptingEnabled( 438 mTreeBuilder->isScriptingEnabled()); 439 mDocWriteSpeculativeTreeBuilder->setAllowDeclarativeShadowRoots( 440 mTreeBuilder->isAllowDeclarativeShadowRoots()); 441 mDocWriteSpeculativeTokenizer = mozilla::MakeUnique<nsHtml5Tokenizer>( 442 mDocWriteSpeculativeTreeBuilder.get(), false); 443 mDocWriteSpeculativeTokenizer->setInterner(&mAtomTable); 444 mDocWriteSpeculativeTokenizer->start(); 445 } 446 mDocWriteSpeculativeTokenizer->resetToDataState(); 447 mDocWriteSpeculativeTreeBuilder->loadState(mTreeBuilder.get()); 448 mDocWriteSpeculativeLastWasCR = false; 449 } 450 451 // Note that with multilevel document.write if we didn't just activate the 452 // speculator, it's possible that the speculator is now in the wrong state. 453 // That's OK for the sake of simplicity. The worst that can happen is 454 // that the speculative loads aren't exactly right. The content will be 455 // reparsed anyway for non-preload purposes. 456 457 // The buffer position for subsequent non-speculative parsing now lives 458 // in heapBuffer, so it's ok to let the buffer position of stackBuffer 459 // to be overwritten and not restored below. 460 while (stackBuffer.hasMore()) { 461 stackBuffer.adjust(mDocWriteSpeculativeLastWasCR); 462 if (stackBuffer.hasMore()) { 463 if (!mDocWriteSpeculativeTokenizer->EnsureBufferSpace( 464 stackBuffer.getLength())) { 465 return executor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY); 466 } 467 mDocWriteSpeculativeLastWasCR = 468 mDocWriteSpeculativeTokenizer->tokenizeBuffer(&stackBuffer); 469 nsresult rv; 470 if (NS_FAILED((rv = mDocWriteSpeculativeTreeBuilder->IsBroken()))) { 471 return executor->MarkAsBroken(rv); 472 } 473 } 474 } 475 476 auto r = mDocWriteSpeculativeTreeBuilder->Flush(); 477 if (r.isErr()) { 478 return executor->MarkAsBroken(r.unwrapErr()); 479 } 480 mDocWriteSpeculativeTreeBuilder->DropHandles(); 481 executor->FlushSpeculativeLoads(); 482 } 483 484 return NS_OK; 485 } 486 487 NS_IMETHODIMP 488 nsHtml5Parser::Terminate() { 489 if (mTerminationStarted) { 490 return NS_OK; 491 } 492 // `mExecutor->IsComplete()` becomes true too late, if JavaScript event 493 // handlers cause a nested event loop or calls back into the parser during the 494 // termination. It's also hard to make `mExecutor->IsComplete()` to become 495 // true sooner. Hence, let's have yet another flag to prevent further parsing. 496 mTerminationStarted = true; 497 // We should only call DidBuildModel once, so don't do anything if this is 498 // the second time that Terminate has been called. 499 if (mExecutor->IsComplete()) { 500 return NS_OK; 501 } 502 // XXX - [ until we figure out a way to break parser-sink circularity ] 503 // Hack - Hold a reference until we are completely done... 504 nsCOMPtr<nsIParser> kungFuDeathGrip(this); 505 RefPtr<nsHtml5StreamParser> streamParser(GetStreamParser()); 506 RefPtr<nsHtml5TreeOpExecutor> executor(mExecutor); 507 if (streamParser) { 508 streamParser->Terminate(); 509 } 510 return executor->DidBuildModel(true); 511 } 512 513 bool nsHtml5Parser::IsInsertionPointDefined() { 514 return !mExecutor->IsFlushing() && !mInsertionPointPermanentlyUndefined && 515 (!GetStreamParser() || mScriptNestingLevel != 0); 516 } 517 518 void nsHtml5Parser::IncrementScriptNestingLevel() { ++mScriptNestingLevel; } 519 520 void nsHtml5Parser::DecrementScriptNestingLevel() { --mScriptNestingLevel; } 521 522 bool nsHtml5Parser::HasNonzeroScriptNestingLevel() const { 523 return mScriptNestingLevel != 0; 524 } 525 526 void nsHtml5Parser::MarkAsNotScriptCreated(const char* aCommand) { 527 MOZ_ASSERT(!mStreamListener, "Must not call this twice."); 528 eParserMode mode = NORMAL; 529 if (!nsCRT::strcmp(aCommand, "view-source")) { 530 mode = VIEW_SOURCE_HTML; 531 } else if (!nsCRT::strcmp(aCommand, "view-source-xml")) { 532 mode = VIEW_SOURCE_XML; 533 } else if (!nsCRT::strcmp(aCommand, "view-source-plain")) { 534 mode = VIEW_SOURCE_PLAIN; 535 } else if (!nsCRT::strcmp(aCommand, "plain-text")) { 536 mode = PLAIN_TEXT; 537 } else if (!nsCRT::strcmp(aCommand, kLoadAsData)) { 538 mode = LOAD_AS_DATA; 539 } else if (!nsCRT::strcmp(aCommand, "about-blank")) { 540 mode = ABOUT_BLANK; 541 } 542 #ifdef DEBUG 543 else { 544 NS_ASSERTION(!nsCRT::strcmp(aCommand, "view") || 545 !nsCRT::strcmp(aCommand, "external-resource") || 546 !nsCRT::strcmp(aCommand, "import"), 547 "Unsupported parser command!"); 548 } 549 #endif 550 if (mode == ABOUT_BLANK) { 551 mAboutBlankMode = true; 552 } else { 553 mStreamListener = new nsHtml5StreamListener( 554 new nsHtml5StreamParser(mExecutor, this, mode)); 555 } 556 } 557 558 bool nsHtml5Parser::IsScriptCreated() { return !GetStreamParser(); } 559 560 bool nsHtml5Parser::IsAboutBlankMode() { return mAboutBlankMode; } 561 562 /* End nsIParser */ 563 564 // not from interface 565 nsresult nsHtml5Parser::ParseUntilBlocked() { 566 nsresult rv = mExecutor->IsBroken(); 567 NS_ENSURE_SUCCESS(rv, rv); 568 if (mBlocked || mInsertionPointPermanentlyUndefined || mTerminationStarted || 569 mExecutor->IsComplete()) { 570 return NS_OK; 571 } 572 NS_ASSERTION(mExecutor->HasStarted(), "Bad life cycle."); 573 NS_ASSERTION(!mInDocumentWrite, 574 "ParseUntilBlocked entered while in doc.write!"); 575 576 mDocWriteSpeculatorActive = false; 577 578 for (;;) { 579 if (!mFirstBuffer->hasMore()) { 580 if (mFirstBuffer == mLastBuffer) { 581 if (mExecutor->IsComplete()) { 582 // something like cache manisfests stopped the parse in mid-flight 583 return NS_OK; 584 } 585 if (mDocumentClosed) { 586 PermanentlyUndefineInsertionPoint(); 587 nsresult rv; 588 MOZ_RELEASE_ASSERT( 589 !GetStreamParser(), 590 "This should only happen with script-created parser."); 591 if (NS_SUCCEEDED((rv = mExecutor->IsBroken()))) { 592 mTokenizer->eof(); 593 if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) { 594 mExecutor->MarkAsBroken(rv); 595 } else { 596 mTreeBuilder->StreamEnded(); 597 } 598 } 599 auto r = mTreeBuilder->Flush(); 600 if (r.isErr()) { 601 return mExecutor->MarkAsBroken(r.unwrapErr()); 602 } 603 mExecutor->FlushDocumentWrite(); 604 // The below call does memory cleanup, so call it even if the 605 // parser has been marked as broken. 606 mTokenizer->end(); 607 return rv; 608 } 609 // never release the last buffer. 610 NS_ASSERTION(!mLastBuffer->getStart() && !mLastBuffer->getEnd(), 611 "Sentinel buffer had its indeces changed."); 612 if (GetStreamParser()) { 613 if (mReturnToStreamParserPermitted && 614 !mExecutor->IsScriptExecuting()) { 615 auto r = mTreeBuilder->Flush(); 616 if (r.isErr()) { 617 return mExecutor->MarkAsBroken(r.unwrapErr()); 618 } 619 mReturnToStreamParserPermitted = false; 620 GetStreamParser()->ContinueAfterScriptsOrEncodingCommitment( 621 mTokenizer.get(), mTreeBuilder.get(), mLastWasCR); 622 } 623 } else { 624 // Script-created parser 625 auto r = mTreeBuilder->Flush(); 626 if (r.isErr()) { 627 return mExecutor->MarkAsBroken(r.unwrapErr()); 628 } 629 // No need to flush the executor, because the executor is already 630 // in a flush 631 NS_ASSERTION(mExecutor->IsInFlushLoop(), 632 "How did we come here without being in the flush loop?"); 633 } 634 return NS_OK; // no more data for now but expecting more 635 } 636 mFirstBuffer = mFirstBuffer->next; 637 continue; 638 } 639 640 if (mBlocked || mExecutor->IsComplete()) { 641 return NS_OK; 642 } 643 644 // now we have a non-empty buffer 645 mFirstBuffer->adjust(mLastWasCR); 646 mLastWasCR = false; 647 if (mFirstBuffer->hasMore()) { 648 bool inRootContext = (!GetStreamParser() && !mFirstBuffer->key); 649 if (inRootContext) { 650 mTokenizer->setLineNumber(mRootContextLineNumber); 651 } 652 if (!mTokenizer->EnsureBufferSpace(mFirstBuffer->getLength())) { 653 return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY); 654 } 655 mLastWasCR = mTokenizer->tokenizeBuffer(mFirstBuffer); 656 nsresult rv; 657 if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) { 658 return mExecutor->MarkAsBroken(rv); 659 } 660 if (inRootContext) { 661 mRootContextLineNumber = mTokenizer->getLineNumber(); 662 } 663 if (mTreeBuilder->HasScriptThatMayDocumentWriteOrBlock()) { 664 auto r = mTreeBuilder->Flush(); 665 if (r.isErr()) { 666 return mExecutor->MarkAsBroken(r.unwrapErr()); 667 } 668 rv = mExecutor->FlushDocumentWrite(); 669 NS_ENSURE_SUCCESS(rv, rv); 670 } 671 if (mBlocked) { 672 return NS_OK; 673 } 674 } 675 } 676 } 677 678 nsresult nsHtml5Parser::StartExecutor() { 679 MOZ_ASSERT(!GetStreamParser(), 680 "Had stream parser but document.write started life cycle."); 681 // This is part of the setup document.open() does. 682 RefPtr<nsHtml5TreeOpExecutor> executor(mExecutor); 683 executor->SetParser(this); 684 mTreeBuilder->setScriptingEnabled(executor->IsScriptEnabled()); 685 mTreeBuilder->setAllowDeclarativeShadowRoots( 686 executor->GetDocument()->AllowsDeclarativeShadowRoots()); 687 688 mTreeBuilder->setIsSrcdocDocument(false); 689 690 mTokenizer->start(); 691 executor->Start(); 692 693 /* 694 * We know we're in document.open(), so our document must already 695 * have a script global andthe WillBuildModel call is safe. 696 */ 697 return executor->WillBuildModel(); 698 } 699 700 nsresult nsHtml5Parser::Initialize(mozilla::dom::Document* aDoc, nsIURI* aURI, 701 nsISupports* aContainer, 702 nsIChannel* aChannel) { 703 mTreeBuilder->setAllowDeclarativeShadowRoots( 704 aDoc->AllowsDeclarativeShadowRoots()); 705 return mExecutor->Init(aDoc, aURI, aContainer, aChannel); 706 } 707 708 void nsHtml5Parser::StartTokenizer(bool aScriptingEnabled) { 709 bool isSrcdoc = false; 710 nsCOMPtr<nsIChannel> channel; 711 nsresult rv = GetChannel(getter_AddRefs(channel)); 712 if (NS_SUCCEEDED(rv)) { 713 isSrcdoc = NS_IsSrcdocChannel(channel); 714 } 715 mTreeBuilder->setIsSrcdocDocument(isSrcdoc); 716 717 mTreeBuilder->SetPreventScriptExecution(!aScriptingEnabled); 718 mTreeBuilder->setScriptingEnabled(aScriptingEnabled); 719 mTreeBuilder->setAllowDeclarativeShadowRoots( 720 mExecutor->GetDocument()->AllowsDeclarativeShadowRoots()); 721 mTokenizer->start(); 722 } 723 724 void nsHtml5Parser::InitializeDocWriteParserState( 725 nsAHtml5TreeBuilderState* aState, int32_t aLine) { 726 mTokenizer->resetToDataState(); 727 mTokenizer->setLineNumber(aLine); 728 mTreeBuilder->loadState(aState); 729 mLastWasCR = false; 730 mReturnToStreamParserPermitted = true; 731 } 732 733 void nsHtml5Parser::ContinueAfterFailedCharsetSwitch() { 734 MOZ_ASSERT( 735 GetStreamParser(), 736 "Tried to continue after failed charset switch without a stream parser"); 737 GetStreamParser()->ContinueAfterFailedCharsetSwitch(); 738 } 739 740 NS_IMETHODIMP nsHtml5Parser::OnStartRequest(nsIRequest* aRequest) { 741 if (!mAboutBlankMode) { 742 MOZ_ASSERT(false, 743 "Attempted to use nsHtml5Parser as stream listener in " 744 "non-about:blank mode."); 745 return NS_ERROR_NOT_IMPLEMENTED; 746 } 747 MOZ_RELEASE_ASSERT(!GetStreamParser(), 748 "Should not have stream parser in about:blank mode."); 749 mTokenizer->start(); 750 mExecutor->Start(); 751 nsresult rv = mExecutor->WillBuildModel(); 752 NS_ENSURE_SUCCESS(rv, rv); 753 PermanentlyUndefineInsertionPoint(); 754 mTokenizer->eof(); 755 if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) { 756 mExecutor->MarkAsBroken(rv); 757 } else { 758 mTreeBuilder->StreamEnded(); 759 } 760 auto r = mTreeBuilder->Flush(); 761 if (r.isErr()) { 762 return mExecutor->MarkAsBroken(r.unwrapErr()); 763 } 764 mExecutor->FlushDocumentWrite(); 765 // The below call does memory cleanup, so call it even if the 766 // parser has been marked as broken. 767 mTokenizer->end(); 768 return rv; 769 } 770 771 NS_IMETHODIMP nsHtml5Parser::OnDataAvailable(nsIRequest* aRequest, 772 nsIInputStream* aInStream, 773 uint64_t aSourceOffset, 774 uint32_t aLength) { 775 if (!mAboutBlankMode) { 776 MOZ_ASSERT(false, 777 "Attempted to use nsHtml5Parser as stream listener in " 778 "non-about:blank mode."); 779 return NS_ERROR_NOT_IMPLEMENTED; 780 } 781 if (aLength) { 782 MOZ_ASSERT(false, "Non-zero-length stream in about:blank mode."); 783 return NS_ERROR_ILLEGAL_INPUT; 784 } 785 return NS_OK; 786 } 787 788 NS_IMETHODIMP nsHtml5Parser::OnStopRequest(nsIRequest* aRequest, 789 nsresult aStatus) { 790 if (!mAboutBlankMode) { 791 MOZ_ASSERT(false, 792 "Attempted to use nsHtml5Parser as stream listener in " 793 "non-about:blank mode."); 794 return NS_ERROR_NOT_IMPLEMENTED; 795 } 796 return NS_OK; 797 }