Bug 503473 - Prevent document.write() in the HTML5 parser where prohibited by HTML5. r=bnewman, sr=sicking.

--HG--
extra : rebase_source : 0f574695c4d6fad936e9d0960f07261aa192b13d
This commit is contained in:
Henri Sivonen 2009-10-15 14:29:11 +03:00
parent 3d0ac8ccf0
commit 62853870ee
12 changed files with 321 additions and 100 deletions

View File

@ -43,11 +43,12 @@
#include "nsIURI.h"
#include "nsCOMPtr.h"
#include "nsIScriptLoaderObserver.h"
#include "nsWeakPtr.h"
#include "nsIParser.h"
// e68ddc48-4055-4ba9-978d-c49d9cf3189a
#define NS_ISCRIPTELEMENT_IID \
{ 0xe68ddc48, 0x4055, 0x4ba9, \
{ 0x97, 0x8d, 0xc4, 0x9d, 0x9c, 0xf3, 0x18, 0x9a } }
{ 0xa28c198e, 0x14f0, 0x42b1, \
{ 0x8f, 0x6b, 0x0e, 0x7f, 0xca, 0xb4, 0xf4, 0xe8 } }
/**
* Internal interface implemented by script elements
@ -60,7 +61,8 @@ public:
: mLineNumber(0),
mIsEvaluated(PR_FALSE),
mMalformed(PR_FALSE),
mDoneAddingChildren(PR_TRUE)
mDoneAddingChildren(PR_TRUE),
mCreatorParser(nsnull)
{
}
@ -122,11 +124,52 @@ public:
mDoneAddingChildren = PR_FALSE;
}
void SetCreatorParser(nsIParser* aParser)
{
mCreatorParser = getter_AddRefs(NS_GetWeakReference(aParser));
}
/**
* Informs the creator parser that the evaluation of this script is starting
*/
void BeginEvaluating()
{
// Once the async attribute is supported, don't do this if this is an
// async script.
nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
if (parser) {
parser->BeginEvaluatingParserInsertedScript();
}
}
/**
* Informs the creator parser that the evaluation of this script is ending
*/
void EndEvaluating()
{
// Once the async attribute is supported, don't do this if this is an
// async script.
nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
if (parser) {
parser->EndEvaluatingParserInsertedScript();
}
}
/**
* Retrieves a pointer to the creator parser if this has one or null if not
*/
already_AddRefed<nsIParser> GetCreatorParser()
{
nsCOMPtr<nsIParser> parser = do_QueryReferent(mCreatorParser);
return parser.forget();
}
protected:
PRUint32 mLineNumber;
PRPackedBool mIsEvaluated;
PRPackedBool mMalformed;
PRPackedBool mDoneAddingChildren;
nsWeakPtr mCreatorParser;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIScriptElement, NS_ISCRIPTELEMENT_IID)

View File

@ -647,7 +647,9 @@ nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest)
}
FireScriptAvailable(NS_OK, aRequest);
aRequest->mElement->BeginEvaluating();
nsresult rv = EvaluateScript(aRequest, *script);
aRequest->mElement->EndEvaluating();
FireScriptEvaluated(rv, aRequest);
return rv;

View File

