Bug 482919 - Add speculative parsing to the HTML5 parser. r=bnewman.

--HG--
extra : rebase_source : d8b0840910d47aadee767fcbecf6832d244b1c4b
This commit is contained in:
Henri Sivonen 2009-10-12 16:08:04 +03:00
Родитель 213aca73bc
Коммит 98a6ad4f84
20 изменённых файлов: 934 добавлений и 215 удалений

Просмотреть файл

@ -73,6 +73,8 @@ CPPSRCS = \
nsHtml5StateSnapshot.cpp \
nsHtml5TreeOpExecutor.cpp \
nsHtml5StreamParser.cpp \
nsHtml5Speculation.cpp \
nsHtml5SpeculativeLoader.cpp \
$(NULL)
FORCE_STATIC_LIB = 1

Просмотреть файл

@ -119,11 +119,7 @@ nsHtml5Parser::nsHtml5Parser()
nsHtml5Parser::~nsHtml5Parser()
{
mTokenizer->end();
while (mFirstBuffer) {
nsHtml5UTF16Buffer* old = mFirstBuffer;
mFirstBuffer = mFirstBuffer->next;
delete old;
}
mFirstBuffer = nsnull;
}
NS_IMETHODIMP_(void)
@ -161,7 +157,7 @@ nsHtml5Parser::SetCommand(eParserCommands aParserCommand)
NS_IMETHODIMP_(void)
nsHtml5Parser::SetDocumentCharset(const nsACString& aCharset, PRInt32 aCharsetSource)
{
NS_PRECONDITION(mExecutor->GetLifeCycle() == NOT_STARTED,
NS_PRECONDITION(!mExecutor->HasStarted(),
"Document charset set too late.");
NS_PRECONDITION(mStreamParser, "Tried to set charset on a script-only parser.");
mStreamParser->SetDocumentCharset(aCharset, aCharsetSource);
@ -196,6 +192,10 @@ nsHtml5Parser::GetStreamListener(nsIStreamListener** aListener)
{
if (!mStreamParser) {
mStreamParser = new nsHtml5StreamParser(mExecutor, this);
nsIDocument* doc = mExecutor->GetDocument();
if (doc) {
mStreamParser->SetSpeculativeLoaderWithDocument(doc);
}
}
NS_ADDREF(*aListener = mStreamParser);
return NS_OK;
@ -262,10 +262,14 @@ nsHtml5Parser::Parse(nsIURI* aURL, // legacy parameter; ignored
* Do NOT cause WillBuildModel to be called synchronously from here!
* The document won't be ready for it until OnStartRequest!
*/
NS_PRECONDITION(mExecutor->GetLifeCycle() == NOT_STARTED,
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);
}
}
mStreamParser->SetObserver(aObserver);
mExecutor->SetStreamParser(mStreamParser);
@ -293,27 +297,23 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
nsRefPtr<nsHtml5TreeOpExecutor> treeOpKungFuDeathGrip(mExecutor);
// Return early if the parser has processed EOF
switch (mExecutor->GetLifeCycle()) {
case TERMINATED:
return NS_OK;
case NOT_STARTED:
NS_ASSERTION(!mStreamParser,
"Had stream parser but document.write started life cycle.");
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);
break;
default:
break;
if (!mExecutor->HasStarted()) {
NS_ASSERTION(!mStreamParser,
"Had stream parser but document.write started life cycle.");
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);
}
if (mExecutor->IsComplete()) {
return NS_OK;
}
if (aLastCall && aSourceBuffer.IsEmpty() && aKey == GetRootContextKey()) {
// document.close()
NS_ASSERTION(!mStreamParser,
@ -326,7 +326,7 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
PRInt32 lineNumberSave = mTokenizer->getLineNumber();
if (!aSourceBuffer.IsEmpty()) {
nsHtml5UTF16Buffer* buffer = new nsHtml5UTF16Buffer(aSourceBuffer.Length());
nsRefPtr<nsHtml5UTF16Buffer> buffer = new nsHtml5UTF16Buffer(aSourceBuffer.Length());
memcpy(buffer->getBuffer(), aSourceBuffer.BeginReading(), aSourceBuffer.Length() * sizeof(PRUnichar));
buffer->setEnd(aSourceBuffer.Length());
if (!mBlocked) {
@ -337,12 +337,11 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
if (buffer->hasMore()) {
mLastWasCR = mTokenizer->tokenizeBuffer(buffer);
if (mTreeBuilder->HasScript()) {
mTreeBuilder->Flush(); // moves ops to executor queue
mExecutor->Flush(); // executes the queue
// Is mBlocked always true here?
mTreeBuilder->flushCharacters(); // Flush trailing characters
mTreeBuilder->Flush(); // Move ops to the executor
mExecutor->Flush(); // run the ops
}
if (mBlocked) {
// XXX is the tail insertion and script exec in the wrong order?
// mExecutor->WillInterrupt();
break;
}
@ -388,16 +387,17 @@ nsHtml5Parser::Parse(const nsAString& aSourceBuffer,
buffer->next = keyHolder;
mFirstBuffer = buffer;
}
MaybePostContinueEvent();
} else {
delete 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
}
}
// Scripting semantics require a forced tree builder flush here
// TODO: Also flush the pending text node from tree builder
mTreeBuilder->Flush();
mExecutor->Flush();
mTokenizer->setLineNumber(lineNumberSave);
return NS_OK;
}
@ -416,7 +416,7 @@ nsHtml5Parser::Terminate(void)
{
// We should only call DidBuildModel once, so don't do anything if this is
// the second time that Terminate has been called.
if (mExecutor->GetLifeCycle() == TERMINATED) {
if (mExecutor->IsComplete()) {
return NS_OK;
}
// XXX - [ until we figure out a way to break parser-sink circularity ]
@ -474,7 +474,7 @@ nsHtml5Parser::ParseFragment(const nsAString& aSourceBuffer,
mTreeBuilder->setFragmentContext(aContextLocalName, aContextNamespace, &weakTarget, aQuirks);
mFragmentMode = PR_TRUE;
NS_PRECONDITION(mExecutor->GetLifeCycle() == NOT_STARTED, "Tried to start parse without initializing the parser properly.");
NS_PRECONDITION(!mExecutor->HasStarted(), "Tried to start parse without initializing the parser properly.");
mTreeBuilder->setScriptingEnabled(mExecutor->IsScriptEnabled());
mTokenizer->start();
mExecutor->Start(); // Don't call WillBuildModel in fragment case
@ -497,7 +497,6 @@ nsHtml5Parser::ParseFragment(const nsAString& aSourceBuffer,
mTreeBuilder->Flush();
mExecutor->Flush();
mTokenizer->end();
mExecutor->SetLifeCycle(TERMINATED);
mExecutor->DropParserAndPerfHint();
mAtomTable.Clear();
return NS_OK;
@ -530,11 +529,7 @@ nsHtml5Parser::Reset()
mContinueEvent = nsnull; // weak ref
mAtomTable.Clear(); // should be already cleared in the fragment case anyway
// Portable parser objects
while (mFirstBuffer->next) {
nsHtml5UTF16Buffer* oldBuf = mFirstBuffer;
mFirstBuffer = mFirstBuffer->next;
delete oldBuf;
}
mFirstBuffer->next = nsnull;
mFirstBuffer->setStart(0);
mFirstBuffer->setEnd(0);
}
@ -568,58 +563,51 @@ nsHtml5Parser::ParseUntilScript()
return;
}
switch (mExecutor->GetLifeCycle()) {
case TERMINATED:
return;
case NOT_STARTED:
NS_NOTREACHED("Bad life cycle!");
break;
default:
break;
if (mExecutor->IsComplete()) {
return;
}
NS_ASSERTION(mExecutor->HasStarted(), "Bad life cycle.");
mExecutor->WillResume();
for (;;) {
if (!mFirstBuffer->hasMore()) {
if (mFirstBuffer == mLastBuffer) {
switch (mExecutor->GetLifeCycle()) {
case TERMINATED:
// something like cache manisfests stopped the parse in mid-flight
return;
case PARSING:
if (mDocumentClosed) {
NS_ASSERTION(!mStreamParser,
"This should only happen with script-created parser.");
mTokenizer->eof();
mTreeBuilder->StreamEnded();
mTreeBuilder->Flush();
mExecutor->Flush();
mTokenizer->end();
return;
} else {
// never release the last buffer. instead just zero its indeces for refill
mFirstBuffer->setStart(0);
mFirstBuffer->setEnd(0);
if (mStreamParser) {
mStreamParser->ContinueAfterScripts(mTokenizer,
mTreeBuilder,
mLastWasCR);
}
return; // no more data for now but expecting more
}
default:
NS_NOTREACHED("It should be impossible to reach this.");
return;
if (mExecutor->IsComplete()) {
// something like cache manisfests stopped the parse in mid-flight
return;
}
if (mDocumentClosed) {
NS_ASSERTION(!mStreamParser,
"This should only happen with script-created parser.");
mTokenizer->eof();
mTreeBuilder->StreamEnded();
mTreeBuilder->Flush();
mExecutor->Flush();
mTokenizer->end();
return;
} else {
// never release the last buffer.
NS_ASSERTION(!mLastBuffer->getStart(),
"Sentinel buffer had its indeces changed.");
NS_ASSERTION(!mLastBuffer->getEnd(),
"Sentinel buffer had its indeces changed.");
if (mStreamParser &&
mReturnToStreamParserPermitted &&
!mExecutor->IsScriptExecuting()) {
mReturnToStreamParserPermitted = PR_FALSE;
mStreamParser->ContinueAfterScripts(mTokenizer,
mTreeBuilder,
mLastWasCR);
}
return; // no more data for now but expecting more
}
} else {
nsHtml5UTF16Buffer* oldBuf = mFirstBuffer;
mFirstBuffer = mFirstBuffer->next;
delete oldBuf;
continue;
}
}
if (mBlocked || (mExecutor->GetLifeCycle() == TERMINATED)) {
if (mBlocked || mExecutor->IsComplete()) {
return;
}
@ -644,7 +632,7 @@ nsHtml5Parser::ParseUntilScript()
void
nsHtml5Parser::MaybePostContinueEvent()
{
NS_PRECONDITION(mExecutor->GetLifeCycle() != TERMINATED,
NS_PRECONDITION(!mExecutor->IsComplete(),
"Tried to post continue event when the parser is done.");
if (mContinueEvent) {
return; // we already have a pending event
@ -665,6 +653,9 @@ nsHtml5Parser::Initialize(nsIDocument* aDoc,
nsISupports* aContainer,
nsIChannel* aChannel)
{
if (mStreamParser && aDoc) {
mStreamParser->SetSpeculativeLoaderWithDocument(aDoc);
}
return mExecutor->Init(aDoc, aURI, aContainer, aChannel);
}
@ -680,4 +671,5 @@ nsHtml5Parser::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState)
mTokenizer->resetToDataState();
mTreeBuilder->loadState(aState, &mAtomTable);
mLastWasCR = PR_FALSE;
mReturnToStreamParserPermitted = PR_TRUE;
}

Просмотреть файл

@ -331,7 +331,7 @@ class nsHtml5Parser : public nsIParser {
/**
* The first buffer in the pending UTF-16 buffer queue
*/
nsHtml5UTF16Buffer* mFirstBuffer; // manually managed strong ref
nsRefPtr<nsHtml5UTF16Buffer> mFirstBuffer;
/**
* The last buffer in the pending UTF-16 buffer queue
@ -358,6 +358,11 @@ class nsHtml5Parser : public nsIParser {
* The stream parser.
*/
nsRefPtr<nsHtml5StreamParser> mStreamParser;
/**
* Whether it's OK to transfer parsing back to the stream parser
*/
PRBool mReturnToStreamParserPermitted;
/**
* The scoped atom table

Просмотреть файл

@ -0,0 +1,75 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is HTML Parser C++ Translator code.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henri Sivonen <hsivonen@iki.fi>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsHtml5Speculation.h"
nsHtml5Speculation::nsHtml5Speculation(nsHtml5UTF16Buffer* aBuffer,
PRInt32 aStart,
nsAHtml5TreeBuilderState* aSnapshot)
: mBuffer(aBuffer)
, mStart(aStart)
, mSnapshot(aSnapshot)
{
MOZ_COUNT_CTOR(nsHtml5Speculation);
}
nsHtml5Speculation::~nsHtml5Speculation()
{
MOZ_COUNT_DTOR(nsHtml5Speculation);
}
void
nsHtml5Speculation::MaybeFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
{
// No-op
}
void
nsHtml5Speculation::ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue)
{
if (mOpQueue.IsEmpty()) {
mOpQueue.SwapElements(aOpQueue);
return;
}
mOpQueue.MoveElementsFrom(aOpQueue);
}
void
nsHtml5Speculation::FlushToSink(nsAHtml5TreeOpSink* aSink)
{
aSink->ForcedFlush(mOpQueue);
}

Просмотреть файл

@ -0,0 +1,98 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is HTML Parser C++ Translator code.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henri Sivonen <hsivonen@iki.fi>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef nsHtml5Speculation_h__
#define nsHtml5Speculation_h__
#include "nsHtml5UTF16Buffer.h"
#include "nsAHtml5TreeBuilderState.h"
#include "nsHtml5TreeOperation.h"
#include "nsAHtml5TreeOpSink.h"
#include "nsTArray.h"
#include "nsAutoPtr.h"
class nsHtml5Speculation : public nsAHtml5TreeOpSink
{
public:
nsHtml5Speculation(nsHtml5UTF16Buffer* aBuffer,
PRInt32 aStart,
nsAHtml5TreeBuilderState* aSnapshot);
~nsHtml5Speculation();
nsHtml5UTF16Buffer* GetBuffer() {
return mBuffer;
}
PRInt32 GetStart() {
return mStart;
}
nsAHtml5TreeBuilderState* GetSnapshot() {
return mSnapshot;
}
/**
* No-op.
*/
virtual void MaybeFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue);
/**
* Flush the operations from the tree operations from the argument
* queue unconditionally.
*/
virtual void ForcedFlush(nsTArray<nsHtml5TreeOperation>& aOpQueue);
void FlushToSink(nsAHtml5TreeOpSink* aSink);
private:
/**
* The first buffer in the pending UTF-16 buffer queue
*/
nsRefPtr<nsHtml5UTF16Buffer> mBuffer;
/**
* The start index of this speculation in the first buffer
*/
PRInt32 mStart;
nsAutoPtr<nsAHtml5TreeBuilderState> mSnapshot;
nsTArray<nsHtml5TreeOperation> mOpQueue;
};
#endif // nsHtml5Speculation_h__

Просмотреть файл

@ -0,0 +1,134 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=2 et tw=79: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Pierre Phaneuf <pp@ludusdesign.com>
* Henri Sivonen <hsivonen@iki.fi>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsHtml5SpeculativeLoader.h"
#include "nsICSSLoader.h"
#include "nsNetUtil.h"
#include "nsScriptLoader.h"
#include "nsICSSLoaderObserver.h"
/**
* Used if we need to pass an nsICSSLoaderObserver as parameter,
* but don't really need its services
*/
class nsHtml5DummyCSSLoaderObserver : public nsICSSLoaderObserver {
public:
NS_IMETHOD
StyleSheetLoaded(nsICSSStyleSheet* aSheet, PRBool aWasAlternate, nsresult aStatus) {
return NS_OK;
}
NS_DECL_ISUPPORTS
};
NS_IMPL_ISUPPORTS1(nsHtml5DummyCSSLoaderObserver, nsICSSLoaderObserver)
nsHtml5SpeculativeLoader::nsHtml5SpeculativeLoader(nsIDocument* aDocument)
: mDocument(aDocument)
{
MOZ_COUNT_CTOR(nsHtml5SpeculativeLoader);
mPreloadedURLs.Init(23); // Mean # of preloadable resources per page on dmoz
}
nsHtml5SpeculativeLoader::~nsHtml5SpeculativeLoader()
{
MOZ_COUNT_DTOR(nsHtml5SpeculativeLoader);
}
NS_IMPL_THREADSAFE_ADDREF(nsHtml5SpeculativeLoader)
NS_IMPL_THREADSAFE_RELEASE(nsHtml5SpeculativeLoader)
already_AddRefed<nsIURI>
nsHtml5SpeculativeLoader::ConvertIfNotPreloadedYet(const nsAString& aURL)
{
nsIURI* base = mDocument->GetBaseURI();
const nsCString& charset = mDocument->GetDocumentCharacterSet();
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, charset.get(), base);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to create a URI");
return nsnull;
}
nsCAutoString spec;
uri->GetSpec(spec);
if (mPreloadedURLs.Contains(spec)) {
return nsnull;
}
mPreloadedURLs.Put(spec);
nsIURI* retURI = uri;
NS_ADDREF(retURI);
return retURI;
}
void
nsHtml5SpeculativeLoader::PreloadScript(const nsAString& aURL,
const nsAString& aCharset,
const nsAString& aType)
{
nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
if (!uri) {
return;
}
mDocument->ScriptLoader()->PreloadURI(uri, aCharset, aType);
}
void
nsHtml5SpeculativeLoader::PreloadStyle(const nsAString& aURL,
const nsAString& aCharset)
{
nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
if (!uri) {
return;
}
nsCOMPtr<nsICSSLoaderObserver> obs = new nsHtml5DummyCSSLoaderObserver();
mDocument->CSSLoader()->LoadSheet(uri, mDocument->NodePrincipal(),
NS_LossyConvertUTF16toASCII(aCharset),
obs);
}
void
nsHtml5SpeculativeLoader::PreloadImage(const nsAString& aURL)
{
nsCOMPtr<nsIURI> uri = ConvertIfNotPreloadedYet(aURL);
if (!uri) {
return;
}
mDocument->MaybePreLoadImage(uri);
}

Просмотреть файл

@ -0,0 +1,85 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is HTML Parser C++ Translator code.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Henri Sivonen <hsivonen@iki.fi>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef nsHtml5SpeculativeLoader_h__
#define nsHtml5SpeculativeLoader_h__
#include "mozilla/Mutex.h"
#include "nsIURI.h"
#include "nsString.h"
#include "nsCOMPtr.h"
#include "nsIDocument.h"
#include "nsHashSets.h"
class nsHtml5SpeculativeLoader
{
public:
nsHtml5SpeculativeLoader(nsIDocument* aDocument);
~nsHtml5SpeculativeLoader();
NS_IMETHOD_(nsrefcnt) AddRef(void);
NS_IMETHOD_(nsrefcnt) Release(void);
void PreloadScript(const nsAString& aURL,
const nsAString& aCharset,
const nsAString& aType);
void PreloadStyle(const nsAString& aURL, const nsAString& aCharset);
void PreloadImage(const nsAString& aURL);
private:
/**
* Get a nsIURI for an nsString if the URL hasn't been preloaded yet.
*/
already_AddRefed<nsIURI> ConvertIfNotPreloadedYet(const nsAString& aURL);
nsAutoRefCnt mRefCnt;
/**
* The document to use as the context for preloading.
*/
nsCOMPtr<nsIDocument> mDocument;
/**
* URLs already preloaded/preloading.
*/
nsCStringHashSet mPreloadedURLs;
};
#endif // nsHtml5SpeculativeLoader_h__

Просмотреть файл

@ -72,6 +72,8 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsHtml5StreamParser)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOwner)
tmp->mExecutorFlusher = nsnull;
tmp->mExecutor = nsnull;
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument)
tmp->mTreeBuilder->DropSpeculativeLoader();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsHtml5StreamParser)
@ -83,6 +85,13 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsHtml5StreamParser)
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mExecutorFlusher->mExecutor");
cb.NoteXPCOMChild(static_cast<nsIContentSink*> (tmp->mExecutor));
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocument)
// hack: count the strongly owned edge wrapped in the speculative loader
if (tmp->mDocument) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
"mTreeBuilder->mSpeculativeLoader->mDocument");
cb.NoteXPCOMChild(tmp->mDocument);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
class nsHtml5ExecutorFlusher : public nsRunnable
@ -109,6 +118,7 @@ nsHtml5StreamParser::nsHtml5StreamParser(nsHtml5TreeOpExecutor* aExecutor,
, mTokenizer(new nsHtml5Tokenizer(mTreeBuilder))
, mTokenizerMutex("nsHtml5StreamParser mTokenizerMutex")
, mOwner(aOwner)
, mSpeculationMutex("nsHtml5StreamParser mSpeculationMutex")
, mTerminatedMutex("nsHtml5StreamParser mTerminatedMutex")
, mThread(nsHtml5Module::GetStreamParserThread())
, mExecutorFlusher(new nsHtml5ExecutorFlusher(aExecutor))
@ -132,17 +142,20 @@ nsHtml5StreamParser::~nsHtml5StreamParser()
mUnicodeDecoder = nsnull;
mSniffingBuffer = nsnull;
mMetaScanner = nsnull;
while (mFirstBuffer) {
nsHtml5UTF16Buffer* old = mFirstBuffer;
mFirstBuffer = mFirstBuffer->next;
delete old;
}
mFirstBuffer = nsnull;
mExecutor = nsnull;
mTreeBuilder = nsnull;
mTokenizer = nsnull;
mOwner = nsnull;
}
void
nsHtml5StreamParser::SetSpeculativeLoaderWithDocument(nsIDocument* aDocument) {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
mDocument = aDocument;
mTreeBuilder->SetSpeculativeLoaderWithDocument(aDocument);
}
nsresult
nsHtml5StreamParser::GetChannel(nsIChannel** aChannel)
{
@ -444,7 +457,7 @@ nsHtml5StreamParser::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
{
NS_PRECONDITION(STREAM_NOT_STARTED == mStreamState,
"Got OnStartRequest when the stream had already started.");
NS_PRECONDITION(mExecutor->GetLifeCycle() == NOT_STARTED,
NS_PRECONDITION(!mExecutor->HasStarted(),
"Got OnStartRequest at the wrong stage in the executor life cycle.");
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
if (mObserver) {
@ -503,6 +516,12 @@ nsHtml5StreamParser::DoStopRequest()
NS_ASSERTION(IsParserThread(), "Wrong thread!");
NS_PRECONDITION(STREAM_BEING_READ == mStreamState,
"Stream ended without being open.");
mTokenizerMutex.AssertCurrentThreadOwns();
if (IsTerminated()) {
return;
}
if (!mUnicodeDecoder) {
PRUint32 writeCount;
FinalizeSniffing(nsnull, 0, &writeCount, 0);
@ -510,10 +529,12 @@ nsHtml5StreamParser::DoStopRequest()
}
mStreamState = STREAM_ENDED;
if (!mWaitingForScripts) {
ParseUntilScript();
}
if (IsTerminatedOrInterrupted()) {
return;
}
ParseAvailableData();
}
class nsHtml5RequestStopper : public nsRunnable
@ -526,6 +547,7 @@ class nsHtml5RequestStopper : public nsRunnable
{}
NS_IMETHODIMP Run()
{
mozilla::MutexAutoLock autoLock(mStreamParser->mTokenizerMutex);
mStreamParser->DoStopRequest();
return NS_OK;
}
@ -554,14 +576,23 @@ nsHtml5StreamParser::DoDataAvailable(PRUint8* aBuffer, PRUint32 aLength)
NS_ASSERTION(IsParserThread(), "Wrong thread!");
NS_PRECONDITION(STREAM_BEING_READ == mStreamState,
"DoDataAvailable called when stream not open.");
mTokenizerMutex.AssertCurrentThreadOwns();
if (IsTerminated()) {
return;
}
PRUint32 writeCount;
HasDecoder() ? WriteStreamBytes(aBuffer, aLength, &writeCount) :
SniffStreamBytes(aBuffer, aLength, &writeCount);
// dropping nsresult here
NS_ASSERTION(writeCount == aLength, "Wrong number of stream bytes written/sniffed.");
if (!mWaitingForScripts) {
ParseUntilScript();
if (IsTerminatedOrInterrupted()) {
return;
}
ParseAvailableData();
}
class nsHtml5DataAvailable : public nsRunnable
@ -580,6 +611,7 @@ class nsHtml5DataAvailable : public nsRunnable
{}
NS_IMETHODIMP Run()
{
mozilla::MutexAutoLock autoLock(mStreamParser->mTokenizerMutex);
mStreamParser->DoDataAvailable(mData, mLength);
return NS_OK;
}
@ -648,48 +680,46 @@ nsHtml5StreamParser::internalEncodingDeclaration(nsString* aEncoding)
}
void
nsHtml5StreamParser::ParseUntilScript()
nsHtml5StreamParser::ParseAvailableData()
{
NS_ASSERTION(IsParserThread(), "Wrong thread!");
if (IsTerminated()) {
mTokenizerMutex.AssertCurrentThreadOwns();
if (IsTerminatedOrInterrupted()) {
return;
}
// TODO: Relax this mutex so that the parser doesn't speculate to
// completion when it's already known that the speculation will fail.
// Maybe have another boolean and mutex for checking IsSpeculationFailing()
// or something like that instead of trying to be fancy with this mutex.
mozilla::MutexAutoLock autoLock(mTokenizerMutex);
mWaitingForScripts = PR_FALSE;
for (;;) {
if (!mFirstBuffer->hasMore()) {
if (mFirstBuffer == mLastBuffer) {
switch (mStreamState) {
case STREAM_BEING_READ:
// never release the last buffer. instead just zero its indeces for refill
mFirstBuffer->setStart(0);
mFirstBuffer->setEnd(0);
// never release the last buffer.
if (!mSpeculating) {
// reuse buffer space if not speculating
mFirstBuffer->setStart(0);
mFirstBuffer->setEnd(0);
}
mTreeBuilder->Flush();
return; // no more data for now but expecting more
case STREAM_ENDED:
Terminate(); // TODO Don't terminate if this is a speculation
if (mAtEOF) {
return;
}
mAtEOF = PR_TRUE;
mTokenizer->eof();
mTreeBuilder->StreamEnded();
mTreeBuilder->Flush();
if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
NS_WARNING("failed to dispatch executor flush event");
}
}
return; // no more data and not expecting more
default:
NS_NOTREACHED("It should be impossible to reach this.");
return;
}
} else {
nsHtml5UTF16Buffer* oldBuf = mFirstBuffer;
mFirstBuffer = mFirstBuffer->next;
delete oldBuf;
continue;
}
}
@ -703,18 +733,23 @@ nsHtml5StreamParser::ParseUntilScript()
// Terminate, but that never happens together with script.
// Can't assert that here, though, because it's possible that the main
// thread has called Terminate() while this thread was parsing.
if (IsTerminated()) {
return;
}
if (mTreeBuilder->HasScript()) {
mTreeBuilder->AddSnapshotToScript(mTreeBuilder->newSnapshot());
mozilla::MutexAutoLock speculationAutoLock(mSpeculationMutex);
nsHtml5Speculation* speculation =
new nsHtml5Speculation(mFirstBuffer,
mFirstBuffer->getStart(),
mTreeBuilder->newSnapshot());
mTreeBuilder->AddSnapshotToScript(speculation->GetSnapshot());
mTreeBuilder->Flush();
if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
NS_WARNING("failed to dispatch executor flush event");
}
// XXX start speculation
mWaitingForScripts = PR_TRUE;
return; // ContinueAfterScripts() will re-enable this parser
}
mTreeBuilder->SetOpSink(speculation);
mSpeculations.AppendElement(speculation); // adopts the pointer
mSpeculating = PR_TRUE;
}
if (IsTerminatedOrInterrupted()) {
return;
}
mTreeBuilder->MaybeFlush();
}
@ -722,17 +757,18 @@ nsHtml5StreamParser::ParseUntilScript()
}
}
class nsHtml5ContinueAfterScript : public nsRunnable
class nsHtml5StreamParserContinuation : public nsRunnable
{
private:
nsHtml5RefPtr<nsHtml5StreamParser> mStreamParser;
public:
nsHtml5ContinueAfterScript(nsHtml5StreamParser* aStreamParser)
nsHtml5StreamParserContinuation(nsHtml5StreamParser* aStreamParser)
: mStreamParser(aStreamParser)
{}
NS_IMETHODIMP Run()
{
mStreamParser->ParseUntilScript();
mozilla::MutexAutoLock autoLock(mStreamParser->mTokenizerMutex);
mStreamParser->ParseAvailableData();
return NS_OK;
}
};
@ -743,40 +779,102 @@ nsHtml5StreamParser::ContinueAfterScripts(nsHtml5Tokenizer* aTokenizer,
PRBool aLastWasCR)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
mExecutor->StartReadingFromStage();
// TODO:
// test if the state of the argument tokenizer and tree builder match
// the earliest speculation.
// If not, rendez-vous at barrier, zaps all speculations, rewind the stream
// and copy over the state.
// If yes:
// If there are multiple speculations or the stream parser has terminated.
// load the tree op queue from the earliest speculation into the tree op
// executor and discard the stream data for that speculation. Return.
// Otherwise, rendez-vous at barrier, load the tree op queue from the
// speculation into the tree op executor, set the tree op executor to read
// from the stage, set the stream parser tree builder to write to stage,
// discard the stream data for the speculation.
#ifdef DEBUG
mExecutor->AssertStageEmpty();
#endif
PRBool speculationFailed = PR_FALSE;
{
mozilla::MutexAutoLock autoLock(mTokenizerMutex);
mozilla::MutexAutoLock speculationAutoLock(mSpeculationMutex);
if (mSpeculations.IsEmpty()) {
// Not quite sure how exactly this happens...
// Maybe an artifact of defer scripts?
return;
}
nsHtml5Speculation* speculation = mSpeculations.ElementAt(0);
if (aLastWasCR ||
!aTokenizer->isInDataState() ||
!aTreeBuilder->snapshotMatches(speculation->GetSnapshot())) {
speculationFailed = PR_TRUE;
// We've got a failed speculation :-(
Interrupt(); // Make the parser thread release the tokenizer mutex sooner
// now fall out of the speculationAutoLock into the tokenizerAutoLock block
} else {
// We've got a successful speculation!
if (mSpeculations.Length() > 1) {
// the first speculation isn't the current speculation, so there's
// no need to bother the parser thread.
speculation->FlushToSink(mExecutor);
if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
NS_WARNING("failed to dispatch executor flush event");
}
mSpeculations.RemoveElementAt(0);
return;
}
// else
Interrupt(); // Make the parser thread release the tokenizer mutex sooner
// now fall through
// the first speculation is the current speculation. Need to
// release the the speculation mutex and acquire the tokenizer
// mutex. (Just acquiring the other mutex here would deadlock)
}
}
{
mozilla::MutexAutoLock tokenizerAutoLock(mTokenizerMutex);
#ifdef DEBUG
nsCOMPtr<nsIThread> mainThread;
NS_GetMainThread(getter_AddRefs(mainThread));
mAtomTable.SetPermittedLookupThread(mainThread);
#endif
// Approximation: Copy state over for now unconditionally.
mLastWasCR = aLastWasCR;
mTokenizer->loadState(aTokenizer);
mTreeBuilder->loadState(aTreeBuilder, &mAtomTable);
// In principle, the speculation mutex should be acquired here,
// but there's no point, because the parser thread only acquires it
// when it has also acquired the tokenizer mutex and we are already
// holding the tokenizer mutex.
if (speculationFailed) {
// Rewind the stream
mAtEOF = PR_FALSE;
nsHtml5Speculation* speculation = mSpeculations.ElementAt(0);
mFirstBuffer = speculation->GetBuffer();
mFirstBuffer->setStart(speculation->GetStart());
nsHtml5UTF16Buffer* buffer = mFirstBuffer->next;
while (buffer) {
buffer->setStart(0);
buffer = buffer->next;
}
mSpeculations.Clear(); // potentially a huge number of destructors
// run here synchronously on the main thread...
mTreeBuilder->SetOpSink(mExecutor->GetStage());
mExecutor->StartReadingFromStage();
mSpeculating = PR_FALSE;
// Copy state over
mLastWasCR = aLastWasCR;
mTokenizer->loadState(aTokenizer);
mTreeBuilder->loadState(aTreeBuilder, &mAtomTable);
} else {
// We've got a successful speculation and at least a moment ago it was
// the current speculation
mSpeculations.ElementAt(0)->FlushToSink(mExecutor);
if (NS_FAILED(NS_DispatchToMainThread(mExecutorFlusher))) {
NS_WARNING("failed to dispatch executor flush event");
}
mSpeculations.RemoveElementAt(0);
if (mSpeculations.IsEmpty()) {
// yes, it was still the only speculation. Now stop speculating
mTreeBuilder->SetOpSink(mExecutor->GetStage());
mExecutor->StartReadingFromStage();
mSpeculating = PR_FALSE;
}
}
Uninterrupt();
nsCOMPtr<nsIRunnable> event = new nsHtml5StreamParserContinuation(this);
if (NS_FAILED(mThread->Dispatch(event, nsIThread::DISPATCH_NORMAL))) {
NS_WARNING("Failed to dispatch ParseAvailableData event");
}
// A stream event might run before this event runs, but that's harmless.
#ifdef DEBUG
mAtomTable.SetPermittedLookupThread(mThread);
#endif
}
nsCOMPtr<nsIRunnable> event = new nsHtml5ContinueAfterScript(this);
if (NS_FAILED(mThread->Dispatch(event, nsIThread::DISPATCH_NORMAL))) {
NS_WARNING("Failed to dispatch ParseUntilScript event");
}
}

