Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set sw=2 ts=2 et tw=79: */
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/nsCSPService.h"
10 : #include "mozilla/dom/ScriptLoader.h"
11 :
12 : #include "nsError.h"
13 : #include "nsHtml5TreeOpExecutor.h"
14 : #include "nsIContentViewer.h"
15 : #include "nsIContentSecurityPolicy.h"
16 : #include "nsIDocShellTreeItem.h"
17 : #include "nsIDocShell.h"
18 : #include "nsIDOMDocument.h"
19 : #include "nsIScriptGlobalObject.h"
20 : #include "nsIWebShellServices.h"
21 : #include "nsContentUtils.h"
22 : #include "mozAutoDocUpdate.h"
23 : #include "nsNetUtil.h"
24 : #include "nsHtml5Parser.h"
25 : #include "nsHtml5Tokenizer.h"
26 : #include "nsHtml5TreeBuilder.h"
27 : #include "nsHtml5StreamParser.h"
28 : #include "mozilla/css/Loader.h"
29 : #include "GeckoProfiler.h"
30 : #include "nsIScriptError.h"
31 : #include "nsIScriptContext.h"
32 : #include "mozilla/Preferences.h"
33 : #include "nsIHTMLDocument.h"
34 : #include "nsIViewSourceChannel.h"
35 : #include "xpcpublic.h"
36 :
37 : using namespace mozilla;
38 :
39 9 : NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor)
40 7 : NS_INTERFACE_TABLE_INHERITED(nsHtml5TreeOpExecutor,
41 : nsIContentSink)
42 7 : NS_INTERFACE_TABLE_TAIL_INHERITING(nsHtml5DocumentBuilder)
43 :
44 66 : NS_IMPL_ADDREF_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
45 :
46 62 : NS_IMPL_RELEASE_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
47 :
48 24 : class nsHtml5ExecutorReflusher : public Runnable
49 : {
50 : private:
51 : RefPtr<nsHtml5TreeOpExecutor> mExecutor;
52 : public:
53 8 : explicit nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
54 8 : : mozilla::Runnable("nsHtml5ExecutorReflusher")
55 8 : , mExecutor(aExecutor)
56 8 : {}
57 8 : NS_IMETHOD Run() override
58 : {
59 8 : mExecutor->RunFlushLoop();
60 8 : return NS_OK;
61 : }
62 : };
63 :
64 : static mozilla::LinkedList<nsHtml5TreeOpExecutor>* gBackgroundFlushList = nullptr;
65 : static nsITimer* gFlushTimer = nullptr;
66 :
67 2 : nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
68 : : nsHtml5DocumentBuilder(false)
69 : , mSuppressEOF(false)
70 : , mReadingFromStage(false)
71 : , mStreamParser(nullptr)
72 : , mPreloadedURLs(23) // Mean # of preloadable resources per page on dmoz
73 : , mSpeculationReferrerPolicy(mozilla::net::RP_Unset)
74 : , mStarted(false)
75 : , mRunFlushLoopOnStack(false)
76 : , mCallContinueInterruptedParsingIfEnabled(false)
77 2 : , mAlreadyComplainedAboutCharset(false)
78 : {
79 2 : }
80 :
81 0 : nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor()
82 : {
83 0 : if (gBackgroundFlushList && isInList()) {
84 0 : mOpQueue.Clear();
85 0 : removeFrom(*gBackgroundFlushList);
86 0 : if (gBackgroundFlushList->isEmpty()) {
87 0 : delete gBackgroundFlushList;
88 0 : gBackgroundFlushList = nullptr;
89 0 : if (gFlushTimer) {
90 0 : gFlushTimer->Cancel();
91 0 : NS_RELEASE(gFlushTimer);
92 : }
93 : }
94 : }
95 0 : NS_ASSERTION(mOpQueue.IsEmpty(), "Somehow there's stuff in the op queue.");
96 0 : }
97 :
98 : // nsIContentSink
99 : NS_IMETHODIMP
100 0 : nsHtml5TreeOpExecutor::WillParse()
101 : {
102 0 : NS_NOTREACHED("No one should call this");
103 0 : return NS_ERROR_NOT_IMPLEMENTED;
104 : }
105 :
106 : NS_IMETHODIMP
107 2 : nsHtml5TreeOpExecutor::WillBuildModel(nsDTDMode aDTDMode)
108 : {
109 2 : mDocument->AddObserver(this);
110 2 : WillBuildModelImpl();
111 2 : GetDocument()->BeginLoad();
112 2 : if (mDocShell && !GetDocument()->GetWindow() &&
113 0 : !IsExternalViewSource()) {
114 : // Not loading as data but script global object not ready
115 0 : return MarkAsBroken(NS_ERROR_DOM_INVALID_STATE_ERR);
116 : }
117 2 : return NS_OK;
118 : }
119 :
120 :
121 : // This is called when the tree construction has ended
122 : NS_IMETHODIMP
123 2 : nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated)
124 : {
125 2 : if (!aTerminated) {
126 : // This is needed to avoid unblocking loads too many times on one hand
127 : // and on the other hand to avoid destroying the frame constructor from
128 : // within an update batch. See bug 537683.
129 2 : EndDocUpdate();
130 :
131 : // If the above caused a call to nsIParser::Terminate(), let that call
132 : // win.
133 2 : if (!mParser) {
134 0 : return NS_OK;
135 : }
136 : }
137 :
138 2 : if (mRunsToCompletion) {
139 0 : return NS_OK;
140 : }
141 :
142 2 : GetParser()->DropStreamParser();
143 :
144 : // This comes from nsXMLContentSink and nsHTMLContentSink
145 : // If this parser has been marked as broken, treat the end of parse as
146 : // forced termination.
147 2 : DidBuildModelImpl(aTerminated || NS_FAILED(IsBroken()));
148 :
149 2 : if (!mLayoutStarted) {
150 : // We never saw the body, and layout never got started. Force
151 : // layout *now*, to get an initial reflow.
152 :
153 : // NOTE: only force the layout if we are NOT destroying the
154 : // docshell. If we are destroying it, then starting layout will
155 : // likely cause us to crash, or at best waste a lot of time as we
156 : // are just going to tear it down anyway.
157 0 : bool destroying = true;
158 0 : if (mDocShell) {
159 0 : mDocShell->IsBeingDestroyed(&destroying);
160 : }
161 :
162 0 : if (!destroying) {
163 0 : nsContentSink::StartLayout(false);
164 : }
165 : }
166 :
167 2 : ScrollToRef();
168 2 : mDocument->RemoveObserver(this);
169 2 : if (!mParser) {
170 : // DidBuildModelImpl may cause mParser to be nulled out
171 : // Return early to avoid unblocking the onload event too many times.
172 0 : return NS_OK;
173 : }
174 :
175 : // We may not have called BeginLoad() if loading is terminated before
176 : // OnStartRequest call.
177 2 : if (mStarted) {
178 2 : mDocument->EndLoad();
179 : }
180 2 : DropParserAndPerfHint();
181 : #ifdef GATHER_DOCWRITE_STATISTICS
182 : printf("UNSAFE SCRIPTS: %d\n", sUnsafeDocWrites);
183 : printf("TOKENIZER-SAFE SCRIPTS: %d\n", sTokenSafeDocWrites);
184 : printf("TREEBUILDER-SAFE SCRIPTS: %d\n", sTreeSafeDocWrites);
185 : #endif
186 : #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
187 : printf("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize);
188 : if (sAppendBatchExaminations != 0) {
189 : printf("AVERAGE SLOTS EXAMINED: %d\n", sAppendBatchSlotsExamined / sAppendBatchExaminations);
190 : }
191 : #endif
192 2 : return NS_OK;
193 : }
194 :
195 : NS_IMETHODIMP
196 0 : nsHtml5TreeOpExecutor::WillInterrupt()
197 : {
198 0 : NS_NOTREACHED("Don't call. For interface compat only.");
199 0 : return NS_ERROR_NOT_IMPLEMENTED;
200 : }
201 :
202 : NS_IMETHODIMP
203 0 : nsHtml5TreeOpExecutor::WillResume()
204 : {
205 0 : NS_NOTREACHED("Don't call. For interface compat only.");
206 0 : return NS_ERROR_NOT_IMPLEMENTED;
207 : }
208 :
209 : NS_IMETHODIMP
210 2 : nsHtml5TreeOpExecutor::SetParser(nsParserBase* aParser)
211 : {
212 2 : mParser = aParser;
213 2 : return NS_OK;
214 : }
215 :
216 : void
217 0 : nsHtml5TreeOpExecutor::FlushPendingNotifications(FlushType aType)
218 : {
219 0 : if (aType >= FlushType::EnsurePresShellInitAndFrames) {
220 : // Bug 577508 / 253951
221 0 : nsContentSink::StartLayout(true);
222 : }
223 0 : }
224 :
225 : nsISupports*
226 5 : nsHtml5TreeOpExecutor::GetTarget()
227 : {
228 5 : return mDocument;
229 : }
230 :
231 : nsresult
232 0 : nsHtml5TreeOpExecutor::MarkAsBroken(nsresult aReason)
233 : {
234 0 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
235 0 : mBroken = aReason;
236 0 : if (mStreamParser) {
237 0 : mStreamParser->Terminate();
238 : }
239 : // We are under memory pressure, but let's hope the following allocation
240 : // works out so that we get to terminate and clean up the parser from
241 : // a safer point.
242 0 : if (mParser && mDocument) { // can mParser ever be null here?
243 : nsCOMPtr<nsIRunnable> terminator =
244 0 : NewRunnableMethod("nsHtml5Parser::Terminate", GetParser(), &nsHtml5Parser::Terminate);
245 0 : if (NS_FAILED(mDocument->Dispatch("nsHtml5Parser::Terminate",
246 : TaskCategory::Network,
247 : terminator.forget()))) {
248 0 : NS_WARNING("failed to dispatch executor flush event");
249 : }
250 : }
251 0 : return aReason;
252 : }
253 :
254 : void
255 0 : FlushTimerCallback(nsITimer* aTimer, void* aClosure)
256 : {
257 0 : RefPtr<nsHtml5TreeOpExecutor> ex = gBackgroundFlushList->popFirst();
258 0 : if (ex) {
259 0 : ex->RunFlushLoop();
260 : }
261 0 : if (gBackgroundFlushList && gBackgroundFlushList->isEmpty()) {
262 0 : delete gBackgroundFlushList;
263 0 : gBackgroundFlushList = nullptr;
264 0 : gFlushTimer->Cancel();
265 0 : NS_RELEASE(gFlushTimer);
266 : }
267 0 : }
268 :
269 : void
270 8 : nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync()
271 : {
272 8 : if (!mDocument || !mDocument->IsInBackgroundWindow()) {
273 16 : nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this);
274 8 : if (NS_FAILED(mDocument->Dispatch("nsHtml5ExecutorReflusher",
275 : TaskCategory::Network,
276 : flusher.forget()))) {
277 0 : NS_WARNING("failed to dispatch executor flush event");
278 : }
279 : } else {
280 0 : if (!gBackgroundFlushList) {
281 0 : gBackgroundFlushList = new mozilla::LinkedList<nsHtml5TreeOpExecutor>();
282 : }
283 0 : if (!isInList()) {
284 0 : gBackgroundFlushList->insertBack(this);
285 : }
286 0 : if (!gFlushTimer) {
287 0 : nsCOMPtr<nsITimer> t = do_CreateInstance("@mozilla.org/timer;1");
288 0 : t.swap(gFlushTimer);
289 : // The timer value 50 should not hopefully slow down background pages too
290 : // much, yet lets event loop to process enough between ticks.
291 : // See bug 734015.
292 0 : gFlushTimer->InitWithNamedFuncCallback(FlushTimerCallback, nullptr,
293 : 50, nsITimer::TYPE_REPEATING_SLACK,
294 0 : "FlushTimerCallback");
295 : }
296 : }
297 8 : }
298 :
299 : void
300 7 : nsHtml5TreeOpExecutor::FlushSpeculativeLoads()
301 : {
302 14 : nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
303 7 : mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue);
304 7 : const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
305 7 : const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
306 10 : for (nsHtml5SpeculativeLoad* iter = const_cast<nsHtml5SpeculativeLoad*>(start);
307 10 : iter < end;
308 : ++iter) {
309 3 : if (MOZ_UNLIKELY(!mParser)) {
310 : // An extension terminated the parser from a HTTP observer.
311 0 : return;
312 : }
313 3 : iter->Perform(this);
314 : }
315 : }
316 :
317 : class nsHtml5FlushLoopGuard
318 : {
319 : private:
320 : RefPtr<nsHtml5TreeOpExecutor> mExecutor;
321 : #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
322 : uint32_t mStartTime;
323 : #endif
324 : public:
325 16 : explicit nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor)
326 16 : : mExecutor(aExecutor)
327 : #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
328 : , mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow()))
329 : #endif
330 : {
331 16 : mExecutor->mRunFlushLoopOnStack = true;
332 16 : }
333 16 : ~nsHtml5FlushLoopGuard()
334 16 : {
335 : #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
336 : uint32_t timeOffTheEventLoop =
337 : PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime;
338 : if (timeOffTheEventLoop >
339 : nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) {
340 : nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop =
341 : timeOffTheEventLoop;
342 : }
343 : printf("Longest time off the event loop: %d\n",
344 : nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop);
345 : #endif
346 :
347 16 : mExecutor->mRunFlushLoopOnStack = false;
348 16 : }
349 : };
350 :
351 : /**
352 : * The purpose of the loop here is to avoid returning to the main event loop
353 : */
354 : void
355 16 : nsHtml5TreeOpExecutor::RunFlushLoop()
356 : {
357 32 : AUTO_PROFILER_LABEL("nsHtml5TreeOpExecutor::RunFlushLoop", OTHER);
358 :
359 16 : if (mRunFlushLoopOnStack) {
360 : // There's already a RunFlushLoop() on the call stack.
361 0 : return;
362 : }
363 :
364 32 : nsHtml5FlushLoopGuard guard(this); // this is also the self-kungfu!
365 :
366 32 : RefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
367 :
368 : // Remember the entry time
369 16 : (void) nsContentSink::WillParseImpl();
370 :
371 : for (;;) {
372 21 : if (!mParser) {
373 : // Parse has terminated.
374 4 : mOpQueue.Clear(); // clear in order to be able to assert in destructor
375 4 : return;
376 : }
377 :
378 17 : if (NS_FAILED(IsBroken())) {
379 0 : return;
380 : }
381 :
382 17 : if (!parserKungFuDeathGrip->IsParserEnabled()) {
383 : // The parser is blocked.
384 10 : return;
385 : }
386 :
387 7 : if (mFlushState != eNotFlushing) {
388 : // XXX Can this happen? In case it can, let's avoid crashing.
389 0 : return;
390 : }
391 :
392 : // If there are scripts executing, then the content sink is jumping the gun
393 : // (probably due to a synchronous XMLHttpRequest) and will re-enable us
394 : // later, see bug 460706.
395 7 : if (IsScriptExecuting()) {
396 0 : return;
397 : }
398 :
399 7 : if (mReadingFromStage) {
400 4 : nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
401 2 : mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue, speculativeLoadQueue);
402 : // Make sure speculative loads never start after the corresponding
403 : // normal loads for the same URLs.
404 2 : const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
405 2 : const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
406 9 : for (nsHtml5SpeculativeLoad* iter = (nsHtml5SpeculativeLoad*)start;
407 9 : iter < end;
408 : ++iter) {
409 7 : iter->Perform(this);
410 7 : if (MOZ_UNLIKELY(!mParser)) {
411 : // An extension terminated the parser from a HTTP observer.
412 0 : mOpQueue.Clear(); // clear in order to be able to assert in destructor
413 0 : return;
414 : }
415 : }
416 : } else {
417 5 : FlushSpeculativeLoads(); // Make sure speculative loads never start after
418 : // the corresponding normal loads for the same
419 : // URLs.
420 5 : if (MOZ_UNLIKELY(!mParser)) {
421 : // An extension terminated the parser from a HTTP observer.
422 0 : mOpQueue.Clear(); // clear in order to be able to assert in destructor
423 0 : return;
424 : }
425 : // Not sure if this grip is still needed, but previously, the code
426 : // gripped before calling ParseUntilBlocked();
427 : RefPtr<nsHtml5StreamParser> streamKungFuDeathGrip =
428 10 : GetParser()->GetStreamParser();
429 : mozilla::Unused << streamKungFuDeathGrip; // Not used within function
430 : // Now parse content left in the document.write() buffer queue if any.
431 : // This may generate tree ops on its own or dequeue a speculation.
432 5 : nsresult rv = GetParser()->ParseUntilBlocked();
433 5 : if (NS_FAILED(rv)) {
434 0 : MarkAsBroken(rv);
435 0 : return;
436 : }
437 : }
438 :
439 7 : if (mOpQueue.IsEmpty()) {
440 : // Avoid bothering the rest of the engine with a doc update if there's
441 : // nothing to do.
442 0 : return;
443 : }
444 :
445 7 : mFlushState = eInFlush;
446 :
447 7 : nsIContent* scriptElement = nullptr;
448 7 : bool interrupted = false;
449 :
450 7 : BeginDocUpdate();
451 :
452 7 : uint32_t numberOfOpsToFlush = mOpQueue.Length();
453 :
454 7 : const nsHtml5TreeOperation* first = mOpQueue.Elements();
455 7 : const nsHtml5TreeOperation* last = first + numberOfOpsToFlush - 1;
456 7 : for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(first);;) {
457 67 : if (MOZ_UNLIKELY(!mParser)) {
458 : // The previous tree op caused a call to nsIParser::Terminate().
459 0 : break;
460 : }
461 67 : NS_ASSERTION(mFlushState == eInDocUpdate,
462 : "Tried to perform tree op outside update batch.");
463 67 : nsresult rv = iter->Perform(this, &scriptElement, &interrupted);
464 67 : if (NS_FAILED(rv)) {
465 0 : MarkAsBroken(rv);
466 0 : break;
467 : }
468 :
469 : // Be sure not to check the deadline if the last op was just performed.
470 67 : if (MOZ_UNLIKELY(iter == last)) {
471 7 : break;
472 120 : } else if (MOZ_UNLIKELY(interrupted) ||
473 60 : MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() ==
474 : NS_ERROR_HTMLPARSER_INTERRUPTED)) {
475 0 : mOpQueue.RemoveElementsAt(0, (iter - first) + 1);
476 :
477 0 : EndDocUpdate();
478 :
479 0 : mFlushState = eNotFlushing;
480 :
481 : #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
482 : printf("REFLUSH SCHEDULED (executing ops): %d\n",
483 : ++sTimesFlushLoopInterrupted);
484 : #endif
485 0 : nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
486 0 : return;
487 : }
488 60 : ++iter;
489 60 : }
490 :
491 7 : mOpQueue.Clear();
492 :
493 7 : EndDocUpdate();
494 :
495 7 : mFlushState = eNotFlushing;
496 :
497 7 : if (MOZ_UNLIKELY(!mParser)) {
498 : // The parse ended already.
499 2 : return;
500 : }
501 :
502 5 : if (scriptElement) {
503 : // must be tail call when mFlushState is eNotFlushing
504 5 : RunScript(scriptElement);
505 :
506 : // Always check the clock in nsContentSink right after a script
507 5 : StopDeflecting();
508 5 : if (nsContentSink::DidProcessATokenImpl() ==
509 : NS_ERROR_HTMLPARSER_INTERRUPTED) {
510 : #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
511 : printf("REFLUSH SCHEDULED (after script): %d\n",
512 : ++sTimesFlushLoopInterrupted);
513 : #endif
514 0 : nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
515 0 : return;
516 : }
517 : }
518 5 : }
519 : }
520 :
521 : nsresult
522 0 : nsHtml5TreeOpExecutor::FlushDocumentWrite()
523 : {
524 0 : nsresult rv = IsBroken();
525 0 : NS_ENSURE_SUCCESS(rv, rv);
526 :
527 0 : FlushSpeculativeLoads(); // Make sure speculative loads never start after the
528 : // corresponding normal loads for the same URLs.
529 :
530 0 : if (MOZ_UNLIKELY(!mParser)) {
531 : // The parse has ended.
532 0 : mOpQueue.Clear(); // clear in order to be able to assert in destructor
533 0 : return rv;
534 : }
535 :
536 0 : if (mFlushState != eNotFlushing) {
537 : // XXX Can this happen? In case it can, let's avoid crashing.
538 0 : return rv;
539 : }
540 :
541 0 : mFlushState = eInFlush;
542 :
543 : // avoid crashing near EOF
544 0 : RefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this);
545 0 : RefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
546 : mozilla::Unused << parserKungFuDeathGrip; // Intentionally not used within function
547 :
548 0 : NS_ASSERTION(!mReadingFromStage,
549 : "Got doc write flush when reading from stage");
550 :
551 : #ifdef DEBUG
552 0 : mStage.AssertEmpty();
553 : #endif
554 :
555 0 : nsIContent* scriptElement = nullptr;
556 0 : bool interrupted = false;
557 :
558 0 : BeginDocUpdate();
559 :
560 0 : uint32_t numberOfOpsToFlush = mOpQueue.Length();
561 :
562 0 : const nsHtml5TreeOperation* start = mOpQueue.Elements();
563 0 : const nsHtml5TreeOperation* end = start + numberOfOpsToFlush;
564 0 : for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(start);
565 0 : iter < end;
566 : ++iter) {
567 0 : if (MOZ_UNLIKELY(!mParser)) {
568 : // The previous tree op caused a call to nsIParser::Terminate().
569 0 : break;
570 : }
571 0 : NS_ASSERTION(mFlushState == eInDocUpdate,
572 : "Tried to perform tree op outside update batch.");
573 0 : rv = iter->Perform(this, &scriptElement, &interrupted);
574 0 : if (NS_FAILED(rv)) {
575 0 : MarkAsBroken(rv);
576 0 : break;
577 : }
578 : }
579 :
580 0 : mOpQueue.Clear();
581 :
582 0 : EndDocUpdate();
583 :
584 0 : mFlushState = eNotFlushing;
585 :
586 0 : if (MOZ_UNLIKELY(!mParser)) {
587 : // Ending the doc update caused a call to nsIParser::Terminate().
588 0 : return rv;
589 : }
590 :
591 0 : if (scriptElement) {
592 : // must be tail call when mFlushState is eNotFlushing
593 0 : RunScript(scriptElement);
594 : }
595 0 : return rv;
596 : }
597 :
598 : // copied from HTML content sink
599 : bool
600 2 : nsHtml5TreeOpExecutor::IsScriptEnabled()
601 : {
602 : // Note that if we have no document or no docshell or no global or whatnot we
603 : // want to claim script _is_ enabled, so we don't parse the contents of
604 : // <noscript> tags!
605 2 : if (!mDocument || !mDocShell)
606 0 : return true;
607 4 : nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(mDocument->GetInnerWindow());
608 : // Getting context is tricky if the document hasn't had its
609 : // GlobalObject set yet
610 2 : if (!globalObject) {
611 0 : globalObject = mDocShell->GetScriptGlobalObject();
612 : }
613 2 : NS_ENSURE_TRUE(globalObject && globalObject->GetGlobalJSObject(), true);
614 2 : return xpc::Scriptability::Get(globalObject->GetGlobalJSObject()).Allowed();
615 : }
616 :
617 : void
618 2 : nsHtml5TreeOpExecutor::StartLayout(bool* aInterrupted) {
619 2 : if (mLayoutStarted || !mDocument) {
620 0 : return;
621 : }
622 :
623 2 : EndDocUpdate();
624 :
625 2 : if (MOZ_UNLIKELY(!mParser)) {
626 : // got terminate
627 0 : return;
628 : }
629 :
630 2 : nsContentSink::StartLayout(false);
631 :
632 2 : if (mParser) {
633 2 : *aInterrupted = !GetParser()->IsParserEnabled();
634 :
635 2 : BeginDocUpdate();
636 : }
637 : }
638 :
639 : void
640 2 : nsHtml5TreeOpExecutor::PauseDocUpdate(bool* aInterrupted) {
641 : // Pausing the document update allows JS to run, and potentially block
642 : // further parsing.
643 2 : EndDocUpdate();
644 :
645 2 : if (MOZ_LIKELY(mParser)) {
646 2 : *aInterrupted = !GetParser()->IsParserEnabled();
647 :
648 2 : BeginDocUpdate();
649 : }
650 2 : }
651 :
652 : /**
653 : * The reason why this code is here and not in the tree builder even in the
654 : * main-thread case is to allow the control to return from the tokenizer
655 : * before scripts run. This way, the tokenizer is not invoked re-entrantly
656 : * although the parser is.
657 : *
658 : * The reason why this is called as a tail call when mFlushState is set to
659 : * eNotFlushing is to allow re-entry to Flush() but only after the current
660 : * Flush() has cleared the op queue and is otherwise done cleaning up after
661 : * itself.
662 : */
663 : void
664 5 : nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement)
665 : {
666 5 : if (mRunsToCompletion) {
667 : // We are in createContextualFragment() or in the upcoming document.parse().
668 : // Do nothing. Let's not even mark scripts malformed here, because that
669 : // could cause serialization weirdness later.
670 0 : return;
671 : }
672 :
673 5 : NS_ASSERTION(aScriptElement, "No script to run");
674 10 : nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aScriptElement);
675 5 : if (!sele) {
676 0 : MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled, "Node didn't QI to script, but SVG wasn't disabled.");
677 0 : return;
678 : }
679 :
680 5 : if (!mParser) {
681 0 : NS_ASSERTION(sele->IsMalformed(), "Script wasn't marked as malformed.");
682 : // We got here not because of an end tag but because the tree builder
683 : // popped an incomplete script element on EOF. Returning here to avoid
684 : // calling back into mParser anymore.
685 0 : return;
686 : }
687 :
688 5 : if (sele->GetScriptDeferred() || sele->GetScriptAsync()) {
689 0 : DebugOnly<bool> block = sele->AttemptToExecute();
690 0 : NS_ASSERTION(!block, "Defer or async script tried to block.");
691 0 : return;
692 : }
693 :
694 5 : NS_ASSERTION(mFlushState == eNotFlushing, "Tried to run script when flushing.");
695 :
696 5 : mReadingFromStage = false;
697 :
698 5 : sele->SetCreatorParser(GetParser());
699 :
700 : // Copied from nsXMLContentSink
701 : // Now tell the script that it's ready to go. This may execute the script
702 : // or return true, or neither if the script doesn't need executing.
703 5 : bool block = sele->AttemptToExecute();
704 :
705 : // If the act of insertion evaluated the script, we're fine.
706 : // Else, block the parser till the script has loaded.
707 5 : if (block) {
708 3 : if (mParser) {
709 3 : GetParser()->BlockParser();
710 : }
711 : } else {
712 : // mParser may have been nulled out by now, but the flusher deals
713 :
714 : // If this event isn't needed, it doesn't do anything. It is sometimes
715 : // necessary for the parse to continue after complex situations.
716 2 : nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
717 : }
718 : }
719 :
720 : void
721 2 : nsHtml5TreeOpExecutor::Start()
722 : {
723 2 : NS_PRECONDITION(!mStarted, "Tried to start when already started.");
724 2 : mStarted = true;
725 2 : }
726 :
727 : void
728 0 : nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(NotNull<const Encoding*> aEncoding,
729 : int32_t aSource,
730 : uint32_t aLineNumber)
731 : {
732 0 : EndDocUpdate();
733 :
734 0 : if (MOZ_UNLIKELY(!mParser)) {
735 : // got terminate
736 0 : return;
737 : }
738 :
739 0 : nsCOMPtr<nsIWebShellServices> wss = do_QueryInterface(mDocShell);
740 0 : if (!wss) {
741 0 : return;
742 : }
743 :
744 : // ask the webshellservice to load the URL
745 0 : if (NS_SUCCEEDED(wss->StopDocumentLoad())) {
746 0 : nsAutoCString charset;
747 0 : aEncoding->Name(charset);
748 0 : wss->ReloadDocument(charset.get(), aSource);
749 : }
750 : // if the charset switch was accepted, wss has called Terminate() on the
751 : // parser by now
752 :
753 0 : if (!mParser) {
754 : // success
755 0 : if (aSource == kCharsetFromMetaTag) {
756 0 : MaybeComplainAboutCharset("EncLateMetaReload", false, aLineNumber);
757 : }
758 0 : return;
759 : }
760 :
761 0 : if (aSource == kCharsetFromMetaTag) {
762 0 : MaybeComplainAboutCharset("EncLateMetaTooLate", true, aLineNumber);
763 : }
764 :
765 0 : GetParser()->ContinueAfterFailedCharsetSwitch();
766 :
767 0 : BeginDocUpdate();
768 : }
769 :
770 : void
771 0 : nsHtml5TreeOpExecutor::MaybeComplainAboutCharset(const char* aMsgId,
772 : bool aError,
773 : uint32_t aLineNumber)
774 : {
775 0 : if (mAlreadyComplainedAboutCharset) {
776 0 : return;
777 : }
778 : // The EncNoDeclaration case for advertising iframes is so common that it
779 : // would result is way too many errors. The iframe case doesn't matter
780 : // when the ad is an image or a Flash animation anyway. When the ad is
781 : // textual, a misrendered ad probably isn't a huge loss for users.
782 : // Let's suppress the message in this case.
783 : // This means that errors about other different-origin iframes in mashups
784 : // are lost as well, but generally, the site author isn't in control of
785 : // the embedded different-origin pages anyway and can't fix problems even
786 : // if alerted about them.
787 0 : if (!strcmp(aMsgId, "EncNoDeclaration") && mDocShell) {
788 0 : nsCOMPtr<nsIDocShellTreeItem> parent;
789 0 : mDocShell->GetSameTypeParent(getter_AddRefs(parent));
790 0 : if (parent) {
791 0 : return;
792 : }
793 : }
794 0 : mAlreadyComplainedAboutCharset = true;
795 0 : nsContentUtils::ReportToConsole(aError ? nsIScriptError::errorFlag
796 : : nsIScriptError::warningFlag,
797 0 : NS_LITERAL_CSTRING("HTML parser"),
798 : mDocument,
799 : nsContentUtils::eHTMLPARSER_PROPERTIES,
800 : aMsgId,
801 : nullptr,
802 : 0,
803 : nullptr,
804 : EmptyString(),
805 0 : aLineNumber);
806 : }
807 :
808 : void
809 0 : nsHtml5TreeOpExecutor::ComplainAboutBogusProtocolCharset(nsIDocument* aDoc)
810 : {
811 0 : NS_ASSERTION(!mAlreadyComplainedAboutCharset,
812 : "How come we already managed to complain?");
813 0 : mAlreadyComplainedAboutCharset = true;
814 0 : nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
815 0 : NS_LITERAL_CSTRING("HTML parser"),
816 : aDoc,
817 : nsContentUtils::eHTMLPARSER_PROPERTIES,
818 0 : "EncProtocolUnsupported");
819 0 : }
820 :
821 : nsHtml5Parser*
822 29 : nsHtml5TreeOpExecutor::GetParser()
823 : {
824 29 : MOZ_ASSERT(!mRunsToCompletion);
825 29 : return static_cast<nsHtml5Parser*>(mParser.get());
826 : }
827 :
828 : void
829 5 : nsHtml5TreeOpExecutor::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue)
830 : {
831 5 : NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution.");
832 5 : mOpQueue.AppendElements(Move(aOpQueue));
833 5 : }
834 :
835 : void
836 5 : nsHtml5TreeOpExecutor::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, int32_t aLine)
837 : {
838 5 : GetParser()->InitializeDocWriteParserState(aState, aLine);
839 5 : }
840 :
841 : nsIURI*
842 0 : nsHtml5TreeOpExecutor::GetViewSourceBaseURI()
843 : {
844 0 : if (!mViewSourceBaseURI) {
845 :
846 : // We query the channel for the baseURI because in certain situations it
847 : // cannot otherwise be determined. If this process fails, fall back to the
848 : // standard method.
849 : nsCOMPtr<nsIViewSourceChannel> vsc =
850 0 : do_QueryInterface(mDocument->GetChannel());
851 0 : if (vsc) {
852 0 : nsresult rv = vsc->GetBaseURI(getter_AddRefs(mViewSourceBaseURI));
853 0 : if (NS_SUCCEEDED(rv) && mViewSourceBaseURI) {
854 0 : return mViewSourceBaseURI;
855 : }
856 : }
857 :
858 0 : nsCOMPtr<nsIURI> orig = mDocument->GetOriginalURI();
859 : bool isViewSource;
860 0 : orig->SchemeIs("view-source", &isViewSource);
861 0 : if (isViewSource) {
862 0 : nsCOMPtr<nsINestedURI> nested = do_QueryInterface(orig);
863 0 : NS_ASSERTION(nested, "URI with scheme view-source didn't QI to nested!");
864 0 : nested->GetInnerURI(getter_AddRefs(mViewSourceBaseURI));
865 : } else {
866 : // Fail gracefully if the base URL isn't a view-source: URL.
867 : // Not sure if this can ever happen.
868 0 : mViewSourceBaseURI = orig;
869 : }
870 : }
871 0 : return mViewSourceBaseURI;
872 : }
873 :
874 : //static
875 : void
876 3 : nsHtml5TreeOpExecutor::InitializeStatics()
877 : {
878 : mozilla::Preferences::AddBoolVarCache(&sExternalViewSource,
879 3 : "view_source.editor.external");
880 3 : }
881 :
882 : bool
883 0 : nsHtml5TreeOpExecutor::IsExternalViewSource()
884 : {
885 0 : if (!sExternalViewSource) {
886 0 : return false;
887 : }
888 0 : bool isViewSource = false;
889 0 : if (mDocumentURI) {
890 0 : mDocumentURI->SchemeIs("view-source", &isViewSource);
891 : }
892 0 : return isViewSource;
893 : }
894 :
895 : // Speculative loading
896 :
897 : nsIURI*
898 4 : nsHtml5TreeOpExecutor::BaseURIForPreload()
899 : {
900 : // The URL of the document without <base>
901 4 : nsIURI* documentURI = mDocument->GetDocumentURI();
902 : // The URL of the document with non-speculative <base>
903 4 : nsIURI* documentBaseURI = mDocument->GetDocBaseURI();
904 :
905 : // If the two above are different, use documentBaseURI. If they are the same,
906 : // the document object isn't aware of a <base>, so attempt to use the
907 : // mSpeculationBaseURI or, failing, that, documentURI.
908 8 : return (documentURI == documentBaseURI) ?
909 4 : (mSpeculationBaseURI ?
910 4 : mSpeculationBaseURI.get() : documentURI)
911 4 : : documentBaseURI;
912 : }
913 :
914 : already_AddRefed<nsIURI>
915 4 : nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet(const nsAString& aURL)
916 : {
917 4 : if (aURL.IsEmpty()) {
918 0 : return nullptr;
919 : }
920 :
921 4 : nsIURI* base = BaseURIForPreload();
922 4 : auto encoding = mDocument->GetDocumentCharacterSet();
923 8 : nsCOMPtr<nsIURI> uri;
924 4 : nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, encoding, base);
925 4 : if (NS_FAILED(rv)) {
926 0 : NS_WARNING("Failed to create a URI");
927 0 : return nullptr;
928 : }
929 :
930 4 : if (ShouldPreloadURI(uri)) {
931 4 : return uri.forget();
932 : }
933 :
934 0 : return nullptr;
935 : }
936 :
937 : bool
938 4 : nsHtml5TreeOpExecutor::ShouldPreloadURI(nsIURI *aURI)
939 : {
940 8 : nsAutoCString spec;
941 4 : nsresult rv = aURI->GetSpec(spec);
942 4 : NS_ENSURE_SUCCESS(rv, false);
943 4 : return mPreloadedURLs.EnsureInserted(spec);
944 : }
945 :
946 : void
947 4 : nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL,
948 : const nsAString& aCharset,
949 : const nsAString& aType,
950 : const nsAString& aCrossOrigin,
951 : const nsAString& aIntegrity,
952 : bool aScriptFromHead)
953 : {
954 8 : nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
955 4 : if (!uri) {
956 0 : return;
957 : }
958 4 : mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType, aCrossOrigin,
959 : aIntegrity, aScriptFromHead,
960 4 : mSpeculationReferrerPolicy);
961 : }
962 :
963 : void
964 0 : nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL,
965 : const nsAString& aCharset,
966 : const nsAString& aCrossOrigin,
967 : const nsAString& aIntegrity)
968 : {
969 0 : nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
970 0 : if (!uri) {
971 0 : return;
972 : }
973 0 : mDocument->PreloadStyle(uri, aCharset, aCrossOrigin,
974 0 : mSpeculationReferrerPolicy, aIntegrity);
975 : }
976 :
977 : void
978 0 : nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL,
979 : const nsAString& aCrossOrigin,
980 : const nsAString& aSrcset,
981 : const nsAString& aSizes,
982 : const nsAString& aImageReferrerPolicy)
983 : {
984 0 : nsCOMPtr<nsIURI> baseURI = BaseURIForPreload();
985 0 : nsCOMPtr<nsIURI> uri = mDocument->ResolvePreloadImage(baseURI, aURL, aSrcset,
986 0 : aSizes);
987 0 : if (uri && ShouldPreloadURI(uri)) {
988 : // use document wide referrer policy
989 0 : mozilla::net::ReferrerPolicy referrerPolicy = mSpeculationReferrerPolicy;
990 : mozilla::net::ReferrerPolicy imageReferrerPolicy =
991 0 : mozilla::net::AttributeReferrerPolicyFromString(aImageReferrerPolicy);
992 0 : if (imageReferrerPolicy != mozilla::net::RP_Unset) {
993 0 : referrerPolicy = imageReferrerPolicy;
994 : }
995 :
996 0 : mDocument->MaybePreLoadImage(uri, aCrossOrigin, referrerPolicy);
997 : }
998 0 : }
999 :
1000 : // These calls inform the document of picture state and seen sources, such that
1001 : // it can use them to inform ResolvePreLoadImage as necessary
1002 : void
1003 0 : nsHtml5TreeOpExecutor::PreloadPictureSource(const nsAString& aSrcset,
1004 : const nsAString& aSizes,
1005 : const nsAString& aType,
1006 : const nsAString& aMedia)
1007 : {
1008 0 : mDocument->PreloadPictureImageSource(aSrcset, aSizes, aType, aMedia);
1009 0 : }
1010 :
1011 : void
1012 0 : nsHtml5TreeOpExecutor::PreloadOpenPicture()
1013 : {
1014 0 : mDocument->PreloadPictureOpened();
1015 0 : }
1016 :
1017 : void
1018 0 : nsHtml5TreeOpExecutor::PreloadEndPicture()
1019 : {
1020 0 : mDocument->PreloadPictureClosed();
1021 0 : }
1022 :
1023 : void
1024 0 : nsHtml5TreeOpExecutor::AddBase(const nsAString& aURL)
1025 : {
1026 0 : auto encoding = mDocument->GetDocumentCharacterSet();
1027 0 : nsresult rv = NS_NewURI(getter_AddRefs(mViewSourceBaseURI), aURL,
1028 0 : encoding, GetViewSourceBaseURI());
1029 0 : if (NS_FAILED(rv)) {
1030 0 : mViewSourceBaseURI = nullptr;
1031 : }
1032 0 : }
1033 : void
1034 0 : nsHtml5TreeOpExecutor::SetSpeculationBase(const nsAString& aURL)
1035 : {
1036 0 : if (mSpeculationBaseURI) {
1037 : // the first one wins
1038 0 : return;
1039 : }
1040 0 : auto encoding = mDocument->GetDocumentCharacterSet();
1041 0 : DebugOnly<nsresult> rv = NS_NewURI(getter_AddRefs(mSpeculationBaseURI), aURL,
1042 0 : encoding, mDocument->GetDocumentURI());
1043 0 : NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to create a URI");
1044 : }
1045 :
1046 : void
1047 0 : nsHtml5TreeOpExecutor::SetSpeculationReferrerPolicy(const nsAString& aReferrerPolicy)
1048 : {
1049 : // Specs says:
1050 : // - Let value be the result of stripping leading and trailing whitespace from
1051 : // the value of element's content attribute.
1052 : // - If value is not the empty string, then:
1053 0 : if (aReferrerPolicy.IsEmpty()) {
1054 0 : return;
1055 : }
1056 :
1057 0 : ReferrerPolicy policy = mozilla::net::ReferrerPolicyFromString(aReferrerPolicy);
1058 : // Specs says:
1059 : // - If policy is not the empty string, then set element's node document's
1060 : // referrer policy to policy
1061 0 : if (policy != mozilla::net::RP_Unset) {
1062 0 : SetSpeculationReferrerPolicy(policy);
1063 : }
1064 : }
1065 :
1066 : void
1067 0 : nsHtml5TreeOpExecutor::AddSpeculationCSP(const nsAString& aCSP)
1068 : {
1069 0 : if (!CSPService::sCSPEnabled) {
1070 0 : return;
1071 : }
1072 :
1073 0 : NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
1074 :
1075 0 : nsIPrincipal* principal = mDocument->NodePrincipal();
1076 0 : nsCOMPtr<nsIContentSecurityPolicy> preloadCsp;
1077 0 : nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(mDocument);
1078 0 : nsresult rv = principal->EnsurePreloadCSP(domDoc, getter_AddRefs(preloadCsp));
1079 0 : NS_ENSURE_SUCCESS_VOID(rv);
1080 :
1081 : // please note that meta CSPs and CSPs delivered through a header need
1082 : // to be joined together.
1083 0 : rv = preloadCsp->AppendPolicy(aCSP,
1084 : false, // csp via meta tag can not be report only
1085 0 : true); // delivered through the meta tag
1086 0 : NS_ENSURE_SUCCESS_VOID(rv);
1087 :
1088 : // Record "speculated" referrer policy for preloads
1089 0 : bool hasReferrerPolicy = false;
1090 0 : uint32_t referrerPolicy = mozilla::net::RP_Unset;
1091 0 : rv = preloadCsp->GetReferrerPolicy(&referrerPolicy, &hasReferrerPolicy);
1092 0 : NS_ENSURE_SUCCESS_VOID(rv);
1093 0 : if (hasReferrerPolicy) {
1094 0 : SetSpeculationReferrerPolicy(static_cast<ReferrerPolicy>(referrerPolicy));
1095 : }
1096 :
1097 0 : mDocument->ApplySettingsFromCSP(true);
1098 : }
1099 :
1100 : void
1101 0 : nsHtml5TreeOpExecutor::SetSpeculationReferrerPolicy(ReferrerPolicy aReferrerPolicy)
1102 : {
1103 : // Record "speculated" referrer policy locally and thread through the
1104 : // speculation phase. The actual referrer policy will be set by
1105 : // HTMLMetaElement::BindToTree().
1106 0 : mSpeculationReferrerPolicy = aReferrerPolicy;
1107 0 : }
1108 :
1109 : #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
1110 : uint32_t nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0;
1111 : uint32_t nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0;
1112 : uint32_t nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0;
1113 : uint32_t nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0;
1114 : uint32_t nsHtml5TreeOpExecutor::sTimesFlushLoopInterrupted = 0;
1115 : #endif
1116 : bool nsHtml5TreeOpExecutor::sExternalViewSource = false;
|