@ -735,6 +735,7 @@ nsHTMLDocument::StartDocumentLoad(const char* aCommand,
if (needsParser) {
if (loadAsHtml5) {
mParser = nsHtml5Module::NewHtml5Parser();
mParser->MarkAsNotScriptCreated();
} else {
mParser = do_CreateInstance(kCParserCID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
@ -2142,7 +2143,8 @@ nsHTMLDocument::WriteCommon(const nsAString& aText,
void *key = GenerateParserKey();
if (mWriteState == eDocumentClosed ||
(mWriteState == ePendingClose &&
!mPendingScripts.Contains(key))) {
!mPendingScripts.Contains(key)) ||
(mParser && !mParser->IsInsertionPointDefined())) {
mWriteState = eDocumentClosed;
mParser->Terminate();
NS_ASSERTION(!mParser, "mParser should have been null'd out");
@ -2935,7 +2937,21 @@ nsHTMLDocument::GenerateParserKey(void)
// The script loader provides us with the currently executing script element,
// which is guaranteed to be unique per script.
return mScriptLoader->GetCurrentScript();
if (nsHtml5Module::sEnabled) {
nsIScriptElement* script = mScriptLoader->GetCurrentScript();
if (script && mParser && mParser->IsScriptCreated()) {
nsCOMPtr<nsIParser> creatorParser = script->GetCreatorParser();
if (creatorParser != mParser) {
// Make scripts that aren't inserted by the active parser of this document
// participate in the context of the script that document.open()ed
// this document.
return mParser->GetRootContextKey();
}
}
return script;
} else {
return mScriptLoader->GetCurrentScript();
}
}
/* attribute DOMString designMode; */

View File

@ -85,7 +85,7 @@ public:
NS_INTERFACE_TABLE_HEAD(nsHtml5Parser)
NS_INTERFACE_TABLE1(nsHtml5Parser, nsIParser)
NS_INTERFACE_TABLE2(nsHtml5Parser, nsIParser, nsISupportsWeakReference)
NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsHtml5Parser)
NS_INTERFACE_MAP_END
@ -190,14 +190,7 @@ nsHtml5Parser::GetDTD(nsIDTD** aDTD)
NS_IMETHODIMP
nsHtml5Parser::GetStreamListener(nsIStreamListener** aListener)
{
if (!mStreamParser) {
mStreamParser = new nsHtml5StreamParser(mExecutor, this);
nsIDocument* doc = mExecutor->GetDocument();
if (doc) {
mStreamParser->SetSpeculativeLoaderWithDocument(doc);
}
}
NS_ADDREF(*aListener = mStreamParser);
NS_IF_ADDREF(*aListener = mStreamParser);
return NS_OK;
}
@ -224,6 +217,7 @@ nsHtml5Parser::ContinueInterruptedParsing()
nsCOMPtr<nsIParser> kungFuDeathGrip(this);
nsRefPtr<nsHtml5StreamParser> streamKungFuDeathGrip(mStreamParser);
nsRefPtr<nsHtml5TreeOpExecutor> treeOpKungFuDeathGrip(mExecutor);
CancelParsingEvents(); // If the executor caused us to continue, ignore event
ParseUntilScript();
return NS_OK;
}
@ -264,13 +258,8 @@ nsHtml5Parser::Parse(nsIURI* aURL, // legacy parameter; ignored
*/
NS_PRECONDITION(!mExecutor->HasStarted(),
"Tried to start parse without initializing the parser properly.");
if (!mStreamParser) {
mStreamParser = new nsHtml5StreamParser(mExecutor, this);
nsIDocument* doc = mExecutor->GetDocument();
if (doc) {
mStreamParser->SetSpeculativeLoaderWithDocument(doc);
}
}
NS_PRECONDITION(mStreamParser,
"Can't call this variant of Parse() on script-created parser");
mStreamParser->SetObserver(aObserver);
mExecutor->SetStreamParser(mStreamParser);
mExecutor->SetParser(this);
@ -319,84 +308,93 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
NS_ASSERTION(!mStreamParser,
"Had stream parser but got document.close().");
mDocumentClosed = PR_TRUE;
// TODO: Try to tokenize: http://www.w3.org/Bugs/Public/show_bug.cgi?id=7917
MaybePostContinueEvent();
return NS_OK;
}
NS_PRECONDITION(IsInsertionPointDefined(),
"Document.write called when insertion point not defined.");
if (aSourceBuffer.IsEmpty()) {
return NS_OK;
}
PRInt32 lineNumberSave = mTokenizer->getLineNumber();
if (!aSourceBuffer.IsEmpty()) {
nsRefPtr<nsHtml5UTF16Buffer> buffer = new nsHtml5UTF16Buffer(aSourceBuffer.Length());
memcpy(buffer->getBuffer(), aSourceBuffer.BeginReading(), aSourceBuffer.Length() * sizeof(PRUnichar));
buffer->setEnd(aSourceBuffer.Length());
if (!mBlocked) {
// mExecutor->WillResume();
while (buffer->hasMore()) {
buffer->adjust(mLastWasCR);
mLastWasCR = PR_FALSE;
if (buffer->hasMore()) {
mLastWasCR = mTokenizer->tokenizeBuffer(buffer);
if (mTreeBuilder->HasScript()) {
mTreeBuilder->flushCharacters(); // Flush trailing characters
mTreeBuilder->Flush(); // Move ops to the executor
mExecutor->Flush(); // run the ops
}
if (mBlocked) {
// mExecutor->WillInterrupt();
break;
}
// Ignore suspension requests
}
}
}
nsRefPtr<nsHtml5UTF16Buffer> buffer = new nsHtml5UTF16Buffer(aSourceBuffer.Length());
memcpy(buffer->getBuffer(), aSourceBuffer.BeginReading(), aSourceBuffer.Length() * sizeof(PRUnichar));
buffer->setEnd(aSourceBuffer.Length());
if (buffer->hasMore()) {
// If we got here, the buffer wasn't parsed synchronously to completion
// and its tail needs to go into the chain of pending buffers.
// 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.
nsHtml5UTF16Buffer* prevSearchBuf = nsnull;
nsHtml5UTF16Buffer* searchBuf = mFirstBuffer;
if (aKey) { // after document.open, the first level of document.write has null key
while (searchBuf != mLastBuffer) {
if (searchBuf->key == aKey) {
// found a key holder
// now insert the new buffer between the previous buffer
// and the key holder.
buffer->next = searchBuf;
if (prevSearchBuf) {
prevSearchBuf->next = buffer;
} else {
mFirstBuffer = buffer;
}
break;
}
prevSearchBuf = searchBuf;
searchBuf = searchBuf->next;
// 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.
nsHtml5UTF16Buffer* prevSearchBuf = nsnull;
nsHtml5UTF16Buffer* searchBuf = mFirstBuffer;
if (aKey) { // after document.open, the first level of document.write has null key
while (searchBuf != mLastBuffer) {
if (searchBuf->key == aKey) {
// found a key holder
// now insert the new buffer between the previous buffer
// and the key holder.
buffer->next = searchBuf;
if (prevSearchBuf) {
prevSearchBuf->next = buffer;
} else {
mFirstBuffer = buffer;
}
break;
}
if (searchBuf == mLastBuffer || !aKey) {
// key was not found or we have a first-level write after document.open
// we'll insert to the head of the queue
nsHtml5UTF16Buffer* keyHolder = new nsHtml5UTF16Buffer(aKey);
keyHolder->next = mFirstBuffer;
buffer->next = keyHolder;
mFirstBuffer = buffer;
}
if (!mStreamParser) {
MaybePostContinueEvent();
}
} else { // buffer didn't have more
// Scripting semantics require a forced tree builder flush here
mTreeBuilder->flushCharacters(); // Flush trailing characters
mTreeBuilder->Flush(); // Move ops to the executor
mExecutor->Flush(); // run the ops
prevSearchBuf = searchBuf;
searchBuf = searchBuf->next;
}
}
if (searchBuf == mLastBuffer || !aKey) {
// key was not found or we have a first-level write after document.open
// we'll insert to the head of the queue
nsHtml5UTF16Buffer* keyHolder = new nsHtml5UTF16Buffer(aKey);
keyHolder->next = mFirstBuffer;
buffer->next = keyHolder;
mFirstBuffer = buffer;
}
if (!mBlocked) {
// mExecutor->WillResume();
while (buffer->hasMore()) {
buffer->adjust(mLastWasCR);
mLastWasCR = PR_FALSE;
if (buffer->hasMore()) {
mLastWasCR = mTokenizer->tokenizeBuffer(buffer);
if (mTreeBuilder->HasScript()) {
// No need to flush characters, because an end tag was tokenized last
mTreeBuilder->Flush(); // Move ops to the executor
mExecutor->Flush(); // run the ops
}
if (mBlocked) {
// mExecutor->WillInterrupt();
break;
}
// Ignore suspension requests
}
}
}
if (!mBlocked) { // buffer was tokenized to completion
// Scripting semantics require a forced tree builder flush here
mTreeBuilder->flushCharacters(); // Flush trailing characters
mTreeBuilder->Flush(); // Move ops to the executor
mExecutor->Flush(); // run the ops
} else if (!mStreamParser && buffer->hasMore() && aKey == mRootContextKey) {
// The buffer wasn't parsed completely, the document was created by
// document.open() and the script that wrote wasn't created by this parser.
// Can't rely on the executor causing the parser to continue.
MaybePostContinueEvent();
}
mTokenizer->setLineNumber(lineNumberSave);
return NS_OK;
@ -525,6 +523,7 @@ nsHtml5Parser::Reset()
UnblockParser();
mDocumentClosed = PR_FALSE;
mStreamParser = nsnull;
mParserInsertedScriptsBeingEvaluated = 0;
mRootContextKey = nsnull;
mContinueEvent = nsnull; // weak ref
mAtomTable.Clear(); // should be already cleared in the fragment case anyway
@ -540,6 +539,38 @@ nsHtml5Parser::CanInterrupt()
return !mFragmentMode;
}
PRBool
nsHtml5Parser::IsInsertionPointDefined()
{
return !mExecutor->IsFlushing() &&
(!mStreamParser || mParserInsertedScriptsBeingEvaluated);
}
void
nsHtml5Parser::BeginEvaluatingParserInsertedScript()
{
++mParserInsertedScriptsBeingEvaluated;
}
void
nsHtml5Parser::EndEvaluatingParserInsertedScript()
{
--mParserInsertedScriptsBeingEvaluated;
}
void
nsHtml5Parser::MarkAsNotScriptCreated()
{
NS_PRECONDITION(!mStreamParser, "Must not call this twice.");
mStreamParser = new nsHtml5StreamParser(mExecutor, this);
}
PRBool
nsHtml5Parser::IsScriptCreated()
{
return !mStreamParser;
}
/* End nsIParser */
// not from interface

View File

@ -61,8 +61,11 @@
#include "nsHtml5TreeOpExecutor.h"
#include "nsHtml5StreamParser.h"
#include "nsHtml5AtomTable.h"
#include "nsWeakReference.h"
class nsHtml5Parser : public nsIParser {
class nsHtml5Parser : public nsIParser,
public nsSupportsWeakReference
{
public:
NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@ -250,7 +253,33 @@ class nsHtml5Parser : public nsIParser {
* True in fragment mode and during synchronous document.write
*/
virtual PRBool CanInterrupt();
/**
* True if the insertion point (per HTML5) is defined.
*/
virtual PRBool IsInsertionPointDefined();
/**
* Call immediately before starting to evaluate a parser-inserted script.
*/
virtual void BeginEvaluatingParserInsertedScript();
/**
* Call immediately after having evaluated a parser-inserted script.
*/
virtual void EndEvaluatingParserInsertedScript();
/**
* Marks the HTML5 parser as not a script-created parser: Prepares the
* parser to be able to read a stream.
*/
virtual void MarkAsNotScriptCreated();
/**
* True if this is a script-created HTML5 parser.
*/
virtual PRBool IsScriptCreated();
/* End nsIParser */
/**
@ -318,6 +347,11 @@ class nsHtml5Parser : public nsIParser {
*/
PRBool mBlocked;
/**
* The number of parser-inserted script currently being evaluated.
*/
PRInt32 mParserInsertedScriptsBeingEvaluated;
/**
* True if document.close() has been called.
*/

View File

@ -69,7 +69,7 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(nsHtml5StreamParser)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHtml5StreamParser)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mObserver)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRequest)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOwner)
tmp->mOwner = nsnull;
tmp->mExecutorFlusher = nsnull;
tmp->mExecutor = nsnull;
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument)
@ -79,7 +79,10 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsHtml5StreamParser)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mObserver)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRequest)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOwner)
if (tmp->mOwner) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mOwner");
cb.NoteXPCOMChild(static_cast<nsIParser*> (tmp->mOwner));
}
// hack: count the strongly owned edge wrapped in the runnable
if (tmp->mExecutorFlusher) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mExecutorFlusher->mExecutor");

