From dc5ce5cefa53c5ed0d42094dde8c7b7dbc2779cb Mon Sep 17 00:00:00 2001 From: Ben Turner Date: Fri, 6 Apr 2012 13:40:10 -0700 Subject: [PATCH] Bug 672667 - ' IndexedDB demo causes leaks and never-ending assertions'. r=bsmedberg+smichaud+khuey. --- dom/indexedDB/IDBTransaction.cpp | 70 +--- dom/indexedDB/IDBTransaction.h | 6 +- widget/nsIAppShell.idl | 14 +- widget/tests/Makefile.in | 2 + widget/tests/TestAppShellSteadyState.cpp | 500 +++++++++++++++++++++++ widget/xpwidgets/nsBaseAppShell.cpp | 142 +++++-- widget/xpwidgets/nsBaseAppShell.h | 37 +- 7 files changed, 668 insertions(+), 103 deletions(-) create mode 100644 widget/tests/TestAppShellSteadyState.cpp diff --git a/dom/indexedDB/IDBTransaction.cpp b/dom/indexedDB/IDBTransaction.cpp index 9729b26233ac..b82163ca9ebd 100644 --- a/dom/indexedDB/IDBTransaction.cpp +++ b/dom/indexedDB/IDBTransaction.cpp @@ -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& aStatement, @@ -138,19 +142,10 @@ IDBTransaction::Create(IDBDatabase* aDatabase, } if (!aDispatchDelayed) { - nsCOMPtr thread = - do_QueryInterface(NS_GetCurrentThread()); - NS_ENSURE_TRUE(thread, nsnull); + nsCOMPtr 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!"); } } diff --git a/dom/indexedDB/IDBTransaction.h b/dom/indexedDB/IDBTransaction.h index 6856abfbcd7c..3d7520ac5809 100644 --- a/dom/indexedDB/IDBTransaction.h +++ b/dom/indexedDB/IDBTransaction.h @@ -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) diff --git a/widget/nsIAppShell.idl b/widget/nsIAppShell.idl index ec4abec74ddc..442e2489e192 100644 --- a/widget/nsIAppShell.idl +++ b/widget/nsIAppShell.idl @@ -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); }; diff --git a/widget/tests/Makefile.in b/widget/tests/Makefile.in index 9f48565dca58..e749e36822f9 100644 --- a/widget/tests/Makefile.in +++ b/widget/tests/Makefile.in @@ -60,6 +60,8 @@ endif # $(NULL) endif +CPP_UNIT_TESTS += TestAppShellSteadyState.cpp + include $(topsrcdir)/config/rules.mk _TEST_FILES = diff --git a/widget/tests/TestAppShellSteadyState.cpp b/widget/tests/TestAppShellSteadyState.cpp new file mode 100644 index 000000000000..5c39ce597c88 --- /dev/null +++ b/widget/tests/TestAppShellSteadyState.cpp @@ -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 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 mAppShell; + +public: + ScheduleStableStateRunnable(nsIAppShell* aAppShell) + : CheckStableStateRunnable(false), mAppShell(aAppShell) + { } + + NS_IMETHOD + Run() + { + CheckStableStateRunnable::Run(); + + nsCOMPtr 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 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 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 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 target; + if (NS_FAILED(aEvent->GetTarget(getter_AddRefs(target)))) { + fail("Failed to get event type"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr document = do_QueryInterface(target); + if (!document) { + fail("Failed to QI to nsIDocument!"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr window = document->GetWindow(); + if (!window) { + fail("Failed to get window from document!"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr 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 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 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 appShell = dont_AddRef(sAppShell); + + if (!gStableStateEventHasRun) { + fail("StableStateRunnable didn't run yet"); + } else { + passed("StableStateRunnable state correct (true)"); + } + + nsCOMPtr 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 utils = aWindowUtils; + utils.forget(&sWindowUtils); + + nsCOMPtr 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 +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 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 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 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 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 appService = + do_GetService(NS_APPSHELLSERVICE_CONTRACTID); + if (!appService) { + fail("Failed to get appshell service!"); + return false; + } + + nsCOMPtr 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 xulWindow; + if (NS_FAILED(appService->CreateTopLevelWindow(NULL, uri, flags, 100, 100, + getter_AddRefs(xulWindow)))) { + fail("Failed to create new window"); + return false; + } + + nsCOMPtr window = do_GetInterface(xulWindow); + if (!window) { + fail("Can't get dom window!"); + return false; + } + + nsCOMPtr target = do_QueryInterface(window); + if (!target) { + fail("Can't QI to nsIDOMEventTarget!"); + return false; + } + + nsCOMPtr 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 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 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 appShell = GetAppShell(); + if (!appShell) { + fail("Couldn't get appshell!"); + } else { + nsCOMPtr 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; +} diff --git a/widget/xpwidgets/nsBaseAppShell.cpp b/widget/xpwidgets/nsBaseAppShell.cpp index ce8ee1abb0b8..5ebad575393f 100644 --- a/widget/xpwidgets/nsBaseAppShell.cpp +++ b/widget/xpwidgets/nsBaseAppShell.cpp @@ -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 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 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; +} diff --git a/widget/xpwidgets/nsBaseAppShell.h b/widget/xpwidgets/nsBaseAppShell.h index cd66c151f175..6355ba442108 100644 --- a/widget/xpwidgets/nsBaseAppShell.h +++ b/widget/xpwidgets/nsBaseAppShell.h @@ -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 mRunnable; + }; nsCOMPtr mDummyEvent; /** @@ -132,7 +161,7 @@ private: eEventloopOther // innermost native event loop is a native library/plugin etc }; EventloopNestingState mEventloopNestingState; - nsCOMArray mSyncSections; + nsTArray mSyncSections; bool mRunning; bool mExiting; /**