nsHtml5TreeOpExecutor.cpp (48671B)
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 "mozilla/DebugOnly.h" 8 #include "mozilla/Likely.h" 9 #include "mozilla/dom/BrowsingContext.h" 10 #include "mozilla/dom/MediaList.h" 11 #include "mozilla/dom/ScriptLoader.h" 12 #include "mozilla/dom/nsCSPContext.h" 13 #include "mozilla/dom/nsCSPService.h" 14 #include "mozilla/dom/PolicyContainer.h" 15 16 #include "imgLoader.h" 17 #include "mozAutoDocUpdate.h" 18 #include "mozilla/IdleTaskRunner.h" 19 #include "mozilla/Preferences.h" 20 #include "mozilla/ProfilerLabels.h" 21 #include "mozilla/ProfilerMarkers.h" 22 #include "mozilla/StaticPrefs_content.h" 23 #include "mozilla/StaticPrefs_security.h" 24 #include "mozilla/StaticPrefs_view_source.h" 25 #include "mozilla/css/Loader.h" 26 #include "mozilla/fallible.h" 27 #include "nsContentUtils.h" 28 #include "nsDocShell.h" 29 #include "nsError.h" 30 #include "nsHTMLDocument.h" 31 #include "nsHtml5AutoPauseUpdate.h" 32 #include "nsHtml5Parser.h" 33 #include "nsHtml5StreamParser.h" 34 #include "nsHtml5Tokenizer.h" 35 #include "nsHtml5TreeBuilder.h" 36 #include "nsHtml5TreeOpExecutor.h" 37 #include "nsIContentSecurityPolicy.h" 38 #include "nsIDocShell.h" 39 #include "nsIDocShellTreeItem.h" 40 #include "nsINestedURI.h" 41 #include "nsIHttpChannel.h" 42 #include "nsIScriptContext.h" 43 #include "nsIScriptError.h" 44 #include "nsIScriptGlobalObject.h" 45 #include "nsIViewSourceChannel.h" 46 #include "nsNetUtil.h" 47 #include "xpcpublic.h" 48 49 using namespace mozilla; 50 51 #ifdef DEBUG 52 static LazyLogModule gHtml5TreeOpExecutorLog("Html5TreeOpExecutor"); 53 #endif // DEBUG 54 static LazyLogModule gCharsetMenuLog("Chardetng"); 55 56 #define LOG(args) MOZ_LOG(gHtml5TreeOpExecutorLog, LogLevel::Debug, args) 57 #define LOGCHARDETNG(args) MOZ_LOG(gCharsetMenuLog, LogLevel::Debug, args) 58 59 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor, 60 nsHtml5DocumentBuilder, 61 nsIContentSink) 62 63 class nsHtml5ExecutorReflusher : public Runnable { 64 private: 65 RefPtr<nsHtml5TreeOpExecutor> mExecutor; 66 67 public: 68 explicit nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor) 69 : Runnable("nsHtml5ExecutorReflusher"), mExecutor(aExecutor) {} 70 NS_IMETHOD Run() override { 71 dom::Document* doc = mExecutor->GetDocument(); 72 if (XRE_IsContentProcess() && 73 nsContentUtils:: 74 HighPriorityEventPendingForTopLevelDocumentBeforeContentfulPaint( 75 doc)) { 76 // Possible early paint pending, reuse the runnable and try to 77 // call RunFlushLoop later. 78 nsCOMPtr<nsIRunnable> flusher = this; 79 if (NS_SUCCEEDED(doc->Dispatch(flusher.forget()))) { 80 PROFILER_MARKER_UNTYPED("HighPrio blocking parser flushing(2)", DOM); 81 return NS_OK; 82 } 83 } 84 mExecutor->RunFlushLoop(); 85 return NS_OK; 86 } 87 }; 88 89 class MOZ_RAII nsHtml5AutoFlush final { 90 private: 91 RefPtr<nsHtml5TreeOpExecutor> mExecutor; 92 size_t mOpsToRemove; 93 94 public: 95 explicit nsHtml5AutoFlush(nsHtml5TreeOpExecutor* aExecutor) 96 : mExecutor(aExecutor), mOpsToRemove(aExecutor->OpQueueLength()) { 97 mExecutor->BeginFlush(); 98 mExecutor->BeginDocUpdate(); 99 } 100 ~nsHtml5AutoFlush() { 101 if (mExecutor->IsInDocUpdate()) { 102 mExecutor->EndDocUpdate(); 103 } else { 104 // We aren't in an update if nsHtml5AutoPauseUpdate 105 // caused something to terminate the parser. 106 MOZ_RELEASE_ASSERT( 107 mExecutor->IsComplete(), 108 "How do we have mParser but the doc update isn't open?"); 109 } 110 mExecutor->EndFlush(); 111 if (mExecutor->IsComplete()) { 112 // `mExecutor->EndDocUpdate()` caused a call to `nsIParser::Terminate`, 113 // so now we should clear the whole op queue in order to be able to 114 // assert in the destructor of `nsHtml5TreeOpExecutor`. 115 mOpsToRemove = mExecutor->OpQueueLength(); 116 } 117 mExecutor->RemoveFromStartOfOpQueue(mOpsToRemove); 118 // We might have missed a speculative load flush due to sync XHR 119 mExecutor->FlushSpeculativeLoads(); 120 } 121 void SetNumberOfOpsToRemove(size_t aOpsToRemove) { 122 MOZ_ASSERT(aOpsToRemove < mOpsToRemove, 123 "Requested partial clearing of op queue but the number to clear " 124 "wasn't less than the length of the queue."); 125 mOpsToRemove = aOpsToRemove; 126 } 127 }; 128 129 static LinkedList<nsHtml5TreeOpExecutor>* gBackgroundFlushList = nullptr; 130 StaticRefPtr<IdleTaskRunner> gBackgroundFlushRunner; 131 132 nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor() 133 : nsHtml5DocumentBuilder(false), 134 mSuppressEOF(false), 135 mReadingFromStage(false), 136 mStreamParser(nullptr), 137 mPreloadedURLs(23), // Mean # of preloadable resources per page on dmoz 138 mStarted(false), 139 mRunFlushLoopOnStack(false), 140 mCallContinueInterruptedParsingIfEnabled(false), 141 mAlreadyComplainedAboutCharset(false), 142 mAlreadyComplainedAboutDeepTree(false) {} 143 144 nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor() { 145 if (gBackgroundFlushList && isInList()) { 146 ClearOpQueue(); 147 removeFrom(*gBackgroundFlushList); 148 if (gBackgroundFlushList->isEmpty()) { 149 delete gBackgroundFlushList; 150 gBackgroundFlushList = nullptr; 151 if (gBackgroundFlushRunner) { 152 gBackgroundFlushRunner->Cancel(); 153 gBackgroundFlushRunner = nullptr; 154 } 155 } 156 } 157 MOZ_ASSERT(NS_FAILED(mBroken) || mOpQueue.IsEmpty(), 158 "Somehow there's stuff in the op queue."); 159 } 160 161 // nsIContentSink 162 NS_IMETHODIMP 163 nsHtml5TreeOpExecutor::WillParse() { 164 MOZ_ASSERT_UNREACHABLE("No one should call this"); 165 return NS_ERROR_NOT_IMPLEMENTED; 166 } 167 168 NS_IMETHODIMP 169 nsHtml5TreeOpExecutor::WillBuildModel() { 170 mDocument->AddObserver(this); 171 WillBuildModelImpl(); 172 GetDocument()->BeginLoad(); 173 if (mDocShell && !GetDocument()->GetWindow() && !IsExternalViewSource()) { 174 // Not loading as data but script global object not ready 175 return MarkAsBroken(NS_ERROR_DOM_INVALID_STATE_ERR); 176 } 177 return NS_OK; 178 } 179 180 // This is called when the tree construction has ended 181 NS_IMETHODIMP 182 nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated) { 183 if (mRunsToCompletion) { 184 return NS_OK; 185 } 186 187 MOZ_RELEASE_ASSERT(!IsInDocUpdate(), 188 "DidBuildModel from inside a doc update."); 189 190 RefPtr<nsHtml5TreeOpExecutor> pin(this); 191 auto queueClearer = MakeScopeExit([&] { 192 if (aTerminated && (mFlushState == eNotFlushing)) { 193 ClearOpQueue(); // clear in order to be able to assert in destructor 194 } 195 }); 196 197 // This comes from nsXMLContentSink and the old (now removed) 198 // nsHTMLContentSink. If this parser has been marked as broken, treat the end 199 // of parse as forced termination. 200 DidBuildModelImpl(aTerminated || NS_FAILED(IsBroken())); 201 202 bool destroying = true; 203 if (mDocShell) { 204 mDocShell->IsBeingDestroyed(&destroying); 205 } 206 207 if (!destroying) { 208 mDocument->OnParsingCompleted(); 209 210 if (!mLayoutStarted) { 211 // We never saw the body, and layout never got started. Force 212 // layout *now*, to get an initial reflow. 213 214 // NOTE: only force the layout if we are NOT destroying the 215 // docshell. If we are destroying it, then starting layout will 216 // likely cause us to crash, or at best waste a lot of time as we 217 // are just going to tear it down anyway. 218 nsContentSink::StartLayout(false); 219 } 220 } 221 222 ScrollToRef(); 223 mDocument->RemoveObserver(this); 224 if (!mParser) { 225 // DidBuildModelImpl may cause mParser to be nulled out 226 // Return early to avoid unblocking the onload event too many times. 227 return NS_OK; 228 } 229 230 // We may not have called BeginLoad() if loading is terminated before 231 // OnStartRequest call. 232 if (mStarted) { 233 mDocument->EndLoad(); 234 235 // Log outcome only for top-level content navigations in order to 236 // avoid noise from ad iframes. 237 bool topLevel = false; 238 if (mozilla::dom::BrowsingContext* bc = mDocument->GetBrowsingContext()) { 239 topLevel = bc->IsTopContent(); 240 } 241 242 // Log outcome only for text/html and text/plain (excluding CSS, JS, 243 // etc. being viewed as text.) 244 nsAutoString contentType; 245 mDocument->GetContentType(contentType); 246 bool htmlOrPlain = contentType.EqualsLiteral(u"text/html") || 247 contentType.EqualsLiteral(u"text/plain"); 248 249 // Log outcome only for HTTP status code 200 in order to exclude 250 // error pages. 251 bool httpOk = false; 252 nsCOMPtr<nsIChannel> channel; 253 nsresult rv = GetParser()->GetChannel(getter_AddRefs(channel)); 254 if (NS_SUCCEEDED(rv) && channel) { 255 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel); 256 if (httpChannel) { 257 uint32_t httpStatus; 258 rv = httpChannel->GetResponseStatus(&httpStatus); 259 if (NS_SUCCEEDED(rv) && httpStatus == 200) { 260 httpOk = true; 261 } 262 } 263 } 264 265 // Log chardetng outcome 266 MOZ_ASSERT(mDocument->IsHTMLDocument()); 267 if (httpOk && htmlOrPlain && topLevel && !aTerminated && 268 !mDocument->AsHTMLDocument()->IsViewSource()) { 269 // We deliberately measure only normally-completed (non-aborted) loads 270 // that are not View Source loads. This seems like a better place for 271 // checking normal completion than anything in nsHtml5StreamParser. 272 bool plain = mDocument->AsHTMLDocument()->IsPlainText(); 273 int32_t charsetSource = mDocument->GetDocumentCharacterSetSource(); 274 switch (charsetSource) { 275 case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8: 276 if (plain) { 277 LOGCHARDETNG(("TEXT::UtfInitial")); 278 } else { 279 LOGCHARDETNG(("HTML::UtfInitial")); 280 } 281 break; 282 case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic: 283 if (plain) { 284 LOGCHARDETNG(("TEXT::GenericInitial")); 285 } else { 286 LOGCHARDETNG(("HTML::GenericInitial")); 287 } 288 break; 289 case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content: 290 if (plain) { 291 LOGCHARDETNG(("TEXT::ContentInitial")); 292 } else { 293 LOGCHARDETNG(("HTML::ContentInitial")); 294 } 295 break; 296 case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: 297 if (plain) { 298 LOGCHARDETNG(("TEXT::TldInitial")); 299 } else { 300 LOGCHARDETNG(("HTML::TldInitial")); 301 } 302 break; 303 case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII: 304 if (plain) { 305 LOGCHARDETNG(("TEXT::UtfFinal")); 306 } else { 307 LOGCHARDETNG(("HTML::UtfFinal")); 308 } 309 break; 310 case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic: 311 if (plain) { 312 LOGCHARDETNG(("TEXT::GenericFinal")); 313 } else { 314 LOGCHARDETNG(("HTML::GenericFinal")); 315 } 316 break; 317 case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII: 318 if (plain) { 319 LOGCHARDETNG(("TEXT::GenericFinalA")); 320 } else { 321 LOGCHARDETNG(("HTML::GenericFinalA")); 322 } 323 break; 324 case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content: 325 if (plain) { 326 LOGCHARDETNG(("TEXT::ContentFinal")); 327 } else { 328 LOGCHARDETNG(("HTML::ContentFinal")); 329 } 330 break; 331 case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII: 332 if (plain) { 333 LOGCHARDETNG(("TEXT::ContentFinalA")); 334 } else { 335 LOGCHARDETNG(("HTML::ContentFinalA")); 336 } 337 break; 338 case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: 339 if (plain) { 340 LOGCHARDETNG(("TEXT::TldFinal")); 341 } else { 342 LOGCHARDETNG(("HTML::TldFinal")); 343 } 344 break; 345 case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII: 346 if (plain) { 347 LOGCHARDETNG(("TEXT::TldFinalA")); 348 } else { 349 LOGCHARDETNG(("HTML::TldFinalA")); 350 } 351 break; 352 default: 353 // Chardetng didn't run automatically or the input was all ASCII. 354 break; 355 } 356 } 357 } 358 359 // Dropping the stream parser changes the parser's apparent 360 // script-createdness, which is why the stream parser must not be dropped 361 // before this executor's nsHtml5Parser has been made unreachable from its 362 // nsHTMLDocument. (mDocument->EndLoad() above drops the parser from the 363 // document.) 364 GetParser()->DropStreamParser(); 365 DropParserAndPerfHint(); 366 #ifdef GATHER_DOCWRITE_STATISTICS 367 printf("UNSAFE SCRIPTS: %d\n", sUnsafeDocWrites); 368 printf("TOKENIZER-SAFE SCRIPTS: %d\n", sTokenSafeDocWrites); 369 printf("TREEBUILDER-SAFE SCRIPTS: %d\n", sTreeSafeDocWrites); 370 #endif 371 #ifdef DEBUG 372 LOG(("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize)); 373 if (sAppendBatchExaminations != 0) { 374 LOG(("AVERAGE SLOTS EXAMINED: %d\n", 375 sAppendBatchSlotsExamined / sAppendBatchExaminations)); 376 } 377 #endif 378 return NS_OK; 379 } 380 381 NS_IMETHODIMP 382 nsHtml5TreeOpExecutor::WillInterrupt() { 383 MOZ_ASSERT_UNREACHABLE("Don't call. For interface compat only."); 384 return NS_ERROR_NOT_IMPLEMENTED; 385 } 386 387 void nsHtml5TreeOpExecutor::WillResume() { 388 MOZ_ASSERT_UNREACHABLE("Don't call. For interface compat only."); 389 } 390 391 NS_IMETHODIMP 392 nsHtml5TreeOpExecutor::SetParser(nsParserBase* aParser) { 393 mParser = aParser; 394 return NS_OK; 395 } 396 397 void nsHtml5TreeOpExecutor::InitialTranslationCompleted() { 398 nsContentSink::StartLayout(false); 399 } 400 401 void nsHtml5TreeOpExecutor::FlushPendingNotifications(FlushType aType) { 402 if (aType >= FlushType::EnsurePresShellInitAndFrames) { 403 // Bug 577508 / 253951 404 nsContentSink::StartLayout(true); 405 } 406 } 407 408 nsISupports* nsHtml5TreeOpExecutor::GetTarget() { 409 return ToSupports(mDocument); 410 } 411 412 nsresult nsHtml5TreeOpExecutor::MarkAsBroken(nsresult aReason) { 413 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); 414 mBroken = aReason; 415 if (mStreamParser) { 416 mStreamParser->Terminate(); 417 } 418 // We are under memory pressure, but let's hope the following allocation 419 // works out so that we get to terminate and clean up the parser from 420 // a safer point. 421 if (mParser && mDocument) { // can mParser ever be null here? 422 nsCOMPtr<nsIRunnable> terminator = NewRunnableMethod( 423 "nsHtml5Parser::Terminate", GetParser(), &nsHtml5Parser::Terminate); 424 if (NS_FAILED(mDocument->Dispatch(terminator.forget()))) { 425 NS_WARNING("failed to dispatch executor flush event"); 426 } 427 } 428 return aReason; 429 } 430 431 static bool BackgroundFlushCallback(TimeStamp /*aDeadline*/) { 432 RefPtr<nsHtml5TreeOpExecutor> ex = gBackgroundFlushList->popFirst(); 433 if (ex) { 434 ex->RunFlushLoop(); 435 } 436 if (gBackgroundFlushList && gBackgroundFlushList->isEmpty()) { 437 delete gBackgroundFlushList; 438 gBackgroundFlushList = nullptr; 439 gBackgroundFlushRunner->Cancel(); 440 gBackgroundFlushRunner = nullptr; 441 return true; 442 } 443 return true; 444 } 445 446 void nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync() { 447 if (mDocument && !mDocument->IsInBackgroundWindow()) { 448 nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this); 449 if (NS_FAILED(mDocument->Dispatch(flusher.forget()))) { 450 NS_WARNING("failed to dispatch executor flush event"); 451 } 452 } else { 453 if (!gBackgroundFlushList) { 454 gBackgroundFlushList = new LinkedList<nsHtml5TreeOpExecutor>(); 455 } 456 if (!isInList()) { 457 gBackgroundFlushList->insertBack(this); 458 } 459 if (gBackgroundFlushRunner) { 460 return; 461 } 462 // Now we set up a repetitive idle scheduler for flushing background list. 463 gBackgroundFlushRunner = IdleTaskRunner::Create( 464 &BackgroundFlushCallback, 465 "nsHtml5TreeOpExecutor::BackgroundFlushCallback"_ns, 466 0, // Start looking for idle time immediately. 467 TimeDuration::FromMilliseconds(250), // The hard deadline. 468 TimeDuration::FromMicroseconds( 469 StaticPrefs::content_sink_interactive_parse_time()), // Required 470 // budget. 471 true, // repeating 472 [] { return false; }); // MayStopProcessing 473 } 474 } 475 476 void nsHtml5TreeOpExecutor::FlushSpeculativeLoads() { 477 nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue; 478 mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue); 479 nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements(); 480 nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length(); 481 for (nsHtml5SpeculativeLoad* iter = start; iter < end; ++iter) { 482 if (MOZ_UNLIKELY(!mParser)) { 483 // An extension terminated the parser from a HTTP observer. 484 return; 485 } 486 iter->Perform(this); 487 } 488 } 489 490 class nsHtml5FlushLoopGuard { 491 private: 492 RefPtr<nsHtml5TreeOpExecutor> mExecutor; 493 #ifdef DEBUG 494 uint32_t mStartTime; 495 #endif 496 public: 497 explicit nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor) 498 : mExecutor(aExecutor) 499 #ifdef DEBUG 500 , 501 mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow())) 502 #endif 503 { 504 mExecutor->mRunFlushLoopOnStack = true; 505 } 506 ~nsHtml5FlushLoopGuard() { 507 #ifdef DEBUG 508 uint32_t timeOffTheEventLoop = 509 PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime; 510 if (timeOffTheEventLoop > 511 nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) { 512 nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = timeOffTheEventLoop; 513 } 514 LOG(("Longest time off the event loop: %d\n", 515 nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop)); 516 #endif 517 518 mExecutor->mRunFlushLoopOnStack = false; 519 } 520 }; 521 522 /** 523 * The purpose of the loop here is to avoid returning to the main event loop 524 */ 525 void nsHtml5TreeOpExecutor::RunFlushLoop() { 526 AUTO_PROFILER_LABEL("nsHtml5TreeOpExecutor::RunFlushLoop", OTHER); 527 528 if (mRunFlushLoopOnStack) { 529 // There's already a RunFlushLoop() on the call stack. 530 // The instance of RunFlushLoop() already on the stack is 531 // expected to ensure that we don't stall. 532 return; 533 } 534 535 nsHtml5FlushLoopGuard guard(this); // this is also the self-kungfu! 536 537 RefPtr<nsParserBase> parserKungFuDeathGrip(mParser); 538 RefPtr<nsHtml5StreamParser> streamParserGrip; 539 if (mParser) { 540 streamParserGrip = GetParser()->GetStreamParser(); 541 } 542 (void)streamParserGrip; // Intentionally not used within function 543 544 // Remember the entry time 545 (void)nsContentSink::WillParseImpl(); 546 547 for (;;) { 548 if (!mParser) { 549 // Parse has terminated. 550 ClearOpQueue(); // clear in order to be able to assert in destructor 551 return; 552 } 553 554 if (NS_FAILED(IsBroken())) { 555 return; 556 } 557 558 if (!parserKungFuDeathGrip->IsParserEnabled()) { 559 // The parser is blocked. 560 // Whatever blocked the parser is responsible for ensuring that 561 // we don't stall. 562 return; 563 } 564 565 if (mFlushState != eNotFlushing) { 566 // It's not clear whether this case can ever happen. It's not 567 // supposed to, but in case this can happen, let's return 568 // early to avoid an even worse state. In case this happens, 569 // there in no obvious other mechanism to prevent stalling, 570 // so let's call `ContinueInterruptedParsingAsync()` to 571 // make sure we don't stall. 572 nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync(); 573 return; 574 } 575 576 // If there are scripts executing, this is probably due to a synchronous 577 // XMLHttpRequest, see bug 460706 and 1938290. 578 if (IsScriptExecuting()) { 579 ContinueParsingDocumentAfterCurrentScript(); 580 return; 581 } 582 583 if (mReadingFromStage) { 584 nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue; 585 MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing, 586 "mOpQueue modified during flush."); 587 if (!mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue, 588 speculativeLoadQueue)) { 589 MarkAsBroken(nsresult::NS_ERROR_OUT_OF_MEMORY); 590 return; 591 } 592 593 // Make sure speculative loads never start after the corresponding 594 // normal loads for the same URLs. 595 nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements(); 596 nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length(); 597 for (nsHtml5SpeculativeLoad* iter = start; iter < end; ++iter) { 598 iter->Perform(this); 599 if (MOZ_UNLIKELY(!mParser)) { 600 // An extension terminated the parser from a HTTP observer. 601 ClearOpQueue(); // clear in order to be able to assert in destructor 602 return; 603 } 604 } 605 } else { 606 FlushSpeculativeLoads(); // Make sure speculative loads never start after 607 // the corresponding normal loads for the same 608 // URLs. 609 if (MOZ_UNLIKELY(!mParser)) { 610 // An extension terminated the parser from a HTTP observer. 611 ClearOpQueue(); // clear in order to be able to assert in destructor 612 return; 613 } 614 // Now parse content left in the document.write() buffer queue if any. 615 // This may generate tree ops on its own or dequeue a speculation. 616 nsresult rv = GetParser()->ParseUntilBlocked(); 617 618 // ParseUntilBlocked flushes operations from the stage to the OpQueue. 619 // Those operations may have accompanying speculative operations. 620 // If so, we have to flush those speculative loads so that we maintain 621 // the invariant that no speculative load starts after the corresponding 622 // normal load for the same URL. See 623 // https://bugzilla.mozilla.org/show_bug.cgi?id=1513292#c80 624 // for a more detailed explanation of why this is necessary. 625 FlushSpeculativeLoads(); 626 627 if (NS_FAILED(rv)) { 628 MarkAsBroken(rv); 629 return; 630 } 631 } 632 633 if (mOpQueue.IsEmpty()) { 634 // Avoid bothering the rest of the engine with a doc update if there's 635 // nothing to do. 636 return; 637 } 638 639 nsIContent* scriptElement = nullptr; 640 bool interrupted = false; 641 bool streamEnded = false; 642 643 { 644 // autoFlush clears mOpQueue in its destructor unless 645 // SetNumberOfOpsToRemove is called first, in which case only 646 // some ops from the start of the queue are cleared. 647 nsHtml5AutoFlush autoFlush(this); 648 // Profiler marker deliberately not counting layout and script 649 // execution. 650 AUTO_PROFILER_MARKER_UNTYPED( 651 "HTMLParserTreeOps", DOM, 652 MarkerOptions(MarkerInnerWindowIdFromDocShell(mDocShell))); 653 654 nsHtml5TreeOperation* first = mOpQueue.Elements(); 655 nsHtml5TreeOperation* last = first + mOpQueue.Length() - 1; 656 for (nsHtml5TreeOperation* iter = first;; ++iter) { 657 if (MOZ_UNLIKELY(!mParser)) { 658 // The previous tree op caused a call to nsIParser::Terminate(). 659 return; 660 } 661 MOZ_ASSERT(IsInDocUpdate(), 662 "Tried to perform tree op outside update batch."); 663 nsresult rv = 664 iter->Perform(this, &scriptElement, &interrupted, &streamEnded); 665 if (NS_FAILED(rv)) { 666 MarkAsBroken(rv); 667 break; 668 } 669 670 // Be sure not to check the deadline if the last op was just performed. 671 if (MOZ_UNLIKELY(iter == last)) { 672 break; 673 } else if (MOZ_UNLIKELY(interrupted) || 674 MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() == 675 NS_ERROR_HTMLPARSER_INTERRUPTED)) { 676 autoFlush.SetNumberOfOpsToRemove((iter - first) + 1); 677 678 nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync(); 679 if (!interrupted) { 680 PROFILER_MARKER_UNTYPED("HTMLParserTreeOpsYieldedOnDeadline", DOM, 681 MarkerInnerWindowIdFromDocShell(mDocShell)); 682 } 683 return; 684 } 685 } 686 687 if (MOZ_UNLIKELY(!mParser)) { 688 // The parse ended during an update pause. 689 return; 690 } 691 if (streamEnded) { 692 GetParser()->PermanentlyUndefineInsertionPoint(); 693 } 694 } // end autoFlush 695 696 if (MOZ_UNLIKELY(!mParser)) { 697 // Ending the doc update caused a call to nsIParser::Terminate(). 698 return; 699 } 700 701 if (streamEnded) { 702 DidBuildModel(false); 703 #ifdef DEBUG 704 if (scriptElement) { 705 nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(scriptElement); 706 if (!sele) { 707 MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled, 708 "Node didn't QI to script, but SVG wasn't disabled."); 709 } 710 MOZ_ASSERT(sele->IsMalformed(), "Script wasn't marked as malformed."); 711 } 712 #endif 713 } else if (scriptElement) { 714 // must be tail call when mFlushState is eNotFlushing 715 RunScript(scriptElement, true); 716 717 // Always check the clock in nsContentSink right after a script 718 StopDeflecting(); 719 if (nsContentSink::DidProcessATokenImpl() == 720 NS_ERROR_HTMLPARSER_INTERRUPTED) { 721 #ifdef DEBUG 722 LOG(("REFLUSH SCHEDULED (after script): %d\n", 723 ++sTimesFlushLoopInterrupted)); 724 #endif 725 nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync(); 726 return; 727 } 728 } 729 } 730 } 731 732 nsresult nsHtml5TreeOpExecutor::FlushDocumentWrite() { 733 nsresult rv = IsBroken(); 734 NS_ENSURE_SUCCESS(rv, rv); 735 736 FlushSpeculativeLoads(); // Make sure speculative loads never start after the 737 // corresponding normal loads for the same URLs. 738 739 if (MOZ_UNLIKELY(!mParser)) { 740 // The parse has ended. 741 ClearOpQueue(); // clear in order to be able to assert in destructor 742 return rv; 743 } 744 745 if (mFlushState != eNotFlushing) { 746 // XXX Can this happen? In case it can, let's avoid crashing. 747 return rv; 748 } 749 750 // avoid crashing near EOF 751 RefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this); 752 RefPtr<nsParserBase> parserKungFuDeathGrip(mParser); 753 (void)parserKungFuDeathGrip; // Intentionally not used within function 754 RefPtr<nsHtml5StreamParser> streamParserGrip; 755 if (mParser) { 756 streamParserGrip = GetParser()->GetStreamParser(); 757 } 758 (void)streamParserGrip; // Intentionally not used within function 759 760 MOZ_RELEASE_ASSERT(!mReadingFromStage, 761 "Got doc write flush when reading from stage"); 762 763 #ifdef DEBUG 764 mStage.AssertEmpty(); 765 #endif 766 767 nsIContent* scriptElement = nullptr; 768 bool interrupted = false; 769 bool streamEnded = false; 770 771 { 772 // autoFlush clears mOpQueue in its destructor. 773 nsHtml5AutoFlush autoFlush(this); 774 // Profiler marker deliberately not counting layout and script 775 // execution. 776 AUTO_PROFILER_MARKER_TEXT( 777 "HTMLParserTreeOps", DOM, 778 MarkerOptions(MarkerInnerWindowIdFromDocShell(mDocShell)), 779 "document.write"_ns); 780 781 nsHtml5TreeOperation* start = mOpQueue.Elements(); 782 nsHtml5TreeOperation* end = start + mOpQueue.Length(); 783 for (nsHtml5TreeOperation* iter = start; iter < end; ++iter) { 784 if (MOZ_UNLIKELY(!mParser)) { 785 // The previous tree op caused a call to nsIParser::Terminate(). 786 return rv; 787 } 788 NS_ASSERTION(IsInDocUpdate(), 789 "Tried to perform tree op outside update batch."); 790 rv = iter->Perform(this, &scriptElement, &interrupted, &streamEnded); 791 if (NS_FAILED(rv)) { 792 MarkAsBroken(rv); 793 break; 794 } 795 } 796 797 if (MOZ_UNLIKELY(!mParser)) { 798 // The parse ended during an update pause. 799 return rv; 800 } 801 if (streamEnded) { 802 // This should be redundant but let's do it just in case. 803 GetParser()->PermanentlyUndefineInsertionPoint(); 804 } 805 } // autoFlush 806 807 if (MOZ_UNLIKELY(!mParser)) { 808 // Ending the doc update caused a call to nsIParser::Terminate(). 809 return rv; 810 } 811 812 if (streamEnded) { 813 DidBuildModel(false); 814 #ifdef DEBUG 815 if (scriptElement) { 816 nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(scriptElement); 817 if (!sele) { 818 MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled, 819 "Node didn't QI to script, but SVG wasn't disabled."); 820 } 821 MOZ_ASSERT(sele->IsMalformed(), "Script wasn't marked as malformed."); 822 } 823 #endif 824 } else if (scriptElement) { 825 // must be tail call when mFlushState is eNotFlushing 826 RunScript(scriptElement, true); 827 } 828 return rv; 829 } 830 831 void nsHtml5TreeOpExecutor::CommitToInternalEncoding() { 832 if (MOZ_UNLIKELY(!mParser || !mStreamParser)) { 833 // An extension terminated the parser from a HTTP observer. 834 ClearOpQueue(); // clear in order to be able to assert in destructor 835 return; 836 } 837 mStreamParser->ContinueAfterScriptsOrEncodingCommitment(nullptr, nullptr, 838 false); 839 ContinueInterruptedParsingAsync(); 840 } 841 842 [[nodiscard]] bool nsHtml5TreeOpExecutor::TakeOpsFromStage() { 843 return mStage.MoveOpsTo(mOpQueue); 844 } 845 846 // copied from HTML content sink 847 bool nsHtml5TreeOpExecutor::IsScriptEnabled() { 848 // Note that if we have no document or no docshell or no global or whatnot we 849 // want to claim script _is_ enabled, so we don't parse the contents of 850 // <noscript> tags! 851 if (!mDocument || !mDocShell) { 852 return true; 853 } 854 855 return mDocument->IsScriptEnabled(); 856 } 857 858 void nsHtml5TreeOpExecutor::StartLayout(bool* aInterrupted) { 859 if (mLayoutStarted || !mDocument) { 860 return; 861 } 862 863 nsHtml5AutoPauseUpdate autoPause(this); 864 865 if (MOZ_UNLIKELY(!mParser)) { 866 // got terminate 867 return; 868 } 869 870 nsContentSink::StartLayout(false); 871 872 if (mParser) { 873 *aInterrupted = !GetParser()->IsParserEnabled(); 874 } 875 } 876 877 void nsHtml5TreeOpExecutor::PauseDocUpdate(bool* aInterrupted) { 878 // Pausing the document update allows JS to run, and potentially block 879 // further parsing. 880 nsHtml5AutoPauseUpdate autoPause(this); 881 882 if (MOZ_LIKELY(mParser)) { 883 *aInterrupted = !GetParser()->IsParserEnabled(); 884 } 885 } 886 887 /** 888 * The reason why this code is here and not in the tree builder even in the 889 * main-thread case is to allow the control to return from the tokenizer 890 * before scripts run. This way, the tokenizer is not invoked re-entrantly 891 * although the parser is. 892 * 893 * The reason why this is called with `aMayDocumentWriteOrBlock=true` as a 894 * tail call when `mFlushState` is set to `eNotFlushing` is to allow re-entry 895 * to `Flush()` but only after the current `Flush()` has cleared the op queue 896 * and is otherwise done cleaning up after itself. 897 */ 898 void nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement, 899 bool aMayDocumentWriteOrBlock) { 900 if (mRunsToCompletion) { 901 // We are in createContextualFragment() or in the upcoming document.parse(). 902 // Do nothing. Let's not even mark scripts malformed here, because that 903 // could cause serialization weirdness later. 904 return; 905 } 906 907 MOZ_ASSERT(mParser, "Trying to run script with a terminated parser."); 908 MOZ_ASSERT(aScriptElement, "No script to run"); 909 nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aScriptElement); 910 if (!sele) { 911 MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled, 912 "Node didn't QI to script, but SVG wasn't disabled."); 913 return; 914 } 915 916 sele->SetCreatorParser(GetParser()); 917 918 if (!aMayDocumentWriteOrBlock) { 919 MOZ_ASSERT(sele->GetScriptDeferred() || sele->GetScriptAsync() || 920 sele->GetScriptIsModule() || sele->GetScriptIsImportMap() || 921 aScriptElement->AsElement()->HasAttr(nsGkAtoms::nomodule)); 922 sele->AttemptToExecute(nullptr /* aParser */); 923 return; 924 } 925 926 MOZ_RELEASE_ASSERT( 927 mFlushState == eNotFlushing, 928 "Tried to run a potentially-blocking script while flushing."); 929 930 mReadingFromStage = false; 931 932 // Copied from nsXMLContentSink 933 // Now tell the script that it's ready to go. This may execute the script 934 // or return true, or neither if the script doesn't need executing. 935 bool block = sele->AttemptToExecute(GetParser()); 936 937 // If the act of insertion evaluated the script, we're fine. 938 if (!block) { 939 // mParser may have been nulled out by now, but the flusher deals 940 941 // If this event isn't needed, it doesn't do anything. It is sometimes 942 // necessary for the parse to continue after complex situations. 943 nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync(); 944 } 945 } 946 947 void nsHtml5TreeOpExecutor::Start() { 948 MOZ_ASSERT(!mStarted, "Tried to start when already started."); 949 mStarted = true; 950 } 951 952 void nsHtml5TreeOpExecutor::UpdateCharsetSource( 953 nsCharsetSource aCharsetSource) { 954 if (mDocument) { 955 mDocument->SetDocumentCharacterSetSource(aCharsetSource); 956 } 957 } 958 959 void nsHtml5TreeOpExecutor::SetDocumentCharsetAndSource( 960 NotNull<const Encoding*> aEncoding, nsCharsetSource aCharsetSource) { 961 if (mDocument) { 962 mDocument->SetDocumentCharacterSetSource(aCharsetSource); 963 mDocument->SetDocumentCharacterSet(aEncoding); 964 } 965 } 966 967 void nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo( 968 NotNull<const Encoding*> aEncoding, int32_t aSource, uint32_t aLineNumber) { 969 nsHtml5AutoPauseUpdate autoPause(this); 970 if (MOZ_UNLIKELY(!mParser)) { 971 // got terminate 972 return; 973 } 974 975 if (!mDocShell) { 976 return; 977 } 978 979 RefPtr<nsDocShell> docShell = static_cast<nsDocShell*>(mDocShell.get()); 980 981 if (NS_SUCCEEDED(docShell->CharsetChangeStopDocumentLoad())) { 982 docShell->CharsetChangeReloadDocument(aEncoding, aSource); 983 } 984 // if the charset switch was accepted, mDocShell has called Terminate() on the 985 // parser by now 986 if (!mParser) { 987 return; 988 } 989 990 GetParser()->ContinueAfterFailedCharsetSwitch(); 991 } 992 993 void nsHtml5TreeOpExecutor::MaybeComplainAboutCharset(const char* aMsgId, 994 bool aError, 995 uint32_t aLineNumber) { 996 // Encoding errors don't count towards already complaining 997 if (!(!strcmp(aMsgId, "EncError") || !strcmp(aMsgId, "EncErrorFrame") || 998 !strcmp(aMsgId, "EncErrorFramePlain"))) { 999 if (mAlreadyComplainedAboutCharset) { 1000 return; 1001 } 1002 mAlreadyComplainedAboutCharset = true; 1003 } 1004 nsContentUtils::ReportToConsole( 1005 aError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag, 1006 "HTML parser"_ns, mDocument, nsContentUtils::eHTMLPARSER_PROPERTIES, 1007 aMsgId, nsTArray<nsString>(), 1008 SourceLocation{mDocument->GetDocumentURI(), aLineNumber}); 1009 } 1010 1011 void nsHtml5TreeOpExecutor::ComplainAboutBogusProtocolCharset( 1012 Document* aDoc, bool aUnrecognized) { 1013 NS_ASSERTION(!mAlreadyComplainedAboutCharset, 1014 "How come we already managed to complain?"); 1015 mAlreadyComplainedAboutCharset = true; 1016 nsContentUtils::ReportToConsole( 1017 nsIScriptError::errorFlag, "HTML parser"_ns, aDoc, 1018 nsContentUtils::eHTMLPARSER_PROPERTIES, 1019 aUnrecognized ? "EncProtocolUnsupported" : "EncProtocolReplacement"); 1020 } 1021 1022 void nsHtml5TreeOpExecutor::MaybeComplainAboutDeepTree(uint32_t aLineNumber) { 1023 if (mAlreadyComplainedAboutDeepTree) { 1024 return; 1025 } 1026 mAlreadyComplainedAboutDeepTree = true; 1027 nsContentUtils::ReportToConsole( 1028 nsIScriptError::errorFlag, "HTML parser"_ns, mDocument, 1029 nsContentUtils::eHTMLPARSER_PROPERTIES, "errDeepTree", 1030 nsTArray<nsString>(), 1031 SourceLocation{mDocument->GetDocumentURI(), aLineNumber}); 1032 } 1033 1034 nsHtml5Parser* nsHtml5TreeOpExecutor::GetParser() { 1035 MOZ_ASSERT(!mRunsToCompletion); 1036 return static_cast<nsHtml5Parser*>(mParser.get()); 1037 } 1038 1039 [[nodiscard]] bool nsHtml5TreeOpExecutor::MoveOpsFrom( 1040 nsTArray<nsHtml5TreeOperation>& aOpQueue) { 1041 MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing, 1042 "Ops added to mOpQueue during tree op execution."); 1043 return !!mOpQueue.AppendElements(std::move(aOpQueue), mozilla::fallible_t()); 1044 } 1045 1046 void nsHtml5TreeOpExecutor::ClearOpQueue() { 1047 MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing, 1048 "mOpQueue cleared during tree op execution."); 1049 mOpQueue.Clear(); 1050 } 1051 1052 void nsHtml5TreeOpExecutor::RemoveFromStartOfOpQueue( 1053 size_t aNumberOfOpsToRemove) { 1054 MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing, 1055 "Ops removed from mOpQueue during tree op execution."); 1056 mOpQueue.RemoveElementsAt(0, aNumberOfOpsToRemove); 1057 } 1058 1059 void nsHtml5TreeOpExecutor::InitializeDocWriteParserState( 1060 nsAHtml5TreeBuilderState* aState, int32_t aLine) { 1061 GetParser()->InitializeDocWriteParserState(aState, aLine); 1062 } 1063 1064 nsIURI* nsHtml5TreeOpExecutor::GetViewSourceBaseURI() { 1065 if (!mViewSourceBaseURI) { 1066 // We query the channel for the baseURI because in certain situations it 1067 // cannot otherwise be determined. If this process fails, fall back to the 1068 // standard method. 1069 nsCOMPtr<nsIViewSourceChannel> vsc = 1070 do_QueryInterface(mDocument->GetChannel()); 1071 if (vsc) { 1072 nsresult rv = vsc->GetBaseURI(getter_AddRefs(mViewSourceBaseURI)); 1073 if (NS_SUCCEEDED(rv) && mViewSourceBaseURI) { 1074 return mViewSourceBaseURI; 1075 } 1076 } 1077 1078 nsCOMPtr<nsIURI> orig = mDocument->GetOriginalURI(); 1079 if (orig->SchemeIs("view-source")) { 1080 nsCOMPtr<nsINestedURI> nested = do_QueryInterface(orig); 1081 NS_ASSERTION(nested, "URI with scheme view-source didn't QI to nested!"); 1082 nested->GetInnerURI(getter_AddRefs(mViewSourceBaseURI)); 1083 } else { 1084 // Fail gracefully if the base URL isn't a view-source: URL. 1085 // Not sure if this can ever happen. 1086 mViewSourceBaseURI = orig; 1087 } 1088 } 1089 return mViewSourceBaseURI; 1090 } 1091 1092 bool nsHtml5TreeOpExecutor::IsExternalViewSource() { 1093 if (!StaticPrefs::view_source_editor_external()) { 1094 return false; 1095 } 1096 if (mDocumentURI) { 1097 return mDocumentURI->SchemeIs("view-source"); 1098 } 1099 return false; 1100 } 1101 1102 // Speculative loading 1103 1104 nsIURI* nsHtml5TreeOpExecutor::BaseURIForPreload() { 1105 // The URL of the document without <base> 1106 nsIURI* documentURI = mDocument->GetDocumentURI(); 1107 // The URL of the document with non-speculative <base> 1108 nsIURI* documentBaseURI = mDocument->GetDocBaseURI(); 1109 1110 // If the two above are different, use documentBaseURI. If they are the same, 1111 // the document object isn't aware of a <base>, so attempt to use the 1112 // mSpeculationBaseURI or, failing, that, documentURI. 1113 return (documentURI == documentBaseURI) 1114 ? (mSpeculationBaseURI ? mSpeculationBaseURI.get() : documentURI) 1115 : documentBaseURI; 1116 } 1117 1118 already_AddRefed<nsIURI> 1119 nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYetAndMediaApplies( 1120 const nsAString& aURL, const nsAString& aMedia) { 1121 nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL); 1122 if (!uri) { 1123 return nullptr; 1124 } 1125 1126 if (!MediaApplies(aMedia)) { 1127 return nullptr; 1128 } 1129 return uri.forget(); 1130 } 1131 1132 bool nsHtml5TreeOpExecutor::MediaApplies(const nsAString& aMedia) { 1133 using dom::MediaList; 1134 1135 if (aMedia.IsEmpty()) { 1136 return true; 1137 } 1138 RefPtr<MediaList> media = MediaList::Create(NS_ConvertUTF16toUTF8(aMedia)); 1139 return media->Matches(*mDocument); 1140 } 1141 1142 already_AddRefed<nsIURI> nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet( 1143 const nsAString& aURL) { 1144 if (aURL.IsEmpty()) { 1145 return nullptr; 1146 } 1147 1148 nsIURI* base = BaseURIForPreload(); 1149 auto encoding = mDocument->GetDocumentCharacterSet(); 1150 nsCOMPtr<nsIURI> uri; 1151 nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, encoding, base); 1152 if (NS_FAILED(rv)) { 1153 NS_WARNING("Failed to create a URI"); 1154 return nullptr; 1155 } 1156 1157 if (ShouldPreloadURI(uri)) { 1158 return uri.forget(); 1159 } 1160 1161 return nullptr; 1162 } 1163 1164 bool nsHtml5TreeOpExecutor::ShouldPreloadURI(nsIURI* aURI) { 1165 nsAutoCString spec; 1166 nsresult rv = aURI->GetSpec(spec); 1167 NS_ENSURE_SUCCESS(rv, false); 1168 return mPreloadedURLs.EnsureInserted(spec); 1169 } 1170 1171 bool nsHtml5TreeOpExecutor::ImageTypeSupports(const nsAString& aType) { 1172 if (aType.IsEmpty()) { 1173 return true; 1174 } 1175 return imgLoader::SupportImageWithMimeType( 1176 NS_ConvertUTF16toUTF8(aType), AcceptedMimeTypes::IMAGES_AND_DOCUMENTS); 1177 } 1178 1179 dom::ReferrerPolicy nsHtml5TreeOpExecutor::GetPreloadReferrerPolicy( 1180 const nsAString& aReferrerPolicy) { 1181 dom::ReferrerPolicy referrerPolicy = 1182 dom::ReferrerInfo::ReferrerPolicyAttributeFromString(aReferrerPolicy); 1183 return GetPreloadReferrerPolicy(referrerPolicy); 1184 } 1185 1186 dom::ReferrerPolicy nsHtml5TreeOpExecutor::GetPreloadReferrerPolicy( 1187 ReferrerPolicy aReferrerPolicy) { 1188 if (aReferrerPolicy != dom::ReferrerPolicy::_empty) { 1189 return aReferrerPolicy; 1190 } 1191 1192 return mDocument->GetPreloadReferrerInfo()->ReferrerPolicy(); 1193 } 1194 1195 void nsHtml5TreeOpExecutor::PreloadScript( 1196 const nsAString& aURL, const nsAString& aCharset, const nsAString& aType, 1197 const nsAString& aCrossOrigin, const nsAString& aMedia, 1198 const nsAString& aNonce, const nsAString& aFetchPriority, 1199 const nsAString& aIntegrity, dom::ReferrerPolicy aReferrerPolicy, 1200 bool aScriptFromHead, bool aAsync, bool aDefer, bool aLinkPreload) { 1201 dom::ScriptLoader* loader = mDocument->GetScriptLoader(); 1202 if (!loader) { 1203 return; 1204 } 1205 nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia); 1206 if (!uri) { 1207 return; 1208 } 1209 auto key = PreloadHashKey::CreateAsScript(uri, aCrossOrigin, aType); 1210 if (mDocument->Preloads().PreloadExists(key)) { 1211 return; 1212 } 1213 loader->PreloadURI(uri, aCharset, aType, aCrossOrigin, aNonce, aFetchPriority, 1214 aIntegrity, aScriptFromHead, aAsync, aDefer, aLinkPreload, 1215 GetPreloadReferrerPolicy(aReferrerPolicy), 0); 1216 } 1217 1218 void nsHtml5TreeOpExecutor::PreloadStyle( 1219 const nsAString& aURL, const nsAString& aCharset, 1220 const nsAString& aCrossOrigin, const nsAString& aMedia, 1221 const nsAString& aReferrerPolicy, const nsAString& aNonce, 1222 const nsAString& aIntegrity, bool aLinkPreload, 1223 const nsAString& aFetchPriority) { 1224 nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia); 1225 if (!uri) { 1226 return; 1227 } 1228 1229 if (aLinkPreload) { 1230 auto hashKey = PreloadHashKey::CreateAsStyle( 1231 uri, mDocument->NodePrincipal(), 1232 dom::Element::StringToCORSMode(aCrossOrigin), 1233 css::eAuthorSheetFeatures); 1234 if (mDocument->Preloads().PreloadExists(hashKey)) { 1235 return; 1236 } 1237 } 1238 1239 mDocument->PreloadStyle( 1240 uri, Encoding::ForLabel(aCharset), aCrossOrigin, 1241 GetPreloadReferrerPolicy(aReferrerPolicy), aNonce, aIntegrity, 1242 aLinkPreload ? css::StylePreloadKind::FromLinkRelPreloadElement 1243 : css::StylePreloadKind::FromParser, 1244 0, aFetchPriority); 1245 } 1246 1247 void nsHtml5TreeOpExecutor::PreloadImage( 1248 const nsAString& aURL, const nsAString& aCrossOrigin, 1249 const nsAString& aMedia, const nsAString& aSrcset, const nsAString& aSizes, 1250 const nsAString& aImageReferrerPolicy, bool aLinkPreload, 1251 const nsAString& aFetchPriority, const nsAString& aType) { 1252 nsCOMPtr<nsIURI> baseURI = BaseURIForPreload(); 1253 bool isImgSet = false; 1254 nsCOMPtr<nsIURI> uri = 1255 mDocument->ResolvePreloadImage(baseURI, aURL, aSrcset, aSizes, &isImgSet); 1256 if (uri && ShouldPreloadURI(uri) && MediaApplies(aMedia) && 1257 ImageTypeSupports(aType)) { 1258 // use document wide referrer policy 1259 mDocument->MaybePreLoadImage(uri, aCrossOrigin, 1260 GetPreloadReferrerPolicy(aImageReferrerPolicy), 1261 isImgSet, aLinkPreload, aFetchPriority); 1262 } 1263 } 1264 1265 // These calls inform the document of picture state and seen sources, such that 1266 // it can use them to inform ResolvePreLoadImage as necessary 1267 void nsHtml5TreeOpExecutor::PreloadPictureSource(const nsAString& aSrcset, 1268 const nsAString& aSizes, 1269 const nsAString& aType, 1270 const nsAString& aMedia) { 1271 mDocument->PreloadPictureImageSource(aSrcset, aSizes, aType, aMedia); 1272 } 1273 1274 void nsHtml5TreeOpExecutor::PreloadFont(const nsAString& aURL, 1275 const nsAString& aCrossOrigin, 1276 const nsAString& aMedia, 1277 const nsAString& aReferrerPolicy, 1278 const nsAString& aFetchPriority) { 1279 nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia); 1280 if (!uri) { 1281 return; 1282 } 1283 1284 mDocument->Preloads().PreloadFont(uri, aCrossOrigin, aReferrerPolicy, 0, 1285 aFetchPriority); 1286 } 1287 1288 void nsHtml5TreeOpExecutor::PreloadFetch(const nsAString& aURL, 1289 const nsAString& aCrossOrigin, 1290 const nsAString& aMedia, 1291 const nsAString& aReferrerPolicy, 1292 const nsAString& aFetchPriority) { 1293 nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYetAndMediaApplies(aURL, aMedia); 1294 if (!uri) { 1295 return; 1296 } 1297 1298 mDocument->Preloads().PreloadFetch(uri, aCrossOrigin, aReferrerPolicy, 0, 1299 aFetchPriority); 1300 } 1301 1302 void nsHtml5TreeOpExecutor::PreloadOpenPicture() { 1303 mDocument->PreloadPictureOpened(); 1304 } 1305 1306 void nsHtml5TreeOpExecutor::PreloadEndPicture() { 1307 mDocument->PreloadPictureClosed(); 1308 } 1309 1310 void nsHtml5TreeOpExecutor::AddBase(const nsAString& aURL) { 1311 auto encoding = mDocument->GetDocumentCharacterSet(); 1312 nsresult rv = NS_NewURI(getter_AddRefs(mViewSourceBaseURI), aURL, encoding, 1313 GetViewSourceBaseURI()); 1314 if (NS_FAILED(rv)) { 1315 mViewSourceBaseURI = nullptr; 1316 } 1317 } 1318 void nsHtml5TreeOpExecutor::SetSpeculationBase(const nsAString& aURL) { 1319 if (mSpeculationBaseURI) { 1320 // the first one wins 1321 return; 1322 } 1323 1324 auto encoding = mDocument->GetDocumentCharacterSet(); 1325 nsCOMPtr<nsIURI> newBaseURI; 1326 DebugOnly<nsresult> rv = NS_NewURI(getter_AddRefs(newBaseURI), aURL, encoding, 1327 mDocument->GetDocumentURI()); 1328 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to create a URI"); 1329 if (!newBaseURI) { 1330 return; 1331 } 1332 1333 // See 1334 // https://html.spec.whatwg.org/multipage/semantics.html#set-the-frozen-base-url 1335 // data: and javascript: base URLs are not allowed. 1336 if (newBaseURI->SchemeIs("data") || newBaseURI->SchemeIs("javascript")) { 1337 return; 1338 } 1339 1340 // Check the document's CSP usually delivered via the CSP header. 1341 if (nsCOMPtr<nsIContentSecurityPolicy> csp = 1342 PolicyContainer::GetCSP(mDocument->GetPolicyContainer())) { 1343 // base-uri should not fallback to the default-src and preloads should not 1344 // trigger violation reports. 1345 bool cspPermitsBaseURI = true; 1346 nsresult rv = csp->Permits( 1347 nullptr, nullptr, newBaseURI, 1348 nsIContentSecurityPolicy::BASE_URI_DIRECTIVE, true /* aSpecific */, 1349 false /* aSendViolationReports */, &cspPermitsBaseURI); 1350 if (NS_FAILED(rv) || !cspPermitsBaseURI) { 1351 return; 1352 } 1353 } 1354 1355 // Also check the CSP discovered from the <meta> tag during speculative 1356 // parsing. 1357 if (nsCOMPtr<nsIContentSecurityPolicy> csp = mDocument->GetPreloadCsp()) { 1358 bool cspPermitsBaseURI = true; 1359 nsresult rv = csp->Permits( 1360 nullptr, nullptr, newBaseURI, 1361 nsIContentSecurityPolicy::BASE_URI_DIRECTIVE, true /* aSpecific */, 1362 false /* aSendViolationReports */, &cspPermitsBaseURI); 1363 if (NS_FAILED(rv) || !cspPermitsBaseURI) { 1364 return; 1365 } 1366 } 1367 1368 mSpeculationBaseURI = newBaseURI; 1369 mDocument->Preloads().SetSpeculationBase(mSpeculationBaseURI); 1370 } 1371 1372 void nsHtml5TreeOpExecutor::UpdateReferrerInfoFromMeta( 1373 const nsAString& aMetaReferrer) { 1374 mDocument->UpdateReferrerInfoFromMeta(aMetaReferrer, true); 1375 } 1376 1377 void nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP) { 1378 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); 1379 1380 nsresult rv = NS_OK; 1381 nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = mDocument->GetPreloadCsp(); 1382 if (!preloadCsp) { 1383 RefPtr<nsCSPContext> csp = new nsCSPContext(); 1384 csp->SuppressParserLogMessages(); 1385 preloadCsp = csp; 1386 rv = preloadCsp->SetRequestContextWithDocument(mDocument); 1387 NS_ENSURE_SUCCESS_VOID(rv); 1388 } 1389 1390 // Please note that multiple meta CSPs need to be joined together. 1391 rv = preloadCsp->AppendPolicy( 1392 aCSP, 1393 false, // csp via meta tag can not be report only 1394 true); // delivered through the meta tag 1395 NS_ENSURE_SUCCESS_VOID(rv); 1396 1397 nsPIDOMWindowInner* inner = mDocument->GetInnerWindow(); 1398 if (inner) { 1399 inner->SetPreloadCsp(preloadCsp); 1400 } 1401 mDocument->ApplySettingsFromCSP(true); 1402 } 1403 1404 #ifdef DEBUG 1405 uint32_t nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0; 1406 uint32_t nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0; 1407 uint32_t nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0; 1408 uint32_t nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0; 1409 uint32_t nsHtml5TreeOpExecutor::sTimesFlushLoopInterrupted = 0; 1410 #endif