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