Bug 1410848 - Use RAII with nsHtml5TreeOpExecutor::mFlushState. r=smaug.

MozReview-Commit-ID: 3a1tyYGT8u5
This commit is contained in:
Henri Sivonen 2017-10-27 10:06:33 +03:00
Родитель 45d7cc2fc4
Коммит 24aae439f6
8 изменённых файлов: 205 добавлений и 137 удалений

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

@ -0,0 +1,30 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=78: */
/* 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/. */
#ifndef nsHtml5AutoPauseUpdate_h
#define nsHtml5AutoPauseUpdate_h
class MOZ_RAII nsHtml5AutoPauseUpdate final
{
private:
RefPtr<nsHtml5DocumentBuilder> mBuilder;
public:
explicit nsHtml5AutoPauseUpdate(nsHtml5DocumentBuilder* aBuilder)
: mBuilder(aBuilder)
{
mBuilder->EndDocUpdate();
}
~nsHtml5AutoPauseUpdate()
{
// Something may have terminated the parser during the update pause.
if (!mBuilder->IsComplete()) {
mBuilder->BeginDocUpdate();
}
}
};
#endif // nsHtml5AutoPauseUpdate_h

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

@ -18,7 +18,6 @@ enum eHtml5FlushState {
eNotFlushing = 0, // not flushing
eInFlush = 1, // the Flush() method is on the call stack
eInDocUpdate = 2, // inside an update batch on the document
eNotifying = 3 // flushing pending append notifications
};
class nsHtml5DocumentBuilder : public nsContentSink
@ -67,28 +66,45 @@ public:
return mBroken;
}
inline bool IsComplete()
{
return !mParser;
}
inline void BeginDocUpdate()
{
NS_PRECONDITION(mFlushState == eInFlush, "Tried to double-open update.");
NS_PRECONDITION(mParser, "Started update without parser.");
MOZ_RELEASE_ASSERT(IsInFlush(), "Tried to double-open doc update.");
MOZ_RELEASE_ASSERT(mParser, "Started doc update without parser.");
mFlushState = eInDocUpdate;
mDocument->BeginUpdate(UPDATE_CONTENT_MODEL);
}
inline void EndDocUpdate()
{
NS_PRECONDITION(mFlushState != eNotifying, "mFlushState out of sync");
if (mFlushState == eInDocUpdate) {
mFlushState = eInFlush;
mDocument->EndUpdate(UPDATE_CONTENT_MODEL);
}
MOZ_RELEASE_ASSERT(IsInDocUpdate(),
"Tried to end doc update without one open.");
mFlushState = eInFlush;
mDocument->EndUpdate(UPDATE_CONTENT_MODEL);
}
bool IsInDocUpdate()
inline void BeginFlush()
{
return mFlushState == eInDocUpdate;
MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
"Tried to start a flush when already flushing.");
MOZ_RELEASE_ASSERT(mParser, "Started a flush without parser.");
mFlushState = eInFlush;
}
inline void EndFlush()
{
MOZ_RELEASE_ASSERT(IsInFlush(), "Tried to end flush when not flushing.");
mFlushState = eNotFlushing;
}
inline bool IsInDocUpdate() { return mFlushState == eInDocUpdate; }
inline bool IsInFlush() { return mFlushState == eInFlush; }
void SetDocumentCharsetAndSource(NotNull<const Encoding*> aEncoding,
int32_t aCharsetSource);

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

@ -23,7 +23,7 @@ nsHtml5OplessBuilder::~nsHtml5OplessBuilder()
void
nsHtml5OplessBuilder::Start()
{
mFlushState = eInFlush;
BeginFlush();
BeginDocUpdate();
}
@ -31,6 +31,7 @@ void
nsHtml5OplessBuilder::Finish()
{
EndDocUpdate();
EndFlush();
DropParserAndPerfHint();
mScriptLoader = nullptr;
mDocument = nullptr;
@ -39,7 +40,6 @@ nsHtml5OplessBuilder::Finish()
mDocumentURI = nullptr;
mDocShell = nullptr;
mOwnedElements.Clear();
mFlushState = eNotFlushing;
}
void

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

