/* -*- Mode: c++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 4; -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // Make sure the order of included headers #include "base/basictypes.h" #include "nspr/prtypes.h" #include "base/message_loop.h" #include "base/task.h" #include "mozilla/Hal.h" #include "nsAppShell.h" #include "nsWindow.h" #include "nsThreadUtils.h" #include "nsICommandLineRunner.h" #include "nsIObserverService.h" #include "nsIAppStartup.h" #include "nsIGeolocationProvider.h" #include "nsCacheService.h" #include "nsIDOMEventListener.h" #include "nsDOMNotifyPaintEvent.h" #include "nsIDOMClientRectList.h" #include "nsIDOMClientRect.h" #include "nsIDOMWakeLockListener.h" #include "nsIPowerManagerService.h" #include "nsFrameManager.h" #include "mozilla/Services.h" #include "mozilla/unused.h" #include "mozilla/Preferences.h" #include "mozilla/Hal.h" #include "prenv.h" #include "AndroidBridge.h" #include #include #include #include "mozilla/dom/ScreenOrientation.h" #include "sampler.h" #ifdef MOZ_ANDROID_HISTORY #include "nsAndroidHistory.h" #endif #ifdef MOZ_LOGGING #define FORCE_PR_LOG #include "prlog.h" #endif #ifdef DEBUG_ANDROID_EVENTS #define EVLOG(args...) ALOG(args) #else #define EVLOG(args...) do { } while (0) #endif using namespace mozilla; #ifdef PR_LOGGING PRLogModuleInfo *gWidgetLog = nullptr; #endif nsIGeolocationUpdate *gLocationCallback = nullptr; nsAutoPtr gLastSizeChange; nsAppShell *nsAppShell::gAppShell = nullptr; NS_IMPL_ISUPPORTS_INHERITED1(nsAppShell, nsBaseAppShell, nsIObserver) class ScreenshotRunnable : public nsRunnable { public: ScreenshotRunnable(nsIAndroidBrowserApp* aBrowserApp, int aTabId, nsTArray& aPoints, int aToken, RefCountedJavaObject* aBuffer): mBrowserApp(aBrowserApp), mPoints(aPoints), mTabId(aTabId), mToken(aToken), mBuffer(aBuffer) {} virtual nsresult Run() { nsCOMPtr domWindow; nsCOMPtr tab; mBrowserApp->GetBrowserTab(mTabId, getter_AddRefs(tab)); if (!tab) return NS_OK; tab->GetWindow(getter_AddRefs(domWindow)); if (!domWindow) return NS_OK; NS_ASSERTION(mPoints.Length() == 5, "Screenshot event does not have enough coordinates"); AndroidBridge::Bridge()->TakeScreenshot(domWindow, mPoints[0].x, mPoints[0].y, mPoints[1].x, mPoints[1].y, mPoints[2].x, mPoints[2].y, mPoints[3].x, mPoints[3].y, mPoints[4].x, mPoints[4].y, mTabId, mToken, mBuffer->GetObject()); return NS_OK; } private: nsCOMPtr mBrowserApp; nsTArray mPoints; int mTabId, mToken; nsRefPtr mBuffer; }; class AfterPaintListener : public nsIDOMEventListener { public: NS_DECL_ISUPPORTS void Register(nsIDOMWindow* window) { if (mEventTarget) Unregister(); nsCOMPtr win = do_QueryInterface(window); if (!win) return; mEventTarget = win->GetChromeEventHandler(); if (mEventTarget) mEventTarget->AddEventListener(NS_LITERAL_STRING("MozAfterPaint"), this, false); } void Unregister() { if (mEventTarget) mEventTarget->RemoveEventListener(NS_LITERAL_STRING("MozAfterPaint"), this, false); mEventTarget = nullptr; } virtual nsresult HandleEvent(nsIDOMEvent* aEvent) { uint32_t generation = nsFrameManager::GetGlobalGenerationNumber(); if (mLastGeneration == generation) { // the frame tree has not changed since our last AfterPaint // so we can drop this event. return NS_OK; } mLastGeneration = generation; nsCOMPtr paintEvent = do_QueryInterface(aEvent); if (!paintEvent) return NS_OK; nsCOMPtr rect; paintEvent->GetBoundingClientRect(getter_AddRefs(rect)); float top, left, bottom, right; rect->GetTop(&top); rect->GetLeft(&left); rect->GetRight(&right); rect->GetBottom(&bottom); __android_log_print(ANDROID_LOG_INFO, "GeckoScreenshot", "rect: %f, %f, %f, %f", top, left, right, bottom); AndroidBridge::NotifyPaintedRect(top, left, bottom, right); return NS_OK; } ~AfterPaintListener() { if (mEventTarget) Unregister(); } private: uint32_t mLastGeneration; nsCOMPtr mEventTarget; }; class WakeLockListener : public nsIDOMMozWakeLockListener { public: NS_DECL_ISUPPORTS; nsresult Callback(const nsAString& topic, const nsAString& state) { AndroidBridge::Bridge()->NotifyWakeLockChanged(topic, state); return NS_OK; } }; NS_IMPL_ISUPPORTS1(AfterPaintListener, nsIDOMEventListener) nsCOMPtr sAfterPaintListener = nullptr; NS_IMPL_ISUPPORTS1(WakeLockListener, nsIDOMMozWakeLockListener) nsCOMPtr sPowerManagerService = nullptr; nsCOMPtr sWakeLockListener = nullptr; nsAppShell::nsAppShell() : mQueueLock("nsAppShell.mQueueLock"), mCondLock("nsAppShell.mCondLock"), mQueueCond(mCondLock, "nsAppShell.mQueueCond"), mQueuedDrawEvent(nullptr), mQueuedViewportEvent(nullptr), mAllowCoalescingNextDraw(false) { gAppShell = this; sAfterPaintListener = new AfterPaintListener(); if (XRE_GetProcessType() != GeckoProcessType_Default) { return; } sPowerManagerService = do_GetService(POWERMANAGERSERVICE_CONTRACTID); if (sPowerManagerService) { sWakeLockListener = new WakeLockListener(); } else { NS_WARNING("Failed to retrieve PowerManagerService, wakelocks will be broken!"); } } nsAppShell::~nsAppShell() { gAppShell = nullptr; sAfterPaintListener = nullptr; if (sPowerManagerService) { sPowerManagerService->RemoveWakeLockListener(sWakeLockListener); sPowerManagerService = nullptr; sWakeLockListener = nullptr; } } void nsAppShell::NotifyNativeEvent() { MutexAutoLock lock(mCondLock); mQueueCond.Notify(); } #define PREFNAME_MATCH_OS "intl.locale.matchOS" #define PREFNAME_UA_LOCALE "general.useragent.locale" #define PREFNAME_COALESCE_TOUCHES "dom.event.touch.coalescing.enabled" static const char* kObservedPrefs[] = { PREFNAME_MATCH_OS, PREFNAME_UA_LOCALE, PREFNAME_COALESCE_TOUCHES, nullptr }; nsresult nsAppShell::Init() { #ifdef PR_LOGGING if (!gWidgetLog) gWidgetLog = PR_NewLogModule("Widget"); #endif mObserversHash.Init(); nsresult rv = nsBaseAppShell::Init(); AndroidBridge* bridge = AndroidBridge::Bridge(); nsCOMPtr obsServ = mozilla::services::GetObserverService(); if (obsServ) { obsServ->AddObserver(this, "xpcom-shutdown", false); } if (sPowerManagerService) sPowerManagerService->AddWakeLockListener(sWakeLockListener); if (!bridge) return rv; Preferences::AddStrongObservers(this, kObservedPrefs); bool match; rv = Preferences::GetBool(PREFNAME_MATCH_OS, &match); NS_ENSURE_SUCCESS(rv, rv); if (match) { bridge->SetSelectedLocale(EmptyString()); return NS_OK; } nsAutoString locale; rv = Preferences::GetLocalizedString(PREFNAME_UA_LOCALE, &locale); if (NS_FAILED(rv)) { rv = Preferences::GetString(PREFNAME_UA_LOCALE, &locale); } bridge->SetSelectedLocale(locale); mAllowCoalescingTouches = Preferences::GetBool(PREFNAME_COALESCE_TOUCHES, true); return rv; } NS_IMETHODIMP nsAppShell::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aData) { if (!strcmp(aTopic, "xpcom-shutdown")) { // We need to ensure no observers stick around after XPCOM shuts down // or we'll see crashes, as the app shell outlives XPConnect. mObserversHash.Clear(); return nsBaseAppShell::Observe(aSubject, aTopic, aData); } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) && aData && ( nsDependentString(aData).Equals( NS_LITERAL_STRING(PREFNAME_UA_LOCALE)) || nsDependentString(aData).Equals( NS_LITERAL_STRING(PREFNAME_COALESCE_TOUCHES)) || nsDependentString(aData).Equals( NS_LITERAL_STRING(PREFNAME_MATCH_OS)))) { AndroidBridge* bridge = AndroidBridge::Bridge(); if (!bridge) { return NS_OK; } bool match; nsresult rv = Preferences::GetBool(PREFNAME_MATCH_OS, &match); NS_ENSURE_SUCCESS(rv, rv); if (match) { bridge->SetSelectedLocale(EmptyString()); return NS_OK; } nsAutoString locale; if (NS_FAILED(Preferences::GetLocalizedString(PREFNAME_UA_LOCALE, &locale))) { locale = Preferences::GetString(PREFNAME_UA_LOCALE); } bridge->SetSelectedLocale(locale); mAllowCoalescingTouches = Preferences::GetBool(PREFNAME_COALESCE_TOUCHES, true); return NS_OK; } return NS_OK; } void nsAppShell::ScheduleNativeEventCallback() { EVLOG("nsAppShell::ScheduleNativeEventCallback pth: %p thread: %p main: %d", (void*) pthread_self(), (void*) NS_GetCurrentThread(), NS_IsMainThread()); // this is valid to be called from any thread, so do so. PostEvent(new AndroidGeckoEvent(AndroidGeckoEvent::NATIVE_POKE)); } bool nsAppShell::ProcessNextNativeEvent(bool mayWait) { EVLOG("nsAppShell::ProcessNextNativeEvent %d", mayWait); SAMPLE_LABEL("nsAppShell", "ProcessNextNativeEvent"); nsAutoPtr curEvent; { MutexAutoLock lock(mCondLock); curEvent = PopNextEvent(); if (!curEvent && mayWait) { SAMPLE_LABEL("nsAppShell::ProcessNextNativeEvent", "Wait"); // hmm, should we really hardcode this 10s? #if defined(DEBUG_ANDROID_EVENTS) PRTime t0, t1; EVLOG("nsAppShell: waiting on mQueueCond"); t0 = PR_Now(); mQueueCond.Wait(PR_MillisecondsToInterval(10000)); t1 = PR_Now(); EVLOG("nsAppShell: wait done, waited %d ms", (int)(t1-t0)/1000); #else mQueueCond.Wait(); #endif curEvent = PopNextEvent(); } } if (!curEvent) return false; EVLOG("nsAppShell: event %p %d", (void*)curEvent.get(), curEvent->Type()); switch (curEvent->Type()) { case AndroidGeckoEvent::NATIVE_POKE: NativeEventCallback(); break; case AndroidGeckoEvent::SENSOR_EVENT: { InfallibleTArray values; mozilla::hal::SensorType type = (mozilla::hal::SensorType) curEvent->Flags(); switch (type) { case hal::SENSOR_ORIENTATION: case hal::SENSOR_LINEAR_ACCELERATION: case hal::SENSOR_ACCELERATION: case hal::SENSOR_GYROSCOPE: case hal::SENSOR_PROXIMITY: values.AppendElement(curEvent->X()); values.AppendElement(curEvent->Y()); values.AppendElement(curEvent->Z()); break; case hal::SENSOR_LIGHT: values.AppendElement(curEvent->X()); break; default: __android_log_print(ANDROID_LOG_ERROR, "Gecko", "### SENSOR_EVENT fired, but type wasn't known %d", type); } const hal::SensorAccuracyType &accuracy = (hal::SensorAccuracyType) curEvent->MetaState(); hal::SensorData sdata(type, PR_Now(), values, accuracy); hal::NotifySensorChange(sdata); } break; case AndroidGeckoEvent::LOCATION_EVENT: { if (!gLocationCallback) break; nsGeoPosition* p = curEvent->GeoPosition(); if (p) gLocationCallback->Update(curEvent->GeoPosition()); else NS_WARNING("Received location event without geoposition!"); break; } case AndroidGeckoEvent::ACTIVITY_STOPPING: { if (curEvent->Flags() > 0) break; nsCOMPtr obsServ = mozilla::services::GetObserverService(); NS_NAMED_LITERAL_STRING(minimize, "heap-minimize"); obsServ->NotifyObservers(nullptr, "memory-pressure", minimize.get()); obsServ->NotifyObservers(nullptr, "application-background", nullptr); break; } case AndroidGeckoEvent::ACTIVITY_SHUTDOWN: { nsCOMPtr obsServ = mozilla::services::GetObserverService(); NS_NAMED_LITERAL_STRING(context, "shutdown-persist"); obsServ->NotifyObservers(nullptr, "quit-application-granted", nullptr); obsServ->NotifyObservers(nullptr, "quit-application-forced", nullptr); obsServ->NotifyObservers(nullptr, "profile-change-net-teardown", context.get()); obsServ->NotifyObservers(nullptr, "profile-change-teardown", context.get()); obsServ->NotifyObservers(nullptr, "profile-before-change", context.get()); nsCOMPtr appSvc = do_GetService("@mozilla.org/toolkit/app-startup;1"); if (appSvc) appSvc->Quit(nsIAppStartup::eForceQuit); break; } case AndroidGeckoEvent::ACTIVITY_PAUSING: { if (curEvent->Flags() == 0) { // We aren't transferring to one of our own activities, so set // background status nsCOMPtr obsServ = mozilla::services::GetObserverService(); obsServ->NotifyObservers(nullptr, "application-background", nullptr); // If we are OOM killed with the disk cache enabled, the entire // cache will be cleared (bug 105843), so shut down the cache here // and re-init on resume if (nsCacheService::GlobalInstance()) nsCacheService::GlobalInstance()->Shutdown(); } // We really want to send a notification like profile-before-change, // but profile-before-change ends up shutting some things down instead // of flushing data nsIPrefService* prefs = Preferences::GetService(); if (prefs) { // reset the crash loop state nsCOMPtr prefBranch; prefs->GetBranch("browser.sessionstore.", getter_AddRefs(prefBranch)); if (prefBranch) prefBranch->SetIntPref("recent_crashes", 0); prefs->SavePrefFile(nullptr); } break; } case AndroidGeckoEvent::ACTIVITY_START: { if (curEvent->Flags() > 0) break; nsCOMPtr obsServ = mozilla::services::GetObserverService(); obsServ->NotifyObservers(nullptr, "application-foreground", nullptr); break; } case AndroidGeckoEvent::PAINT_LISTEN_START_EVENT: { nsCOMPtr domWindow; nsCOMPtr tab; mBrowserApp->GetBrowserTab(curEvent->MetaState(), getter_AddRefs(tab)); if (!tab) break; tab->GetWindow(getter_AddRefs(domWindow)); if (!domWindow) break; sAfterPaintListener->Register(domWindow); break; } case AndroidGeckoEvent::SCREENSHOT: { if (!mBrowserApp) break; AndroidBridge* bridge = AndroidBridge::Bridge(); if (!bridge) break; int32_t token = curEvent->Flags(); int32_t tabId = curEvent->MetaState(); nsTArray points = curEvent->Points(); RefCountedJavaObject* buffer = curEvent->ByteBuffer(); nsCOMPtr sr = new ScreenshotRunnable(mBrowserApp, tabId, points, token, buffer); MessageLoop::current()->PostIdleTask( FROM_HERE, NewRunnableMethod(sr.get(), &ScreenshotRunnable::Run)); break; } case AndroidGeckoEvent::VIEWPORT: case AndroidGeckoEvent::BROADCAST: { if (curEvent->Characters().Length() == 0) break; nsCOMPtr obsServ = mozilla::services::GetObserverService(); const NS_ConvertUTF16toUTF8 topic(curEvent->Characters()); const nsPromiseFlatString& data = PromiseFlatString(curEvent->CharactersExtra()); obsServ->NotifyObservers(nullptr, topic.get(), data.get()); break; } case AndroidGeckoEvent::LOAD_URI: { nsCOMPtr cmdline (do_CreateInstance("@mozilla.org/toolkit/command-line;1")); if (!cmdline) break; if (curEvent->Characters().Length() == 0) break; char *uri = ToNewUTF8String(curEvent->Characters()); if (!uri) break; char *flag = ToNewUTF8String(curEvent->CharactersExtra()); const char *argv[4] = { "dummyappname", "-url", uri, flag ? flag : "" }; nsresult rv = cmdline->Init(4, const_cast(argv), nullptr, nsICommandLine::STATE_REMOTE_AUTO); if (NS_SUCCEEDED(rv)) cmdline->Run(); nsMemory::Free(uri); if (flag) nsMemory::Free(flag); break; } case AndroidGeckoEvent::SIZE_CHANGED: { // store the last resize event to dispatch it to new windows with a FORCED_RESIZE event if (curEvent != gLastSizeChange) { gLastSizeChange = new AndroidGeckoEvent(curEvent); } nsWindow::OnGlobalAndroidEvent(curEvent); break; } case AndroidGeckoEvent::VISITED: { #ifdef MOZ_ANDROID_HISTORY nsAndroidHistory::NotifyURIVisited(nsString(curEvent->Characters())); #endif break; } case AndroidGeckoEvent::NETWORK_CHANGED: { hal::NotifyNetworkChange(hal::NetworkInformation(curEvent->Bandwidth(), curEvent->CanBeMetered())); break; } case AndroidGeckoEvent::ACTIVITY_RESUMING: { if (curEvent->Flags() == 0) { // If we are OOM killed with the disk cache enabled, the entire // cache will be cleared (bug 105843), so shut down cache on pause // and re-init here if (nsCacheService::GlobalInstance()) nsCacheService::GlobalInstance()->Init(); // We didn't return from one of our own activities, so restore // to foreground status nsCOMPtr obsServ = mozilla::services::GetObserverService(); obsServ->NotifyObservers(nullptr, "application-foreground", nullptr); } break; } case AndroidGeckoEvent::SCREENORIENTATION_CHANGED: { nsresult rv; nsCOMPtr screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1", &rv); if (NS_FAILED(rv)) { NS_ERROR("Can't find nsIScreenManager!"); break; } nsIntRect rect; int32_t colorDepth, pixelDepth; dom::ScreenOrientation orientation; nsCOMPtr screen; screenMgr->GetPrimaryScreen(getter_AddRefs(screen)); screen->GetRect(&rect.x, &rect.y, &rect.width, &rect.height); screen->GetColorDepth(&colorDepth); screen->GetPixelDepth(&pixelDepth); orientation = static_cast(curEvent->ScreenOrientation()); hal::NotifyScreenConfigurationChange( hal::ScreenConfiguration(rect, orientation, colorDepth, pixelDepth)); break; } default: nsWindow::OnGlobalAndroidEvent(curEvent); } EVLOG("nsAppShell: -- done event %p %d", (void*)curEvent.get(), curEvent->Type()); return true; } void nsAppShell::ResendLastResizeEvent(nsWindow* aDest) { if (gLastSizeChange) { nsWindow::OnGlobalAndroidEvent(gLastSizeChange); } } AndroidGeckoEvent* nsAppShell::PopNextEvent() { AndroidGeckoEvent *ae = nullptr; MutexAutoLock lock(mQueueLock); if (mEventQueue.Length()) { ae = mEventQueue[0]; mEventQueue.RemoveElementAt(0); if (mQueuedDrawEvent == ae) { mQueuedDrawEvent = nullptr; } else if (mQueuedViewportEvent == ae) { mQueuedViewportEvent = nullptr; } } return ae; } AndroidGeckoEvent* nsAppShell::PeekNextEvent() { AndroidGeckoEvent *ae = nullptr; MutexAutoLock lock(mQueueLock); if (mEventQueue.Length()) { ae = mEventQueue[0]; } return ae; } void nsAppShell::PostEvent(AndroidGeckoEvent *ae) { { // set this to true when inserting events that we can coalesce // viewport events across. this is effectively maintaining a whitelist // of events that are unaffected by viewport changes. bool allowCoalescingNextViewport = false; MutexAutoLock lock(mQueueLock); EVLOG("nsAppShell::PostEvent %p %d", ae, ae->Type()); switch (ae->Type()) { case AndroidGeckoEvent::SURFACE_DESTROYED: // Give priority to this event, and discard any pending // SURFACE_CREATED events. mEventQueue.InsertElementAt(0, ae); AndroidGeckoEvent *event; for (int i = mEventQueue.Length() - 1; i >= 1; i--) { event = mEventQueue[i]; if (event->Type() == AndroidGeckoEvent::SURFACE_CREATED) { EVLOG("nsAppShell: Dropping old SURFACE_CREATED event at %p %d", event, i); mEventQueue.RemoveElementAt(i); delete event; } } break; case AndroidGeckoEvent::COMPOSITOR_PAUSE: case AndroidGeckoEvent::COMPOSITOR_RESUME: // Give priority to these events, but maintain their order wrt each other. { uint32_t i = 0; while (i < mEventQueue.Length() && (mEventQueue[i]->Type() == AndroidGeckoEvent::COMPOSITOR_PAUSE || mEventQueue[i]->Type() == AndroidGeckoEvent::COMPOSITOR_RESUME)) { i++; } EVLOG("nsAppShell: Inserting compositor event %d at position %d to maintain priority order", ae->Type(), i); mEventQueue.InsertElementAt(i, ae); } break; case AndroidGeckoEvent::DRAW: if (mQueuedDrawEvent) { // coalesce this new draw event with the one already in the queue const nsIntRect& oldRect = mQueuedDrawEvent->Rect(); const nsIntRect& newRect = ae->Rect(); int combinedArea = (oldRect.width * oldRect.height) + (newRect.width * newRect.height); nsIntRect combinedRect = oldRect.Union(newRect); // XXX We may want to consider using regions instead of rectangles. // Print an error if we're upload a lot more than we would // if we handled this as two separate events. int boundsArea = combinedRect.width * combinedRect.height; if (boundsArea > combinedArea * 8) ALOG("nsAppShell: Area of bounds greatly exceeds combined area: %d > %d", boundsArea, combinedArea); // coalesce into the new draw event rather than the queued one because // it is not always safe to move draws earlier in the queue; there may // be events between the two draws that affect scroll position or something. ae->Init(AndroidGeckoEvent::DRAW, combinedRect); EVLOG("nsAppShell: Coalescing previous DRAW event at %p into new DRAW event %p", mQueuedDrawEvent, ae); mEventQueue.RemoveElement(mQueuedDrawEvent); delete mQueuedDrawEvent; } if (!mAllowCoalescingNextDraw) { // if we're not allowing coalescing of this draw event, then // don't set mQueuedDrawEvent to point to this; that way the // next draw event that comes in won't kill this one. mAllowCoalescingNextDraw = true; mQueuedDrawEvent = nullptr; } else { mQueuedDrawEvent = ae; } allowCoalescingNextViewport = true; mEventQueue.AppendElement(ae); break; case AndroidGeckoEvent::VIEWPORT: if (mQueuedViewportEvent) { // drop the previous viewport event now that we have a new one EVLOG("nsAppShell: Dropping old viewport event at %p in favour of new VIEWPORT event %p", mQueuedViewportEvent, ae); mEventQueue.RemoveElement(mQueuedViewportEvent); delete mQueuedViewportEvent; } mQueuedViewportEvent = ae; // temporarily turn off draw-coalescing, so that we process a draw // event as soon as possible after a viewport change mAllowCoalescingNextDraw = false; allowCoalescingNextViewport = true; mEventQueue.AppendElement(ae); break; case AndroidGeckoEvent::MOTION_EVENT: if (ae->Action() == AndroidMotionEvent::ACTION_MOVE && mAllowCoalescingTouches) { int len = mEventQueue.Length(); if (len > 0) { AndroidGeckoEvent* event = mEventQueue[len - 1]; if (event->Type() == AndroidGeckoEvent::MOTION_EVENT && event->Action() == AndroidMotionEvent::ACTION_MOVE) { // consecutive motion-move events; drop the last one before adding the new one EVLOG("nsAppShell: Dropping old move event at %p in favour of new move event %p", event, ae); mEventQueue.RemoveElementAt(len - 1); delete event; } } } mEventQueue.AppendElement(ae); break; case AndroidGeckoEvent::NATIVE_POKE: allowCoalescingNextViewport = true; // fall through default: mEventQueue.AppendElement(ae); break; } // if the event wasn't on our whitelist then reset mQueuedViewportEvent // so that we don't coalesce future viewport events into the last viewport // event we added if (!allowCoalescingNextViewport) mQueuedViewportEvent = nullptr; } NotifyNativeEvent(); } void nsAppShell::OnResume() { } nsresult nsAppShell::AddObserver(const nsAString &aObserverKey, nsIObserver *aObserver) { NS_ASSERTION(aObserver != nullptr, "nsAppShell::AddObserver: aObserver is null!"); mObserversHash.Put(aObserverKey, aObserver); return NS_OK; } /** * The XPCOM event that will call the observer on the main thread. */ class ObserverCaller : public nsRunnable { public: ObserverCaller(nsIObserver *aObserver, const char *aTopic, const PRUnichar *aData) : mObserver(aObserver), mTopic(aTopic), mData(aData) { NS_ASSERTION(aObserver != nullptr, "ObserverCaller: aObserver is null!"); } NS_IMETHOD Run() { ALOG("ObserverCaller::Run: observer = %p, topic = '%s')", (nsIObserver*)mObserver, mTopic.get()); mObserver->Observe(nullptr, mTopic.get(), mData.get()); return NS_OK; } private: nsCOMPtr mObserver; nsCString mTopic; nsString mData; }; void nsAppShell::CallObserver(const nsAString &aObserverKey, const nsAString &aTopic, const nsAString &aData) { nsCOMPtr observer; mObserversHash.Get(aObserverKey, getter_AddRefs(observer)); if (!observer) { ALOG("nsAppShell::CallObserver: Observer was not found!"); return; } const NS_ConvertUTF16toUTF8 sTopic(aTopic); const nsPromiseFlatString& sData = PromiseFlatString(aData); if (NS_IsMainThread()) { // This branch will unlikely be hit, have it just in case observer->Observe(nullptr, sTopic.get(), sData.get()); } else { // Java is not running on main thread, so we have to use NS_DispatchToMainThread nsCOMPtr observerCaller = new ObserverCaller(observer, sTopic.get(), sData.get()); nsresult rv = NS_DispatchToMainThread(observerCaller); ALOG("NS_DispatchToMainThread result: %d", rv); unused << rv; } } void nsAppShell::RemoveObserver(const nsAString &aObserverKey) { mObserversHash.Remove(aObserverKey); } // NotifyObservers support. NotifyObservers only works on main thread. class NotifyObserversCaller : public nsRunnable { public: NotifyObserversCaller(nsISupports *aSupports, const char *aTopic, const PRUnichar *aData) : mSupports(aSupports), mTopic(aTopic), mData(aData) { } NS_IMETHOD Run() { nsCOMPtr os = mozilla::services::GetObserverService(); if (os) os->NotifyObservers(mSupports, mTopic.get(), mData.get()); return NS_OK; } private: nsCOMPtr mSupports; nsCString mTopic; nsString mData; }; void nsAppShell::NotifyObservers(nsISupports *aSupports, const char *aTopic, const PRUnichar *aData) { // This isn't main thread, so post this to main thread nsCOMPtr caller = new NotifyObserversCaller(aSupports, aTopic, aData); NS_DispatchToMainThread(caller); } // Used by IPC code namespace mozilla { bool ProcessNextEvent() { return nsAppShell::gAppShell->ProcessNextNativeEvent(true) ? true : false; } void NotifyEvent() { nsAppShell::gAppShell->NotifyNativeEvent(); } }