Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing 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/. */
7 #include "mozilla/DebugOnly.h"
8 #include "mozilla/Likely.h"
10 #include "nsError.h"
11 #include "nsHtml5TreeOpExecutor.h"
12 #include "nsScriptLoader.h"
13 #include "nsIMarkupDocumentViewer.h"
14 #include "nsIContentViewer.h"
15 #include "nsIDocShellTreeItem.h"
16 #include "nsIDocShell.h"
17 #include "nsIScriptGlobalObject.h"
18 #include "nsIScriptSecurityManager.h"
19 #include "nsIWebShellServices.h"
20 #include "nsContentUtils.h"
21 #include "mozAutoDocUpdate.h"
22 #include "nsNetUtil.h"
23 #include "nsHtml5Parser.h"
24 #include "nsHtml5Tokenizer.h"
25 #include "nsHtml5TreeBuilder.h"
26 #include "nsHtml5StreamParser.h"
27 #include "mozilla/css/Loader.h"
28 #include "GeckoProfiler.h"
29 #include "nsIScriptError.h"
30 #include "nsIScriptContext.h"
31 #include "mozilla/Preferences.h"
32 #include "nsIHTMLDocument.h"
34 using namespace mozilla;
36 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor)
37 NS_INTERFACE_TABLE_INHERITED(nsHtml5TreeOpExecutor,
38 nsIContentSink)
39 NS_INTERFACE_TABLE_TAIL_INHERITING(nsHtml5DocumentBuilder)
41 NS_IMPL_ADDREF_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
43 NS_IMPL_RELEASE_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
45 class nsHtml5ExecutorReflusher : public nsRunnable
46 {
47 private:
48 nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
49 public:
50 nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
51 : mExecutor(aExecutor)
52 {}
53 NS_IMETHODIMP Run()
54 {
55 mExecutor->RunFlushLoop();
56 return NS_OK;
57 }
58 };
60 static mozilla::LinkedList<nsHtml5TreeOpExecutor>* gBackgroundFlushList = nullptr;
61 static nsITimer* gFlushTimer = nullptr;
63 nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
64 : nsHtml5DocumentBuilder(false)
65 , mPreloadedURLs(23) // Mean # of preloadable resources per page on dmoz
66 {
67 // zeroing operator new for everything else
68 }
70 nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor()
71 {
72 if (gBackgroundFlushList && isInList()) {
73 mOpQueue.Clear();
74 removeFrom(*gBackgroundFlushList);
75 if (gBackgroundFlushList->isEmpty()) {
76 delete gBackgroundFlushList;
77 gBackgroundFlushList = nullptr;
78 if (gFlushTimer) {
79 gFlushTimer->Cancel();
80 NS_RELEASE(gFlushTimer);
81 }
82 }
83 }
84 NS_ASSERTION(mOpQueue.IsEmpty(), "Somehow there's stuff in the op queue.");
85 }
87 // nsIContentSink
88 NS_IMETHODIMP
89 nsHtml5TreeOpExecutor::WillParse()
90 {
91 NS_NOTREACHED("No one should call this");
92 return NS_ERROR_NOT_IMPLEMENTED;
93 }
95 NS_IMETHODIMP
96 nsHtml5TreeOpExecutor::WillBuildModel(nsDTDMode aDTDMode)
97 {
98 mDocument->AddObserver(this);
99 WillBuildModelImpl();
100 GetDocument()->BeginLoad();
101 if (mDocShell && !GetDocument()->GetWindow() &&
102 !IsExternalViewSource()) {
103 // Not loading as data but script global object not ready
104 return MarkAsBroken(NS_ERROR_DOM_INVALID_STATE_ERR);
105 }
106 return NS_OK;
107 }
110 // This is called when the tree construction has ended
111 NS_IMETHODIMP
112 nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated)
113 {
114 if (!aTerminated) {
115 // This is needed to avoid unblocking loads too many times on one hand
116 // and on the other hand to avoid destroying the frame constructor from
117 // within an update batch. See bug 537683.
118 EndDocUpdate();
120 // If the above caused a call to nsIParser::Terminate(), let that call
121 // win.
122 if (!mParser) {
123 return NS_OK;
124 }
125 }
127 if (mRunsToCompletion) {
128 return NS_OK;
129 }
131 GetParser()->DropStreamParser();
133 // This comes from nsXMLContentSink and nsHTMLContentSink
134 // If this parser has been marked as broken, treat the end of parse as
135 // forced termination.
136 DidBuildModelImpl(aTerminated || NS_FAILED(IsBroken()));
138 if (!mLayoutStarted) {
139 // We never saw the body, and layout never got started. Force
140 // layout *now*, to get an initial reflow.
142 // NOTE: only force the layout if we are NOT destroying the
143 // docshell. If we are destroying it, then starting layout will
144 // likely cause us to crash, or at best waste a lot of time as we
145 // are just going to tear it down anyway.
146 bool destroying = true;
147 if (mDocShell) {
148 mDocShell->IsBeingDestroyed(&destroying);
149 }
151 if (!destroying) {
152 nsContentSink::StartLayout(false);
153 }
154 }
156 ScrollToRef();
157 mDocument->RemoveObserver(this);
158 if (!mParser) {
159 // DidBuildModelImpl may cause mParser to be nulled out
160 // Return early to avoid unblocking the onload event too many times.
161 return NS_OK;
162 }
164 // We may not have called BeginLoad() if loading is terminated before
165 // OnStartRequest call.
166 if (mStarted) {
167 mDocument->EndLoad();
168 }
169 DropParserAndPerfHint();
170 #ifdef GATHER_DOCWRITE_STATISTICS
171 printf("UNSAFE SCRIPTS: %d\n", sUnsafeDocWrites);
172 printf("TOKENIZER-SAFE SCRIPTS: %d\n", sTokenSafeDocWrites);
173 printf("TREEBUILDER-SAFE SCRIPTS: %d\n", sTreeSafeDocWrites);
174 #endif
175 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
176 printf("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize);
177 if (sAppendBatchExaminations != 0) {
178 printf("AVERAGE SLOTS EXAMINED: %d\n", sAppendBatchSlotsExamined / sAppendBatchExaminations);
179 }
180 #endif
181 return NS_OK;
182 }
184 NS_IMETHODIMP
185 nsHtml5TreeOpExecutor::WillInterrupt()
186 {
187 NS_NOTREACHED("Don't call. For interface compat only.");
188 return NS_ERROR_NOT_IMPLEMENTED;
189 }
191 NS_IMETHODIMP
192 nsHtml5TreeOpExecutor::WillResume()
193 {
194 NS_NOTREACHED("Don't call. For interface compat only.");
195 return NS_ERROR_NOT_IMPLEMENTED;
196 }
198 NS_IMETHODIMP
199 nsHtml5TreeOpExecutor::SetParser(nsParserBase* aParser)
200 {
201 mParser = aParser;
202 return NS_OK;
203 }
205 void
206 nsHtml5TreeOpExecutor::FlushPendingNotifications(mozFlushType aType)
207 {
208 if (aType >= Flush_InterruptibleLayout) {
209 // Bug 577508 / 253951
210 nsContentSink::StartLayout(true);
211 }
212 }
214 nsISupports*
215 nsHtml5TreeOpExecutor::GetTarget()
216 {
217 return mDocument;
218 }
220 nsresult
221 nsHtml5TreeOpExecutor::MarkAsBroken(nsresult aReason)
222 {
223 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
224 mBroken = aReason;
225 if (mStreamParser) {
226 mStreamParser->Terminate();
227 }
228 // We are under memory pressure, but let's hope the following allocation
229 // works out so that we get to terminate and clean up the parser from
230 // a safer point.
231 if (mParser) { // can mParser ever be null here?
232 nsCOMPtr<nsIRunnable> terminator =
233 NS_NewRunnableMethod(GetParser(), &nsHtml5Parser::Terminate);
234 if (NS_FAILED(NS_DispatchToMainThread(terminator))) {
235 NS_WARNING("failed to dispatch executor flush event");
236 }
237 }
238 return aReason;
239 }
241 void
242 FlushTimerCallback(nsITimer* aTimer, void* aClosure)
243 {
244 nsRefPtr<nsHtml5TreeOpExecutor> ex = gBackgroundFlushList->popFirst();
245 if (ex) {
246 ex->RunFlushLoop();
247 }
248 if (gBackgroundFlushList && gBackgroundFlushList->isEmpty()) {
249 delete gBackgroundFlushList;
250 gBackgroundFlushList = nullptr;
251 gFlushTimer->Cancel();
252 NS_RELEASE(gFlushTimer);
253 }
254 }
256 void
257 nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync()
258 {
259 if (!mDocument || !mDocument->IsInBackgroundWindow()) {
260 nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this);
261 if (NS_FAILED(NS_DispatchToMainThread(flusher))) {
262 NS_WARNING("failed to dispatch executor flush event");
263 }
264 } else {
265 if (!gBackgroundFlushList) {
266 gBackgroundFlushList = new mozilla::LinkedList<nsHtml5TreeOpExecutor>();
267 }
268 if (!isInList()) {
269 gBackgroundFlushList->insertBack(this);
270 }
271 if (!gFlushTimer) {
272 nsCOMPtr<nsITimer> t = do_CreateInstance("@mozilla.org/timer;1");
273 t.swap(gFlushTimer);
274 // The timer value 50 should not hopefully slow down background pages too
275 // much, yet lets event loop to process enough between ticks.
276 // See bug 734015.
277 gFlushTimer->InitWithFuncCallback(FlushTimerCallback, nullptr,
278 50, nsITimer::TYPE_REPEATING_SLACK);
279 }
280 }
281 }
283 void
284 nsHtml5TreeOpExecutor::FlushSpeculativeLoads()
285 {
286 nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
287 mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue);
288 const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
289 const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
290 for (nsHtml5SpeculativeLoad* iter = const_cast<nsHtml5SpeculativeLoad*>(start);
291 iter < end;
292 ++iter) {
293 if (MOZ_UNLIKELY(!mParser)) {
294 // An extension terminated the parser from a HTTP observer.
295 return;
296 }
297 iter->Perform(this);
298 }
299 }
301 class nsHtml5FlushLoopGuard
302 {
303 private:
304 nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
305 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
306 uint32_t mStartTime;
307 #endif
308 public:
309 nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor)
310 : mExecutor(aExecutor)
311 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
312 , mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow()))
313 #endif
314 {
315 mExecutor->mRunFlushLoopOnStack = true;
316 }
317 ~nsHtml5FlushLoopGuard()
318 {
319 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
320 uint32_t timeOffTheEventLoop =
321 PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime;
322 if (timeOffTheEventLoop >
323 nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) {
324 nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop =
325 timeOffTheEventLoop;
326 }
327 printf("Longest time off the event loop: %d\n",
328 nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop);
329 #endif
331 mExecutor->mRunFlushLoopOnStack = false;
332 }
333 };
335 /**
336 * The purpose of the loop here is to avoid returning to the main event loop
337 */
338 void
339 nsHtml5TreeOpExecutor::RunFlushLoop()
340 {
341 PROFILER_LABEL("html5", "RunFlushLoop");
342 if (mRunFlushLoopOnStack) {
343 // There's already a RunFlushLoop() on the call stack.
344 return;
345 }
347 nsHtml5FlushLoopGuard guard(this); // this is also the self-kungfu!
349 nsCOMPtr<nsISupports> parserKungFuDeathGrip(mParser);
351 // Remember the entry time
352 (void) nsContentSink::WillParseImpl();
354 for (;;) {
355 if (!mParser) {
356 // Parse has terminated.
357 mOpQueue.Clear(); // clear in order to be able to assert in destructor
358 return;
359 }
361 if (NS_FAILED(IsBroken())) {
362 return;
363 }
365 if (!mParser->IsParserEnabled()) {
366 // The parser is blocked.
367 return;
368 }
370 if (mFlushState != eNotFlushing) {
371 // XXX Can this happen? In case it can, let's avoid crashing.
372 return;
373 }
375 // If there are scripts executing, then the content sink is jumping the gun
376 // (probably due to a synchronous XMLHttpRequest) and will re-enable us
377 // later, see bug 460706.
378 if (IsScriptExecuting()) {
379 return;
380 }
382 if (mReadingFromStage) {
383 nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
384 mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue, speculativeLoadQueue);
385 // Make sure speculative loads never start after the corresponding
386 // normal loads for the same URLs.
387 const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
388 const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
389 for (nsHtml5SpeculativeLoad* iter = (nsHtml5SpeculativeLoad*)start;
390 iter < end;
391 ++iter) {
392 iter->Perform(this);
393 if (MOZ_UNLIKELY(!mParser)) {
394 // An extension terminated the parser from a HTTP observer.
395 mOpQueue.Clear(); // clear in order to be able to assert in destructor
396 return;
397 }
398 }
399 } else {
400 FlushSpeculativeLoads(); // Make sure speculative loads never start after
401 // the corresponding normal loads for the same
402 // URLs.
403 if (MOZ_UNLIKELY(!mParser)) {
404 // An extension terminated the parser from a HTTP observer.
405 mOpQueue.Clear(); // clear in order to be able to assert in destructor
406 return;
407 }
408 // Not sure if this grip is still needed, but previously, the code
409 // gripped before calling ParseUntilBlocked();
410 nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip =
411 GetParser()->GetStreamParser();
412 // Now parse content left in the document.write() buffer queue if any.
413 // This may generate tree ops on its own or dequeue a speculation.
414 nsresult rv = GetParser()->ParseUntilBlocked();
415 if (NS_FAILED(rv)) {
416 MarkAsBroken(rv);
417 return;
418 }
419 }
421 if (mOpQueue.IsEmpty()) {
422 // Avoid bothering the rest of the engine with a doc update if there's
423 // nothing to do.
424 return;
425 }
427 mFlushState = eInFlush;
429 nsIContent* scriptElement = nullptr;
431 BeginDocUpdate();
433 uint32_t numberOfOpsToFlush = mOpQueue.Length();
435 SetAppendBatchCapacity(numberOfOpsToFlush * 2);
437 const nsHtml5TreeOperation* first = mOpQueue.Elements();
438 const nsHtml5TreeOperation* last = first + numberOfOpsToFlush - 1;
439 for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(first);;) {
440 if (MOZ_UNLIKELY(!mParser)) {
441 // The previous tree op caused a call to nsIParser::Terminate().
442 break;
443 }
444 NS_ASSERTION(mFlushState == eInDocUpdate,
445 "Tried to perform tree op outside update batch.");
446 nsresult rv = iter->Perform(this, &scriptElement);
447 if (NS_FAILED(rv)) {
448 MarkAsBroken(rv);
449 break;
450 }
452 // Be sure not to check the deadline if the last op was just performed.
453 if (MOZ_UNLIKELY(iter == last)) {
454 break;
455 } else if (MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() ==
456 NS_ERROR_HTMLPARSER_INTERRUPTED)) {
457 mOpQueue.RemoveElementsAt(0, (iter - first) + 1);
459 EndDocUpdate();
461 mFlushState = eNotFlushing;
463 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
464 printf("REFLUSH SCHEDULED (executing ops): %d\n",
465 ++sTimesFlushLoopInterrupted);
466 #endif
467 nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
468 return;
469 }
470 ++iter;
471 }
473 mOpQueue.Clear();
475 EndDocUpdate();
477 mFlushState = eNotFlushing;
479 if (MOZ_UNLIKELY(!mParser)) {
480 // The parse ended already.
481 return;
482 }
484 if (scriptElement) {
485 // must be tail call when mFlushState is eNotFlushing
486 RunScript(scriptElement);
488 // Always check the clock in nsContentSink right after a script
489 StopDeflecting();
490 if (nsContentSink::DidProcessATokenImpl() ==
491 NS_ERROR_HTMLPARSER_INTERRUPTED) {
492 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
493 printf("REFLUSH SCHEDULED (after script): %d\n",
494 ++sTimesFlushLoopInterrupted);
495 #endif
496 nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
497 return;
498 }
499 }
500 }
501 }
503 nsresult
504 nsHtml5TreeOpExecutor::FlushDocumentWrite()
505 {
506 nsresult rv = IsBroken();
507 NS_ENSURE_SUCCESS(rv, rv);
509 FlushSpeculativeLoads(); // Make sure speculative loads never start after the
510 // corresponding normal loads for the same URLs.
512 if (MOZ_UNLIKELY(!mParser)) {
513 // The parse has ended.
514 mOpQueue.Clear(); // clear in order to be able to assert in destructor
515 return rv;
516 }
518 if (mFlushState != eNotFlushing) {
519 // XXX Can this happen? In case it can, let's avoid crashing.
520 return rv;
521 }
523 mFlushState = eInFlush;
525 // avoid crashing near EOF
526 nsRefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this);
527 nsRefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
529 NS_ASSERTION(!mReadingFromStage,
530 "Got doc write flush when reading from stage");
532 #ifdef DEBUG
533 mStage.AssertEmpty();
534 #endif
536 nsIContent* scriptElement = nullptr;
538 BeginDocUpdate();
540 uint32_t numberOfOpsToFlush = mOpQueue.Length();
542 SetAppendBatchCapacity(numberOfOpsToFlush * 2);
544 const nsHtml5TreeOperation* start = mOpQueue.Elements();
545 const nsHtml5TreeOperation* end = start + numberOfOpsToFlush;
546 for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(start);
547 iter < end;
548 ++iter) {
549 if (MOZ_UNLIKELY(!mParser)) {
550 // The previous tree op caused a call to nsIParser::Terminate().
551 break;
552 }
553 NS_ASSERTION(mFlushState == eInDocUpdate,
554 "Tried to perform tree op outside update batch.");
555 rv = iter->Perform(this, &scriptElement);
556 if (NS_FAILED(rv)) {
557 MarkAsBroken(rv);
558 break;
559 }
560 }
562 mOpQueue.Clear();
564 EndDocUpdate();
566 mFlushState = eNotFlushing;
568 if (MOZ_UNLIKELY(!mParser)) {
569 // Ending the doc update caused a call to nsIParser::Terminate().
570 return rv;
571 }
573 if (scriptElement) {
574 // must be tail call when mFlushState is eNotFlushing
575 RunScript(scriptElement);
576 }
577 return rv;
578 }
580 // copied from HTML content sink
581 bool
582 nsHtml5TreeOpExecutor::IsScriptEnabled()
583 {
584 if (!mDocument || !mDocShell)
585 return true;
586 nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(mDocument->GetInnerWindow());
587 // Getting context is tricky if the document hasn't had its
588 // GlobalObject set yet
589 if (!globalObject) {
590 globalObject = mDocShell->GetScriptGlobalObject();
591 NS_ENSURE_TRUE(globalObject, true);
592 }
593 NS_ENSURE_TRUE(globalObject && globalObject->GetGlobalJSObject(), true);
594 return nsContentUtils::GetSecurityManager()->
595 ScriptAllowed(globalObject->GetGlobalJSObject());
596 }
598 void
599 nsHtml5TreeOpExecutor::StartLayout() {
600 if (mLayoutStarted || !mDocument) {
601 return;
602 }
604 EndDocUpdate();
606 if (MOZ_UNLIKELY(!mParser)) {
607 // got terminate
608 return;
609 }
611 nsContentSink::StartLayout(false);
613 BeginDocUpdate();
614 }
616 /**
617 * The reason why this code is here and not in the tree builder even in the
618 * main-thread case is to allow the control to return from the tokenizer
619 * before scripts run. This way, the tokenizer is not invoked re-entrantly
620 * although the parser is.
621 *
622 * The reason why this is called as a tail call when mFlushState is set to
623 * eNotFlushing is to allow re-entry to Flush() but only after the current
624 * Flush() has cleared the op queue and is otherwise done cleaning up after
625 * itself.
626 */
627 void
628 nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement)
629 {
630 if (mRunsToCompletion) {
631 // We are in createContextualFragment() or in the upcoming document.parse().
632 // Do nothing. Let's not even mark scripts malformed here, because that
633 // could cause serialization weirdness later.
634 return;
635 }
637 NS_ASSERTION(aScriptElement, "No script to run");
638 nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aScriptElement);
640 if (!mParser) {
641 NS_ASSERTION(sele->IsMalformed(), "Script wasn't marked as malformed.");
642 // We got here not because of an end tag but because the tree builder
643 // popped an incomplete script element on EOF. Returning here to avoid
644 // calling back into mParser anymore.
645 return;
646 }
648 if (sele->GetScriptDeferred() || sele->GetScriptAsync()) {
649 DebugOnly<bool> block = sele->AttemptToExecute();
650 NS_ASSERTION(!block, "Defer or async script tried to block.");
651 return;
652 }
654 NS_ASSERTION(mFlushState == eNotFlushing, "Tried to run script when flushing.");
656 mReadingFromStage = false;
658 sele->SetCreatorParser(GetParser());
660 // Copied from nsXMLContentSink
661 // Now tell the script that it's ready to go. This may execute the script
662 // or return true, or neither if the script doesn't need executing.
663 bool block = sele->AttemptToExecute();
665 // If the act of insertion evaluated the script, we're fine.
666 // Else, block the parser till the script has loaded.
667 if (block) {
668 if (mParser) {
669 GetParser()->BlockParser();
670 }
671 } else {
672 // mParser may have been nulled out by now, but the flusher deals
674 // If this event isn't needed, it doesn't do anything. It is sometimes
675 // necessary for the parse to continue after complex situations.
676 nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
677 }
678 }
680 void
681 nsHtml5TreeOpExecutor::Start()
682 {
683 NS_PRECONDITION(!mStarted, "Tried to start when already started.");
684 mStarted = true;
685 }
687 void
688 nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(const char* aEncoding,
689 int32_t aSource,
690 uint32_t aLineNumber)
691 {
692 EndDocUpdate();
694 if (MOZ_UNLIKELY(!mParser)) {
695 // got terminate
696 return;
697 }
699 nsCOMPtr<nsIWebShellServices> wss = do_QueryInterface(mDocShell);
700 if (!wss) {
701 return;
702 }
704 // ask the webshellservice to load the URL
705 if (NS_SUCCEEDED(wss->StopDocumentLoad())) {
706 wss->ReloadDocument(aEncoding, aSource);
707 }
708 // if the charset switch was accepted, wss has called Terminate() on the
709 // parser by now
711 if (!mParser) {
712 // success
713 if (aSource == kCharsetFromMetaTag) {
714 MaybeComplainAboutCharset("EncLateMetaReload", false, aLineNumber);
715 }
716 return;
717 }
719 if (aSource == kCharsetFromMetaTag) {
720 MaybeComplainAboutCharset("EncLateMetaTooLate", true, aLineNumber);
721 }
723 GetParser()->ContinueAfterFailedCharsetSwitch();
725 BeginDocUpdate();
726 }
728 void
729 nsHtml5TreeOpExecutor::MaybeComplainAboutCharset(const char* aMsgId,
730 bool aError,
731 uint32_t aLineNumber)
732 {
733 if (mAlreadyComplainedAboutCharset) {
734 return;
735 }
736 // The EncNoDeclaration case for advertising iframes is so common that it
737 // would result is way too many errors. The iframe case doesn't matter
738 // when the ad is an image or a Flash animation anyway. When the ad is
739 // textual, a misrendered ad probably isn't a huge loss for users.
740 // Let's suppress the message in this case.
741 // This means that errors about other different-origin iframes in mashups
742 // are lost as well, but generally, the site author isn't in control of
743 // the embedded different-origin pages anyway and can't fix problems even
744 // if alerted about them.
745 if (!strcmp(aMsgId, "EncNoDeclaration") && mDocShell) {
746 nsCOMPtr<nsIDocShellTreeItem> parent;
747 mDocShell->GetSameTypeParent(getter_AddRefs(parent));
748 if (parent) {
749 return;
750 }
751 }
752 mAlreadyComplainedAboutCharset = true;
753 nsContentUtils::ReportToConsole(aError ? nsIScriptError::errorFlag
754 : nsIScriptError::warningFlag,
755 NS_LITERAL_CSTRING("HTML parser"),
756 mDocument,
757 nsContentUtils::eHTMLPARSER_PROPERTIES,
758 aMsgId,
759 nullptr,
760 0,
761 nullptr,
762 EmptyString(),
763 aLineNumber);
764 }
766 void
767 nsHtml5TreeOpExecutor::ComplainAboutBogusProtocolCharset(nsIDocument* aDoc)
768 {
769 NS_ASSERTION(!mAlreadyComplainedAboutCharset,
770 "How come we already managed to complain?");
771 mAlreadyComplainedAboutCharset = true;
772 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
773 NS_LITERAL_CSTRING("HTML parser"),
774 aDoc,
775 nsContentUtils::eHTMLPARSER_PROPERTIES,
776 "EncProtocolUnsupported");
777 }
779 nsHtml5Parser*
780 nsHtml5TreeOpExecutor::GetParser()
781 {
782 MOZ_ASSERT(!mRunsToCompletion);
783 return static_cast<nsHtml5Parser*>(mParser.get());
784 }
786 void
787 nsHtml5TreeOpExecutor::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue)
788 {
789 NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution.");
790 if (mOpQueue.IsEmpty()) {
791 mOpQueue.SwapElements(aOpQueue);
792 return;
793 }
794 mOpQueue.MoveElementsFrom(aOpQueue);
795 }
797 void
798 nsHtml5TreeOpExecutor::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, int32_t aLine)
799 {
800 GetParser()->InitializeDocWriteParserState(aState, aLine);
801 }
803 nsIURI*
804 nsHtml5TreeOpExecutor::GetViewSourceBaseURI()
805 {
806 if (!mViewSourceBaseURI) {
808 // We query the channel for the baseURI because in certain situations it
809 // cannot otherwise be determined. If this process fails, fall back to the
810 // standard method.
811 nsCOMPtr<nsIViewSourceChannel> vsc;
812 vsc = do_QueryInterface(mDocument->GetChannel());
813 if (vsc) {
814 nsresult rv = vsc->GetBaseURI(getter_AddRefs(mViewSourceBaseURI));
815 if (NS_SUCCEEDED(rv) && mViewSourceBaseURI) {
816 return mViewSourceBaseURI;
817 }
818 }
820 nsCOMPtr<nsIURI> orig = mDocument->GetOriginalURI();
821 bool isViewSource;
822 orig->SchemeIs("view-source", &isViewSource);
823 if (isViewSource) {
824 nsCOMPtr<nsINestedURI> nested = do_QueryInterface(orig);
825 NS_ASSERTION(nested, "URI with scheme view-source didn't QI to nested!");
826 nested->GetInnerURI(getter_AddRefs(mViewSourceBaseURI));
827 } else {
828 // Fail gracefully if the base URL isn't a view-source: URL.
829 // Not sure if this can ever happen.
830 mViewSourceBaseURI = orig;
831 }
832 }
833 return mViewSourceBaseURI;
834 }
836 //static
837 void
838 nsHtml5TreeOpExecutor::InitializeStatics()
839 {
840 mozilla::Preferences::AddBoolVarCache(&sExternalViewSource,
841 "view_source.editor.external");
842 }
844 bool
845 nsHtml5TreeOpExecutor::IsExternalViewSource()
846 {
847 if (!sExternalViewSource) {
848 return false;
849 }
850 bool isViewSource = false;
851 if (mDocumentURI) {
852 mDocumentURI->SchemeIs("view-source", &isViewSource);
853 }
854 return isViewSource;
855 }
857 // Speculative loading
859 already_AddRefed<nsIURI>
860 nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet(const nsAString& aURL)
861 {
862 if (aURL.IsEmpty()) {
863 return nullptr;
864 }
865 // The URL of the document without <base>
866 nsIURI* documentURI = mDocument->GetDocumentURI();
867 // The URL of the document with non-speculative <base>
868 nsIURI* documentBaseURI = mDocument->GetDocBaseURI();
870 // If the two above are different, use documentBaseURI. If they are the
871 // same, the document object isn't aware of a <base>, so attempt to use the
872 // mSpeculationBaseURI or, failing, that, documentURI.
873 nsIURI* base = (documentURI == documentBaseURI) ?
874 (mSpeculationBaseURI ?
875 mSpeculationBaseURI.get() : documentURI)
876 : documentBaseURI;
877 const nsCString& charset = mDocument->GetDocumentCharacterSet();
878 nsCOMPtr<nsIURI> uri;
879 nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, charset.get(), base);
880 if (NS_FAILED(rv)) {
881 NS_WARNING("Failed to create a URI");
882 return nullptr;
883 }
884 nsAutoCString spec;
885 uri->GetSpec(spec);
886 if (mPreloadedURLs.Contains(spec)) {
887 return nullptr;
888 }
889 mPreloadedURLs.PutEntry(spec);
890 return uri.forget();
891 }
893 void
894 nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL,
895 const nsAString& aCharset,
896 const nsAString& aType,
897 const nsAString& aCrossOrigin,
898 bool aScriptFromHead)
899 {
900 nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
901 if (!uri) {
902 return;
903 }
904 mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType, aCrossOrigin,
905 aScriptFromHead);
906 }
908 void
909 nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL,
910 const nsAString& aCharset,
911 const nsAString& aCrossOrigin)
912 {
913 nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
914 if (!uri) {
915 return;
916 }
917 mDocument->PreloadStyle(uri, aCharset, aCrossOrigin);
918 }
920 void
921 nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL,
922 const nsAString& aCrossOrigin)
923 {
924 nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
925 if (!uri) {
926 return;
927 }
928 mDocument->MaybePreLoadImage(uri, aCrossOrigin);
929 }
931 void
932 nsHtml5TreeOpExecutor::SetSpeculationBase(const nsAString& aURL)
933 {
934 if (mSpeculationBaseURI) {
935 // the first one wins
936 return;
937 }
938 const nsCString& charset = mDocument->GetDocumentCharacterSet();
939 DebugOnly<nsresult> rv = NS_NewURI(getter_AddRefs(mSpeculationBaseURI), aURL,
940 charset.get(), mDocument->GetDocumentURI());
941 NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to create a URI");
942 }
944 #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
945 uint32_t nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0;
946 uint32_t nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0;
947 uint32_t nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0;
948 uint32_t nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0;
949 uint32_t nsHtml5TreeOpExecutor::sTimesFlushLoopInterrupted = 0;
950 #endif
951 bool nsHtml5TreeOpExecutor::sExternalViewSource = false;