/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=2 et tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
nsHtml5Parser::~nsHtml5Parser() {
mTokenizer->end(); if (mDocWriteSpeculativeTokenizer) {
mDocWriteSpeculativeTokenizer->end();
}
}
NS_IMETHODIMP_(void)
nsHtml5Parser::SetContentSink(nsIContentSink* aSink) {
NS_ASSERTION(aSink == static_cast<nsIContentSink*>(mExecutor), "Attempt to set a foreign sink.");
}
NS_IMETHODIMP
nsHtml5Parser::Parse(nsIURI* aURL) { /* * Do NOT cause WillBuildModel to be called synchronously from here! * The document won't be ready for it until OnStartRequest!
*/
MOZ_ASSERT(!mExecutor->HasStarted(), "Tried to start parse without initializing the parser.");
MOZ_ASSERT(GetStreamParser(), "Can't call this Parse() variant on script-created parser");
GetStreamParser()->SetViewSourceTitle(aURL); // In case we're viewing source
mExecutor->SetStreamParser(GetStreamParser());
mExecutor->SetParser(this); return NS_OK;
}
// Maintain a reference to ourselves so we don't go away // till we're completely done. The old parser grips itself in this method.
nsCOMPtr<nsIParser> kungFuDeathGrip(this);
// Gripping the other objects just in case, since the other old grip // required grips to these, too.
RefPtr<nsHtml5StreamParser> streamKungFuDeathGrip(GetStreamParser());
mozilla::Unused << streamKungFuDeathGrip; // Not used within function
RefPtr<nsHtml5TreeOpExecutor> executor(mExecutor);
MOZ_RELEASE_ASSERT(executor->HasStarted());
// Return early if the parser has processed EOF if (executor->IsComplete()) { return NS_OK;
}
if (aLastCall && aSourceBuffer.IsEmpty() && !aKey) { // document.close()
NS_ASSERTION(!GetStreamParser(), "Had stream parser but got document.close()."); if (mDocumentClosed) { // already closed return NS_OK;
}
mDocumentClosed = true; if (!mBlocked && !mInDocumentWrite) { return ParseUntilBlocked();
} return NS_OK;
}
// If we got this far, we are dealing with a document.write or // document.writeln call--not document.close().
MOZ_RELEASE_ASSERT(
IsInsertionPointDefined(), "Doc.write reached parser with undefined insertion point.");
MOZ_RELEASE_ASSERT(!(GetStreamParser() && !aKey), "Got a null key in a non-script-created parser");
// XXX is this optimization bogus? if (aSourceBuffer.IsEmpty()) { return NS_OK;
}
// This guard is here to prevent document.close from tokenizing synchronously // while a document.write (that wrote the script that called document.close!) // is still on the call stack.
mozilla::AutoRestore<bool> guard(mInDocumentWrite);
mInDocumentWrite = true;
// The script is identified by aKey. If there's nothing in the buffer // chain for that key, we'll insert at the head of the queue. // When the script leaves something in the queue, a zero-length // key-holder "buffer" is inserted in the queue. If the same script // leaves something in the chain again, it will be inserted immediately // before the old key holder belonging to the same script. // // We don't do the actual data insertion yet in the hope that the data gets // tokenized and there no data or less data to copy to the heap after // tokenization. Also, this way, we avoid inserting one empty data buffer // per document.write, which matters for performance when the parser isn't // blocked and a badly-authored script calls document.write() once per // input character. (As seen in a benchmark!) // // The insertion into the input stream happens conceptually before anything // gets tokenized. To make sure multi-level document.write works right, // it's necessary to establish the location of our parser key up front // in case this is the first write with this key. // // In a document.open() case, the first write level has a null key, so that // case is handled separately, because normal buffers containing data // have null keys.
// These don't need to be owning references, because they always point to // the buffer queue and buffers can't be removed from the buffer queue // before document.write() returns. The buffer queue clean-up happens the // next time ParseUntilBlocked() is called. // However, they are made owning just in case the reasoning above is flawed // and a flaw would lead to worse problems with plain pointers. If this // turns out to be a perf problem, it's worthwhile to consider making // prevSearchbuf a plain pointer again.
RefPtr<nsHtml5OwningUTF16Buffer> prevSearchBuf;
RefPtr<nsHtml5OwningUTF16Buffer> firstLevelMarker;
if (aKey) { if (mFirstBuffer == mLastBuffer) {
nsHtml5OwningUTF16Buffer* keyHolder = new nsHtml5OwningUTF16Buffer(aKey);
keyHolder->next = mLastBuffer;
mFirstBuffer = keyHolder;
} elseif (mFirstBuffer->key != aKey) {
prevSearchBuf = mFirstBuffer; for (;;) { if (prevSearchBuf->next == mLastBuffer) { // key was not found
nsHtml5OwningUTF16Buffer* keyHolder = new nsHtml5OwningUTF16Buffer(aKey);
keyHolder->next = mFirstBuffer;
mFirstBuffer = keyHolder;
prevSearchBuf = nullptr; break;
} if (prevSearchBuf->next->key == aKey) { // found a key holder break;
}
prevSearchBuf = prevSearchBuf->next;
}
} // else mFirstBuffer is the keyholder
// prevSearchBuf is the previous buffer before the keyholder or null if // there isn't one.
} else { // We have a first-level write in the document.open() case. We insert before // mLastBuffer, effectively, by making mLastBuffer be a new sentinel object // and redesignating the previous mLastBuffer as our firstLevelMarker. We // need to put a marker there, because otherwise additional document.writes // from nested event loops would insert in the wrong place. Sigh.
mLastBuffer->next = new nsHtml5OwningUTF16Buffer((void*)nullptr);
firstLevelMarker = mLastBuffer;
mLastBuffer = mLastBuffer->next;
}
while (!mBlocked && stackBuffer.hasMore()) {
stackBuffer.adjust(mLastWasCR);
mLastWasCR = false; if (stackBuffer.hasMore()) {
int32_t lineNumberSave; bool inRootContext = (!GetStreamParser() && !aKey); if (inRootContext) {
mTokenizer->setLineNumber(mRootContextLineNumber);
} else { // we aren't the root context, so save the line number on the // *stack* so that we can restore it.
lineNumberSave = mTokenizer->getLineNumber();
}
if (!mTokenizer->EnsureBufferSpace(stackBuffer.getLength())) { return executor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
}
mLastWasCR = mTokenizer->tokenizeBuffer(&stackBuffer); if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) { return executor->MarkAsBroken(rv);
}
if (mTreeBuilder->HasScriptThatMayDocumentWriteOrBlock()) { auto r = mTreeBuilder->Flush(); // Move ops to the executor if (r.isErr()) { return executor->MarkAsBroken(r.unwrapErr());
}
rv = executor->FlushDocumentWrite(); // run the ops
NS_ENSURE_SUCCESS(rv, rv); // Flushing tree ops can cause all sorts of things. // Return early if the parser got terminated. if (executor->IsComplete()) { return NS_OK;
}
} // Ignore suspension requests
}
}
RefPtr<nsHtml5OwningUTF16Buffer> heapBuffer; if (stackBuffer.hasMore()) { // The buffer wasn't tokenized to completion. Create a copy of the tail // on the heap.
heapBuffer = stackBuffer.FalliblyCopyAsOwningBuffer(); if (!heapBuffer) { // Allocation failed. The parser is now broken. return executor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
}
}
if (heapBuffer) { // We have something to insert before the keyholder holding in the non-null // aKey case and we have something to swap into firstLevelMarker in the // null aKey case. if (aKey) {
NS_ASSERTION(mFirstBuffer != mLastBuffer, "Where's the keyholder?"); // the key holder is still somewhere further down the list from // prevSearchBuf (which may be null) if (mFirstBuffer->key == aKey) {
NS_ASSERTION(
!prevSearchBuf, "Non-null prevSearchBuf when mFirstBuffer is the key holder?");
heapBuffer->next = mFirstBuffer;
mFirstBuffer = heapBuffer;
} else { if (!prevSearchBuf) {
prevSearchBuf = mFirstBuffer;
} // We created a key holder earlier, so we will find it without walking // past the end of the list. while (prevSearchBuf->next->key != aKey) {
prevSearchBuf = prevSearchBuf->next;
}
heapBuffer->next = prevSearchBuf->next;
prevSearchBuf->next = heapBuffer;
}
} else {
NS_ASSERTION(firstLevelMarker, "How come we don't have a marker.");
firstLevelMarker->Swap(heapBuffer);
}
}
if (!mBlocked) { // buffer was tokenized to completion
NS_ASSERTION(!stackBuffer.hasMore(), "Buffer wasn't tokenized to completion?"); // Scripting semantics require a forced tree builder flush here auto r = mTreeBuilder->Flush(); // Move ops to the executor if (r.isErr()) { return executor->MarkAsBroken(r.unwrapErr());
}
rv = executor->FlushDocumentWrite(); // run the ops
NS_ENSURE_SUCCESS(rv, rv);
} elseif (stackBuffer.hasMore()) { // The buffer wasn't tokenized to completion. Tokenize the untokenized // content in order to preload stuff. This content will be retokenized // later for normal parsing. if (!mDocWriteSpeculatorActive) {
mDocWriteSpeculatorActive = true; if (!mDocWriteSpeculativeTreeBuilder) { // Lazily initialize if uninitialized
mDocWriteSpeculativeTreeBuilder =
mozilla::MakeUnique<nsHtml5TreeBuilder>(nullptr,
executor->GetStage(), true);
mDocWriteSpeculativeTreeBuilder->setScriptingEnabled(
mTreeBuilder->isScriptingEnabled());
mDocWriteSpeculativeTreeBuilder->setAllowDeclarativeShadowRoots(
mTreeBuilder->isAllowDeclarativeShadowRoots());
mDocWriteSpeculativeTokenizer = mozilla::MakeUnique<nsHtml5Tokenizer>(
mDocWriteSpeculativeTreeBuilder.get(), false);
mDocWriteSpeculativeTokenizer->setInterner(&mAtomTable);
mDocWriteSpeculativeTokenizer->start();
}
mDocWriteSpeculativeTokenizer->resetToDataState();
mDocWriteSpeculativeTreeBuilder->loadState(mTreeBuilder.get());
mDocWriteSpeculativeLastWasCR = false;
}
// Note that with multilevel document.write if we didn't just activate the // speculator, it's possible that the speculator is now in the wrong state. // That's OK for the sake of simplicity. The worst that can happen is // that the speculative loads aren't exactly right. The content will be // reparsed anyway for non-preload purposes.
// The buffer position for subsequent non-speculative parsing now lives // in heapBuffer, so it's ok to let the buffer position of stackBuffer // to be overwritten and not restored below. while (stackBuffer.hasMore()) {
stackBuffer.adjust(mDocWriteSpeculativeLastWasCR); if (stackBuffer.hasMore()) { if (!mDocWriteSpeculativeTokenizer->EnsureBufferSpace(
stackBuffer.getLength())) { return executor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
}
mDocWriteSpeculativeLastWasCR =
mDocWriteSpeculativeTokenizer->tokenizeBuffer(&stackBuffer);
nsresult rv; if (NS_FAILED((rv = mDocWriteSpeculativeTreeBuilder->IsBroken()))) { return executor->MarkAsBroken(rv);
}
}
}
auto r = mDocWriteSpeculativeTreeBuilder->Flush(); if (r.isErr()) { return executor->MarkAsBroken(r.unwrapErr());
}
mDocWriteSpeculativeTreeBuilder->DropHandles();
executor->FlushSpeculativeLoads();
}
return NS_OK;
}
NS_IMETHODIMP
nsHtml5Parser::Terminate() { // Prevent a second call to DidBuildModel via document.close()
mDocumentClosed = true; // We should only call DidBuildModel once, so don't do anything if this is // the second time that Terminate has been called. if (mExecutor->IsComplete()) { return NS_OK;
} // XXX - [ until we figure out a way to break parser-sink circularity ] // Hack - Hold a reference until we are completely done...
nsCOMPtr<nsIParser> kungFuDeathGrip(this);
RefPtr<nsHtml5StreamParser> streamParser(GetStreamParser());
RefPtr<nsHtml5TreeOpExecutor> executor(mExecutor); if (streamParser) {
streamParser->Terminate();
} return executor->DidBuildModel(true);
}
// not from interface
nsresult nsHtml5Parser::ParseUntilBlocked() {
nsresult rv = mExecutor->IsBroken();
NS_ENSURE_SUCCESS(rv, rv); if (mBlocked || mInsertionPointPermanentlyUndefined ||
mExecutor->IsComplete()) { return NS_OK;
}
NS_ASSERTION(mExecutor->HasStarted(), "Bad life cycle.");
NS_ASSERTION(!mInDocumentWrite, "ParseUntilBlocked entered while in doc.write!");
mDocWriteSpeculatorActive = false;
for (;;) { if (!mFirstBuffer->hasMore()) { if (mFirstBuffer == mLastBuffer) { if (mExecutor->IsComplete()) { // something like cache manisfests stopped the parse in mid-flight return NS_OK;
} if (mDocumentClosed) {
PermanentlyUndefineInsertionPoint();
nsresult rv;
MOZ_RELEASE_ASSERT(
!GetStreamParser(), "This should only happen with script-created parser."); if (NS_SUCCEEDED((rv = mExecutor->IsBroken()))) {
mTokenizer->eof(); if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) {
mExecutor->MarkAsBroken(rv);
} else {
mTreeBuilder->StreamEnded();
}
} auto r = mTreeBuilder->Flush(); if (r.isErr()) { return mExecutor->MarkAsBroken(r.unwrapErr());
}
mExecutor->FlushDocumentWrite(); // The below call does memory cleanup, so call it even if the // parser has been marked as broken.
mTokenizer->end(); return rv;
} // never release the last buffer.
NS_ASSERTION(!mLastBuffer->getStart() && !mLastBuffer->getEnd(), "Sentinel buffer had its indeces changed."); if (GetStreamParser()) { if (mReturnToStreamParserPermitted &&
!mExecutor->IsScriptExecuting()) { auto r = mTreeBuilder->Flush(); if (r.isErr()) { return mExecutor->MarkAsBroken(r.unwrapErr());
}
mReturnToStreamParserPermitted = false;
GetStreamParser()->ContinueAfterScriptsOrEncodingCommitment(
mTokenizer.get(), mTreeBuilder.get(), mLastWasCR);
}
} else { // Script-created parser auto r = mTreeBuilder->Flush(); if (r.isErr()) { return mExecutor->MarkAsBroken(r.unwrapErr());
} // No need to flush the executor, because the executor is already // in a flush
NS_ASSERTION(mExecutor->IsInFlushLoop(), "How did we come here without being in the flush loop?");
} return NS_OK; // no more data for now but expecting more
}
mFirstBuffer = mFirstBuffer->next; continue;
}
if (mBlocked || mExecutor->IsComplete()) { return NS_OK;
}
// now we have a non-empty buffer
mFirstBuffer->adjust(mLastWasCR);
mLastWasCR = false; if (mFirstBuffer->hasMore()) { bool inRootContext = (!GetStreamParser() && !mFirstBuffer->key); if (inRootContext) {
mTokenizer->setLineNumber(mRootContextLineNumber);
} if (!mTokenizer->EnsureBufferSpace(mFirstBuffer->getLength())) { return mExecutor->MarkAsBroken(NS_ERROR_OUT_OF_MEMORY);
}
mLastWasCR = mTokenizer->tokenizeBuffer(mFirstBuffer);
nsresult rv; if (NS_FAILED((rv = mTreeBuilder->IsBroken()))) { return mExecutor->MarkAsBroken(rv);
} if (inRootContext) {
mRootContextLineNumber = mTokenizer->getLineNumber();
} if (mTreeBuilder->HasScriptThatMayDocumentWriteOrBlock()) { auto r = mTreeBuilder->Flush(); if (r.isErr()) { return mExecutor->MarkAsBroken(r.unwrapErr());
}
rv = mExecutor->FlushDocumentWrite();
NS_ENSURE_SUCCESS(rv, rv);
} if (mBlocked) { return NS_OK;
}
}
}
}
nsresult nsHtml5Parser::StartExecutor() {
MOZ_ASSERT(!GetStreamParser(), "Had stream parser but document.write started life cycle."); // This is part of the setup document.open() does.
RefPtr<nsHtml5TreeOpExecutor> executor(mExecutor);
executor->SetParser(this);
mTreeBuilder->setScriptingEnabled(executor->IsScriptEnabled());
mTreeBuilder->setAllowDeclarativeShadowRoots(
executor->GetDocument()->AllowsDeclarativeShadowRoots());
mTreeBuilder->setIsSrcdocDocument(false);
mTokenizer->start();
executor->Start();
/* * We know we're in document.open(), so our document must already * have a script global andthe WillBuildModel call is safe.
*/ return executor->WillBuildModel();
}
void nsHtml5Parser::ContinueAfterFailedCharsetSwitch() {
MOZ_ASSERT(
GetStreamParser(), "Tried to continue after failed charset switch without a stream parser");
GetStreamParser()->ContinueAfterFailedCharsetSwitch();
}
¤ Dauer der Verarbeitung: 0.39 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.