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