parser/html/nsHtml5TreeOpExecutor.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/parser/html/nsHtml5TreeOpExecutor.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,951 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim: set sw=2 ts=2 et tw=79: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "mozilla/DebugOnly.h"
    1.11 +#include "mozilla/Likely.h"
    1.12 +
    1.13 +#include "nsError.h"
    1.14 +#include "nsHtml5TreeOpExecutor.h"
    1.15 +#include "nsScriptLoader.h"
    1.16 +#include "nsIMarkupDocumentViewer.h"
    1.17 +#include "nsIContentViewer.h"
    1.18 +#include "nsIDocShellTreeItem.h"
    1.19 +#include "nsIDocShell.h"
    1.20 +#include "nsIScriptGlobalObject.h"
    1.21 +#include "nsIScriptSecurityManager.h"
    1.22 +#include "nsIWebShellServices.h"
    1.23 +#include "nsContentUtils.h"
    1.24 +#include "mozAutoDocUpdate.h"
    1.25 +#include "nsNetUtil.h"
    1.26 +#include "nsHtml5Parser.h"
    1.27 +#include "nsHtml5Tokenizer.h"
    1.28 +#include "nsHtml5TreeBuilder.h"
    1.29 +#include "nsHtml5StreamParser.h"
    1.30 +#include "mozilla/css/Loader.h"
    1.31 +#include "GeckoProfiler.h"
    1.32 +#include "nsIScriptError.h"
    1.33 +#include "nsIScriptContext.h"
    1.34 +#include "mozilla/Preferences.h"
    1.35 +#include "nsIHTMLDocument.h"
    1.36 +
    1.37 +using namespace mozilla;
    1.38 +
    1.39 +NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor)
    1.40 +  NS_INTERFACE_TABLE_INHERITED(nsHtml5TreeOpExecutor, 
    1.41 +                               nsIContentSink)
    1.42 +NS_INTERFACE_TABLE_TAIL_INHERITING(nsHtml5DocumentBuilder)
    1.43 +
    1.44 +NS_IMPL_ADDREF_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
    1.45 +
    1.46 +NS_IMPL_RELEASE_INHERITED(nsHtml5TreeOpExecutor, nsContentSink)
    1.47 +
    1.48 +class nsHtml5ExecutorReflusher : public nsRunnable
    1.49 +{
    1.50 +  private:
    1.51 +    nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
    1.52 +  public:
    1.53 +    nsHtml5ExecutorReflusher(nsHtml5TreeOpExecutor* aExecutor)
    1.54 +      : mExecutor(aExecutor)
    1.55 +    {}
    1.56 +    NS_IMETHODIMP Run()
    1.57 +    {
    1.58 +      mExecutor->RunFlushLoop();
    1.59 +      return NS_OK;
    1.60 +    }
    1.61 +};
    1.62 +
    1.63 +static mozilla::LinkedList<nsHtml5TreeOpExecutor>* gBackgroundFlushList = nullptr;
    1.64 +static nsITimer* gFlushTimer = nullptr;
    1.65 +
    1.66 +nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
    1.67 +  : nsHtml5DocumentBuilder(false)
    1.68 +  , mPreloadedURLs(23)  // Mean # of preloadable resources per page on dmoz
    1.69 +{
    1.70 +  // zeroing operator new for everything else
    1.71 +}
    1.72 +
    1.73 +nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor()
    1.74 +{
    1.75 +  if (gBackgroundFlushList && isInList()) {
    1.76 +    mOpQueue.Clear();
    1.77 +    removeFrom(*gBackgroundFlushList);
    1.78 +    if (gBackgroundFlushList->isEmpty()) {
    1.79 +      delete gBackgroundFlushList;
    1.80 +      gBackgroundFlushList = nullptr;
    1.81 +      if (gFlushTimer) {
    1.82 +        gFlushTimer->Cancel();
    1.83 +        NS_RELEASE(gFlushTimer);
    1.84 +      }
    1.85 +    }
    1.86 +  }
    1.87 +  NS_ASSERTION(mOpQueue.IsEmpty(), "Somehow there's stuff in the op queue.");
    1.88 +}
    1.89 +
    1.90 +// nsIContentSink
    1.91 +NS_IMETHODIMP
    1.92 +nsHtml5TreeOpExecutor::WillParse()
    1.93 +{
    1.94 +  NS_NOTREACHED("No one should call this");
    1.95 +  return NS_ERROR_NOT_IMPLEMENTED;
    1.96 +}
    1.97 +
    1.98 +NS_IMETHODIMP
    1.99 +nsHtml5TreeOpExecutor::WillBuildModel(nsDTDMode aDTDMode)
   1.100 +{
   1.101 +  mDocument->AddObserver(this);
   1.102 +  WillBuildModelImpl();
   1.103 +  GetDocument()->BeginLoad();
   1.104 +  if (mDocShell && !GetDocument()->GetWindow() &&
   1.105 +      !IsExternalViewSource()) {
   1.106 +    // Not loading as data but script global object not ready
   1.107 +    return MarkAsBroken(NS_ERROR_DOM_INVALID_STATE_ERR);
   1.108 +  }
   1.109 +  return NS_OK;
   1.110 +}
   1.111 +
   1.112 +
   1.113 +// This is called when the tree construction has ended
   1.114 +NS_IMETHODIMP
   1.115 +nsHtml5TreeOpExecutor::DidBuildModel(bool aTerminated)
   1.116 +{
   1.117 +  if (!aTerminated) {
   1.118 +    // This is needed to avoid unblocking loads too many times on one hand
   1.119 +    // and on the other hand to avoid destroying the frame constructor from
   1.120 +    // within an update batch. See bug 537683.
   1.121 +    EndDocUpdate();
   1.122 +    
   1.123 +    // If the above caused a call to nsIParser::Terminate(), let that call
   1.124 +    // win.
   1.125 +    if (!mParser) {
   1.126 +      return NS_OK;
   1.127 +    }
   1.128 +  }
   1.129 +  
   1.130 +  if (mRunsToCompletion) {
   1.131 +    return NS_OK;
   1.132 +  }
   1.133 +
   1.134 +  GetParser()->DropStreamParser();
   1.135 +
   1.136 +  // This comes from nsXMLContentSink and nsHTMLContentSink
   1.137 +  // If this parser has been marked as broken, treat the end of parse as
   1.138 +  // forced termination.
   1.139 +  DidBuildModelImpl(aTerminated || NS_FAILED(IsBroken()));
   1.140 +
   1.141 +  if (!mLayoutStarted) {
   1.142 +    // We never saw the body, and layout never got started. Force
   1.143 +    // layout *now*, to get an initial reflow.
   1.144 +
   1.145 +    // NOTE: only force the layout if we are NOT destroying the
   1.146 +    // docshell. If we are destroying it, then starting layout will
   1.147 +    // likely cause us to crash, or at best waste a lot of time as we
   1.148 +    // are just going to tear it down anyway.
   1.149 +    bool destroying = true;
   1.150 +    if (mDocShell) {
   1.151 +      mDocShell->IsBeingDestroyed(&destroying);
   1.152 +    }
   1.153 +
   1.154 +    if (!destroying) {
   1.155 +      nsContentSink::StartLayout(false);
   1.156 +    }
   1.157 +  }
   1.158 +
   1.159 +  ScrollToRef();
   1.160 +  mDocument->RemoveObserver(this);
   1.161 +  if (!mParser) {
   1.162 +    // DidBuildModelImpl may cause mParser to be nulled out
   1.163 +    // Return early to avoid unblocking the onload event too many times.
   1.164 +    return NS_OK;
   1.165 +  }
   1.166 +
   1.167 +  // We may not have called BeginLoad() if loading is terminated before
   1.168 +  // OnStartRequest call.
   1.169 +  if (mStarted) {
   1.170 +    mDocument->EndLoad();
   1.171 +  }
   1.172 +  DropParserAndPerfHint();
   1.173 +#ifdef GATHER_DOCWRITE_STATISTICS
   1.174 +  printf("UNSAFE SCRIPTS: %d\n", sUnsafeDocWrites);
   1.175 +  printf("TOKENIZER-SAFE SCRIPTS: %d\n", sTokenSafeDocWrites);
   1.176 +  printf("TREEBUILDER-SAFE SCRIPTS: %d\n", sTreeSafeDocWrites);
   1.177 +#endif
   1.178 +#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
   1.179 +  printf("MAX NOTIFICATION BATCH LEN: %d\n", sAppendBatchMaxSize);
   1.180 +  if (sAppendBatchExaminations != 0) {
   1.181 +    printf("AVERAGE SLOTS EXAMINED: %d\n", sAppendBatchSlotsExamined / sAppendBatchExaminations);
   1.182 +  }
   1.183 +#endif
   1.184 +  return NS_OK;
   1.185 +}
   1.186 +
   1.187 +NS_IMETHODIMP
   1.188 +nsHtml5TreeOpExecutor::WillInterrupt()
   1.189 +{
   1.190 +  NS_NOTREACHED("Don't call. For interface compat only.");
   1.191 +  return NS_ERROR_NOT_IMPLEMENTED;
   1.192 +}
   1.193 +
   1.194 +NS_IMETHODIMP
   1.195 +nsHtml5TreeOpExecutor::WillResume()
   1.196 +{
   1.197 +  NS_NOTREACHED("Don't call. For interface compat only.");
   1.198 +  return NS_ERROR_NOT_IMPLEMENTED;
   1.199 +}
   1.200 +
   1.201 +NS_IMETHODIMP
   1.202 +nsHtml5TreeOpExecutor::SetParser(nsParserBase* aParser)
   1.203 +{
   1.204 +  mParser = aParser;
   1.205 +  return NS_OK;
   1.206 +}
   1.207 +
   1.208 +void
   1.209 +nsHtml5TreeOpExecutor::FlushPendingNotifications(mozFlushType aType)
   1.210 +{
   1.211 +  if (aType >= Flush_InterruptibleLayout) {
   1.212 +    // Bug 577508 / 253951
   1.213 +    nsContentSink::StartLayout(true);
   1.214 +  }
   1.215 +}
   1.216 +
   1.217 +nsISupports*
   1.218 +nsHtml5TreeOpExecutor::GetTarget()
   1.219 +{
   1.220 +  return mDocument;
   1.221 +}
   1.222 +
   1.223 +nsresult
   1.224 +nsHtml5TreeOpExecutor::MarkAsBroken(nsresult aReason)
   1.225 +{
   1.226 +  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   1.227 +  mBroken = aReason;
   1.228 +  if (mStreamParser) {
   1.229 +    mStreamParser->Terminate();
   1.230 +  }
   1.231 +  // We are under memory pressure, but let's hope the following allocation
   1.232 +  // works out so that we get to terminate and clean up the parser from
   1.233 +  // a safer point.
   1.234 +  if (mParser) { // can mParser ever be null here?
   1.235 +    nsCOMPtr<nsIRunnable> terminator =
   1.236 +      NS_NewRunnableMethod(GetParser(), &nsHtml5Parser::Terminate);
   1.237 +    if (NS_FAILED(NS_DispatchToMainThread(terminator))) {
   1.238 +      NS_WARNING("failed to dispatch executor flush event");
   1.239 +    }
   1.240 +  }
   1.241 +  return aReason;
   1.242 +}
   1.243 +
   1.244 +void
   1.245 +FlushTimerCallback(nsITimer* aTimer, void* aClosure)
   1.246 +{
   1.247 +  nsRefPtr<nsHtml5TreeOpExecutor> ex = gBackgroundFlushList->popFirst();
   1.248 +  if (ex) {
   1.249 +    ex->RunFlushLoop();
   1.250 +  }
   1.251 +  if (gBackgroundFlushList && gBackgroundFlushList->isEmpty()) {
   1.252 +    delete gBackgroundFlushList;
   1.253 +    gBackgroundFlushList = nullptr;
   1.254 +    gFlushTimer->Cancel();
   1.255 +    NS_RELEASE(gFlushTimer);
   1.256 +  }
   1.257 +}
   1.258 +
   1.259 +void
   1.260 +nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync()
   1.261 +{
   1.262 +  if (!mDocument || !mDocument->IsInBackgroundWindow()) {
   1.263 +    nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this);  
   1.264 +    if (NS_FAILED(NS_DispatchToMainThread(flusher))) {
   1.265 +      NS_WARNING("failed to dispatch executor flush event");
   1.266 +    }
   1.267 +  } else {
   1.268 +    if (!gBackgroundFlushList) {
   1.269 +      gBackgroundFlushList = new mozilla::LinkedList<nsHtml5TreeOpExecutor>();
   1.270 +    }
   1.271 +    if (!isInList()) {
   1.272 +      gBackgroundFlushList->insertBack(this);
   1.273 +    }
   1.274 +    if (!gFlushTimer) {
   1.275 +      nsCOMPtr<nsITimer> t = do_CreateInstance("@mozilla.org/timer;1");
   1.276 +      t.swap(gFlushTimer);
   1.277 +      // The timer value 50 should not hopefully slow down background pages too
   1.278 +      // much, yet lets event loop to process enough between ticks.
   1.279 +      // See bug 734015.
   1.280 +      gFlushTimer->InitWithFuncCallback(FlushTimerCallback, nullptr,
   1.281 +                                        50, nsITimer::TYPE_REPEATING_SLACK);
   1.282 +    }
   1.283 +  }
   1.284 +}
   1.285 +
   1.286 +void
   1.287 +nsHtml5TreeOpExecutor::FlushSpeculativeLoads()
   1.288 +{
   1.289 +  nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
   1.290 +  mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue);
   1.291 +  const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
   1.292 +  const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
   1.293 +  for (nsHtml5SpeculativeLoad* iter = const_cast<nsHtml5SpeculativeLoad*>(start);
   1.294 +       iter < end;
   1.295 +       ++iter) {
   1.296 +    if (MOZ_UNLIKELY(!mParser)) {
   1.297 +      // An extension terminated the parser from a HTTP observer.
   1.298 +      return;
   1.299 +    }
   1.300 +    iter->Perform(this);
   1.301 +  }
   1.302 +}
   1.303 +
   1.304 +class nsHtml5FlushLoopGuard
   1.305 +{
   1.306 +  private:
   1.307 +    nsRefPtr<nsHtml5TreeOpExecutor> mExecutor;
   1.308 +    #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
   1.309 +    uint32_t mStartTime;
   1.310 +    #endif
   1.311 +  public:
   1.312 +    nsHtml5FlushLoopGuard(nsHtml5TreeOpExecutor* aExecutor)
   1.313 +      : mExecutor(aExecutor)
   1.314 +    #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
   1.315 +      , mStartTime(PR_IntervalToMilliseconds(PR_IntervalNow()))
   1.316 +    #endif
   1.317 +    {
   1.318 +      mExecutor->mRunFlushLoopOnStack = true;
   1.319 +    }
   1.320 +    ~nsHtml5FlushLoopGuard()
   1.321 +    {
   1.322 +      #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
   1.323 +        uint32_t timeOffTheEventLoop = 
   1.324 +          PR_IntervalToMilliseconds(PR_IntervalNow()) - mStartTime;
   1.325 +        if (timeOffTheEventLoop > 
   1.326 +            nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop) {
   1.327 +          nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 
   1.328 +            timeOffTheEventLoop;
   1.329 +        }
   1.330 +        printf("Longest time off the event loop: %d\n", 
   1.331 +          nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop);
   1.332 +      #endif
   1.333 +
   1.334 +      mExecutor->mRunFlushLoopOnStack = false;
   1.335 +    }
   1.336 +};
   1.337 +
   1.338 +/**
   1.339 + * The purpose of the loop here is to avoid returning to the main event loop
   1.340 + */
   1.341 +void
   1.342 +nsHtml5TreeOpExecutor::RunFlushLoop()
   1.343 +{
   1.344 +  PROFILER_LABEL("html5", "RunFlushLoop");
   1.345 +  if (mRunFlushLoopOnStack) {
   1.346 +    // There's already a RunFlushLoop() on the call stack.
   1.347 +    return;
   1.348 +  }
   1.349 +  
   1.350 +  nsHtml5FlushLoopGuard guard(this); // this is also the self-kungfu!
   1.351 +  
   1.352 +  nsCOMPtr<nsISupports> parserKungFuDeathGrip(mParser);
   1.353 +
   1.354 +  // Remember the entry time
   1.355 +  (void) nsContentSink::WillParseImpl();
   1.356 +
   1.357 +  for (;;) {
   1.358 +    if (!mParser) {
   1.359 +      // Parse has terminated.
   1.360 +      mOpQueue.Clear(); // clear in order to be able to assert in destructor
   1.361 +      return;
   1.362 +    }
   1.363 +
   1.364 +    if (NS_FAILED(IsBroken())) {
   1.365 +      return;
   1.366 +    }
   1.367 +
   1.368 +    if (!mParser->IsParserEnabled()) {
   1.369 +      // The parser is blocked.
   1.370 +      return;
   1.371 +    }
   1.372 +  
   1.373 +    if (mFlushState != eNotFlushing) {
   1.374 +      // XXX Can this happen? In case it can, let's avoid crashing.
   1.375 +      return;
   1.376 +    }
   1.377 +    
   1.378 +    // If there are scripts executing, then the content sink is jumping the gun
   1.379 +    // (probably due to a synchronous XMLHttpRequest) and will re-enable us
   1.380 +    // later, see bug 460706.
   1.381 +    if (IsScriptExecuting()) {
   1.382 +      return;
   1.383 +    }
   1.384 +
   1.385 +    if (mReadingFromStage) {
   1.386 +      nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
   1.387 +      mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue, speculativeLoadQueue);
   1.388 +      // Make sure speculative loads never start after the corresponding
   1.389 +      // normal loads for the same URLs.
   1.390 +      const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
   1.391 +      const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
   1.392 +      for (nsHtml5SpeculativeLoad* iter = (nsHtml5SpeculativeLoad*)start;
   1.393 +           iter < end;
   1.394 +           ++iter) {
   1.395 +        iter->Perform(this);
   1.396 +        if (MOZ_UNLIKELY(!mParser)) {
   1.397 +          // An extension terminated the parser from a HTTP observer.
   1.398 +          mOpQueue.Clear(); // clear in order to be able to assert in destructor
   1.399 +          return;
   1.400 +        }
   1.401 +      }
   1.402 +    } else {
   1.403 +      FlushSpeculativeLoads(); // Make sure speculative loads never start after
   1.404 +                               // the corresponding normal loads for the same
   1.405 +                               // URLs.
   1.406 +      if (MOZ_UNLIKELY(!mParser)) {
   1.407 +        // An extension terminated the parser from a HTTP observer.
   1.408 +        mOpQueue.Clear(); // clear in order to be able to assert in destructor
   1.409 +        return;
   1.410 +      }
   1.411 +      // Not sure if this grip is still needed, but previously, the code
   1.412 +      // gripped before calling ParseUntilBlocked();
   1.413 +      nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip = 
   1.414 +        GetParser()->GetStreamParser();
   1.415 +      // Now parse content left in the document.write() buffer queue if any.
   1.416 +      // This may generate tree ops on its own or dequeue a speculation.
   1.417 +      nsresult rv = GetParser()->ParseUntilBlocked();
   1.418 +      if (NS_FAILED(rv)) {
   1.419 +        MarkAsBroken(rv);
   1.420 +        return;
   1.421 +      }
   1.422 +    }
   1.423 +
   1.424 +    if (mOpQueue.IsEmpty()) {
   1.425 +      // Avoid bothering the rest of the engine with a doc update if there's 
   1.426 +      // nothing to do.
   1.427 +      return;
   1.428 +    }
   1.429 +
   1.430 +    mFlushState = eInFlush;
   1.431 +
   1.432 +    nsIContent* scriptElement = nullptr;
   1.433 +    
   1.434 +    BeginDocUpdate();
   1.435 +
   1.436 +    uint32_t numberOfOpsToFlush = mOpQueue.Length();
   1.437 +
   1.438 +    SetAppendBatchCapacity(numberOfOpsToFlush * 2);
   1.439 +
   1.440 +    const nsHtml5TreeOperation* first = mOpQueue.Elements();
   1.441 +    const nsHtml5TreeOperation* last = first + numberOfOpsToFlush - 1;
   1.442 +    for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(first);;) {
   1.443 +      if (MOZ_UNLIKELY(!mParser)) {
   1.444 +        // The previous tree op caused a call to nsIParser::Terminate().
   1.445 +        break;
   1.446 +      }
   1.447 +      NS_ASSERTION(mFlushState == eInDocUpdate, 
   1.448 +        "Tried to perform tree op outside update batch.");
   1.449 +      nsresult rv = iter->Perform(this, &scriptElement);
   1.450 +      if (NS_FAILED(rv)) {
   1.451 +        MarkAsBroken(rv);
   1.452 +        break;
   1.453 +      }
   1.454 +
   1.455 +      // Be sure not to check the deadline if the last op was just performed.
   1.456 +      if (MOZ_UNLIKELY(iter == last)) {
   1.457 +        break;
   1.458 +      } else if (MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() == 
   1.459 +                 NS_ERROR_HTMLPARSER_INTERRUPTED)) {
   1.460 +        mOpQueue.RemoveElementsAt(0, (iter - first) + 1);
   1.461 +        
   1.462 +        EndDocUpdate();
   1.463 +
   1.464 +        mFlushState = eNotFlushing;
   1.465 +
   1.466 +        #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
   1.467 +          printf("REFLUSH SCHEDULED (executing ops): %d\n", 
   1.468 +            ++sTimesFlushLoopInterrupted);
   1.469 +        #endif
   1.470 +        nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
   1.471 +        return;
   1.472 +      }
   1.473 +      ++iter;
   1.474 +    }
   1.475 +    
   1.476 +    mOpQueue.Clear();
   1.477 +    
   1.478 +    EndDocUpdate();
   1.479 +
   1.480 +    mFlushState = eNotFlushing;
   1.481 +
   1.482 +    if (MOZ_UNLIKELY(!mParser)) {
   1.483 +      // The parse ended already.
   1.484 +      return;
   1.485 +    }
   1.486 +
   1.487 +    if (scriptElement) {
   1.488 +      // must be tail call when mFlushState is eNotFlushing
   1.489 +      RunScript(scriptElement);
   1.490 +      
   1.491 +      // Always check the clock in nsContentSink right after a script
   1.492 +      StopDeflecting();
   1.493 +      if (nsContentSink::DidProcessATokenImpl() == 
   1.494 +          NS_ERROR_HTMLPARSER_INTERRUPTED) {
   1.495 +        #ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
   1.496 +          printf("REFLUSH SCHEDULED (after script): %d\n", 
   1.497 +            ++sTimesFlushLoopInterrupted);
   1.498 +        #endif
   1.499 +        nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
   1.500 +        return;      
   1.501 +      }
   1.502 +    }
   1.503 +  }
   1.504 +}
   1.505 +
   1.506 +nsresult
   1.507 +nsHtml5TreeOpExecutor::FlushDocumentWrite()
   1.508 +{
   1.509 +  nsresult rv = IsBroken();
   1.510 +  NS_ENSURE_SUCCESS(rv, rv);
   1.511 +
   1.512 +  FlushSpeculativeLoads(); // Make sure speculative loads never start after the
   1.513 +                // corresponding normal loads for the same URLs.
   1.514 +
   1.515 +  if (MOZ_UNLIKELY(!mParser)) {
   1.516 +    // The parse has ended.
   1.517 +    mOpQueue.Clear(); // clear in order to be able to assert in destructor
   1.518 +    return rv;
   1.519 +  }
   1.520 +  
   1.521 +  if (mFlushState != eNotFlushing) {
   1.522 +    // XXX Can this happen? In case it can, let's avoid crashing.
   1.523 +    return rv;
   1.524 +  }
   1.525 +
   1.526 +  mFlushState = eInFlush;
   1.527 +
   1.528 +  // avoid crashing near EOF
   1.529 +  nsRefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this);
   1.530 +  nsRefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
   1.531 +
   1.532 +  NS_ASSERTION(!mReadingFromStage,
   1.533 +    "Got doc write flush when reading from stage");
   1.534 +
   1.535 +#ifdef DEBUG
   1.536 +  mStage.AssertEmpty();
   1.537 +#endif
   1.538 +  
   1.539 +  nsIContent* scriptElement = nullptr;
   1.540 +  
   1.541 +  BeginDocUpdate();
   1.542 +
   1.543 +  uint32_t numberOfOpsToFlush = mOpQueue.Length();
   1.544 +
   1.545 +  SetAppendBatchCapacity(numberOfOpsToFlush * 2);
   1.546 +
   1.547 +  const nsHtml5TreeOperation* start = mOpQueue.Elements();
   1.548 +  const nsHtml5TreeOperation* end = start + numberOfOpsToFlush;
   1.549 +  for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(start);
   1.550 +       iter < end;
   1.551 +       ++iter) {
   1.552 +    if (MOZ_UNLIKELY(!mParser)) {
   1.553 +      // The previous tree op caused a call to nsIParser::Terminate().
   1.554 +      break;
   1.555 +    }
   1.556 +    NS_ASSERTION(mFlushState == eInDocUpdate, 
   1.557 +      "Tried to perform tree op outside update batch.");
   1.558 +    rv = iter->Perform(this, &scriptElement);
   1.559 +    if (NS_FAILED(rv)) {
   1.560 +      MarkAsBroken(rv);
   1.561 +      break;
   1.562 +    }
   1.563 +  }
   1.564 +
   1.565 +  mOpQueue.Clear();
   1.566 +  
   1.567 +  EndDocUpdate();
   1.568 +
   1.569 +  mFlushState = eNotFlushing;
   1.570 +
   1.571 +  if (MOZ_UNLIKELY(!mParser)) {
   1.572 +    // Ending the doc update caused a call to nsIParser::Terminate().
   1.573 +    return rv;
   1.574 +  }
   1.575 +
   1.576 +  if (scriptElement) {
   1.577 +    // must be tail call when mFlushState is eNotFlushing
   1.578 +    RunScript(scriptElement);
   1.579 +  }
   1.580 +  return rv;
   1.581 +}
   1.582 +
   1.583 +// copied from HTML content sink
   1.584 +bool
   1.585 +nsHtml5TreeOpExecutor::IsScriptEnabled()
   1.586 +{
   1.587 +  if (!mDocument || !mDocShell)
   1.588 +    return true;
   1.589 +  nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(mDocument->GetInnerWindow());
   1.590 +  // Getting context is tricky if the document hasn't had its
   1.591 +  // GlobalObject set yet
   1.592 +  if (!globalObject) {
   1.593 +    globalObject = mDocShell->GetScriptGlobalObject();
   1.594 +    NS_ENSURE_TRUE(globalObject, true);
   1.595 +  }
   1.596 +  NS_ENSURE_TRUE(globalObject && globalObject->GetGlobalJSObject(), true);
   1.597 +  return nsContentUtils::GetSecurityManager()->
   1.598 +           ScriptAllowed(globalObject->GetGlobalJSObject());
   1.599 +}
   1.600 +
   1.601 +void
   1.602 +nsHtml5TreeOpExecutor::StartLayout() {
   1.603 +  if (mLayoutStarted || !mDocument) {
   1.604 +    return;
   1.605 +  }
   1.606 +
   1.607 +  EndDocUpdate();
   1.608 +
   1.609 +  if (MOZ_UNLIKELY(!mParser)) {
   1.610 +    // got terminate
   1.611 +    return;
   1.612 +  }
   1.613 +
   1.614 +  nsContentSink::StartLayout(false);
   1.615 +
   1.616 +  BeginDocUpdate();
   1.617 +}
   1.618 +
   1.619 +/**
   1.620 + * The reason why this code is here and not in the tree builder even in the 
   1.621 + * main-thread case is to allow the control to return from the tokenizer 
   1.622 + * before scripts run. This way, the tokenizer is not invoked re-entrantly 
   1.623 + * although the parser is.
   1.624 + *
   1.625 + * The reason why this is called as a tail call when mFlushState is set to
   1.626 + * eNotFlushing is to allow re-entry to Flush() but only after the current 
   1.627 + * Flush() has cleared the op queue and is otherwise done cleaning up after 
   1.628 + * itself.
   1.629 + */
   1.630 +void
   1.631 +nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement)
   1.632 +{
   1.633 +  if (mRunsToCompletion) {
   1.634 +    // We are in createContextualFragment() or in the upcoming document.parse().
   1.635 +    // Do nothing. Let's not even mark scripts malformed here, because that
   1.636 +    // could cause serialization weirdness later.
   1.637 +    return;
   1.638 +  }
   1.639 +
   1.640 +  NS_ASSERTION(aScriptElement, "No script to run");
   1.641 +  nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(aScriptElement);
   1.642 +  
   1.643 +  if (!mParser) {
   1.644 +    NS_ASSERTION(sele->IsMalformed(), "Script wasn't marked as malformed.");
   1.645 +    // We got here not because of an end tag but because the tree builder
   1.646 +    // popped an incomplete script element on EOF. Returning here to avoid
   1.647 +    // calling back into mParser anymore.
   1.648 +    return;
   1.649 +  }
   1.650 +  
   1.651 +  if (sele->GetScriptDeferred() || sele->GetScriptAsync()) {
   1.652 +    DebugOnly<bool> block = sele->AttemptToExecute();
   1.653 +    NS_ASSERTION(!block, "Defer or async script tried to block.");
   1.654 +    return;
   1.655 +  }
   1.656 +  
   1.657 +  NS_ASSERTION(mFlushState == eNotFlushing, "Tried to run script when flushing.");
   1.658 +
   1.659 +  mReadingFromStage = false;
   1.660 +  
   1.661 +  sele->SetCreatorParser(GetParser());
   1.662 +
   1.663 +  // Copied from nsXMLContentSink
   1.664 +  // Now tell the script that it's ready to go. This may execute the script
   1.665 +  // or return true, or neither if the script doesn't need executing.
   1.666 +  bool block = sele->AttemptToExecute();
   1.667 +
   1.668 +  // If the act of insertion evaluated the script, we're fine.
   1.669 +  // Else, block the parser till the script has loaded.
   1.670 +  if (block) {
   1.671 +    if (mParser) {
   1.672 +      GetParser()->BlockParser();
   1.673 +    }
   1.674 +  } else {
   1.675 +    // mParser may have been nulled out by now, but the flusher deals
   1.676 +
   1.677 +    // If this event isn't needed, it doesn't do anything. It is sometimes
   1.678 +    // necessary for the parse to continue after complex situations.
   1.679 +    nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
   1.680 +  }
   1.681 +}
   1.682 +
   1.683 +void
   1.684 +nsHtml5TreeOpExecutor::Start()
   1.685 +{
   1.686 +  NS_PRECONDITION(!mStarted, "Tried to start when already started.");
   1.687 +  mStarted = true;
   1.688 +}
   1.689 +
   1.690 +void
   1.691 +nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(const char* aEncoding,
   1.692 +                                            int32_t aSource,
   1.693 +                                            uint32_t aLineNumber)
   1.694 +{
   1.695 +  EndDocUpdate();
   1.696 +
   1.697 +  if (MOZ_UNLIKELY(!mParser)) {
   1.698 +    // got terminate
   1.699 +    return;
   1.700 +  }
   1.701 +  
   1.702 +  nsCOMPtr<nsIWebShellServices> wss = do_QueryInterface(mDocShell);
   1.703 +  if (!wss) {
   1.704 +    return;
   1.705 +  }
   1.706 +
   1.707 +  // ask the webshellservice to load the URL
   1.708 +  if (NS_SUCCEEDED(wss->StopDocumentLoad())) {
   1.709 +    wss->ReloadDocument(aEncoding, aSource);
   1.710 +  }
   1.711 +  // if the charset switch was accepted, wss has called Terminate() on the
   1.712 +  // parser by now
   1.713 +
   1.714 +  if (!mParser) {
   1.715 +    // success
   1.716 +    if (aSource == kCharsetFromMetaTag) {
   1.717 +      MaybeComplainAboutCharset("EncLateMetaReload", false, aLineNumber);
   1.718 +    }
   1.719 +    return;
   1.720 +  }
   1.721 +
   1.722 +  if (aSource == kCharsetFromMetaTag) {
   1.723 +    MaybeComplainAboutCharset("EncLateMetaTooLate", true, aLineNumber);
   1.724 +  }
   1.725 +
   1.726 +  GetParser()->ContinueAfterFailedCharsetSwitch();
   1.727 +
   1.728 +  BeginDocUpdate();
   1.729 +}
   1.730 +
   1.731 +void
   1.732 +nsHtml5TreeOpExecutor::MaybeComplainAboutCharset(const char* aMsgId,
   1.733 +                                                 bool aError,
   1.734 +                                                 uint32_t aLineNumber)
   1.735 +{
   1.736 +  if (mAlreadyComplainedAboutCharset) {
   1.737 +    return;
   1.738 +  }
   1.739 +  // The EncNoDeclaration case for advertising iframes is so common that it
   1.740 +  // would result is way too many errors. The iframe case doesn't matter
   1.741 +  // when the ad is an image or a Flash animation anyway. When the ad is
   1.742 +  // textual, a misrendered ad probably isn't a huge loss for users.
   1.743 +  // Let's suppress the message in this case.
   1.744 +  // This means that errors about other different-origin iframes in mashups
   1.745 +  // are lost as well, but generally, the site author isn't in control of
   1.746 +  // the embedded different-origin pages anyway and can't fix problems even
   1.747 +  // if alerted about them.
   1.748 +  if (!strcmp(aMsgId, "EncNoDeclaration") && mDocShell) {
   1.749 +    nsCOMPtr<nsIDocShellTreeItem> parent;
   1.750 +    mDocShell->GetSameTypeParent(getter_AddRefs(parent));
   1.751 +    if (parent) {
   1.752 +      return;
   1.753 +    }
   1.754 +  }
   1.755 +  mAlreadyComplainedAboutCharset = true;
   1.756 +  nsContentUtils::ReportToConsole(aError ? nsIScriptError::errorFlag
   1.757 +                                         : nsIScriptError::warningFlag,
   1.758 +                                  NS_LITERAL_CSTRING("HTML parser"),
   1.759 +                                  mDocument,
   1.760 +                                  nsContentUtils::eHTMLPARSER_PROPERTIES,
   1.761 +                                  aMsgId,
   1.762 +                                  nullptr,
   1.763 +                                  0,
   1.764 +                                  nullptr,
   1.765 +                                  EmptyString(),
   1.766 +                                  aLineNumber);
   1.767 +}
   1.768 +
   1.769 +void
   1.770 +nsHtml5TreeOpExecutor::ComplainAboutBogusProtocolCharset(nsIDocument* aDoc)
   1.771 +{
   1.772 +  NS_ASSERTION(!mAlreadyComplainedAboutCharset,
   1.773 +               "How come we already managed to complain?");
   1.774 +  mAlreadyComplainedAboutCharset = true;
   1.775 +  nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
   1.776 +                                  NS_LITERAL_CSTRING("HTML parser"),
   1.777 +                                  aDoc,
   1.778 +                                  nsContentUtils::eHTMLPARSER_PROPERTIES,
   1.779 +                                  "EncProtocolUnsupported");
   1.780 +}
   1.781 +
   1.782 +nsHtml5Parser*
   1.783 +nsHtml5TreeOpExecutor::GetParser()
   1.784 +{
   1.785 +  MOZ_ASSERT(!mRunsToCompletion);
   1.786 +  return static_cast<nsHtml5Parser*>(mParser.get());
   1.787 +}
   1.788 +
   1.789 +void
   1.790 +nsHtml5TreeOpExecutor::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue)
   1.791 +{
   1.792 +  NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution.");
   1.793 +  if (mOpQueue.IsEmpty()) {
   1.794 +    mOpQueue.SwapElements(aOpQueue);
   1.795 +    return;
   1.796 +  }
   1.797 +  mOpQueue.MoveElementsFrom(aOpQueue);
   1.798 +}
   1.799 +
   1.800 +void
   1.801 +nsHtml5TreeOpExecutor::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, int32_t aLine)
   1.802 +{
   1.803 +  GetParser()->InitializeDocWriteParserState(aState, aLine);
   1.804 +}
   1.805 +
   1.806 +nsIURI*
   1.807 +nsHtml5TreeOpExecutor::GetViewSourceBaseURI()
   1.808 +{
   1.809 +  if (!mViewSourceBaseURI) {
   1.810 +
   1.811 +    // We query the channel for the baseURI because in certain situations it
   1.812 +    // cannot otherwise be determined. If this process fails, fall back to the
   1.813 +    // standard method.
   1.814 +    nsCOMPtr<nsIViewSourceChannel> vsc;
   1.815 +    vsc = do_QueryInterface(mDocument->GetChannel());
   1.816 +    if (vsc) {
   1.817 +      nsresult rv =  vsc->GetBaseURI(getter_AddRefs(mViewSourceBaseURI));
   1.818 +      if (NS_SUCCEEDED(rv) && mViewSourceBaseURI) {
   1.819 +        return mViewSourceBaseURI;
   1.820 +      }
   1.821 +    }
   1.822 +
   1.823 +    nsCOMPtr<nsIURI> orig = mDocument->GetOriginalURI();
   1.824 +    bool isViewSource;
   1.825 +    orig->SchemeIs("view-source", &isViewSource);
   1.826 +    if (isViewSource) {
   1.827 +      nsCOMPtr<nsINestedURI> nested = do_QueryInterface(orig);
   1.828 +      NS_ASSERTION(nested, "URI with scheme view-source didn't QI to nested!");
   1.829 +      nested->GetInnerURI(getter_AddRefs(mViewSourceBaseURI));
   1.830 +    } else {
   1.831 +      // Fail gracefully if the base URL isn't a view-source: URL.
   1.832 +      // Not sure if this can ever happen.
   1.833 +      mViewSourceBaseURI = orig;
   1.834 +    }
   1.835 +  }
   1.836 +  return mViewSourceBaseURI;
   1.837 +}
   1.838 +
   1.839 +//static
   1.840 +void
   1.841 +nsHtml5TreeOpExecutor::InitializeStatics()
   1.842 +{
   1.843 +  mozilla::Preferences::AddBoolVarCache(&sExternalViewSource,
   1.844 +                                        "view_source.editor.external");
   1.845 +}
   1.846 +
   1.847 +bool
   1.848 +nsHtml5TreeOpExecutor::IsExternalViewSource()
   1.849 +{
   1.850 +  if (!sExternalViewSource) {
   1.851 +    return false;
   1.852 +  }
   1.853 +  bool isViewSource = false;
   1.854 +  if (mDocumentURI) {
   1.855 +    mDocumentURI->SchemeIs("view-source", &isViewSource);
   1.856 +  }
   1.857 +  return isViewSource;
   1.858 +}
   1.859 +
   1.860 +// Speculative loading
   1.861 +
   1.862 +already_AddRefed<nsIURI>
   1.863 +nsHtml5TreeOpExecutor::ConvertIfNotPreloadedYet(const nsAString& aURL)
   1.864 +{
   1.865 +  if (aURL.IsEmpty()) {
   1.866 +    return nullptr;
   1.867 +  }
   1.868 +  // The URL of the document without <base>
   1.869 +  nsIURI* documentURI = mDocument->GetDocumentURI();
   1.870 +  // The URL of the document with non-speculative <base>
   1.871 +  nsIURI* documentBaseURI = mDocument->GetDocBaseURI();
   1.872 +
   1.873 +  // If the two above are different, use documentBaseURI. If they are the
   1.874 +  // same, the document object isn't aware of a <base>, so attempt to use the
   1.875 +  // mSpeculationBaseURI or, failing, that, documentURI.
   1.876 +  nsIURI* base = (documentURI == documentBaseURI) ?
   1.877 +                  (mSpeculationBaseURI ?
   1.878 +                   mSpeculationBaseURI.get() : documentURI)
   1.879 +                 : documentBaseURI;
   1.880 +  const nsCString& charset = mDocument->GetDocumentCharacterSet();
   1.881 +  nsCOMPtr<nsIURI> uri;
   1.882 +  nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, charset.get(), base);
   1.883 +  if (NS_FAILED(rv)) {
   1.884 +    NS_WARNING("Failed to create a URI");
   1.885 +    return nullptr;
   1.886 +  }
   1.887 +  nsAutoCString spec;
   1.888 +  uri->GetSpec(spec);
   1.889 +  if (mPreloadedURLs.Contains(spec)) {
   1.890 +    return nullptr;
   1.891 +  }
   1.892 +  mPreloadedURLs.PutEntry(spec);
   1.893 +  return uri.forget();
   1.894 +}
   1.895 +
   1.896 +void
   1.897 +nsHtml5TreeOpExecutor::PreloadScript(const nsAString& aURL,
   1.898 +                                     const nsAString& aCharset,
   1.899 +                                     const nsAString& aType,
   1.900 +                                     const nsAString& aCrossOrigin,
   1.901 +                                     bool aScriptFromHead)
   1.902 +{
   1.903 +  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
   1.904 +  if (!uri) {
   1.905 +    return;
   1.906 +  }
   1.907 +  mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType, aCrossOrigin,
   1.908 +                                        aScriptFromHead);
   1.909 +}
   1.910 +
   1.911 +void
   1.912 +nsHtml5TreeOpExecutor::PreloadStyle(const nsAString& aURL,
   1.913 +                                    const nsAString& aCharset,
   1.914 +                                    const nsAString& aCrossOrigin)
   1.915 +{
   1.916 +  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
   1.917 +  if (!uri) {
   1.918 +    return;
   1.919 +  }
   1.920 +  mDocument->PreloadStyle(uri, aCharset, aCrossOrigin);
   1.921 +}
   1.922 +
   1.923 +void
   1.924 +nsHtml5TreeOpExecutor::PreloadImage(const nsAString& aURL,
   1.925 +                                    const nsAString& aCrossOrigin)
   1.926 +{
   1.927 +  nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
   1.928 +  if (!uri) {
   1.929 +    return;
   1.930 +  }
   1.931 +  mDocument->MaybePreLoadImage(uri, aCrossOrigin);
   1.932 +}
   1.933 +
   1.934 +void
   1.935 +nsHtml5TreeOpExecutor::SetSpeculationBase(const nsAString& aURL)
   1.936 +{
   1.937 +  if (mSpeculationBaseURI) {
   1.938 +    // the first one wins
   1.939 +    return;
   1.940 +  }
   1.941 +  const nsCString& charset = mDocument->GetDocumentCharacterSet();
   1.942 +  DebugOnly<nsresult> rv = NS_NewURI(getter_AddRefs(mSpeculationBaseURI), aURL,
   1.943 +                                     charset.get(), mDocument->GetDocumentURI());
   1.944 +  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to create a URI");
   1.945 +}
   1.946 +
   1.947 +#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
   1.948 +uint32_t nsHtml5TreeOpExecutor::sAppendBatchMaxSize = 0;
   1.949 +uint32_t nsHtml5TreeOpExecutor::sAppendBatchSlotsExamined = 0;
   1.950 +uint32_t nsHtml5TreeOpExecutor::sAppendBatchExaminations = 0;
   1.951 +uint32_t nsHtml5TreeOpExecutor::sLongestTimeOffTheEventLoop = 0;
   1.952 +uint32_t nsHtml5TreeOpExecutor::sTimesFlushLoopInterrupted = 0;
   1.953 +#endif
   1.954 +bool nsHtml5TreeOpExecutor::sExternalViewSource = false;

mercurial