mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 10:44:56 +00:00
Bug 672667 - ' IndexedDB demo causes leaks and never-ending assertions'. r=bsmedberg+smichaud+khuey.
This commit is contained in:
parent
f4363e6229
commit
dc5ce5cefa
@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -60,6 +60,8 @@ endif
|
||||
# $(NULL)
|
||||
endif
|
||||
|
||||
CPP_UNIT_TESTS += TestAppShellSteadyState.cpp
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_TEST_FILES =
|
||||
|
500
widget/tests/TestAppShellSteadyState.cpp
Normal file
500
widget/tests/TestAppShellSteadyState.cpp
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user