Просмотреть файл

@ -51,6 +51,7 @@
#include "nsICharsetAlias.h"
#include "mozilla/Mutex.h"
#include "nsHtml5AtomTable.h"
#include "nsHtml5Speculation.h"
class nsHtml5Parser;
@ -104,7 +105,7 @@ class nsHtml5StreamParser : public nsIStreamListener,
friend class nsHtml5RequestStopper;
friend class nsHtml5DataAvailable;
friend class nsHtml5ContinueAfterScript;
friend class nsHtml5StreamParserContinuation;
public:
NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
@ -154,7 +155,9 @@ class nsHtml5StreamParser : public nsIStreamListener,
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
mObserver = aObserver;
}
void SetSpeculativeLoaderWithDocument(nsIDocument* aDocument);
nsresult GetChannel(nsIChannel** aChannel);
/**
@ -169,8 +172,6 @@ class nsHtml5StreamParser : public nsIStreamListener,
void Terminate() {
mozilla::MutexAutoLock autoLock(mTerminatedMutex);
mTerminated = PR_TRUE;
// TODO: Make sure this object stays alive as long as there are
// in-flight runnables coming this way
}
private:
@ -183,15 +184,34 @@ class nsHtml5StreamParser : public nsIStreamListener,
}
#endif
void ParseUntilScript();
void Interrupt() {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
mozilla::MutexAutoLock autoLock(mTerminatedMutex);
mInterrupted = PR_TRUE;
}
void Uninterrupt() {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
mTokenizerMutex.AssertCurrentThreadOwns();
// Not acquiring mTerminatedMutex because mTokenizerMutex is already
// held at this point and is already stronger.
mInterrupted = PR_FALSE;
}
void ParseAvailableData();
void DoStopRequest();
void DoDataAvailable(PRUint8* aBuffer, PRUint32 aLength);
PRBool IsTerminatedOrInterrupted() {
mozilla::MutexAutoLock autoLock(mTerminatedMutex);
return mTerminated || mInterrupted;
}
PRBool IsTerminated() {
mozilla::MutexAutoLock autoLock(mTerminatedMutex);
return mTerminated;
return mTerminated;
}
/**
@ -324,7 +344,7 @@ class nsHtml5StreamParser : public nsIStreamListener,
/**
* The first buffer in the pending UTF-16 buffer queue
*/
nsHtml5UTF16Buffer* mFirstBuffer; // manually managed strong ref
nsRefPtr<nsHtml5UTF16Buffer> mFirstBuffer;
/**
* The last buffer in the pending UTF-16 buffer queue
@ -349,7 +369,7 @@ class nsHtml5StreamParser : public nsIStreamListener,
/**
* Makes sure the main thread can't mess the tokenizer state while it's
* tokenizing
* tokenizing. This mutex also protects the current speculation.
*/
mozilla::Mutex mTokenizerMutex;
@ -374,14 +394,29 @@ class nsHtml5StreamParser : public nsIStreamListener,
eHtml5StreamState mStreamState;
/**
* Whether we are waiting for scripts to be done.
* Whether we are speculating.
*/
PRBool mWaitingForScripts;
PRBool mSpeculating;
/**
* Whether the tokenizer has reached EOF. (Reset when stream rewinded.)
*/
PRBool mAtEOF;
/**
* The speculations. The mutex protects the nsTArray itself.
* To access the queue of current speculation, mTokenizerMutex must be
* obtained.
* The current speculation is the last element
*/
nsTArray<nsAutoPtr<nsHtml5Speculation> > mSpeculations;
mozilla::Mutex mSpeculationMutex;
/**
* True to terminate early; protected by mTerminatedMutex
*/
PRBool mTerminated;
PRBool mInterrupted;
mozilla::Mutex mTerminatedMutex;
/**
@ -390,6 +425,11 @@ class nsHtml5StreamParser : public nsIStreamListener,
nsCOMPtr<nsIThread> mThread;
nsCOMPtr<nsIRunnable> mExecutorFlusher;
/**
* The document wrapped by the speculative loader.
*/
nsCOMPtr<nsIDocument> mDocument;
};
#endif // nsHtml5StreamParser_h__

