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