@ -226,6 +226,9 @@ class nsHtml5SpeculativeLoad {
void Perform(nsHtml5TreeOpExecutor* aExecutor);
private:
nsHtml5SpeculativeLoad(const nsHtml5SpeculativeLoad&) = delete;
nsHtml5SpeculativeLoad& operator=(const nsHtml5SpeculativeLoad&) = delete;
eHtml5SpeculativeLoad mOpCode;

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

@ -34,6 +34,7 @@
#include "nsIViewSourceChannel.h"
#include "xpcpublic.h"
#include "mozilla/IdleTaskRunner.h"
#include "nsHtml5AutoPauseUpdate.h"
using namespace mozilla;
@ -57,6 +58,43 @@ class nsHtml5ExecutorReflusher : public Runnable
}
};
class MOZ_RAII nsHtml5AutoFlush final
{
private:
RefPtr<nsHtml5TreeOpExecutor> mExecutor;
size_t mOpsToRemove;
public:
explicit nsHtml5AutoFlush(nsHtml5TreeOpExecutor* aExecutor)
: mExecutor(aExecutor)
, mOpsToRemove(aExecutor->OpQueueLength())
{
mExecutor->BeginFlush();
mExecutor->BeginDocUpdate();
}
~nsHtml5AutoFlush()
{
if (mExecutor->IsInDocUpdate()) {
mExecutor->EndDocUpdate();
} else {
// We aren't in an update if nsHtml5AutoPauseUpdate
// caused something to terminate the parser.
MOZ_RELEASE_ASSERT(
mExecutor->IsComplete(),
"How do we have mParser but the doc update isn't open?");
}
mExecutor->EndFlush();
mExecutor->RemoveFromStartOfOpQueue(mOpsToRemove);
}
void SetNumberOfOpsToRemove(size_t aOpsToRemove)
{
MOZ_ASSERT(aOpsToRemove < mOpsToRemove,
"Requested partial clearing of op queue but the number to clear "
"wasn't less than the length of the queue.");
mOpsToRemove = aOpsToRemove;
}
};
static mozilla::LinkedList<nsHtml5TreeOpExecutor>* gBackgroundFlushList = nullptr;
StaticRefPtr<IdleTaskRunner> gBackgroundFlushRunner;
@ -77,7 +115,7 @@ nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor()
{
if (gBackgroundFlushList && isInList()) {
mOpQueue.Clear();
ClearOpQueue();
removeFrom(*gBackgroundFlushList);
if (gBackgroundFlushList->isEmpty()) {
delete gBackgroundFlushList;
@ -298,9 +336,9 @@ nsHtml5TreeOpExecutor::FlushSpeculativeLoads()
{
nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue);
const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
for (nsHtml5SpeculativeLoad* iter = const_cast<nsHtml5SpeculativeLoad*>(start);
nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
for (nsHtml5SpeculativeLoad* iter = start;
iter < end;
++iter) {
if (MOZ_UNLIKELY(!mParser)) {
@ -368,7 +406,7 @@ nsHtml5TreeOpExecutor::RunFlushLoop()
for (;;) {
if (!mParser) {
// Parse has terminated.
mOpQueue.Clear(); // clear in order to be able to assert in destructor
ClearOpQueue(); // clear in order to be able to assert in destructor
return;
}
@ -395,18 +433,20 @@ nsHtml5TreeOpExecutor::RunFlushLoop()
if (mReadingFromStage) {
nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
"mOpQueue modified during flush.");
mStage.MoveOpsAndSpeculativeLoadsTo(mOpQueue, speculativeLoadQueue);
// Make sure speculative loads never start after the corresponding
// normal loads for the same URLs.
const nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
const nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
for (nsHtml5SpeculativeLoad* iter = (nsHtml5SpeculativeLoad*)start;
nsHtml5SpeculativeLoad* start = speculativeLoadQueue.Elements();
nsHtml5SpeculativeLoad* end = start + speculativeLoadQueue.Length();
for (nsHtml5SpeculativeLoad* iter = start;
iter < end;
++iter) {
iter->Perform(this);
if (MOZ_UNLIKELY(!mParser)) {
// An extension terminated the parser from a HTTP observer.
mOpQueue.Clear(); // clear in order to be able to assert in destructor
ClearOpQueue(); // clear in order to be able to assert in destructor
return;
}
}
@ -416,7 +456,7 @@ nsHtml5TreeOpExecutor::RunFlushLoop()
// URLs.
if (MOZ_UNLIKELY(!mParser)) {
// An extension terminated the parser from a HTTP observer.
mOpQueue.Clear(); // clear in order to be able to assert in destructor
ClearOpQueue(); // clear in order to be able to assert in destructor
return;
}
// Not sure if this grip is still needed, but previously, the code
@ -439,57 +479,46 @@ nsHtml5TreeOpExecutor::RunFlushLoop()
return;
}
mFlushState = eInFlush;
nsIContent* scriptElement = nullptr;
bool interrupted = false;
BeginDocUpdate();
uint32_t numberOfOpsToFlush = mOpQueue.Length();
{
// autoFlush clears mOpQueue in its destructor unless
// SetNumberOfOpsToRemove is called first, in which case only
// some ops from the start of the queue are cleared.
nsHtml5AutoFlush autoFlush(this);
const nsHtml5TreeOperation* first = mOpQueue.Elements();
const nsHtml5TreeOperation* last = first + numberOfOpsToFlush - 1;
for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(first);;) {
if (MOZ_UNLIKELY(!mParser)) {
// The previous tree op caused a call to nsIParser::Terminate().
break;
}
NS_ASSERTION(mFlushState == eInDocUpdate,
"Tried to perform tree op outside update batch.");
nsresult rv = iter->Perform(this, &scriptElement, &interrupted);
if (NS_FAILED(rv)) {
MarkAsBroken(rv);
break;
nsHtml5TreeOperation* first = mOpQueue.Elements();
nsHtml5TreeOperation* last = first + mOpQueue.Length() - 1;
for (nsHtml5TreeOperation* iter = first;; ++iter) {
if (MOZ_UNLIKELY(!mParser)) {
// The previous tree op caused a call to nsIParser::Terminate().
return;
}
MOZ_ASSERT(IsInDocUpdate(),
"Tried to perform tree op outside update batch.");
nsresult rv = iter->Perform(this, &scriptElement, &interrupted);
if (NS_FAILED(rv)) {
MarkAsBroken(rv);
break;
}
// Be sure not to check the deadline if the last op was just performed.
if (MOZ_UNLIKELY(iter == last)) {
break;
} else if (MOZ_UNLIKELY(interrupted) ||
MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() ==
NS_ERROR_HTMLPARSER_INTERRUPTED)) {
autoFlush.SetNumberOfOpsToRemove((iter - first) + 1);
nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
return;
}
}
// Be sure not to check the deadline if the last op was just performed.
if (MOZ_UNLIKELY(iter == last)) {
break;
} else if (MOZ_UNLIKELY(interrupted) ||
MOZ_UNLIKELY(nsContentSink::DidProcessATokenImpl() ==
NS_ERROR_HTMLPARSER_INTERRUPTED)) {
mOpQueue.RemoveElementsAt(0, (iter - first) + 1);
EndDocUpdate();
mFlushState = eNotFlushing;
#ifdef DEBUG_NS_HTML5_TREE_OP_EXECUTOR_FLUSH
printf("REFLUSH SCHEDULED (executing ops): %d\n",
++sTimesFlushLoopInterrupted);
#endif
nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync();
return;
}
++iter;
}
mOpQueue.Clear();
EndDocUpdate();
mFlushState = eNotFlushing;
} // end autoFlush
if (MOZ_UNLIKELY(!mParser)) {
// The parse ended already.
@ -526,7 +555,7 @@ nsHtml5TreeOpExecutor::FlushDocumentWrite()
if (MOZ_UNLIKELY(!mParser)) {
// The parse has ended.
mOpQueue.Clear(); // clear in order to be able to assert in destructor
ClearOpQueue(); // clear in order to be able to assert in destructor
return rv;
}
@ -535,15 +564,13 @@ nsHtml5TreeOpExecutor::FlushDocumentWrite()
return rv;
}
mFlushState = eInFlush;
// avoid crashing near EOF
RefPtr<nsHtml5TreeOpExecutor> kungFuDeathGrip(this);
RefPtr<nsParserBase> parserKungFuDeathGrip(mParser);
mozilla::Unused << parserKungFuDeathGrip; // Intentionally not used within function
NS_ASSERTION(!mReadingFromStage,
"Got doc write flush when reading from stage");
MOZ_RELEASE_ASSERT(!mReadingFromStage,
"Got doc write flush when reading from stage");
#ifdef DEBUG
mStage.AssertEmpty();
@ -551,34 +578,31 @@ nsHtml5TreeOpExecutor::FlushDocumentWrite()
nsIContent* scriptElement = nullptr;
bool interrupted = false;
BeginDocUpdate();
uint32_t numberOfOpsToFlush = mOpQueue.Length();
{
// autoFlush clears mOpQueue in its destructor.
nsHtml5AutoFlush autoFlush(this);
const nsHtml5TreeOperation* start = mOpQueue.Elements();
const nsHtml5TreeOperation* end = start + numberOfOpsToFlush;
for (nsHtml5TreeOperation* iter = const_cast<nsHtml5TreeOperation*>(start);
nsHtml5TreeOperation* start = mOpQueue.Elements();
nsHtml5TreeOperation* end = start + mOpQueue.Length();
for (nsHtml5TreeOperation* iter = start;
iter < end;
++iter) {
if (MOZ_UNLIKELY(!mParser)) {
// The previous tree op caused a call to nsIParser::Terminate().
break;
if (MOZ_UNLIKELY(!mParser)) {
// The previous tree op caused a call to nsIParser::Terminate().
return rv;
}
NS_ASSERTION(IsInDocUpdate(),
"Tried to perform tree op outside update batch.");
rv = iter->Perform(this, &scriptElement, &interrupted);
if (NS_FAILED(rv)) {
MarkAsBroken(rv);
break;
}
}
NS_ASSERTION(mFlushState == eInDocUpdate,
"Tried to perform tree op outside update batch.");
rv = iter->Perform(this, &scriptElement, &interrupted);
if (NS_FAILED(rv)) {
MarkAsBroken(rv);
break;
}
}
mOpQueue.Clear();
EndDocUpdate();
mFlushState = eNotFlushing;
} // autoFlush
if (MOZ_UNLIKELY(!mParser)) {
// Ending the doc update caused a call to nsIParser::Terminate().
@ -617,7 +641,7 @@ nsHtml5TreeOpExecutor::StartLayout(bool* aInterrupted) {
return;
}
EndDocUpdate();
nsHtml5AutoPauseUpdate autoPause(this);
if (MOZ_UNLIKELY(!mParser)) {
// got terminate
@ -628,8 +652,6 @@ nsHtml5TreeOpExecutor::StartLayout(bool* aInterrupted) {
if (mParser) {
*aInterrupted = !GetParser()->IsParserEnabled();
BeginDocUpdate();
}
}
@ -637,12 +659,10 @@ void
nsHtml5TreeOpExecutor::PauseDocUpdate(bool* aInterrupted) {
// Pausing the document update allows JS to run, and potentially block
// further parsing.
EndDocUpdate();
nsHtml5AutoPauseUpdate autoPause(this);
if (MOZ_LIKELY(mParser)) {
*aInterrupted = !GetParser()->IsParserEnabled();
BeginDocUpdate();
}
}
@ -687,8 +707,9 @@ nsHtml5TreeOpExecutor::RunScript(nsIContent* aScriptElement)
NS_ASSERTION(!block, "Defer or async script tried to block.");
return;
}
NS_ASSERTION(mFlushState == eNotFlushing, "Tried to run script when flushing.");
MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
"Tried to run script while flushing.");
mReadingFromStage = false;
@ -726,8 +747,7 @@ nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(NotNull<const Encoding*> aEncoding,
int32_t aSource,
uint32_t aLineNumber)
{
EndDocUpdate();
nsHtml5AutoPauseUpdate autoPause(this);
if (MOZ_UNLIKELY(!mParser)) {
// got terminate
return;
@ -760,8 +780,6 @@ nsHtml5TreeOpExecutor::NeedsCharsetSwitchTo(NotNull<const Encoding*> aEncoding,
}
GetParser()->ContinueAfterFailedCharsetSwitch();
BeginDocUpdate();
}
void
@ -825,12 +843,31 @@ nsHtml5TreeOpExecutor::GetParser()
void
nsHtml5TreeOpExecutor::MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue)
{
NS_PRECONDITION(mFlushState == eNotFlushing, "mOpQueue modified during tree op execution.");
MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
"Ops added to mOpQueue during tree op execution.");
mOpQueue.AppendElements(Move(aOpQueue));
}
void
nsHtml5TreeOpExecutor::InitializeDocWriteParserState(nsAHtml5TreeBuilderState* aState, int32_t aLine)
nsHtml5TreeOpExecutor::ClearOpQueue()
{
MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
"mOpQueue cleared during tree op execution.");
mOpQueue.Clear();
}
void
nsHtml5TreeOpExecutor::RemoveFromStartOfOpQueue(size_t aNumberOfOpsToRemove)
{
MOZ_RELEASE_ASSERT(mFlushState == eNotFlushing,
"Ops removed from mOpQueue during tree op execution.");
mOpQueue.RemoveElementsAt(0, aNumberOfOpsToRemove);
}
void
nsHtml5TreeOpExecutor::InitializeDocWriteParserState(
nsAHtml5TreeBuilderState* aState,
int32_t aLine)
{
GetParser()->InitializeDocWriteParserState(aState, aLine);
}

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

@ -196,11 +196,6 @@ class nsHtml5TreeOpExecutor final : public nsHtml5DocumentBuilder,
void ComplainAboutBogusProtocolCharset(nsIDocument* aDoc);
bool IsComplete()
{
return !mParser;
}
bool HasStarted()
{
return mStarted;
@ -225,7 +220,13 @@ class nsHtml5TreeOpExecutor final : public nsHtml5DocumentBuilder,
* queue unconditionally. (This is for the main thread case.)
*/
virtual void MoveOpsFrom(nsTArray<nsHtml5TreeOperation>& aOpQueue) override;
void ClearOpQueue();
void RemoveFromStartOfOpQueue(size_t aNumberOfOpsToRemove);
inline size_t OpQueueLength() { return mOpQueue.Length(); }
nsHtml5TreeOpStage* GetStage()
{
return &mStage;

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

@ -41,6 +41,7 @@
#include "nsIHTMLDocument.h"
#include "mozilla/Likely.h"
#include "nsTextNode.h"
#include "nsHtml5AutoPauseUpdate.h"
using namespace mozilla;
@ -74,29 +75,6 @@ class MOZ_STACK_CLASS nsHtml5OtherDocUpdate {
nsCOMPtr<nsIDocument> mDocument;
};
/**
* Helper class to temporary break out of the document update batch. Use this
* with caution as this will cause blocked scripts to run.
*/
class MOZ_RAII mozAutoPauseContentUpdate final
{
public:
explicit mozAutoPauseContentUpdate(nsIDocument* aDocument)
: mDocument(aDocument)
{
MOZ_ASSERT(mDocument);
mDocument->EndUpdate(UPDATE_CONTENT_MODEL);
}
~mozAutoPauseContentUpdate()
{
mDocument->BeginUpdate(UPDATE_CONTENT_MODEL);
}
private:
nsCOMPtr<nsIDocument> mDocument;
};
nsHtml5TreeOperation::nsHtml5TreeOperation()
: mOpCode(eTreeOpUninitialized)
{
@ -442,7 +420,7 @@ nsHtml5TreeOperation::CreateHTMLElement(
if (willExecuteScript) { // This will cause custom element constructors to run
AutoSetThrowOnDynamicMarkupInsertionCounter
throwOnDynamicMarkupInsertionCounter(document);
mozAutoPauseContentUpdate autoPauseContentUpdate(document);
nsHtml5AutoPauseUpdate autoPauseContentUpdate(aBuilder);
{
nsAutoMicroTask mt;
}

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

@ -549,6 +549,9 @@ class nsHtml5TreeOperation final {
bool* aInterrupted);
private:
nsHtml5TreeOperation(const nsHtml5TreeOperation&) = delete;
nsHtml5TreeOperation& operator=(const nsHtml5TreeOperation&) = delete;
// possible optimization:
// Make the queue take items the size of pointer and make the op code
// decide how many operands it dequeues after it.