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