tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsHtml5Parser.cpp (28606B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set sw=2 ts=2 et tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "nsHtml5Parser.h"
      8 
      9 #include "ErrorList.h"
     10 #include "encoding_rs_statics.h"
     11 #include "mozilla/AutoRestore.h"
     12 #include "mozilla/UniquePtr.h"
     13 #include "nsCRT.h"
     14 #include "nsContentUtils.h"  // for kLoadAsData
     15 #include "nsHtml5AtomTable.h"
     16 #include "nsHtml5DependentUTF16Buffer.h"
     17 #include "nsHtml5Tokenizer.h"
     18 #include "nsHtml5TreeBuilder.h"
     19 #include "nsNetUtil.h"
     20 
     21 NS_INTERFACE_TABLE_HEAD(nsHtml5Parser)
     22  NS_INTERFACE_TABLE(nsHtml5Parser, nsIParser, nsISupportsWeakReference,
     23                     nsIStreamListener)
     24  NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsHtml5Parser)
     25 NS_INTERFACE_MAP_END
     26 
     27 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHtml5Parser)
     28 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHtml5Parser)
     29 
     30 NS_IMPL_CYCLE_COLLECTION_CLASS(nsHtml5Parser)
     31 
     32 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsHtml5Parser)
     33  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExecutor)
     34  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(GetStreamParser())
     35 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     36 
     37 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHtml5Parser)
     38  NS_IMPL_CYCLE_COLLECTION_UNLINK(mExecutor)
     39  NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
     40  tmp->DropStreamParser();
     41 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     42 
     43 nsHtml5Parser::nsHtml5Parser()
     44    : mAboutBlankMode(false),
     45      mLastWasCR(false),
     46      mDocWriteSpeculativeLastWasCR(false),
     47      mBlocked(0),
     48      mDocWriteSpeculatorActive(false),
     49      mScriptNestingLevel(0),
     50      mTerminationStarted(false),
     51      mDocumentClosed(false),
     52      mInDocumentWrite(false),
     53      mInsertionPointPermanentlyUndefined(false),
     54      mFirstBuffer(new nsHtml5OwningUTF16Buffer((void*)nullptr)),
     55      mLastBuffer(mFirstBuffer),
     56      mExecutor(new nsHtml5TreeOpExecutor()),
     57      mTreeBuilder(new nsHtml5TreeBuilder(mExecutor, nullptr, false)),
     58      mTokenizer(new nsHtml5Tokenizer(mTreeBuilder.get(), false)),
     59      mRootContextLineNumber(1),
     60      mReturnToStreamParserPermitted(false) {
     61  mTokenizer->setInterner(&mAtomTable);
     62 }
     63 
     64 nsHtml5Parser::~nsHtml5Parser() {
     65  mTokenizer->end();
     66  if (mDocWriteSpeculativeTokenizer) {
     67    mDocWriteSpeculativeTokenizer->end();
     68  }
     69 }
     70 
     71 NS_IMETHODIMP_(void)
     72 nsHtml5Parser::SetContentSink(nsIContentSink* aSink) {
     73  NS_ASSERTION(aSink == static_cast<nsIContentSink*>(mExecutor),
     74               "Attempt to set a foreign sink.");
     75 }
     76 
     77 NS_IMETHODIMP_(nsIContentSink*)
     78 nsHtml5Parser::GetContentSink() {
     79  return static_cast<nsIContentSink*>(mExecutor);
     80 }
     81 
     82 NS_IMETHODIMP_(void)
     83 nsHtml5Parser::GetCommand(nsCString& aCommand) {
     84  aCommand.AssignLiteral("view");
     85 }
     86 
     87 NS_IMETHODIMP_(void)
     88 nsHtml5Parser::SetCommand(const char* aCommand) {
     89  NS_ASSERTION(!strcmp(aCommand, "view") || !strcmp(aCommand, "view-source") ||
     90                   !strcmp(aCommand, "external-resource") ||
     91                   !strcmp(aCommand, "import") ||
     92                   !strcmp(aCommand, kLoadAsData),
     93               "Unsupported parser command");
     94 }
     95 
     96 NS_IMETHODIMP_(void)
     97 nsHtml5Parser::SetCommand(eParserCommands aParserCommand) {
     98  NS_ASSERTION(aParserCommand == eViewNormal,
     99               "Parser command was not eViewNormal.");
    100 }
    101 
    102 void nsHtml5Parser::SetDocumentCharset(NotNull<const Encoding*> aEncoding,
    103                                       int32_t aCharsetSource,
    104                                       bool aForceAutoDetection) {
    105  MOZ_ASSERT(!mExecutor->HasStarted(), "Document charset set too late.");
    106  if (mAboutBlankMode) {
    107    MOZ_ASSERT(aEncoding == UTF_8_ENCODING);
    108  } else {
    109    MOZ_ASSERT(GetStreamParser(), "Setting charset on a script-only parser.");
    110    GetStreamParser()->SetDocumentCharset(
    111        aEncoding, (nsCharsetSource)aCharsetSource, aForceAutoDetection);
    112  }
    113  mExecutor->SetDocumentCharsetAndSource(aEncoding,
    114                                         (nsCharsetSource)aCharsetSource);
    115 }
    116 
    117 nsresult nsHtml5Parser::GetChannel(nsIChannel** aChannel) {
    118  if (GetStreamParser()) {
    119    return GetStreamParser()->GetChannel(aChannel);
    120  }
    121  return NS_ERROR_NOT_AVAILABLE;
    122 }
    123 
    124 nsIStreamListener* nsHtml5Parser::GetStreamListener() {
    125  if (mAboutBlankMode) {
    126    return this;
    127  }
    128  return mStreamListener;
    129 }
    130 
    131 NS_IMETHODIMP
    132 nsHtml5Parser::ContinueInterruptedParsing() {
    133  MOZ_ASSERT_UNREACHABLE("Don't call. For interface compat only.");
    134  return NS_ERROR_NOT_IMPLEMENTED;
    135 }
    136 
    137 NS_IMETHODIMP_(void)
    138 nsHtml5Parser::BlockParser() {
    139  MOZ_ASSERT(!mAboutBlankMode, "Must not block about:blank");
    140  mBlocked++;
    141 }
    142 
    143 NS_IMETHODIMP_(void)
    144 nsHtml5Parser::UnblockParser() {
    145  MOZ_ASSERT(!mAboutBlankMode, "Must not unblock about:blank");
    146  MOZ_DIAGNOSTIC_ASSERT(mBlocked > 0);
    147  if (MOZ_LIKELY(mBlocked > 0)) {
    148    mBlocked--;
    149  }
    150  if (MOZ_LIKELY(mBlocked == 0) && mExecutor) {
    151    mExecutor->ContinueInterruptedParsingAsync();
    152  }
    153 }
    154 
    155 NS_IMETHODIMP_(void)
    156 nsHtml5Parser::ContinueInterruptedParsingAsync() {
    157  if (mExecutor) {
    158    mExecutor->ContinueInterruptedParsingAsync();
    159  }
    160 }
    161 
    162 NS_IMETHODIMP_(bool)
    163 nsHtml5Parser::IsParserEnabled() { return !mBlocked; }
    164 
    165 NS_IMETHODIMP_(bool)
    166 nsHtml5Parser::IsParserClosed() { return mDocumentClosed; }
    167 
    168 NS_IMETHODIMP_(bool)
    169 nsHtml5Parser::IsComplete() { return mExecutor->IsComplete(); }
    170 
    171 NS_IMETHODIMP
    172 nsHtml5Parser::Parse(nsIURI* aURL) {
    173  /*
    174   * Do NOT cause WillBuildModel to be called synchronously from here!
    175   * The document won't be ready for it until OnStartRequest!
    176   */
    177  MOZ_ASSERT(!mExecutor->HasStarted(),
    178             "Tried to start parse without initializing the parser.");
    179  if (!mAboutBlankMode) {
    180    MOZ_ASSERT(GetStreamParser(),
    181               "Can't call this Parse() variant on script-created parser");
    182 
    183    GetStreamParser()->SetViewSourceTitle(
    184        aURL);  // In case we're viewing source
    185    mExecutor->SetStreamParser(GetStreamParser());
    186  }
    187  mExecutor->SetParser(this);
    188  return NS_OK;
    189 }
    190 
    191 nsresult nsHtml5Parser::Parse(const nsAString& aSourceBuffer, void* aKey,
    192                              bool aLastCall) {
    193  nsresult rv;
    194  if (NS_FAILED(rv = mExecutor->IsBroken())) {
    195    return rv;
    196  }
    197  if (aSourceBuffer.Length() > INT32_MAX) {
    198    return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
    199  }
    200 
    201  // Maintain a reference to ourselves so we don't go away
    202  // till we're completely done. The old parser grips itself in this method.
    203  nsCOMPtr<nsIParser> kungFuDeathGrip(this);
    204 
    205  // Gripping the other objects just in case, since the other old grip
    206  // required grips to these, too.
    207  RefPtr<nsHtml5StreamParser> streamKungFuDeathGrip(GetStreamParser());
    208  (void)streamKungFuDeathGrip;  // Not used within function
    209  RefPtr<nsHtml5TreeOpExecutor> executor(mExecutor);
    210 
    211  MOZ_RELEASE_ASSERT(executor->HasStarted());
    212 
    213  // Return early if the parser has processed EOF
    214  if (executor->IsComplete()) {
    215    return NS_OK;
    216  }
    217 
    218  if (aLastCall && aSourceBuffer.IsEmpty() && !aKey) {
    219    // document.close()
    220    NS_ASSERTION(!GetStreamParser(),
    221                 "Had stream parser but got document.close().");
    222    if (mDocumentClosed) {
    223      // already closed
    224      return NS_OK;
    225    }
    226    mDocumentClosed = true;
    227    if (!mBlocked && !mInDocumentWrite && !executor->IsFlushing()) {
    228      return ParseUntilBlocked();
    229    }
    230    return NS_OK;
    231  }
    232 
    233  // If we got this far, we are dealing with a document.write or
    234  // document.writeln call--not document.close().
    235 
    236  MOZ_RELEASE_ASSERT(
    237      IsInsertionPointDefined(),
    238      "Doc.write reached parser with undefined insertion point.");
    239 
    240  MOZ_RELEASE_ASSERT(!(GetStreamParser() && !aKey),
    241                     "Got a null key in a non-script-created parser");
    242 
    243  // XXX is this optimization bogus?
    244  if (aSourceBuffer.IsEmpty()) {
    245    return NS_OK;
    246  }
    247 
    248  // This guard is here to prevent document.close from tokenizing synchronously
    249  // while a document.write (that wrote the script that called document.close!)
    250  // is still on the call stack.
    251  mozilla::AutoRestore<bool> guard(mInDocumentWrite);
    252  mInDocumentWrite = true;
    253 
    254  // The script is identified by aKey. If there's nothing in the buffer
    255  // chain for that key, we'll insert at the head of the queue.
    256  // When the script leaves something in the queue, a zero-length
    257  // key-holder "buffer" is inserted in the queue. If the same script
    258  // leaves something in the chain again, it will be inserted immediately
    259  // before the old key holder belonging to the same script.
    260  //
    261  // We don't do the actual data insertion yet in the hope that the data gets
    262  // tokenized and there no data or less data to copy to the heap after
    263  // tokenization. Also, this way, we avoid inserting one empty data buffer
    264  // per document.write, which matters for performance when the parser isn't
    265  // blocked and a badly-authored script calls document.write() once per
    266  // input character. (As seen in a benchmark!)
    267  //
    268  // The insertion into the input stream happens conceptually before anything
    269  // gets tokenized. To make sure multi-level document.write works right,
    270  // it's necessary to establish the location of our parser key up front
    271  // in case this is the first write with this key.
    272  //
    273  // In a document.open() case, the first write level has a null key, so that
    274  // case is handled separately, because normal buffers containing data
    275  // have null keys.
    276 
    277  // These don't need to be owning references, because they always point to
    278  // the buffer queue and buffers can't be removed from the buffer queue
    279  // before document.write() returns. The buffer queue clean-up happens the
    280  // next time ParseUntilBlocked() is called.
    281  // However, they are made owning just in case the reasoning above is flawed
    282  // and a flaw would lead to worse problems with plain pointers. If this
    283  // turns out to be a perf problem, it's worthwhile to consider making
    284  // prevSearchbuf a plain pointer again.
    285  RefPtr<nsHtml5OwningUTF16Buffer> prevSearchBuf;
    286  RefPtr<nsHtml5OwningUTF16Buffer> firstLevelMarker;
    287 
    288  if (aKey) {
    289    if (mFirstBuffer == mLastBuffer) {
    290      nsHtml5OwningUTF16Buffer* keyHolder = new nsHtml5OwningUTF16Buffer(aKey);
    291      keyHolder->next = mLastBuffer;
    292      mFirstBuffer = keyHolder;
    293    } else if (mFirstBuffer->key != aKey) {
    294      prevSearchBuf = mFirstBuffer;
    295      for (;;) {
    296        if (prevSearchBuf->next == mLastBuffer) {
    297          // key was not found
    298          nsHtml5OwningUTF16Buffer* keyHolder =
    299              new nsHtml5OwningUTF16Buffer(aKey);
    300          keyHolder->next = mFirstBuffer;
    301          mFirstBuffer = keyHolder;
    302          prevSearchBuf = nullptr;
    303          break;
    304        }
    305        if (prevSearchBuf->next->key == aKey) {
    306          // found a key holder
    307          break;
    308        }
    309        prevSearchBuf = prevSearchBuf->next;
    310      }
    311    }  // else mFirstBuffer is the keyholder
    312 
    313    // prevSearchBuf is the previous buffer before the keyholder or null if
    314    // there isn't one.
    315  } else {
    316    // We have a first-level write in the document.open() case. We insert before
    317    // mLastBuffer, effectively, by making mLastBuffer be a new sentinel object
    318    // and redesignating the previous mLastBuffer as our firstLevelMarker.  We
    319    // need to put a marker there, because otherwise additional document.writes
    320    // from nested event loops would insert in the wrong place. Sigh.
    321    mLastBuffer->next = new nsHtml5OwningUTF16Buffer((void*)nullptr);
    322    firstLevelMarker = mLastBuffer;
    323    mLastBuffer = mLastBuffer->next;
    324  }
    325 
    326  nsHtml5DependentUTF16Buffer stackBuffer(aSourceBuffer);
    327 
    328  while (!mBlocked && stackBuffer.hasMore()) {
    329    stackBuffer.adjust(mLastWasCR);
    330    mLastWasCR = false;
    331    if (stackBuffer.hasMore()) {
    332      int32_t lineNumberSave;
    333      bool inRootContext = (!GetStreamParser() && !aKey);
    334      if (inRootContext) {
    335        mTokenizer->setLineNumber(mRootContextLineNumber);
    336      } else {
    337        // we aren't the root context, so save the line number on the
    338        // *stack* so that we can restore it.
    339        lineNumberSave = mTokenizer->getLineNumber();
    340      }
    341 
    342      if (!mTokenizer->EnsureBufferSpace(stackBuffer.getLength())) {
    343        return executor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
    344      }
    345      mLastWasCR = mTokenizer->tokenizeBuffer(&stackBuffer);
    346      if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) {
    347        return executor->MarkAsBroken(rv);
    348      }
    349 
    350      if (inRootContext) {
    351        mRootContextLineNumber = mTokenizer->getLineNumber();
    352      } else {
    353        mTokenizer->setLineNumber(lineNumberSave);
    354      }
    355 
    356      if (mTreeBuilder->HasScriptThatMayDocumentWriteOrBlock()) {
    357        auto r = mTreeBuilder->Flush();  // Move ops to the executor
    358        if (r.isErr()) {
    359          return executor->MarkAsBroken(r.unwrapErr());
    360        }
    361        rv = executor->FlushDocumentWrite();  // run the ops
    362        NS_ENSURE_SUCCESS(rv, rv);
    363        // Flushing tree ops can cause all sorts of things.
    364        // Return early if the parser got terminated.
    365        if (executor->IsComplete()) {
    366          return NS_OK;
    367        }
    368      }
    369      // Ignore suspension requests
    370    }
    371  }
    372 
    373  RefPtr<nsHtml5OwningUTF16Buffer> heapBuffer;
    374  if (stackBuffer.hasMore()) {
    375    // The buffer wasn't tokenized to completion. Create a copy of the tail
    376    // on the heap.
    377    heapBuffer = stackBuffer.FalliblyCopyAsOwningBuffer();
    378    if (!heapBuffer) {
    379      // Allocation failed. The parser is now broken.
    380      return executor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
    381    }
    382  }
    383 
    384  if (heapBuffer) {
    385    // We have something to insert before the keyholder holding in the non-null
    386    // aKey case and we have something to swap into firstLevelMarker in the
    387    // null aKey case.
    388    if (aKey) {
    389      NS_ASSERTION(mFirstBuffer != mLastBuffer, "Where's the keyholder?");
    390      // the key holder is still somewhere further down the list from
    391      // prevSearchBuf (which may be null)
    392      if (mFirstBuffer->key == aKey) {
    393        NS_ASSERTION(
    394            !prevSearchBuf,
    395            "Non-null prevSearchBuf when mFirstBuffer is the key holder?");
    396        heapBuffer->next = mFirstBuffer;
    397        mFirstBuffer = heapBuffer;
    398      } else {
    399        if (!prevSearchBuf) {
    400          prevSearchBuf = mFirstBuffer;
    401        }
    402        // We created a key holder earlier, so we will find it without walking
    403        // past the end of the list.
    404        while (prevSearchBuf->next->key != aKey) {
    405          prevSearchBuf = prevSearchBuf->next;
    406        }
    407        heapBuffer->next = prevSearchBuf->next;
    408        prevSearchBuf->next = heapBuffer;
    409      }
    410    } else {
    411      NS_ASSERTION(firstLevelMarker, "How come we don't have a marker.");
    412      firstLevelMarker->Swap(heapBuffer);
    413    }
    414  }
    415 
    416  if (!mBlocked) {  // buffer was tokenized to completion
    417    NS_ASSERTION(!stackBuffer.hasMore(),
    418                 "Buffer wasn't tokenized to completion?");
    419    // Scripting semantics require a forced tree builder flush here
    420    auto r = mTreeBuilder->Flush();  // Move ops to the executor
    421    if (r.isErr()) {
    422      return executor->MarkAsBroken(r.unwrapErr());
    423    }
    424    rv = executor->FlushDocumentWrite();  // run the ops
    425    NS_ENSURE_SUCCESS(rv, rv);
    426  } else if (stackBuffer.hasMore()) {
    427    // The buffer wasn't tokenized to completion. Tokenize the untokenized
    428    // content in order to preload stuff. This content will be retokenized
    429    // later for normal parsing.
    430    if (!mDocWriteSpeculatorActive) {
    431      mDocWriteSpeculatorActive = true;
    432      if (!mDocWriteSpeculativeTreeBuilder) {
    433        // Lazily initialize if uninitialized
    434        mDocWriteSpeculativeTreeBuilder =
    435            mozilla::MakeUnique<nsHtml5TreeBuilder>(nullptr,
    436                                                    executor->GetStage(), true);
    437        mDocWriteSpeculativeTreeBuilder->setScriptingEnabled(
    438            mTreeBuilder->isScriptingEnabled());
    439        mDocWriteSpeculativeTreeBuilder->setAllowDeclarativeShadowRoots(
    440            mTreeBuilder->isAllowDeclarativeShadowRoots());
    441        mDocWriteSpeculativeTokenizer = mozilla::MakeUnique<nsHtml5Tokenizer>(
    442            mDocWriteSpeculativeTreeBuilder.get(), false);
    443        mDocWriteSpeculativeTokenizer->setInterner(&mAtomTable);
    444        mDocWriteSpeculativeTokenizer->start();
    445      }
    446      mDocWriteSpeculativeTokenizer->resetToDataState();
    447      mDocWriteSpeculativeTreeBuilder->loadState(mTreeBuilder.get());
    448      mDocWriteSpeculativeLastWasCR = false;
    449    }
    450 
    451    // Note that with multilevel document.write if we didn't just activate the
    452    // speculator, it's possible that the speculator is now in the wrong state.
    453    // That's OK for the sake of simplicity. The worst that can happen is
    454    // that the speculative loads aren't exactly right. The content will be
    455    // reparsed anyway for non-preload purposes.
    456 
    457    // The buffer position for subsequent non-speculative parsing now lives
    458    // in heapBuffer, so it's ok to let the buffer position of stackBuffer
    459    // to be overwritten and not restored below.
    460    while (stackBuffer.hasMore()) {
    461      stackBuffer.adjust(mDocWriteSpeculativeLastWasCR);
    462      if (stackBuffer.hasMore()) {
    463        if (!mDocWriteSpeculativeTokenizer->EnsureBufferSpace(
    464                stackBuffer.getLength())) {
    465          return executor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
    466        }
    467        mDocWriteSpeculativeLastWasCR =
    468            mDocWriteSpeculativeTokenizer->tokenizeBuffer(&stackBuffer);
    469        nsresult rv;
    470        if (NS_FAILED((rv = mDocWriteSpeculativeTreeBuilder->IsBroken()))) {
    471          return executor->MarkAsBroken(rv);
    472        }
    473      }
    474    }
    475 
    476    auto r = mDocWriteSpeculativeTreeBuilder->Flush();
    477    if (r.isErr()) {
    478      return executor->MarkAsBroken(r.unwrapErr());
    479    }
    480    mDocWriteSpeculativeTreeBuilder->DropHandles();
    481    executor->FlushSpeculativeLoads();
    482  }
    483 
    484  return NS_OK;
    485 }
    486 
    487 NS_IMETHODIMP
    488 nsHtml5Parser::Terminate() {
    489  if (mTerminationStarted) {
    490    return NS_OK;
    491  }
    492  // `mExecutor->IsComplete()` becomes true too late, if JavaScript event
    493  // handlers cause a nested event loop or calls back into the parser during the
    494  // termination. It's also hard to make `mExecutor->IsComplete()` to become
    495  // true sooner. Hence, let's have yet another flag to prevent further parsing.
    496  mTerminationStarted = true;
    497  // We should only call DidBuildModel once, so don't do anything if this is
    498  // the second time that Terminate has been called.
    499  if (mExecutor->IsComplete()) {
    500    return NS_OK;
    501  }
    502  // XXX - [ until we figure out a way to break parser-sink circularity ]
    503  // Hack - Hold a reference until we are completely done...
    504  nsCOMPtr<nsIParser> kungFuDeathGrip(this);
    505  RefPtr<nsHtml5StreamParser> streamParser(GetStreamParser());
    506  RefPtr<nsHtml5TreeOpExecutor> executor(mExecutor);
    507  if (streamParser) {
    508    streamParser->Terminate();
    509  }
    510  return executor->DidBuildModel(true);
    511 }
    512 
    513 bool nsHtml5Parser::IsInsertionPointDefined() {
    514  return !mExecutor->IsFlushing() && !mInsertionPointPermanentlyUndefined &&
    515         (!GetStreamParser() || mScriptNestingLevel != 0);
    516 }
    517 
    518 void nsHtml5Parser::IncrementScriptNestingLevel() { ++mScriptNestingLevel; }
    519 
    520 void nsHtml5Parser::DecrementScriptNestingLevel() { --mScriptNestingLevel; }
    521 
    522 bool nsHtml5Parser::HasNonzeroScriptNestingLevel() const {
    523  return mScriptNestingLevel != 0;
    524 }
    525 
    526 void nsHtml5Parser::MarkAsNotScriptCreated(const char* aCommand) {
    527  MOZ_ASSERT(!mStreamListener, "Must not call this twice.");
    528  eParserMode mode = NORMAL;
    529  if (!nsCRT::strcmp(aCommand, "view-source")) {
    530    mode = VIEW_SOURCE_HTML;
    531  } else if (!nsCRT::strcmp(aCommand, "view-source-xml")) {
    532    mode = VIEW_SOURCE_XML;
    533  } else if (!nsCRT::strcmp(aCommand, "view-source-plain")) {
    534    mode = VIEW_SOURCE_PLAIN;
    535  } else if (!nsCRT::strcmp(aCommand, "plain-text")) {
    536    mode = PLAIN_TEXT;
    537  } else if (!nsCRT::strcmp(aCommand, kLoadAsData)) {
    538    mode = LOAD_AS_DATA;
    539  } else if (!nsCRT::strcmp(aCommand, "about-blank")) {
    540    mode = ABOUT_BLANK;
    541  }
    542 #ifdef DEBUG
    543  else {
    544    NS_ASSERTION(!nsCRT::strcmp(aCommand, "view") ||
    545                     !nsCRT::strcmp(aCommand, "external-resource") ||
    546                     !nsCRT::strcmp(aCommand, "import"),
    547                 "Unsupported parser command!");
    548  }
    549 #endif
    550  if (mode == ABOUT_BLANK) {
    551    mAboutBlankMode = true;
    552  } else {
    553    mStreamListener = new nsHtml5StreamListener(
    554        new nsHtml5StreamParser(mExecutor, this, mode));
    555  }
    556 }
    557 
    558 bool nsHtml5Parser::IsScriptCreated() { return !GetStreamParser(); }
    559 
    560 bool nsHtml5Parser::IsAboutBlankMode() { return mAboutBlankMode; }
    561 
    562 /* End nsIParser  */
    563 
    564 // not from interface
    565 nsresult nsHtml5Parser::ParseUntilBlocked() {
    566  nsresult rv = mExecutor->IsBroken();
    567  NS_ENSURE_SUCCESS(rv, rv);
    568  if (mBlocked || mInsertionPointPermanentlyUndefined || mTerminationStarted ||
    569      mExecutor->IsComplete()) {
    570    return NS_OK;
    571  }
    572  NS_ASSERTION(mExecutor->HasStarted(), "Bad life cycle.");
    573  NS_ASSERTION(!mInDocumentWrite,
    574               "ParseUntilBlocked entered while in doc.write!");
    575 
    576  mDocWriteSpeculatorActive = false;
    577 
    578  for (;;) {
    579    if (!mFirstBuffer->hasMore()) {
    580      if (mFirstBuffer == mLastBuffer) {
    581        if (mExecutor->IsComplete()) {
    582          // something like cache manisfests stopped the parse in mid-flight
    583          return NS_OK;
    584        }
    585        if (mDocumentClosed) {
    586          PermanentlyUndefineInsertionPoint();
    587          nsresult rv;
    588          MOZ_RELEASE_ASSERT(
    589              !GetStreamParser(),
    590              "This should only happen with script-created parser.");
    591          if (NS_SUCCEEDED((rv = mExecutor->IsBroken()))) {
    592            mTokenizer->eof();
    593            if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) {
    594              mExecutor->MarkAsBroken(rv);
    595            } else {
    596              mTreeBuilder->StreamEnded();
    597            }
    598          }
    599          auto r = mTreeBuilder->Flush();
    600          if (r.isErr()) {
    601            return mExecutor->MarkAsBroken(r.unwrapErr());
    602          }
    603          mExecutor->FlushDocumentWrite();
    604          // The below call does memory cleanup, so call it even if the
    605          // parser has been marked as broken.
    606          mTokenizer->end();
    607          return rv;
    608        }
    609        // never release the last buffer.
    610        NS_ASSERTION(!mLastBuffer->getStart() && !mLastBuffer->getEnd(),
    611                     "Sentinel buffer had its indeces changed.");
    612        if (GetStreamParser()) {
    613          if (mReturnToStreamParserPermitted &&
    614              !mExecutor->IsScriptExecuting()) {
    615            auto r = mTreeBuilder->Flush();
    616            if (r.isErr()) {
    617              return mExecutor->MarkAsBroken(r.unwrapErr());
    618            }
    619            mReturnToStreamParserPermitted = false;
    620            GetStreamParser()->ContinueAfterScriptsOrEncodingCommitment(
    621                mTokenizer.get(), mTreeBuilder.get(), mLastWasCR);
    622          }
    623        } else {
    624          // Script-created parser
    625          auto r = mTreeBuilder->Flush();
    626          if (r.isErr()) {
    627            return mExecutor->MarkAsBroken(r.unwrapErr());
    628          }
    629          // No need to flush the executor, because the executor is already
    630          // in a flush
    631          NS_ASSERTION(mExecutor->IsInFlushLoop(),
    632                       "How did we come here without being in the flush loop?");
    633        }
    634        return NS_OK;  // no more data for now but expecting more
    635      }
    636      mFirstBuffer = mFirstBuffer->next;
    637      continue;
    638    }
    639 
    640    if (mBlocked || mExecutor->IsComplete()) {
    641      return NS_OK;
    642    }
    643 
    644    // now we have a non-empty buffer
    645    mFirstBuffer->adjust(mLastWasCR);
    646    mLastWasCR = false;
    647    if (mFirstBuffer->hasMore()) {
    648      bool inRootContext = (!GetStreamParser() && !mFirstBuffer->key);
    649      if (inRootContext) {
    650        mTokenizer->setLineNumber(mRootContextLineNumber);
    651      }
    652      if (!mTokenizer->EnsureBufferSpace(mFirstBuffer->getLength())) {
    653        return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
    654      }
    655      mLastWasCR = mTokenizer->tokenizeBuffer(mFirstBuffer);
    656      nsresult rv;
    657      if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) {
    658        return mExecutor->MarkAsBroken(rv);
    659      }
    660      if (inRootContext) {
    661        mRootContextLineNumber = mTokenizer->getLineNumber();
    662      }
    663      if (mTreeBuilder->HasScriptThatMayDocumentWriteOrBlock()) {
    664        auto r = mTreeBuilder->Flush();
    665        if (r.isErr()) {
    666          return mExecutor->MarkAsBroken(r.unwrapErr());
    667        }
    668        rv = mExecutor->FlushDocumentWrite();
    669        NS_ENSURE_SUCCESS(rv, rv);
    670      }
    671      if (mBlocked) {
    672        return NS_OK;
    673      }
    674    }
    675  }
    676 }
    677 
    678 nsresult nsHtml5Parser::StartExecutor() {
    679  MOZ_ASSERT(!GetStreamParser(),
    680             "Had stream parser but document.write started life cycle.");
    681  // This is part of the setup document.open() does.
    682  RefPtr<nsHtml5TreeOpExecutor> executor(mExecutor);
    683  executor->SetParser(this);
    684  mTreeBuilder->setScriptingEnabled(executor->IsScriptEnabled());
    685  mTreeBuilder->setAllowDeclarativeShadowRoots(
    686      executor->GetDocument()->AllowsDeclarativeShadowRoots());
    687 
    688  mTreeBuilder->setIsSrcdocDocument(false);
    689 
    690  mTokenizer->start();
    691  executor->Start();
    692 
    693  /*
    694   * We know we're in document.open(), so our document must already
    695   * have a script global andthe WillBuildModel call is safe.
    696   */
    697  return executor->WillBuildModel();
    698 }
    699 
    700 nsresult nsHtml5Parser::Initialize(mozilla::dom::Document* aDoc, nsIURI* aURI,
    701                                   nsISupports* aContainer,
    702                                   nsIChannel* aChannel) {
    703  mTreeBuilder->setAllowDeclarativeShadowRoots(
    704      aDoc->AllowsDeclarativeShadowRoots());
    705  return mExecutor->Init(aDoc, aURI, aContainer, aChannel);
    706 }
    707 
    708 void nsHtml5Parser::StartTokenizer(bool aScriptingEnabled) {
    709  bool isSrcdoc = false;
    710  nsCOMPtr<nsIChannel> channel;
    711  nsresult rv = GetChannel(getter_AddRefs(channel));
    712  if (NS_SUCCEEDED(rv)) {
    713    isSrcdoc = NS_IsSrcdocChannel(channel);
    714  }
    715  mTreeBuilder->setIsSrcdocDocument(isSrcdoc);
    716 
    717  mTreeBuilder->SetPreventScriptExecution(!aScriptingEnabled);
    718  mTreeBuilder->setScriptingEnabled(aScriptingEnabled);
    719  mTreeBuilder->setAllowDeclarativeShadowRoots(
    720      mExecutor->GetDocument()->AllowsDeclarativeShadowRoots());
    721  mTokenizer->start();
    722 }
    723 
    724 void nsHtml5Parser::InitializeDocWriteParserState(
    725    nsAHtml5TreeBuilderState* aState, int32_t aLine) {
    726  mTokenizer->resetToDataState();
    727  mTokenizer->setLineNumber(aLine);
    728  mTreeBuilder->loadState(aState);
    729  mLastWasCR = false;
    730  mReturnToStreamParserPermitted = true;
    731 }
    732 
    733 void nsHtml5Parser::ContinueAfterFailedCharsetSwitch() {
    734  MOZ_ASSERT(
    735      GetStreamParser(),
    736      "Tried to continue after failed charset switch without a stream parser");
    737  GetStreamParser()->ContinueAfterFailedCharsetSwitch();
    738 }
    739 
    740 NS_IMETHODIMP nsHtml5Parser::OnStartRequest(nsIRequest* aRequest) {
    741  if (!mAboutBlankMode) {
    742    MOZ_ASSERT(false,
    743               "Attempted to use nsHtml5Parser as stream listener in "
    744               "non-about:blank mode.");
    745    return NS_ERROR_NOT_IMPLEMENTED;
    746  }
    747  MOZ_RELEASE_ASSERT(!GetStreamParser(),
    748                     "Should not have stream parser in about:blank mode.");
    749  mTokenizer->start();
    750  mExecutor->Start();
    751  nsresult rv = mExecutor->WillBuildModel();
    752  NS_ENSURE_SUCCESS(rv, rv);
    753  PermanentlyUndefineInsertionPoint();
    754  mTokenizer->eof();
    755  if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) {
    756    mExecutor->MarkAsBroken(rv);
    757  } else {
    758    mTreeBuilder->StreamEnded();
    759  }
    760  auto r = mTreeBuilder->Flush();
    761  if (r.isErr()) {
    762    return mExecutor->MarkAsBroken(r.unwrapErr());
    763  }
    764  mExecutor->FlushDocumentWrite();
    765  // The below call does memory cleanup, so call it even if the
    766  // parser has been marked as broken.
    767  mTokenizer->end();
    768  return rv;
    769 }
    770 
    771 NS_IMETHODIMP nsHtml5Parser::OnDataAvailable(nsIRequest* aRequest,
    772                                             nsIInputStream* aInStream,
    773                                             uint64_t aSourceOffset,
    774                                             uint32_t aLength) {
    775  if (!mAboutBlankMode) {
    776    MOZ_ASSERT(false,
    777               "Attempted to use nsHtml5Parser as stream listener in "
    778               "non-about:blank mode.");
    779    return NS_ERROR_NOT_IMPLEMENTED;
    780  }
    781  if (aLength) {
    782    MOZ_ASSERT(false, "Non-zero-length stream in about:blank mode.");
    783    return NS_ERROR_ILLEGAL_INPUT;
    784  }
    785  return NS_OK;
    786 }
    787 
    788 NS_IMETHODIMP nsHtml5Parser::OnStopRequest(nsIRequest* aRequest,
    789                                           nsresult aStatus) {
    790  if (!mAboutBlankMode) {
    791    MOZ_ASSERT(false,
    792               "Attempted to use nsHtml5Parser as stream listener in "
    793               "non-about:blank mode.");
    794    return NS_ERROR_NOT_IMPLEMENTED;
    795  }
    796  return NS_OK;
    797 }