View File

@ -381,7 +381,7 @@ class nsHtml5StreamParser : public nsIStreamListener,
/**
* The owner parser.
*/
nsCOMPtr<nsHtml5Parser> mOwner;
nsRefPtr<nsHtml5Parser> mOwner;
/**
* Whether the last character tokenized was a carriage return (for CRLF)

View File

@ -268,6 +268,11 @@ nsHtml5TreeOpExecutor::Flush()
mFlushTimer->Cancel();
return;
}
if (mFlushing) {
return;
}
mFlushing = PR_TRUE;
nsRefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this); // avoid crashing near EOF
nsCOMPtr<nsIParser> parserKungFuDeathGrip(mParser);
@ -312,9 +317,12 @@ nsHtml5TreeOpExecutor::Flush()
}
}
ScheduleTimer();
mFlushing = PR_FALSE;
if (mScriptElement) {
NS_ASSERTION(!mCallDidBuildModel, "Had a script element and DidBuildModel call");
ExecuteScript();
RunScript();
} else if (mCallDidBuildModel) {
mCallDidBuildModel = PR_FALSE;
DidBuildModel(PR_FALSE);
@ -410,7 +418,7 @@ nsHtml5TreeOpExecutor::DocumentMode(nsHtml5DocumentMode m)
* although the parser is.
*/
void
nsHtml5TreeOpExecutor::ExecuteScript()
nsHtml5TreeOpExecutor::RunScript()
{
mReadingFromStage = PR_FALSE;
NS_ASSERTION(mScriptElement, "No script to run");
@ -423,6 +431,7 @@ nsHtml5TreeOpExecutor::ExecuteScript()
// calling back into mParser anymore. mParser has been nulled out by now.
return;
}
sele->SetCreatorParser(mParser);
// Notify our document that we're loading this script.
nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(mDocument);
NS_ASSERTION(htmlDocument, "Document didn't QI into HTML document.");
@ -516,6 +525,7 @@ nsHtml5TreeOpExecutor::MaybeFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
void
nsHtml5TreeOpExecutor::ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
{
NS_PRECONDITION(!mFlushing, "mOpQueue modified during tree op execution.");
if (mOpQueue.IsEmpty()) {
mOpQueue.SwapElements(aOpQueue);
return;

View File

@ -111,6 +111,8 @@ class nsHtml5TreeOpExecutor : public nsContentSink,
nsCOMPtr<nsIContent> mScriptElement;
nsHtml5TreeOpStage mStage;
PRBool mFlushing;
/**
* Used for deferring DidBuildModel call out of notification batch
@ -315,7 +317,11 @@ class nsHtml5TreeOpExecutor : public nsContentSink,
return mStarted;
}
void ExecuteScript();
PRBool IsFlushing() {
return mFlushing;
}
void RunScript();
void MaybePreventExecution() {
if (mScriptElement) {

View File

@ -54,10 +54,9 @@
#include "nsTArray.h"
#include "nsIAtom.h"
// 506527cc-d832-420b-ba3a-80c05aa105f4
#define NS_IPARSER_IID \
{ 0x506527cc, 0xd832, 0x420b, \
{ 0xba, 0x3a, 0x80, 0xc0, 0x5a, 0xa1, 0x05, 0xf4 } }
{ 0xa44dc586, 0xc521, 0x40a1, \
{ 0xa0, 0xaf, 0xbe, 0x02, 0xa5, 0x51, 0xe0, 0xb7 } }
// {41421C60-310A-11d4-816F-000064657374}
@ -300,6 +299,31 @@ class nsIParser : public nsISupports {
* parsing for example document.write or innerHTML.
*/
virtual PRBool CanInterrupt() = 0;
/**
* True if the insertion point (per HTML5) is defined.
*/
virtual PRBool IsInsertionPointDefined() = 0;
/**
* Call immediately before starting to evaluate a parser-inserted script.
*/
virtual void BeginEvaluatingParserInsertedScript() = 0;
/**
* Call immediately after having evaluated a parser-inserted script.
*/
virtual void EndEvaluatingParserInsertedScript() = 0;
/**
* Marks the HTML5 parser as not a script-created parser.
*/
virtual void MarkAsNotScriptCreated() = 0;
/**
* True if this is a script-created HTML5 parser.
*/
virtual PRBool IsScriptCreated() = 0;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIParser, NS_IPARSER_IID)

View File

@ -1932,6 +1932,33 @@ nsParser::CanInterrupt()
return (mFlags & NS_PARSER_FLAG_CAN_INTERRUPT) != 0;
}
PRBool
nsParser::IsInsertionPointDefined()
{
return PR_TRUE;
}
void
nsParser::BeginEvaluatingParserInsertedScript()
{
}
void
nsParser::EndEvaluatingParserInsertedScript()
{
}
void
nsParser::MarkAsNotScriptCreated()
{
}
PRBool
nsParser::IsScriptCreated()
{
return PR_FALSE;
}
void
nsParser::SetCanInterrupt(PRBool aCanInterrupt)
{

View File

@ -344,6 +344,31 @@ class nsParser : public nsIParser,
*/
virtual PRBool CanInterrupt();
/**
* Return true.
*/
virtual PRBool IsInsertionPointDefined();
/**
* No-op.
*/
virtual void BeginEvaluatingParserInsertedScript();
/**
* No-op.
*/
virtual void EndEvaluatingParserInsertedScript();
/**
* No-op.
*/
virtual void MarkAsNotScriptCreated();
/**
* Always false.
*/
virtual PRBool IsScriptCreated();
/**
* Set to parser state to indicate whether parsing tokens can be interrupted
* @param aCanInterrupt PR_TRUE if parser can be interrupted, PR_FALSE if it can not be interrupted.