Bug 672667 - ' IndexedDB demo causes leaks and never-ending assertions'. r=bsmedberg+smichaud+khuey.

This commit is contained in:
Ben Turner 2012-04-06 13:40:10 -07:00
parent 4ab0ff71e3
commit d4af3d1a48
7 changed files with 668 additions and 103 deletions

View File

@ -39,6 +39,7 @@
#include "IDBTransaction.h"
#include "nsIAppShell.h"
#include "nsIScriptContext.h"
#include "mozilla/storage.h"
@ -49,6 +50,7 @@
#include "nsPIDOMWindow.h"
#include "nsProxyRelease.h"
#include "nsThreadUtils.h"
#include "nsWidgetsCID.h"
#include "AsyncConnectionHelper.h"
#include "DatabaseInfo.h"
@ -65,6 +67,8 @@ USING_INDEXEDDB_NAMESPACE
namespace {
NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
PLDHashOperator
DoomCachedStatements(const nsACString& aQuery,
nsCOMPtr<mozIStorageStatement>& aStatement,
@ -138,19 +142,10 @@ IDBTransaction::Create(IDBDatabase* aDatabase,
}
if (!aDispatchDelayed) {
nsCOMPtr<nsIThreadInternal> thread =
do_QueryInterface(NS_GetCurrentThread());
NS_ENSURE_TRUE(thread, nsnull);
nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
NS_ENSURE_TRUE(appShell, nsnull);
// We need the current recursion depth first.
PRUint32 depth;
nsresult rv = thread->GetRecursionDepth(&depth);
NS_ENSURE_SUCCESS(rv, nsnull);
NS_ASSERTION(depth, "This should never be 0!");
transaction->mCreatedRecursionDepth = depth - 1;
rv = thread->AddObserver(transaction);
nsresult rv = appShell->RunBeforeNextEvent(transaction);
NS_ENSURE_SUCCESS(rv, nsnull);
transaction->mCreating = true;
@ -168,7 +163,6 @@ IDBTransaction::IDBTransaction()
: mReadyState(IDBTransaction::INITIAL),
mMode(IDBTransaction::READ_ONLY),
mPendingRequests(0),
mCreatedRecursionDepth(0),
mSavepointCount(0),
mAborted(false),
mCreating(false)
@ -505,7 +499,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IDBTransaction)
NS_INTERFACE_MAP_ENTRY(nsIIDBTransaction)
NS_INTERFACE_MAP_ENTRY(nsIThreadObserver)
NS_INTERFACE_MAP_ENTRY(nsIRunnable)
NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(IDBTransaction)
NS_INTERFACE_MAP_END_INHERITING(IDBWrapperCache)
@ -642,52 +636,20 @@ IDBTransaction::PreHandleEvent(nsEventChainPreVisitor& aVisitor)
return NS_OK;
}
// XXX Once nsIThreadObserver gets split this method will disappear.
NS_IMETHODIMP
IDBTransaction::OnDispatchedEvent(nsIThreadInternal* aThread)
{
NS_NOTREACHED("Don't call me!");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
IDBTransaction::OnProcessNextEvent(nsIThreadInternal* aThread,
bool aMayWait,
PRUint32 aRecursionDepth)
IDBTransaction::Run()
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aRecursionDepth > mCreatedRecursionDepth,
"Should be impossible!");
NS_ASSERTION(mCreating, "Should be true!");
return NS_OK;
}
NS_IMETHODIMP
IDBTransaction::AfterProcessNextEvent(nsIThreadInternal* aThread,
PRUint32 aRecursionDepth)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(aThread, "This should never be null!");
NS_ASSERTION(aRecursionDepth >= mCreatedRecursionDepth,
"Should be impossible!");
NS_ASSERTION(mCreating, "Should be true!");
// We're back at the event loop, no longer newborn.
mCreating = false;
if (aRecursionDepth == mCreatedRecursionDepth) {
// We're back at the event loop, no longer newborn.
mCreating = false;
// Maybe set the readyState to DONE if there were no requests generated.
if (mReadyState == IDBTransaction::INITIAL) {
mReadyState = IDBTransaction::DONE;
// Maybe set the readyState to DONE if there were no requests generated.
if (mReadyState == IDBTransaction::INITIAL) {
mReadyState = IDBTransaction::DONE;
if (NS_FAILED(CommitOrRollback())) {
NS_WARNING("Failed to commit!");
}
}
// No longer need to observe thread events.
if(NS_FAILED(aThread->RemoveObserver(this))) {
NS_ERROR("Failed to remove observer!");
if (NS_FAILED(CommitOrRollback())) {
NS_WARNING("Failed to commit!");
}
}

View File

@ -47,7 +47,6 @@
#include "mozIStorageFunction.h"
#include "nsIIDBTransaction.h"
#include "nsIRunnable.h"
#include "nsIThreadInternal.h"
#include "nsAutoPtr.h"
#include "nsClassHashtable.h"
@ -79,7 +78,7 @@ public:
class IDBTransaction : public IDBWrapperCache,
public nsIIDBTransaction,
public nsIThreadObserver
public nsIRunnable
{
friend class AsyncConnectionHelper;
friend class CommitHelper;
@ -89,7 +88,7 @@ class IDBTransaction : public IDBWrapperCache,
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIIDBTRANSACTION
NS_DECL_NSITHREADOBSERVER
NS_DECL_NSIRUNNABLE
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IDBTransaction, IDBWrapperCache)
@ -190,7 +189,6 @@ private:
ReadyState mReadyState;
Mode mMode;
PRUint32 mPendingRequests;
PRUint32 mCreatedRecursionDepth;
// Only touched on the main thread.
NS_DECL_EVENT_HANDLER(error)

View File

@ -38,13 +38,14 @@
* ***** END LICENSE BLOCK ***** */
#include "nsISupports.idl"
#include "nsIRunnable.idl"
interface nsIRunnable;
/**
* Interface for the native event system layer. This interface is designed
* to be used on the main application thread only.
*/
[uuid(40bc6280-ad83-471e-b197-80ab90e2065e)]
[uuid(2d10ca53-f143-439a-bb2e-c1fbc71f6a05)]
interface nsIAppShell : nsISupports
{
/**
@ -112,5 +113,12 @@ interface nsIAppShell : nsISupports
* finishes. If called multiple times per task/event, all the runnables will
* be executed, in the order in which runInStableState() was called.
*/
void runInStableState(in nsIRunnable aRunnable);
void runInStableState(in nsIRunnable runnable);
/**
* Run the given runnable before the next iteration of the event loop (this
* includes native events too). If a nested loop is spawned within the current
* event then the runnable will not be run until that loop has terminated.
*/
void runBeforeNextEvent(in nsIRunnable runnable);
};

View File

@ -60,6 +60,8 @@ endif
# $(NULL)
endif
CPP_UNIT_TESTS += TestAppShellSteadyState.cpp
include $(topsrcdir)/config/rules.mk
_TEST_FILES =

View File

@ -0,0 +1,500 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
#include "TestHarness.h"
#include "nsIAppShell.h"
#include "nsIAppShellService.h"
#include "nsIDocument.h"
#include "nsIDOMEvent.h"
#include "nsIDOMEventListener.h"
#include "nsIDOMEventTarget.h"
#include "nsIDOMWindow.h"
#include "nsIDOMWindowUtils.h"
#include "nsIInterfaceRequestor.h"
#include "nsIRunnable.h"
#include "nsIURI.h"
#include "nsIWebBrowserChrome.h"
#include "nsIXULWindow.h"
#include "nsAppShellCID.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsNetUtil.h"
#include "nsThreadUtils.h"
#ifdef XP_WIN
#include "Windows.h"
#endif
using namespace mozilla;
typedef void (*TestFunc)(nsIAppShell*);
bool gStableStateEventHasRun = false;
class ExitAppShellRunnable : public nsRunnable
{
nsCOMPtr<nsIAppShell> mAppShell;
public:
ExitAppShellRunnable(nsIAppShell* aAppShell)
: mAppShell(aAppShell)
{ }
NS_IMETHOD
Run()
{
return mAppShell->Exit();
}
};
class StableStateRunnable : public nsRunnable
{
public:
NS_IMETHOD
Run()
{
if (gStableStateEventHasRun) {
fail("StableStateRunnable already ran");
}
gStableStateEventHasRun = true;
return NS_OK;
}
};
class CheckStableStateRunnable : public nsRunnable
{
bool mShouldHaveRun;
public:
CheckStableStateRunnable(bool aShouldHaveRun)
: mShouldHaveRun(aShouldHaveRun)
{ }
NS_IMETHOD
Run()
{
if (mShouldHaveRun == gStableStateEventHasRun) {
passed("StableStateRunnable state correct (%s)",
mShouldHaveRun ? "true" : "false");
} else {
fail("StableStateRunnable ran at wrong time");
}
return NS_OK;
}
};
class ScheduleStableStateRunnable : public CheckStableStateRunnable
{
protected:
nsCOMPtr<nsIAppShell> mAppShell;
public:
ScheduleStableStateRunnable(nsIAppShell* aAppShell)
: CheckStableStateRunnable(false), mAppShell(aAppShell)
{ }
NS_IMETHOD
Run()
{
CheckStableStateRunnable::Run();
nsCOMPtr<nsIRunnable> runnable = new StableStateRunnable();
nsresult rv = mAppShell->RunBeforeNextEvent(runnable);
if (NS_FAILED(rv)) {
fail("RunBeforeNextEvent returned failure code %u", rv);
}
return rv;
}
};
class NextTestRunnable : public nsRunnable
{
nsCOMPtr<nsIAppShell> mAppShell;
public:
NextTestRunnable(nsIAppShell* aAppShell)
: mAppShell(aAppShell)
{ }
NS_IMETHOD Run();
};
class ScheduleNestedStableStateRunnable : public ScheduleStableStateRunnable
{
public:
ScheduleNestedStableStateRunnable(nsIAppShell* aAppShell)
: ScheduleStableStateRunnable(aAppShell)
{ }
NS_IMETHOD
Run()
{
ScheduleStableStateRunnable::Run();
nsCOMPtr<nsIRunnable> runnable = new CheckStableStateRunnable(false);
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
fail("Failed to dispatch check runnable");
}
if (NS_FAILED(NS_ProcessPendingEvents(NULL))) {
fail("Failed to process all pending events");
}
runnable = new CheckStableStateRunnable(true);
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
fail("Failed to dispatch check runnable");
}
runnable = new NextTestRunnable(mAppShell);
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
fail("Failed to dispatch next test runnable");
}
return NS_OK;
}
};
class EventListener : public nsIDOMEventListener
{
nsCOMPtr<nsIAppShell> mAppShell;
static nsIDOMWindowUtils* sWindowUtils;
static nsIAppShell* sAppShell;
public:
NS_DECL_ISUPPORTS
EventListener(nsIAppShell* aAppShell)
: mAppShell(aAppShell)
{ }
NS_IMETHOD
HandleEvent(nsIDOMEvent* aEvent)
{
nsString type;
if (NS_FAILED(aEvent->GetType(type))) {
fail("Failed to get event type");
return NS_ERROR_FAILURE;
}
if (type.EqualsLiteral("load")) {
passed("Got load event");
nsCOMPtr<nsIDOMEventTarget> target;
if (NS_FAILED(aEvent->GetTarget(getter_AddRefs(target)))) {
fail("Failed to get event type");
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDocument> document = do_QueryInterface(target);
if (!document) {
fail("Failed to QI to nsIDocument!");
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsPIDOMWindow> window = document->GetWindow();
if (!window) {
fail("Failed to get window from document!");
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDOMWindowUtils> utils = do_GetInterface(window);
if (!utils) {
fail("Failed to get DOMWindowUtils!");
return NS_ERROR_FAILURE;
}
if (!ScheduleTimer(utils)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
if (type.EqualsLiteral("keypress")) {
passed("Got keypress event");
nsCOMPtr<nsIRunnable> runnable = new StableStateRunnable();
nsresult rv = mAppShell->RunBeforeNextEvent(runnable);
if (NS_FAILED(rv)) {
fail("RunBeforeNextEvent returned failure code %u", rv);
return NS_ERROR_FAILURE;
}
return NS_OK;
}
fail("Got an unexpected event: %s", NS_ConvertUTF16toUTF8(type).get());
return NS_OK;
}
#ifdef XP_WIN
static VOID CALLBACK
TimerCallback(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
if (sWindowUtils) {
nsCOMPtr<nsIDOMWindowUtils> utils = dont_AddRef(sWindowUtils);
sWindowUtils = NULL;
if (gStableStateEventHasRun) {
fail("StableStateRunnable ran at wrong time");
} else {
passed("StableStateRunnable state correct (false)");
}
PRInt32 layout = 0x409; // US
PRInt32 keyCode = 0x41; // VK_A
NS_NAMED_LITERAL_STRING(a, "a");
if (NS_FAILED(utils->SendNativeKeyEvent(layout, keyCode, 0, a, a))) {
fail("Failed to synthesize native event");
}
return;
}
KillTimer(NULL, idEvent);
nsCOMPtr<nsIAppShell> appShell = dont_AddRef(sAppShell);
if (!gStableStateEventHasRun) {
fail("StableStateRunnable didn't run yet");
} else {
passed("StableStateRunnable state correct (true)");
}
nsCOMPtr<nsIRunnable> runnable = new NextTestRunnable(appShell);
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
fail("Failed to dispatch next test runnable");
}
}
#endif
bool
ScheduleTimer(nsIDOMWindowUtils* aWindowUtils)
{
#ifdef XP_WIN
UINT_PTR timerId = SetTimer(NULL, 0, 1000, TimerCallback);
if (!timerId) {
fail("SetTimer failed!");
return false;
}
nsCOMPtr<nsIDOMWindowUtils> utils = aWindowUtils;
utils.forget(&sWindowUtils);
nsCOMPtr<nsIAppShell> appShell = mAppShell;
appShell.forget(&sAppShell);
return true;
#else
return false;
#endif
}
};
nsIDOMWindowUtils* EventListener::sWindowUtils = NULL;
nsIAppShell* EventListener::sAppShell = NULL;
NS_IMPL_ISUPPORTS1(EventListener, nsIDOMEventListener)
already_AddRefed<nsIAppShell>
GetAppShell()
{
static const char* platforms[] = {
"android", "mac", "gonk", "gtk", "os2", "qt", "win"
};
NS_NAMED_LITERAL_CSTRING(contractPrefix, "@mozilla.org/widget/appshell/");
NS_NAMED_LITERAL_CSTRING(contractSuffix, ";1");
for (size_t index = 0; index < ArrayLength(platforms); index++) {
nsCAutoString contractID(contractPrefix);
contractID.AppendASCII(platforms[index]);
contractID.Append(contractSuffix);
nsCOMPtr<nsIAppShell> appShell = do_GetService(contractID.get());
if (appShell) {
return appShell.forget();
}
}
return NULL;
}
void
Test1(nsIAppShell* aAppShell)
{
// Schedule stable state runnable to be run before next event.
nsCOMPtr<nsIRunnable> runnable = new StableStateRunnable();
if (NS_FAILED(aAppShell->RunBeforeNextEvent(runnable))) {
fail("RunBeforeNextEvent failed");
}
runnable = new CheckStableStateRunnable(true);
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
fail("Failed to dispatch check runnable");
}
runnable = new NextTestRunnable(aAppShell);
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
fail("Failed to dispatch next test runnable");
}
}
void
Test2(nsIAppShell* aAppShell)
{
// Schedule stable state runnable to be run before next event from another
// runnable.
nsCOMPtr<nsIRunnable> runnable = new ScheduleStableStateRunnable(aAppShell);
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
fail("Failed to dispatch schedule runnable");
}
runnable = new CheckStableStateRunnable(true);
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
fail("Failed to dispatch check runnable");
}
runnable = new NextTestRunnable(aAppShell);
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
fail("Failed to dispatch next test runnable");
}
}
void
Test3(nsIAppShell* aAppShell)
{
// Schedule steadystate runnable to be run before next event with nested loop.
nsCOMPtr<nsIRunnable> runnable =
new ScheduleNestedStableStateRunnable(aAppShell);
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
fail("Failed to dispatch schedule runnable");
}
}
bool
Test4Internal(nsIAppShell* aAppShell)
{
#ifndef XP_WIN
// Not sure how to test on other platforms.
return false;
#endif
nsCOMPtr<nsIAppShellService> appService =
do_GetService(NS_APPSHELLSERVICE_CONTRACTID);
if (!appService) {
fail("Failed to get appshell service!");
return false;
}
nsCOMPtr<nsIURI> uri;
if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), "about:blank", NULL))) {
fail("Failed to create new uri");
return false;
}
PRUint32 flags = nsIWebBrowserChrome::CHROME_DEFAULT;
nsCOMPtr<nsIXULWindow> xulWindow;
if (NS_FAILED(appService->CreateTopLevelWindow(NULL, uri, flags, 100, 100,
getter_AddRefs(xulWindow)))) {
fail("Failed to create new window");
return false;
}
nsCOMPtr<nsIDOMWindow> window = do_GetInterface(xulWindow);
if (!window) {
fail("Can't get dom window!");
return false;
}
nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(window);
if (!target) {
fail("Can't QI to nsIDOMEventTarget!");
return false;
}
nsCOMPtr<nsIDOMEventListener> listener = new EventListener(aAppShell);
if (NS_FAILED(target->AddEventListener(NS_LITERAL_STRING("keypress"),
listener, false, false)) ||
NS_FAILED(target->AddEventListener(NS_LITERAL_STRING("load"), listener,
false, false))) {
fail("Can't add event listeners!");
return false;
}
return true;
}
void
Test4(nsIAppShell* aAppShell)
{
if (!Test4Internal(aAppShell)) {
nsCOMPtr<nsIRunnable> runnable = new NextTestRunnable(aAppShell);
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
fail("Failed to dispatch next test runnable");
}
}
}
const TestFunc gTests[] = {
Test1, Test2, Test3, Test4
};
size_t gTestIndex = 0;
NS_IMETHODIMP
NextTestRunnable::Run()
{
if (gTestIndex > 0) {
passed("Finished test %u", gTestIndex);
}
gStableStateEventHasRun = false;
if (gTestIndex < ArrayLength(gTests)) {
gTests[gTestIndex++](mAppShell);
}
else {
nsCOMPtr<nsIRunnable> exitRunnable = new ExitAppShellRunnable(mAppShell);
nsresult rv = NS_DispatchToCurrentThread(exitRunnable);
if (NS_FAILED(rv)) {
fail("Failed to dispatch exit runnable!");
}
}
return NS_OK;
}
int main(int argc, char** argv)
{
ScopedLogging log;
ScopedXPCOM xpcom("TestAppShellSteadyState");
if (!xpcom.failed()) {
nsCOMPtr<nsIAppShell> appShell = GetAppShell();
if (!appShell) {
fail("Couldn't get appshell!");
} else {
nsCOMPtr<nsIRunnable> runnable = new NextTestRunnable(appShell);
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
fail("Failed to dispatch next test runnable");
} else if (NS_FAILED(appShell->Run())) {
fail("Failed to run appshell");
}
}
}
return gFailCount != 0;
}

