parser/html/nsHtml5TreeOpExecutor.cpp

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

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;

mercurial