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