michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set sw=2 ts=2 et tw=79: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsHtml5Parser.h" michael@0: michael@0: #include "mozilla/AutoRestore.h" michael@0: #include "nsContentUtils.h" // for kLoadAsData michael@0: #include "nsHtml5Tokenizer.h" michael@0: #include "nsHtml5TreeBuilder.h" michael@0: #include "nsHtml5AtomTable.h" michael@0: #include "nsHtml5DependentUTF16Buffer.h" michael@0: #include "nsNetUtil.h" michael@0: michael@0: NS_INTERFACE_TABLE_HEAD(nsHtml5Parser) michael@0: NS_INTERFACE_TABLE(nsHtml5Parser, nsIParser, nsISupportsWeakReference) michael@0: NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsHtml5Parser) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHtml5Parser) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHtml5Parser) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsHtml5Parser) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsHtml5Parser) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExecutor) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(GetStreamParser()) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHtml5Parser) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mExecutor) michael@0: tmp->DropStreamParser(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: nsHtml5Parser::nsHtml5Parser() michael@0: : mFirstBuffer(new nsHtml5OwningUTF16Buffer((void*)nullptr)) michael@0: , mLastBuffer(mFirstBuffer) michael@0: , mExecutor(new nsHtml5TreeOpExecutor()) michael@0: , mTreeBuilder(new nsHtml5TreeBuilder(mExecutor, nullptr)) michael@0: , mTokenizer(new nsHtml5Tokenizer(mTreeBuilder, false)) michael@0: , mRootContextLineNumber(1) michael@0: { michael@0: mTokenizer->setInterner(&mAtomTable); michael@0: // There's a zeroing operator new for everything else michael@0: } michael@0: michael@0: nsHtml5Parser::~nsHtml5Parser() michael@0: { michael@0: mTokenizer->end(); michael@0: if (mDocWriteSpeculativeTokenizer) { michael@0: mDocWriteSpeculativeTokenizer->end(); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: nsHtml5Parser::SetContentSink(nsIContentSink* aSink) michael@0: { michael@0: NS_ASSERTION(aSink == static_cast (mExecutor), michael@0: "Attempt to set a foreign sink."); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(nsIContentSink*) michael@0: nsHtml5Parser::GetContentSink() michael@0: { michael@0: return static_cast (mExecutor); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: nsHtml5Parser::GetCommand(nsCString& aCommand) michael@0: { michael@0: aCommand.Assign("view"); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: nsHtml5Parser::SetCommand(const char* aCommand) michael@0: { michael@0: NS_ASSERTION(!strcmp(aCommand, "view") || michael@0: !strcmp(aCommand, "view-source") || michael@0: !strcmp(aCommand, "external-resource") || michael@0: !strcmp(aCommand, kLoadAsData), michael@0: "Unsupported parser command"); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: nsHtml5Parser::SetCommand(eParserCommands aParserCommand) michael@0: { michael@0: NS_ASSERTION(aParserCommand == eViewNormal, michael@0: "Parser command was not eViewNormal."); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: nsHtml5Parser::SetDocumentCharset(const nsACString& aCharset, michael@0: int32_t aCharsetSource) michael@0: { michael@0: NS_PRECONDITION(!mExecutor->HasStarted(), michael@0: "Document charset set too late."); michael@0: NS_PRECONDITION(GetStreamParser(), "Setting charset on a script-only parser."); michael@0: nsAutoCString trimmed; michael@0: trimmed.Assign(aCharset); michael@0: trimmed.Trim(" \t\r\n\f"); michael@0: GetStreamParser()->SetDocumentCharset(trimmed, aCharsetSource); michael@0: mExecutor->SetDocumentCharsetAndSource(trimmed, michael@0: aCharsetSource); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHtml5Parser::GetChannel(nsIChannel** aChannel) michael@0: { michael@0: if (GetStreamParser()) { michael@0: return GetStreamParser()->GetChannel(aChannel); michael@0: } else { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHtml5Parser::GetDTD(nsIDTD** aDTD) michael@0: { michael@0: *aDTD = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIStreamListener* michael@0: nsHtml5Parser::GetStreamListener() michael@0: { michael@0: return mStreamListener; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHtml5Parser::ContinueInterruptedParsing() michael@0: { michael@0: NS_NOTREACHED("Don't call. For interface compat only."); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: nsHtml5Parser::BlockParser() michael@0: { michael@0: mBlocked = true; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: nsHtml5Parser::UnblockParser() michael@0: { michael@0: mBlocked = false; michael@0: mExecutor->ContinueInterruptedParsingAsync(); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(void) michael@0: nsHtml5Parser::ContinueInterruptedParsingAsync() michael@0: { michael@0: mExecutor->ContinueInterruptedParsingAsync(); michael@0: } michael@0: michael@0: NS_IMETHODIMP_(bool) michael@0: nsHtml5Parser::IsParserEnabled() michael@0: { michael@0: return !mBlocked; michael@0: } michael@0: michael@0: NS_IMETHODIMP_(bool) michael@0: nsHtml5Parser::IsComplete() michael@0: { michael@0: return mExecutor->IsComplete(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHtml5Parser::Parse(nsIURI* aURL, michael@0: nsIRequestObserver* aObserver, michael@0: void* aKey, // legacy; ignored michael@0: nsDTDMode aMode) // legacy; ignored michael@0: { michael@0: /* michael@0: * Do NOT cause WillBuildModel to be called synchronously from here! michael@0: * The document won't be ready for it until OnStartRequest! michael@0: */ michael@0: NS_PRECONDITION(!mExecutor->HasStarted(), michael@0: "Tried to start parse without initializing the parser."); michael@0: NS_PRECONDITION(GetStreamParser(), michael@0: "Can't call this Parse() variant on script-created parser"); michael@0: GetStreamParser()->SetObserver(aObserver); michael@0: GetStreamParser()->SetViewSourceTitle(aURL); // In case we're viewing source michael@0: mExecutor->SetStreamParser(GetStreamParser()); michael@0: mExecutor->SetParser(this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHtml5Parser::Parse(const nsAString& aSourceBuffer, michael@0: void* aKey, michael@0: const nsACString& aContentType, michael@0: bool aLastCall, michael@0: nsDTDMode aMode) // ignored michael@0: { michael@0: nsresult rv; michael@0: if (NS_FAILED(rv = mExecutor->IsBroken())) { michael@0: return rv; michael@0: } michael@0: if (aSourceBuffer.Length() > INT32_MAX) { michael@0: return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY); michael@0: } michael@0: michael@0: // Maintain a reference to ourselves so we don't go away michael@0: // till we're completely done. The old parser grips itself in this method. michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: // Gripping the other objects just in case, since the other old grip michael@0: // required grips to these, too. michael@0: nsRefPtr streamKungFuDeathGrip(GetStreamParser()); michael@0: nsRefPtr treeOpKungFuDeathGrip(mExecutor); michael@0: michael@0: if (!mExecutor->HasStarted()) { michael@0: NS_ASSERTION(!GetStreamParser(), michael@0: "Had stream parser but document.write started life cycle."); michael@0: // This is the first document.write() on a document.open()ed document michael@0: mExecutor->SetParser(this); michael@0: mTreeBuilder->setScriptingEnabled(mExecutor->IsScriptEnabled()); michael@0: michael@0: bool isSrcdoc = false; michael@0: nsCOMPtr channel; michael@0: rv = GetChannel(getter_AddRefs(channel)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: isSrcdoc = NS_IsSrcdocChannel(channel); michael@0: } michael@0: mTreeBuilder->setIsSrcdocDocument(isSrcdoc); michael@0: michael@0: mTokenizer->start(); michael@0: mExecutor->Start(); michael@0: if (!aContentType.EqualsLiteral("text/html")) { michael@0: mTreeBuilder->StartPlainText(); michael@0: mTokenizer->StartPlainText(); michael@0: } michael@0: /* michael@0: * If you move the following line, be very careful not to cause michael@0: * WillBuildModel to be called before the document has had its michael@0: * script global object set. michael@0: */ michael@0: rv = mExecutor->WillBuildModel(eDTDMode_unknown); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Return early if the parser has processed EOF michael@0: if (mExecutor->IsComplete()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aLastCall && aSourceBuffer.IsEmpty() && !aKey) { michael@0: // document.close() michael@0: NS_ASSERTION(!GetStreamParser(), michael@0: "Had stream parser but got document.close()."); michael@0: if (mDocumentClosed) { michael@0: // already closed michael@0: return NS_OK; michael@0: } michael@0: mDocumentClosed = true; michael@0: if (!mBlocked && !mInDocumentWrite) { michael@0: return ParseUntilBlocked(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If we got this far, we are dealing with a document.write or michael@0: // document.writeln call--not document.close(). michael@0: michael@0: NS_ASSERTION(IsInsertionPointDefined(), michael@0: "Doc.write reached parser with undefined insertion point."); michael@0: michael@0: NS_ASSERTION(!(GetStreamParser() && !aKey), michael@0: "Got a null key in a non-script-created parser"); michael@0: michael@0: // XXX is this optimization bogus? michael@0: if (aSourceBuffer.IsEmpty()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This guard is here to prevent document.close from tokenizing synchronously michael@0: // while a document.write (that wrote the script that called document.close!) michael@0: // is still on the call stack. michael@0: mozilla::AutoRestore guard(mInDocumentWrite); michael@0: mInDocumentWrite = true; michael@0: michael@0: // The script is identified by aKey. If there's nothing in the buffer michael@0: // chain for that key, we'll insert at the head of the queue. michael@0: // When the script leaves something in the queue, a zero-length michael@0: // key-holder "buffer" is inserted in the queue. If the same script michael@0: // leaves something in the chain again, it will be inserted immediately michael@0: // before the old key holder belonging to the same script. michael@0: // michael@0: // We don't do the actual data insertion yet in the hope that the data gets michael@0: // tokenized and there no data or less data to copy to the heap after michael@0: // tokenization. Also, this way, we avoid inserting one empty data buffer michael@0: // per document.write, which matters for performance when the parser isn't michael@0: // blocked and a badly-authored script calls document.write() once per michael@0: // input character. (As seen in a benchmark!) michael@0: // michael@0: // The insertion into the input stream happens conceptually before anything michael@0: // gets tokenized. To make sure multi-level document.write works right, michael@0: // it's necessary to establish the location of our parser key up front michael@0: // in case this is the first write with this key. michael@0: // michael@0: // In a document.open() case, the first write level has a null key, so that michael@0: // case is handled separately, because normal buffers containing data michael@0: // have null keys. michael@0: michael@0: // These don't need to be owning references, because they always point to michael@0: // the buffer queue and buffers can't be removed from the buffer queue michael@0: // before document.write() returns. The buffer queue clean-up happens the michael@0: // next time ParseUntilBlocked() is called. michael@0: // However, they are made owning just in case the reasoning above is flawed michael@0: // and a flaw would lead to worse problems with plain pointers. If this michael@0: // turns out to be a perf problem, it's worthwhile to consider making michael@0: // prevSearchbuf a plain pointer again. michael@0: nsRefPtr prevSearchBuf; michael@0: nsRefPtr firstLevelMarker; michael@0: michael@0: if (aKey) { michael@0: if (mFirstBuffer == mLastBuffer) { michael@0: nsHtml5OwningUTF16Buffer* keyHolder = new nsHtml5OwningUTF16Buffer(aKey); michael@0: keyHolder->next = mLastBuffer; michael@0: mFirstBuffer = keyHolder; michael@0: } else if (mFirstBuffer->key != aKey) { michael@0: prevSearchBuf = mFirstBuffer; michael@0: for (;;) { michael@0: if (prevSearchBuf->next == mLastBuffer) { michael@0: // key was not found michael@0: nsHtml5OwningUTF16Buffer* keyHolder = michael@0: new nsHtml5OwningUTF16Buffer(aKey); michael@0: keyHolder->next = mFirstBuffer; michael@0: mFirstBuffer = keyHolder; michael@0: prevSearchBuf = nullptr; michael@0: break; michael@0: } michael@0: if (prevSearchBuf->next->key == aKey) { michael@0: // found a key holder michael@0: break; michael@0: } michael@0: prevSearchBuf = prevSearchBuf->next; michael@0: } michael@0: } // else mFirstBuffer is the keyholder michael@0: michael@0: // prevSearchBuf is the previous buffer before the keyholder or null if michael@0: // there isn't one. michael@0: } else { michael@0: // We have a first-level write in the document.open() case. We insert before michael@0: // mLastBuffer, effectively, by making mLastBuffer be a new sentinel object michael@0: // and redesignating the previous mLastBuffer as our firstLevelMarker. We michael@0: // need to put a marker there, because otherwise additional document.writes michael@0: // from nested event loops would insert in the wrong place. Sigh. michael@0: mLastBuffer->next = new nsHtml5OwningUTF16Buffer((void*)nullptr); michael@0: firstLevelMarker = mLastBuffer; michael@0: mLastBuffer = mLastBuffer->next; michael@0: } michael@0: michael@0: nsHtml5DependentUTF16Buffer stackBuffer(aSourceBuffer); michael@0: michael@0: while (!mBlocked && stackBuffer.hasMore()) { michael@0: stackBuffer.adjust(mLastWasCR); michael@0: mLastWasCR = false; michael@0: if (stackBuffer.hasMore()) { michael@0: int32_t lineNumberSave; michael@0: bool inRootContext = (!GetStreamParser() && !aKey); michael@0: if (inRootContext) { michael@0: mTokenizer->setLineNumber(mRootContextLineNumber); michael@0: } else { michael@0: // we aren't the root context, so save the line number on the michael@0: // *stack* so that we can restore it. michael@0: lineNumberSave = mTokenizer->getLineNumber(); michael@0: } michael@0: michael@0: mLastWasCR = mTokenizer->tokenizeBuffer(&stackBuffer); michael@0: michael@0: if (inRootContext) { michael@0: mRootContextLineNumber = mTokenizer->getLineNumber(); michael@0: } else { michael@0: mTokenizer->setLineNumber(lineNumberSave); michael@0: } michael@0: michael@0: if (mTreeBuilder->HasScript()) { michael@0: mTreeBuilder->Flush(); // Move ops to the executor michael@0: rv = mExecutor->FlushDocumentWrite(); // run the ops michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // Flushing tree ops can cause all sorts of things. michael@0: // Return early if the parser got terminated. michael@0: if (mExecutor->IsComplete()) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: // Ignore suspension requests michael@0: } michael@0: } michael@0: michael@0: nsRefPtr heapBuffer; michael@0: if (stackBuffer.hasMore()) { michael@0: // The buffer wasn't tokenized to completion. Create a copy of the tail michael@0: // on the heap. michael@0: heapBuffer = stackBuffer.FalliblyCopyAsOwningBuffer(); michael@0: if (!heapBuffer) { michael@0: // Allocation failed. The parser is now broken. michael@0: return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY); michael@0: } michael@0: } michael@0: michael@0: if (heapBuffer) { michael@0: // We have something to insert before the keyholder holding in the non-null michael@0: // aKey case and we have something to swap into firstLevelMarker in the michael@0: // null aKey case. michael@0: if (aKey) { michael@0: NS_ASSERTION(mFirstBuffer != mLastBuffer, michael@0: "Where's the keyholder?"); michael@0: // the key holder is still somewhere further down the list from michael@0: // prevSearchBuf (which may be null) michael@0: if (mFirstBuffer->key == aKey) { michael@0: NS_ASSERTION(!prevSearchBuf, michael@0: "Non-null prevSearchBuf when mFirstBuffer is the key holder?"); michael@0: heapBuffer->next = mFirstBuffer; michael@0: mFirstBuffer = heapBuffer; michael@0: } else { michael@0: if (!prevSearchBuf) { michael@0: prevSearchBuf = mFirstBuffer; michael@0: } michael@0: // We created a key holder earlier, so we will find it without walking michael@0: // past the end of the list. michael@0: while (prevSearchBuf->next->key != aKey) { michael@0: prevSearchBuf = prevSearchBuf->next; michael@0: } michael@0: heapBuffer->next = prevSearchBuf->next; michael@0: prevSearchBuf->next = heapBuffer; michael@0: } michael@0: } else { michael@0: NS_ASSERTION(firstLevelMarker, "How come we don't have a marker."); michael@0: firstLevelMarker->Swap(heapBuffer); michael@0: } michael@0: } michael@0: michael@0: if (!mBlocked) { // buffer was tokenized to completion michael@0: NS_ASSERTION(!stackBuffer.hasMore(), michael@0: "Buffer wasn't tokenized to completion?"); michael@0: // Scripting semantics require a forced tree builder flush here michael@0: mTreeBuilder->Flush(); // Move ops to the executor michael@0: rv = mExecutor->FlushDocumentWrite(); // run the ops michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } else if (stackBuffer.hasMore()) { michael@0: // The buffer wasn't tokenized to completion. Tokenize the untokenized michael@0: // content in order to preload stuff. This content will be retokenized michael@0: // later for normal parsing. michael@0: if (!mDocWriteSpeculatorActive) { michael@0: mDocWriteSpeculatorActive = true; michael@0: if (!mDocWriteSpeculativeTreeBuilder) { michael@0: // Lazily initialize if uninitialized michael@0: mDocWriteSpeculativeTreeBuilder = michael@0: new nsHtml5TreeBuilder(nullptr, mExecutor->GetStage()); michael@0: mDocWriteSpeculativeTreeBuilder->setScriptingEnabled( michael@0: mTreeBuilder->isScriptingEnabled()); michael@0: mDocWriteSpeculativeTokenizer = michael@0: new nsHtml5Tokenizer(mDocWriteSpeculativeTreeBuilder, false); michael@0: mDocWriteSpeculativeTokenizer->setInterner(&mAtomTable); michael@0: mDocWriteSpeculativeTokenizer->start(); michael@0: } michael@0: mDocWriteSpeculativeTokenizer->resetToDataState(); michael@0: mDocWriteSpeculativeTreeBuilder->loadState(mTreeBuilder, &mAtomTable); michael@0: mDocWriteSpeculativeLastWasCR = false; michael@0: } michael@0: michael@0: // Note that with multilevel document.write if we didn't just activate the michael@0: // speculator, it's possible that the speculator is now in the wrong state. michael@0: // That's OK for the sake of simplicity. The worst that can happen is michael@0: // that the speculative loads aren't exactly right. The content will be michael@0: // reparsed anyway for non-preload purposes. michael@0: michael@0: // The buffer position for subsequent non-speculative parsing now lives michael@0: // in heapBuffer, so it's ok to let the buffer position of stackBuffer michael@0: // to be overwritten and not restored below. michael@0: while (stackBuffer.hasMore()) { michael@0: stackBuffer.adjust(mDocWriteSpeculativeLastWasCR); michael@0: if (stackBuffer.hasMore()) { michael@0: mDocWriteSpeculativeLastWasCR = michael@0: mDocWriteSpeculativeTokenizer->tokenizeBuffer(&stackBuffer); michael@0: } michael@0: } michael@0: michael@0: mDocWriteSpeculativeTreeBuilder->Flush(); michael@0: mDocWriteSpeculativeTreeBuilder->DropHandles(); michael@0: mExecutor->FlushSpeculativeLoads(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHtml5Parser::Terminate() michael@0: { michael@0: // We should only call DidBuildModel once, so don't do anything if this is michael@0: // the second time that Terminate has been called. michael@0: if (mExecutor->IsComplete()) { michael@0: return NS_OK; michael@0: } michael@0: // XXX - [ until we figure out a way to break parser-sink circularity ] michael@0: // Hack - Hold a reference until we are completely done... michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: nsRefPtr streamKungFuDeathGrip(GetStreamParser()); michael@0: nsRefPtr treeOpKungFuDeathGrip(mExecutor); michael@0: if (GetStreamParser()) { michael@0: GetStreamParser()->Terminate(); michael@0: } michael@0: return mExecutor->DidBuildModel(true); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHtml5Parser::ParseFragment(const nsAString& aSourceBuffer, michael@0: nsTArray& aTagStack) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHtml5Parser::BuildModel() michael@0: { michael@0: NS_NOTREACHED("Don't call this!"); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHtml5Parser::CancelParsingEvents() michael@0: { michael@0: NS_NOTREACHED("Don't call this!"); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: void michael@0: nsHtml5Parser::Reset() michael@0: { michael@0: NS_NOTREACHED("Don't call this!"); michael@0: } michael@0: michael@0: bool michael@0: nsHtml5Parser::CanInterrupt() michael@0: { michael@0: // nsContentSink needs this to let nsContentSink::DidProcessATokenImpl michael@0: // interrupt. michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsHtml5Parser::IsInsertionPointDefined() michael@0: { michael@0: return !mExecutor->IsFlushing() && michael@0: (!GetStreamParser() || mParserInsertedScriptsBeingEvaluated); michael@0: } michael@0: michael@0: void michael@0: nsHtml5Parser::BeginEvaluatingParserInsertedScript() michael@0: { michael@0: ++mParserInsertedScriptsBeingEvaluated; michael@0: } michael@0: michael@0: void michael@0: nsHtml5Parser::EndEvaluatingParserInsertedScript() michael@0: { michael@0: --mParserInsertedScriptsBeingEvaluated; michael@0: } michael@0: michael@0: void michael@0: nsHtml5Parser::MarkAsNotScriptCreated(const char* aCommand) michael@0: { michael@0: NS_PRECONDITION(!mStreamListener, "Must not call this twice."); michael@0: eParserMode mode = NORMAL; michael@0: if (!nsCRT::strcmp(aCommand, "view-source")) { michael@0: mode = VIEW_SOURCE_HTML; michael@0: } else if (!nsCRT::strcmp(aCommand, "view-source-xml")) { michael@0: mode = VIEW_SOURCE_XML; michael@0: } else if (!nsCRT::strcmp(aCommand, "view-source-plain")) { michael@0: mode = VIEW_SOURCE_PLAIN; michael@0: } else if (!nsCRT::strcmp(aCommand, "plain-text")) { michael@0: mode = PLAIN_TEXT; michael@0: } else if (!nsCRT::strcmp(aCommand, kLoadAsData)) { michael@0: mode = LOAD_AS_DATA; michael@0: } michael@0: #ifdef DEBUG michael@0: else { michael@0: NS_ASSERTION(!nsCRT::strcmp(aCommand, "view") || michael@0: !nsCRT::strcmp(aCommand, "external-resource"), michael@0: "Unsupported parser command!"); michael@0: } michael@0: #endif michael@0: mStreamListener = michael@0: new nsHtml5StreamListener(new nsHtml5StreamParser(mExecutor, this, mode)); michael@0: } michael@0: michael@0: bool michael@0: nsHtml5Parser::IsScriptCreated() michael@0: { michael@0: return !GetStreamParser(); michael@0: } michael@0: michael@0: /* End nsIParser */ michael@0: michael@0: // not from interface michael@0: nsresult michael@0: nsHtml5Parser::ParseUntilBlocked() michael@0: { michael@0: nsresult rv = mExecutor->IsBroken(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (mBlocked || mExecutor->IsComplete()) { michael@0: return NS_OK; michael@0: } michael@0: NS_ASSERTION(mExecutor->HasStarted(), "Bad life cycle."); michael@0: NS_ASSERTION(!mInDocumentWrite, michael@0: "ParseUntilBlocked entered while in doc.write!"); michael@0: michael@0: mDocWriteSpeculatorActive = false; michael@0: michael@0: for (;;) { michael@0: if (!mFirstBuffer->hasMore()) { michael@0: if (mFirstBuffer == mLastBuffer) { michael@0: if (mExecutor->IsComplete()) { michael@0: // something like cache manisfests stopped the parse in mid-flight michael@0: return NS_OK; michael@0: } michael@0: if (mDocumentClosed) { michael@0: NS_ASSERTION(!GetStreamParser(), michael@0: "This should only happen with script-created parser."); michael@0: mTokenizer->eof(); michael@0: mTreeBuilder->StreamEnded(); michael@0: mTreeBuilder->Flush(); michael@0: mExecutor->FlushDocumentWrite(); michael@0: // The below call does memory cleanup, so call it even if the michael@0: // parser has been marked as broken. michael@0: mTokenizer->end(); michael@0: return NS_OK; michael@0: } michael@0: // never release the last buffer. michael@0: NS_ASSERTION(!mLastBuffer->getStart() && !mLastBuffer->getEnd(), michael@0: "Sentinel buffer had its indeces changed."); michael@0: if (GetStreamParser()) { michael@0: if (mReturnToStreamParserPermitted && michael@0: !mExecutor->IsScriptExecuting()) { michael@0: mTreeBuilder->Flush(); michael@0: mReturnToStreamParserPermitted = false; michael@0: GetStreamParser()->ContinueAfterScripts(mTokenizer, michael@0: mTreeBuilder, michael@0: mLastWasCR); michael@0: } michael@0: } else { michael@0: // Script-created parser michael@0: mTreeBuilder->Flush(); michael@0: // No need to flush the executor, because the executor is already michael@0: // in a flush michael@0: NS_ASSERTION(mExecutor->IsInFlushLoop(), michael@0: "How did we come here without being in the flush loop?"); michael@0: } michael@0: return NS_OK; // no more data for now but expecting more michael@0: } michael@0: mFirstBuffer = mFirstBuffer->next; michael@0: continue; michael@0: } michael@0: michael@0: if (mBlocked || mExecutor->IsComplete()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // now we have a non-empty buffer michael@0: mFirstBuffer->adjust(mLastWasCR); michael@0: mLastWasCR = false; michael@0: if (mFirstBuffer->hasMore()) { michael@0: bool inRootContext = (!GetStreamParser() && !mFirstBuffer->key); michael@0: if (inRootContext) { michael@0: mTokenizer->setLineNumber(mRootContextLineNumber); michael@0: } michael@0: mLastWasCR = mTokenizer->tokenizeBuffer(mFirstBuffer); michael@0: if (inRootContext) { michael@0: mRootContextLineNumber = mTokenizer->getLineNumber(); michael@0: } michael@0: if (mTreeBuilder->HasScript()) { michael@0: mTreeBuilder->Flush(); michael@0: nsresult rv = mExecutor->FlushDocumentWrite(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: if (mBlocked) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsHtml5Parser::Initialize(nsIDocument* aDoc, michael@0: nsIURI* aURI, michael@0: nsISupports* aContainer, michael@0: nsIChannel* aChannel) michael@0: { michael@0: return mExecutor->Init(aDoc, aURI, aContainer, aChannel); michael@0: } michael@0: michael@0: void michael@0: nsHtml5Parser::StartTokenizer(bool aScriptingEnabled) { michael@0: michael@0: bool isSrcdoc = false; michael@0: nsCOMPtr channel; michael@0: nsresult rv = GetChannel(getter_AddRefs(channel)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: isSrcdoc = NS_IsSrcdocChannel(channel); michael@0: } michael@0: mTreeBuilder->setIsSrcdocDocument(isSrcdoc); michael@0: michael@0: mTreeBuilder->SetPreventScriptExecution(!aScriptingEnabled); michael@0: mTreeBuilder->setScriptingEnabled(aScriptingEnabled); michael@0: mTokenizer->start(); michael@0: } michael@0: michael@0: void michael@0: nsHtml5Parser::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, michael@0: int32_t aLine) michael@0: { michael@0: mTokenizer->resetToDataState(); michael@0: mTokenizer->setLineNumber(aLine); michael@0: mTreeBuilder->loadState(aState, &mAtomTable); michael@0: mLastWasCR = false; michael@0: mReturnToStreamParserPermitted = true; michael@0: } michael@0: michael@0: void michael@0: nsHtml5Parser::ContinueAfterFailedCharsetSwitch() michael@0: { michael@0: NS_PRECONDITION(GetStreamParser(), michael@0: "Tried to continue after failed charset switch without a stream parser"); michael@0: GetStreamParser()->ContinueAfterFailedCharsetSwitch(); michael@0: } michael@0: