NS_IMETHODIMP nsHtml5Parser::Parse(const nsAString& aSourceBuffer, void* aKey, const nsACString& aContentType, bool aLastCall, nsDTDMode aMode) // ignored { if (mExecutor->IsBroken()) { return NS_ERROR_OUT_OF_MEMORY; } if (aSourceBuffer.Length() > PR_INT32_MAX) { mExecutor->MarkAsBroken(); return NS_ERROR_OUT_OF_MEMORY; } // 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. nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip(mStreamParser); nsRefPtr<nsHtml5TreeOpExecutor> treeOpKungFuDeathGrip(mExecutor); if (!mExecutor->HasStarted()) { NS_ASSERTION(!mStreamParser, "Had stream parser but document.write started life cycle."); // This is the first document.write() on a document.open()ed document mExecutor->SetParser(this); mTreeBuilder->setScriptingEnabled(mExecutor->IsScriptEnabled()); mTokenizer->start(); mExecutor->Start(); if (!aContentType.EqualsLiteral("text/html")) { mTreeBuilder->StartPlainText(); mTokenizer->StartPlainText(); } /* * If you move the following line, be very careful not to cause * WillBuildModel to be called before the document has had its * script global object set. */ mExecutor->WillBuildModel(eDTDMode_unknown); } // Return early if the parser has processed EOF if (mExecutor->IsComplete()) { return NS_OK; } if (aLastCall && aSourceBuffer.IsEmpty() && !aKey) { // document.close() NS_ASSERTION(!mStreamParser, "Had stream parser but got document.close()."); if (mDocumentClosed) { // already closed return NS_OK; } mDocumentClosed = true; if (!mBlocked && !mInDocumentWrite) { ParseUntilBlocked(); } return NS_OK; } // If we got this far, we are dealing with a document.write or // document.writeln call--not document.close(). NS_ASSERTION(IsInsertionPointDefined(), "Doc.write reached parser with undefined insertion point."); NS_ASSERTION(!(mStreamParser && !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. nsRefPtr<nsHtml5OwningUTF16Buffer> prevSearchBuf; nsRefPtr<nsHtml5OwningUTF16Buffer> firstLevelMarker; if (aKey) { if (mFirstBuffer == mLastBuffer) { nsHtml5OwningUTF16Buffer* keyHolder = new nsHtml5OwningUTF16Buffer(aKey); keyHolder->next = mLastBuffer; mFirstBuffer = keyHolder; } else if (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 = nsnull; 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. We need to put a marker there, because otherwise // additional document.writes from nested event loops would insert in the // wrong place. Sigh. firstLevelMarker = new nsHtml5OwningUTF16Buffer((void*)nsnull); if (mFirstBuffer == mLastBuffer) { firstLevelMarker->next = mLastBuffer; mFirstBuffer = firstLevelMarker; } else { prevSearchBuf = mFirstBuffer; while (prevSearchBuf->next != mLastBuffer) { prevSearchBuf = prevSearchBuf->next; } firstLevelMarker->next = mLastBuffer; prevSearchBuf->next = firstLevelMarker; } } nsHtml5DependentUTF16Buffer stackBuffer(aSourceBuffer); while (!mBlocked && stackBuffer.hasMore()) { stackBuffer.adjust(mLastWasCR); mLastWasCR = false; if (stackBuffer.hasMore()) { PRInt32 lineNumberSave; bool inRootContext = (!mStreamParser && !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(); } mLastWasCR = mTokenizer->tokenizeBuffer(&stackBuffer); if (inRootContext) { mRootContextLineNumber = mTokenizer->getLineNumber(); } else { mTokenizer->setLineNumber(lineNumberSave); } if (mTreeBuilder->HasScript()) { mTreeBuilder->Flush(); // Move ops to the executor mExecutor->FlushDocumentWrite(); // run the ops // Flushing tree ops can cause all sorts of things. // Return early if the parser got terminated. if (mExecutor->IsComplete()) { return NS_OK; } } // Ignore suspension requests } } nsRefPtr<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. mExecutor->MarkAsBroken(); return 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 mTreeBuilder->Flush(); // Move ops to the executor mExecutor->FlushDocumentWrite(); // run the ops } else if (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 = new nsHtml5TreeBuilder(nsnull, mExecutor->GetStage()); mDocWriteSpeculativeTreeBuilder->setScriptingEnabled( mTreeBuilder->isScriptingEnabled()); mDocWriteSpeculativeTokenizer = new nsHtml5Tokenizer(mDocWriteSpeculativeTreeBuilder, false); mDocWriteSpeculativeTokenizer->setInterner(&mAtomTable); mDocWriteSpeculativeTokenizer->start(); } mDocWriteSpeculativeTokenizer->resetToDataState(); mDocWriteSpeculativeTreeBuilder->loadState(mTreeBuilder, &mAtomTable); 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()) { mDocWriteSpeculativeLastWasCR = mDocWriteSpeculativeTokenizer->tokenizeBuffer(&stackBuffer); } } mDocWriteSpeculativeTreeBuilder->Flush(); mDocWriteSpeculativeTreeBuilder->DropHandles(); mExecutor->FlushSpeculativeLoads(); } return NS_OK; }
NS_IMETHODIMP nsHtml5Parser::Parse(const nsAString& aSourceBuffer, void* aKey, const nsACString& aContentType, // ignored bool aLastCall, nsDTDMode aMode) // ignored { NS_PRECONDITION(!mExecutor->IsFragmentMode(), "Document.write called in fragment mode!"); if (mExecutor->IsBroken()) { return NS_ERROR_OUT_OF_MEMORY; } if (aSourceBuffer.Length() > PR_INT32_MAX) { mExecutor->MarkAsBroken(); return NS_ERROR_OUT_OF_MEMORY; } // 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. nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip(mStreamParser); nsRefPtr<nsHtml5TreeOpExecutor> treeOpKungFuDeathGrip(mExecutor); if (!mExecutor->HasStarted()) { NS_ASSERTION(!mStreamParser, "Had stream parser but document.write started life cycle."); // This is the first document.write() on a document.open()ed document mExecutor->SetParser(this); mTreeBuilder->setScriptingEnabled(mExecutor->IsScriptEnabled()); mTokenizer->start(); mExecutor->Start(); /* * If you move the following line, be very careful not to cause * WillBuildModel to be called before the document has had its * script global object set. */ mExecutor->WillBuildModel(eDTDMode_unknown); } // Return early if the parser has processed EOF if (mExecutor->IsComplete()) { return NS_OK; } if (aLastCall && aSourceBuffer.IsEmpty() && aKey == GetRootContextKey()) { // document.close() NS_ASSERTION(!mStreamParser, "Had stream parser but got document.close()."); mDocumentClosed = true; if (!mBlocked) { ParseUntilBlocked(); } return NS_OK; } NS_ASSERTION(IsInsertionPointDefined(), "Doc.write reached parser with undefined insertion point."); NS_ASSERTION(!(mStreamParser && !aKey), "Got a null key in a non-script-created parser"); if (aSourceBuffer.IsEmpty()) { return NS_OK; } nsHtml5DependentUTF16Buffer stackBuffer(aSourceBuffer); while (!mBlocked && stackBuffer.hasMore()) { stackBuffer.adjust(mLastWasCR); mLastWasCR = false; if (stackBuffer.hasMore()) { PRInt32 lineNumberSave; bool inRootContext = (!mStreamParser && (aKey == mRootContextKey)); 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(); } mLastWasCR = mTokenizer->tokenizeBuffer(&stackBuffer); if (inRootContext) { mRootContextLineNumber = mTokenizer->getLineNumber(); } else { mTokenizer->setLineNumber(lineNumberSave); } if (mTreeBuilder->HasScript()) { mTreeBuilder->Flush(); // Move ops to the executor mExecutor->FlushDocumentWrite(); // run the ops } // Ignore suspension requests } } nsRefPtr<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. mExecutor->MarkAsBroken(); return NS_ERROR_OUT_OF_MEMORY; } } // The buffer is inserted to the stream here in case it won't be parsed // to completion. // 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. nsHtml5OwningUTF16Buffer* prevSearchBuf = nsnull; nsHtml5OwningUTF16Buffer* searchBuf = mFirstBuffer; // after document.open, the first level of document.write has null key if (aKey) { while (searchBuf != mLastBuffer) { if (searchBuf->key == aKey) { // found a key holder // now insert the new buffer between the previous buffer // and the key holder if we have a buffer left. if (heapBuffer) { heapBuffer->next = searchBuf; if (prevSearchBuf) { prevSearchBuf->next = heapBuffer; } else { mFirstBuffer = heapBuffer; } } break; } prevSearchBuf = searchBuf; searchBuf = searchBuf->next; } if (searchBuf == mLastBuffer) { // key was not found nsHtml5OwningUTF16Buffer* keyHolder = new nsHtml5OwningUTF16Buffer(aKey); keyHolder->next = mFirstBuffer; if (heapBuffer) { heapBuffer->next = keyHolder; mFirstBuffer = heapBuffer; } else { mFirstBuffer = keyHolder; } } } else if (heapBuffer) { // we have a first level document.write after document.open() // insert immediately before mLastBuffer while (searchBuf != mLastBuffer) { prevSearchBuf = searchBuf; searchBuf = searchBuf->next; } heapBuffer->next = mLastBuffer; if (prevSearchBuf) { prevSearchBuf->next = heapBuffer; } else { mFirstBuffer = 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 mTreeBuilder->Flush(); // Move ops to the executor mExecutor->FlushDocumentWrite(); // run the ops } else if (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 = new nsHtml5TreeBuilder(nsnull, mExecutor->GetStage()); mDocWriteSpeculativeTreeBuilder->setScriptingEnabled( mTreeBuilder->isScriptingEnabled()); mDocWriteSpeculativeTokenizer = new nsHtml5Tokenizer(mDocWriteSpeculativeTreeBuilder); mDocWriteSpeculativeTokenizer->setInterner(&mAtomTable); mDocWriteSpeculativeTokenizer->start(); } mDocWriteSpeculativeTokenizer->resetToDataState(); mDocWriteSpeculativeTreeBuilder->loadState(mTreeBuilder, &mAtomTable); 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()) { mDocWriteSpeculativeLastWasCR = mDocWriteSpeculativeTokenizer->tokenizeBuffer(&stackBuffer); } } mDocWriteSpeculativeTreeBuilder->Flush(); mDocWriteSpeculativeTreeBuilder->DropHandles(); mExecutor->FlushSpeculativeLoads(); } return NS_OK; }