Просмотреть файл

@ -3489,6 +3489,7 @@ nsHtml5Tokenizer::initializeWithoutStarting()
confident = PR_FALSE;
strBuf = jArray<PRUnichar,PRInt32>(64);
longStrBuf = jArray<PRUnichar,PRInt32>(1024);
line = 1;
resetToDataState();
}

Просмотреть файл

@ -46,6 +46,7 @@
#include "nsEventDispatcher.h"
#include "nsContentUtils.h"
#include "nsNodeUtils.h"
#include "nsHtml5SpeculativeLoader.h"
// this really should be autogenerated...
jArray<PRUnichar,PRInt32> nsHtml5TreeBuilder::ISINDEX_PROMPT = jArray<PRUnichar,PRInt32>();
@ -72,9 +73,143 @@ nsHtml5TreeBuilder::~nsHtml5TreeBuilder()
mOpQueue.Clear();
}
class nsHtml5SpeculativeScript : public nsRunnable
{
private:
nsRefPtr<nsHtml5SpeculativeLoader> mSpeculativeLoader;
nsString mURL;
nsString mCharset;
nsString mType;
public:
nsHtml5SpeculativeScript(nsHtml5SpeculativeLoader* aSpeculativeLoader,
const nsAString& aURL,
const nsAString& aCharset,
const nsAString& aType)
: mSpeculativeLoader(aSpeculativeLoader)
, mURL(aURL)
, mCharset(aCharset)
, mType(aType)
{}
NS_IMETHODIMP Run()
{
mSpeculativeLoader->PreloadScript(mURL, mCharset, mType);
return NS_OK;
}
};
class nsHtml5SpeculativeStyle : public nsRunnable
{
private:
nsRefPtr<nsHtml5SpeculativeLoader> mSpeculativeLoader;
nsString mURL;
nsString mCharset;
public:
nsHtml5SpeculativeStyle(nsHtml5SpeculativeLoader* aSpeculativeLoader,
const nsAString& aURL,
const nsAString& aCharset)
: mSpeculativeLoader(aSpeculativeLoader)
, mURL(aURL)
, mCharset(aCharset)
{}
NS_IMETHODIMP Run()
{
mSpeculativeLoader->PreloadStyle(mURL, mCharset);
return NS_OK;
}
};
class nsHtml5SpeculativeImage : public nsRunnable
{
private:
nsRefPtr<nsHtml5SpeculativeLoader> mSpeculativeLoader;
nsString mURL;
public:
nsHtml5SpeculativeImage(nsHtml5SpeculativeLoader* aSpeculativeLoader,
const nsAString& aURL)
: mSpeculativeLoader(aSpeculativeLoader)
, mURL(aURL)
{}
NS_IMETHODIMP Run()
{
mSpeculativeLoader->PreloadImage(mURL);
return NS_OK;
}
};
nsIContent**
nsHtml5TreeBuilder::createElement(PRInt32 aNamespace, nsIAtom* aName, nsHtml5HtmlAttributes* aAttributes)
{
NS_PRECONDITION(aAttributes, "Got null attributes.");
// Start wall of code for speculative loading
if (mSpeculativeLoader) {
switch (aNamespace) {
case kNameSpaceID_XHTML:
if (nsHtml5Atoms::img == aName) {
nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_SRC);
if (url) {
Dispatch(new nsHtml5SpeculativeImage(mSpeculativeLoader, *url));
}
} else if (nsHtml5Atoms::script == aName) {
nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_SRC);
if (url) {
nsString* charset = aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
nsString* type = aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE);
Dispatch(new nsHtml5SpeculativeScript(mSpeculativeLoader,
*url,
(charset) ? *charset : EmptyString(),
(type) ? *type : EmptyString()));
}
} else if (nsHtml5Atoms::link == aName) {
nsString* rel = aAttributes->getValue(nsHtml5AttributeName::ATTR_REL);
// Not splitting on space here is bogus but the old parser didn't even
// do a case-insensitive check.
if (rel && rel->LowerCaseEqualsASCII("stylesheet")) {
nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_HREF);
if (url) {
nsString* charset = aAttributes->getValue(nsHtml5AttributeName::ATTR_CHARSET);
Dispatch(new nsHtml5SpeculativeStyle(mSpeculativeLoader,
*url,
(charset) ? *charset : EmptyString()));
}
}
} else if (nsHtml5Atoms::video == aName) {
nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_POSTER);
if (url) {
Dispatch(new nsHtml5SpeculativeImage(mSpeculativeLoader, *url));
}
}
break;
case kNameSpaceID_SVG:
if (nsHtml5Atoms::image == aName) {
nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
if (url) {
Dispatch(new nsHtml5SpeculativeImage(mSpeculativeLoader, *url));
}
} else if (nsHtml5Atoms::script == aName) {
nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
if (url) {
nsString* type = aAttributes->getValue(nsHtml5AttributeName::ATTR_TYPE);
Dispatch(new nsHtml5SpeculativeScript(mSpeculativeLoader,
*url,
EmptyString(),
(type) ? *type : EmptyString()));
}
} else if (nsHtml5Atoms::style == aName) {
nsString* url = aAttributes->getValue(nsHtml5AttributeName::ATTR_XLINK_HREF);
if (url) {
Dispatch(new nsHtml5SpeculativeStyle(mSpeculativeLoader,
*url,
EmptyString()));
}
}
break;
}
}
// End wall of code for speculative loading
nsIContent** content = AllocateContentHandle();
nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
// XXX if null, OOM!
@ -292,7 +427,7 @@ nsHtml5TreeBuilder::elementPopped(PRInt32 aNamespace, nsIAtom* aName, nsIContent
requestSuspension();
nsHtml5TreeOperation* treeOp = mOpQueue.AppendElement();
// XXX if null, OOM!
treeOp->Init(eTreeOpRunScript, aElement, nsnull);
treeOp->InitScript(aElement);
return;
}
if (aName == nsHtml5Atoms::title) {
@ -426,7 +561,7 @@ nsHtml5TreeBuilder::SetDocumentCharset(nsACString& aCharset)
void
nsHtml5TreeBuilder::StreamEnded()
{
// The fragement mode calls DidBuildModel from nsHtml5Parser.
// The fragment mode calls DidBuildModel from nsHtml5Parser.
// Letting DidBuildModel be called from the executor in the fragment case
// confuses the EndLoad logic of nsHTMLDocument, since nsHTMLDocument
// thinks it is dealing with document.written content as opposed to
@ -450,9 +585,21 @@ void
nsHtml5TreeBuilder::AddSnapshotToScript(nsAHtml5TreeBuilderState* aSnapshot)
{
NS_PRECONDITION(HasScript(), "No script to add a snapshot to!");
NS_PRECONDITION(aSnapshot, "Got null snapshot.");
mOpQueue.ElementAt(mOpQueue.Length() - 1).SetSnapshot(aSnapshot);
}
void
nsHtml5TreeBuilder::SetSpeculativeLoaderWithDocument(nsIDocument* aDocument) {
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
mSpeculativeLoader = new nsHtml5SpeculativeLoader(aDocument);
}
void
nsHtml5TreeBuilder::DropSpeculativeLoader() {
mSpeculativeLoader = nsnull;
}
// DocumentModeHandler
void
nsHtml5TreeBuilder::documentMode(nsHtml5DocumentMode m)

Просмотреть файл

@ -43,7 +43,8 @@
nsAHtml5TreeOpSink* mOpSink;
nsAutoArrayPtr<nsIContent*> mHandles;
PRInt32 mHandlesUsed;
nsTArray<nsAutoArrayPtr<nsIContent*> > mOldHandles;
nsTArray<nsAutoArrayPtr<nsIContent*> > mOldHandles;
nsRefPtr<nsHtml5SpeculativeLoader> mSpeculativeLoader;
#ifdef DEBUG
PRBool mActive;
#endif
@ -68,6 +69,10 @@
mOpSink = aOpSink;
}
void SetSpeculativeLoaderWithDocument(nsIDocument* aDocument);
void DropSpeculativeLoader();
void Flush();
void MaybeFlush();
@ -79,3 +84,9 @@
void NeedsCharsetSwitchTo(const nsACString& aEncoding);
void AddSnapshotToScript(nsAHtml5TreeBuilderState* aSnapshot);
inline void Dispatch(nsIRunnable* aEvent) {
if (NS_FAILED(NS_DispatchToMainThread(aEvent))) {
NS_WARNING("Failed to dispatch speculative load runnable.");
}
}

Просмотреть файл

@ -91,10 +91,9 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHtml5TreeOpExecutor, nsContent
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
: mHasProcessedBase(PR_FALSE)
, mReadingFromStage(PR_FALSE)
, mFlushTimer(do_CreateInstance("@mozilla.org/timer;1"))
: mFlushTimer(do_CreateInstance("@mozilla.org/timer;1"))
{
// zeroing operator new for everything else
}
nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor()
@ -124,9 +123,8 @@ nsHtml5TreeOpExecutor::WillParse()
NS_IMETHODIMP
nsHtml5TreeOpExecutor::DidBuildModel(PRBool aTerminated)
{
NS_PRECONDITION(mLifeCycle == PARSING,
NS_PRECONDITION(mStarted && mParser,
"Bad life cycle.");
mLifeCycle = TERMINATED;
// This is comes from nsXMLContentSink
DidBuildModelImpl(aTerminated);
mDocument->ScriptLoader()->RemoveObserver(this);
@ -266,15 +264,13 @@ nsHtml5TreeOpExecutor::UpdateStyleSheet(nsIContent* aElement)
void
nsHtml5TreeOpExecutor::Flush()
{
nsRefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this); // avoid crashing near EOF
nsCOMPtr<nsIParser> parserKungFuDeathGrip(mParser);
if (mLifeCycle == TERMINATED) {
if (!mParser) {
mFlushTimer->Cancel();
return;
}
NS_ASSERTION(mParser, "mParser was nulled out but life cycle wasn't terminated.");
nsRefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this); // avoid crashing near EOF
nsCOMPtr<nsIParser> parserKungFuDeathGrip(mParser);
if (mReadingFromStage) {
mStage.RetrieveOperations(mOpQueue);
@ -420,7 +416,7 @@ nsHtml5TreeOpExecutor::ExecuteScript()
NS_ASSERTION(mScriptElement, "No script to run");
nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(mScriptElement);
if (mLifeCycle == TERMINATED) {
if (!mParser) {
NS_ASSERTION(sele->IsMalformed(), "Script wasn't marked as malformed.");
// We got here not because of an end tag but because the tree builder
// popped an incomplete script element on EOF. Returning here to avoid
@ -442,11 +438,12 @@ nsHtml5TreeOpExecutor::ExecuteScript()
if (rv == NS_ERROR_HTMLPARSER_BLOCK) {
mScriptElements.AppendObject(sele);
mParser->BlockParser();
} else if (mLifeCycle != TERMINATED) {
} else {
// This may have already happened if the script executed, but in case
// it didn't then remove the element so that it doesn't get stuck forever.
htmlDocument->ScriptExecuted(sele);
static_cast<nsHtml5Parser*> (mParser.get())->MaybePostContinueEvent();
// mParser may have been nulled out by now, but nsContentSink deals
ContinueInterruptedParsingAsync();
}
}
@ -466,8 +463,8 @@ nsHtml5TreeOpExecutor::Init(nsIDocument* aDoc,
void
nsHtml5TreeOpExecutor::Start()
{
NS_PRECONDITION(mLifeCycle == NOT_STARTED, "Tried to start when already started.");
mLifeCycle = PARSING;
NS_PRECONDITION(!mStarted, "Tried to start when already started.");
mStarted = PR_TRUE;
mScriptElement = nsnull;
ScheduleTimer();
}
@ -505,7 +502,7 @@ nsHtml5TreeOpExecutor::Reset() {
mHasProcessedBase = PR_FALSE;
mReadingFromStage = PR_FALSE;
mOpQueue.Clear();
mLifeCycle = NOT_STARTED;
mStarted = PR_FALSE;
mScriptElement = nsnull;
mCallDidBuildModel = PR_FALSE;
}

Просмотреть файл

@ -61,24 +61,6 @@ class nsHtml5TreeBuilder;
class nsHtml5Tokenizer;
class nsHtml5StreamParser;
enum eHtml5ParserLifecycle {
/**
* The parser has told the tokenizer to start yet.
*/
NOT_STARTED = 0,
/**
* The parser has started the tokenizer and as far as the executor is
* aware, the stream hasn't ended.
*/
PARSING = 1,
/**
* The parse has ended.
*/
TERMINATED = 2
};
typedef nsIContent* nsIContentPtr;
class nsHtml5TreeOpExecutor : public nsContentSink,
@ -119,9 +101,9 @@ class nsHtml5TreeOpExecutor : public nsContentSink,
nsCOMArray<nsIContent> mOwnedNonElements;
/**
* The current point on parser life cycle
* Whether the parser has started
*/
eHtml5ParserLifecycle mLifeCycle;
PRBool mStarted;
/**
* Script to run ASAP
@ -326,15 +308,11 @@ class nsHtml5TreeOpExecutor : public nsContentSink,
#endif
PRBool IsComplete() {
return (mLifeCycle == TERMINATED);
return !mParser;
}
eHtml5ParserLifecycle GetLifeCycle() {
return mLifeCycle;
}
void SetLifeCycle(eHtml5ParserLifecycle aLifeCycle) {
mLifeCycle = aLifeCycle;
PRBool HasStarted() {
return mStarted;
}
void ExecuteScript();
@ -383,6 +361,12 @@ class nsHtml5TreeOpExecutor : public nsContentSink,
void ScheduleTimer();
#ifdef DEBUG
void AssertStageEmpty() {
mStage.AssertEmpty();
}
#endif
private:
nsHtml5Tokenizer* GetTokenizer();

Просмотреть файл

@ -76,3 +76,13 @@ nsHtml5TreeOpStage::RetrieveOperations(nsTArray<nsHtml5TreeOperation>& aOpQueue)
}
aOpQueue.MoveElementsFrom(mOpQueue);
}
#ifdef DEBUG
void
nsHtml5TreeOpStage::AssertEmpty()
{
mozilla::MutexAutoLock autoLock(mMutex);
// This shouldn't really need the mutex
NS_ASSERTION(mOpQueue.IsEmpty(), "The stage was supposed to be empty.");
}
#endif

Просмотреть файл

@ -67,6 +67,10 @@ class nsHtml5TreeOpStage : public nsAHtml5TreeOpSink {
*/
void RetrieveOperations(nsTArray<nsHtml5TreeOperation>& aOpQueue);
#ifdef DEBUG
void AssertEmpty();
#endif
private:
nsTArray<nsHtml5TreeOperation> mOpQueue;
mozilla::Mutex mMutex;

Просмотреть файл

@ -88,9 +88,6 @@ nsHtml5TreeOperation::~nsHtml5TreeOperation()
case eTreeOpNeedsCharsetSwitchTo:
delete[] mOne.charPtr;
break;
case eTreeOpRunScript:
delete mTwo.state;
break;
default: // keep the compiler happy
break;
}

Просмотреть файл

@ -148,6 +148,14 @@ class nsHtml5TreeOperation {
mOne.mode = aMode;
}
inline void InitScript(nsIContent** aNode) {
NS_PRECONDITION(mOpCode == eTreeOpUninitialized,
"Op code must be uninitialized when initializing.");
mOpCode = eTreeOpRunScript;
mOne.node = aNode;
mTwo.state = nsnull;
}
inline void Init(PRInt32 aNamespace,
nsIAtom* aName,
nsHtml5HtmlAttributes* aAttributes,

Просмотреть файл

@ -62,3 +62,29 @@ nsHtml5UTF16Buffer::~nsHtml5UTF16Buffer()
MOZ_COUNT_DTOR(nsHtml5UTF16Buffer);
delete[] buffer;
}
// Not using macros for AddRef and Release in order to be able to refcount on
// and create on different threads.
nsrefcnt
nsHtml5UTF16Buffer::AddRef()
{
NS_PRECONDITION(PRInt32(mRefCnt) >= 0, "Illegal refcount.");
++mRefCnt;
NS_LOG_ADDREF(this, mRefCnt, "nsHtml5UTF16Buffer", sizeof(*this));
return mRefCnt;
}
nsrefcnt
nsHtml5UTF16Buffer::Release()
{
NS_PRECONDITION(0 != mRefCnt, "Release without AddRef.");
--mRefCnt;
NS_LOG_RELEASE(this, mRefCnt, "nsHtml5UTF16Buffer");
if (mRefCnt == 0) {
mRefCnt = 1; /* stabilize */
delete this;
return 0;
}
return mRefCnt;
}

Просмотреть файл

@ -39,5 +39,10 @@
nsHtml5UTF16Buffer(PRInt32 size);
nsHtml5UTF16Buffer(void* key);
~nsHtml5UTF16Buffer();
nsHtml5UTF16Buffer* next;
nsRefPtr<nsHtml5UTF16Buffer> next;
void* key;
nsrefcnt AddRef();
nsrefcnt Release();
private:
nsAutoRefCnt mRefCnt;