View File

@ -70,7 +70,7 @@ nsBaseAppShell::nsBaseAppShell()
nsBaseAppShell::~nsBaseAppShell()
{
NS_ASSERTION(mSyncSections.Count() == 0, "Must have run all sync sections");
NS_ASSERTION(mSyncSections.IsEmpty(), "Must have run all sync sections");
}
nsresult
@ -151,7 +151,7 @@ nsBaseAppShell::DoProcessMoreGeckoEvents()
// Main thread via OnProcessNextEvent below
bool
nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait)
nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait, PRUint32 recursionDepth)
{
// The next native event to be processed may trigger our NativeEventCallback,
// in which case we do not want it to process any thread events since we'll
@ -168,7 +168,14 @@ nsBaseAppShell::DoProcessNextNativeEvent(bool mayWait)
mEventloopNestingState = eEventloopXPCOM;
++mEventloopNestingLevel;
bool result = ProcessNextNativeEvent(mayWait);
// Make sure that any sync sections registered during this most recent event
// are run now. This is not considered a stable state because we're not back
// to the event loop yet.
RunSyncSections(false, recursionDepth);
--mEventloopNestingLevel;
mEventloopNestingState = prevVal;
@ -303,13 +310,13 @@ nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait,
bool keepGoing;
do {
mLastNativeEventTime = now;
keepGoing = DoProcessNextNativeEvent(false);
keepGoing = DoProcessNextNativeEvent(false, recursionDepth);
} while (keepGoing && ((now = PR_IntervalNow()) - start) < limit);
} else {
// Avoid starving native events completely when in performance mode
if (start - mLastNativeEventTime > limit) {
mLastNativeEventTime = start;
DoProcessNextNativeEvent(false);
DoProcessNextNativeEvent(false, recursionDepth);
}
}
@ -321,7 +328,7 @@ nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait,
mayWait = false;
mLastNativeEventTime = PR_IntervalNow();
if (!DoProcessNextNativeEvent(mayWait) || !mayWait)
if (!DoProcessNextNativeEvent(mayWait, recursionDepth) || !mayWait)
break;
}
@ -330,33 +337,99 @@ nsBaseAppShell::OnProcessNextEvent(nsIThreadInternal *thr, bool mayWait,
// Make sure that the thread event queue does not block on its monitor, as
// it normally would do if it did not have any pending events. To avoid
// that, we simply insert a dummy event into its queue during shutdown.
if (needEvent && !mExiting && !NS_HasPendingEvents(thr)) {
if (!mDummyEvent)
mDummyEvent = new nsRunnable();
thr->Dispatch(mDummyEvent, NS_DISPATCH_NORMAL);
if (needEvent && !mExiting && !NS_HasPendingEvents(thr)) {
DispatchDummyEvent(thr);
}
// We're about to run an event, so we're in a stable state.
RunSyncSections();
// We're about to run an event, so we're in a stable state.
RunSyncSections(true, recursionDepth);
return NS_OK;
}
void
nsBaseAppShell::RunSyncSections()
bool
nsBaseAppShell::DispatchDummyEvent(nsIThread* aTarget)
{
if (mSyncSections.Count() == 0) {
return;
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
if (!mDummyEvent)
mDummyEvent = new nsRunnable();
return NS_SUCCEEDED(aTarget->Dispatch(mDummyEvent, NS_DISPATCH_NORMAL));
}
void
nsBaseAppShell::RunSyncSectionsInternal(bool aStable,
PRUint32 aThreadRecursionLevel)
{
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
NS_ASSERTION(!mSyncSections.IsEmpty(), "Nothing to do!");
// We've got synchronous sections. Run all of them that are are awaiting a
// stable state if aStable is true (i.e. we really are in a stable state).
// Also run the synchronous sections that are simply waiting for the right
// combination of event loop nesting level and thread recursion level.
// Note that a synchronous section could add another synchronous section, so
// we don't remove elements from mSyncSections until all sections have been
// run, or else we'll screw up our iteration. Any sync sections that are not
// ready to be run are saved for later.
nsTArray<SyncSection> pendingSyncSections;
for (PRUint32 i = 0; i < mSyncSections.Length(); i++) {
SyncSection& section = mSyncSections[i];
if ((aStable && section.mStable) ||
(!section.mStable &&
section.mEventloopNestingLevel == mEventloopNestingLevel &&
section.mThreadRecursionLevel == aThreadRecursionLevel)) {
section.mRunnable->Run();
}
else {
// Add to pending list.
SyncSection* pending = pendingSyncSections.AppendElement();
section.Forget(pending);
}
}
// We've got synchronous sections awaiting a stable state. Run
// all the synchronous sections. Note that a synchronous section could
// add another synchronous section, so we don't remove elements from
// mSyncSections until all sections have been run, else we'll screw up
// our iteration.
for (PRInt32 i = 0; i < mSyncSections.Count(); i++) {
mSyncSections[i]->Run();
mSyncSections.SwapElements(pendingSyncSections);
}
void
nsBaseAppShell::ScheduleSyncSection(nsIRunnable* aRunnable, bool aStable)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
nsIThread* thread = NS_GetCurrentThread();
// Add this runnable to our list of synchronous sections.
SyncSection* section = mSyncSections.AppendElement();
section->mStable = aStable;
section->mRunnable = aRunnable;
// If aStable is false then this synchronous section is supposed to run before
// the next event at the current nesting level. Record the event loop nesting
// level and the thread recursion level so that the synchronous section will
// run at the proper time.
if (!aStable) {
section->mEventloopNestingLevel = mEventloopNestingLevel;
nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread);
NS_ASSERTION(threadInternal, "This should never fail!");
PRUint32 recursionLevel;
if (NS_FAILED(threadInternal->GetRecursionDepth(&recursionLevel))) {
NS_ERROR("This should never fail!");
}
// Due to the weird way that the thread recursion counter is implemented we
// subtract one from the recursion level if we have one.
section->mThreadRecursionLevel = recursionLevel ? recursionLevel - 1 : 0;
}
// Ensure we've got a pending event, else the callbacks will never run.
if (!NS_HasPendingEvents(thread) && !DispatchDummyEvent(thread)) {
RunSyncSections(true, 0);
}
mSyncSections.Clear();
}
// Called from the main thread
@ -365,7 +438,7 @@ nsBaseAppShell::AfterProcessNextEvent(nsIThreadInternal *thr,
PRUint32 recursionDepth)
{
// We've just finished running an event, so we're in a stable state.
RunSyncSections();
RunSyncSections(true, recursionDepth);
return NS_OK;
}
@ -381,20 +454,13 @@ nsBaseAppShell::Observe(nsISupports *subject, const char *topic,
NS_IMETHODIMP
nsBaseAppShell::RunInStableState(nsIRunnable* aRunnable)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
// Record the synchronous section, and run it with any others once
// we reach a stable state.
mSyncSections.AppendObject(aRunnable);
// Ensure we've got a pending event, else the callbacks will never run.
nsIThread* thread = NS_GetCurrentThread();
if (!NS_HasPendingEvents(thread) &&
NS_FAILED(thread->Dispatch(new nsRunnable(), NS_DISPATCH_NORMAL)))
{
// Failed to dispatch dummy event to cause sync sections to run, thread
// is probably done processing events, just run the sync sections now.
RunSyncSections();
}
ScheduleSyncSection(aRunnable, true);
return NS_OK;
}
NS_IMETHODIMP
nsBaseAppShell::RunBeforeNextEvent(nsIRunnable* aRunnable)
{
ScheduleSyncSection(aRunnable, false);
return NS_OK;
}

View File

@ -42,8 +42,8 @@
#include "nsIThreadInternal.h"
#include "nsIObserver.h"
#include "nsIRunnable.h"
#include "nsCOMArray.h"
#include "nsCOMPtr.h"
#include "nsTArray.h"
#include "prinrval.h"
/**
@ -106,12 +106,41 @@ protected:
PRUint32 mEventloopNestingLevel;
private:
bool DoProcessNextNativeEvent(bool mayWait);
bool DoProcessNextNativeEvent(bool mayWait, PRUint32 recursionDepth);
bool DispatchDummyEvent(nsIThread* target);
/**
* Runs all synchronous sections which are queued up in mSyncSections.
*/
void RunSyncSections();
void RunSyncSectionsInternal(bool stable, PRUint32 threadRecursionLevel);
void RunSyncSections(bool stable, PRUint32 threadRecursionLevel)
{
if (!mSyncSections.IsEmpty()) {
RunSyncSectionsInternal(stable, threadRecursionLevel);
}
}
void ScheduleSyncSection(nsIRunnable* runnable, bool stable);
struct SyncSection {
SyncSection()
: mStable(false), mEventloopNestingLevel(0), mThreadRecursionLevel(0)
{ }
void Forget(SyncSection* other) {
other->mStable = mStable;
other->mEventloopNestingLevel = mEventloopNestingLevel;
other->mThreadRecursionLevel = mThreadRecursionLevel;
other->mRunnable = mRunnable.forget();
}
bool mStable;
PRUint32 mEventloopNestingLevel;
PRUint32 mThreadRecursionLevel;
nsCOMPtr<nsIRunnable> mRunnable;
};
nsCOMPtr<nsIRunnable> mDummyEvent;
/**
@ -132,7 +161,7 @@ private:
eEventloopOther // innermost native event loop is a native library/plugin etc
};
EventloopNestingState mEventloopNestingState;
nsCOMArray<nsIRunnable> mSyncSections;
nsTArray<SyncSection> mSyncSections;
bool mRunning;
bool mExiting;
/**