PRBool nsAppShell::ProcessNextNativeEvent(PRBool mayWait) { EVLOG("nsAppShell::ProcessNextNativeEvent %d", mayWait); PR_Lock(mCondLock); nsAutoPtr<AndroidGeckoEvent> curEvent; AndroidGeckoEvent *nextEvent; curEvent = GetNextEvent(); if (!curEvent && mayWait) { // hmm, should we really hardcode this 10s? #if defined(ANDROID_DEBUG_EVENTS) PRTime t0, t1; EVLOG("nsAppShell: waiting on mQueueCond"); t0 = PR_Now(); PR_WaitCondVar(mQueueCond, PR_MillisecondsToInterval(10000)); t1 = PR_Now(); EVLOG("nsAppShell: wait done, waited %d ms", (int)(t1-t0)/1000); #else PR_WaitCondVar(mQueueCond, PR_INTERVAL_NO_TIMEOUT); #endif curEvent = GetNextEvent(); } PR_Unlock(mCondLock); if (!curEvent) return false; // Combine subsequent events of the same type nextEvent = PeekNextEvent(); while (nextEvent) { int curType = curEvent->Type(); int nextType = nextEvent->Type(); while (nextType == AndroidGeckoEvent::DRAW && mNumDraws > 1) { // skip this draw, since there's a later one already in the queue.. this will let us // deal with sequences that look like: // MOVE DRAW MOVE DRAW MOVE DRAW // and end up with just // MOVE DRAW // when we process all the events. RemoveNextEvent(); delete nextEvent; #if defined(ANDROID_DEBUG_EVENTS) ALOG("# Removing DRAW event (%d outstanding)", mNumDraws); #endif nextEvent = PeekNextEvent(); nextType = nextEvent->Type(); } // If the next type of event isn't the same as the current type, // we don't coalesce. if (nextType != curType) break; // Can only coalesce motion move events, for motion events if (curType != AndroidGeckoEvent::MOTION_EVENT) break; if (!(curEvent->Action() == AndroidMotionEvent::ACTION_MOVE && nextEvent->Action() == AndroidMotionEvent::ACTION_MOVE)) break; #if defined(ANDROID_DEBUG_EVENTS) ALOG("# Removing % 2d event", curType); #endif RemoveNextEvent(); curEvent = nextEvent; nextEvent = PeekNextEvent(); } EVLOG("nsAppShell: event %p %d [ndraws %d]", (void*)curEvent.get(), curEvent->Type(), mNumDraws); nsWindow *target = (nsWindow*) curEvent->NativeWindow(); switch (curEvent->Type()) { case AndroidGeckoEvent::NATIVE_POKE: NativeEventCallback(); break; case AndroidGeckoEvent::SENSOR_EVENT: gAccel->AccelerationChanged(-curEvent->X(), curEvent->Y(), curEvent->Z()); break; case AndroidGeckoEvent::LOCATION_EVENT: if (!gLocationCallback) break; if (curEvent->GeoPosition()) gLocationCallback->Update(curEvent->GeoPosition()); else NS_WARNING("Received location event without geoposition!"); break; case AndroidGeckoEvent::ACTIVITY_STOPPING: { nsCOMPtr<nsIObserverService> obsServ = mozilla::services::GetObserverService(); NS_NAMED_LITERAL_STRING(context, "shutdown-persist"); obsServ->NotifyObservers(nsnull, "quit-application-granted", nsnull); obsServ->NotifyObservers(nsnull, "quit-application-forced", nsnull); obsServ->NotifyObservers(nsnull, "profile-change-net-teardown", context.get()); obsServ->NotifyObservers(nsnull, "profile-change-teardown", context.get()); obsServ->NotifyObservers(nsnull, "profile-before-change", context.get()); nsCOMPtr<nsIAppStartup> appSvc = do_GetService("@mozilla.org/toolkit/app-startup;1"); if (appSvc) appSvc->Quit(nsIAppStartup::eForceQuit); break; } case AndroidGeckoEvent::ACTIVITY_PAUSING: { // 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 nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); if (prefs) prefs->SavePrefFile(nsnull); // The OS is sending us to the background, block this thread until // onResume is called to signal that we're back in the foreground PR_WaitCondVar(mPaused, PR_INTERVAL_NO_TIMEOUT); break; } case AndroidGeckoEvent::LOAD_URI: { nsCOMPtr<nsICommandLineRunner> cmdline (do_CreateInstance("@mozilla.org/toolkit/command-line;1")); if (!cmdline) break; char *uri = ToNewUTF8String(curEvent->Characters()); if (!uri) break; char* argv[3] = { "dummyappname", "-remote", uri }; nsresult rv = cmdline->Init(3, argv, nsnull, nsICommandLine::STATE_REMOTE_AUTO); if (NS_SUCCEEDED(rv)) cmdline->Run(); nsMemory::Free(uri); break; } default: if (target) target->OnAndroidEvent(curEvent); else nsWindow::OnGlobalAndroidEvent(curEvent); } EVLOG("nsAppShell: -- done event %p %d", (void*)curEvent.get(), curEvent->Type()); return true; }
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(); }
bool nsAppShell::ProcessNextNativeEvent(bool mayWait) { EVLOG("nsAppShell::ProcessNextNativeEvent %d", mayWait); nsAutoPtr<AndroidGeckoEvent> curEvent; AndroidGeckoEvent *nextEvent; { MutexAutoLock lock(mCondLock); curEvent = PopNextEvent(); if (!curEvent && mayWait) { // 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; // Combine subsequent events of the same type nextEvent = PeekNextEvent(); while (nextEvent) { int curType = curEvent->Type(); int nextType = nextEvent->Type(); while (nextType == AndroidGeckoEvent::VIEWPORT && mNumViewports > 1) { // Skip this viewport change, as there's another one later and // processing this one will only cause more unnecessary work PopNextEvent(); delete nextEvent; nextEvent = PeekNextEvent(); nextType = nextEvent->Type(); } while (nextType == AndroidGeckoEvent::DRAW && mLastDrawEvent && mNumDraws > 1) { // skip this draw, since there's a later one already in the queue.. this will let us // deal with sequences that look like: // MOVE DRAW MOVE DRAW MOVE DRAW // and end up with just // MOVE DRAW // when we process all the events. // Combine the next draw event's rect with the last one in the queue const nsIntRect& nextRect = nextEvent->Rect(); const nsIntRect& lastRect = mLastDrawEvent->Rect(); int combinedArea = (lastRect.width * lastRect.height) + (nextRect.width * nextRect.height); nsIntRect combinedRect = lastRect.Union(nextRect); mLastDrawEvent->Init(AndroidGeckoEvent::DRAW, combinedRect); // 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::ProcessNextNativeEvent: " "Area of bounds greatly exceeds combined area: %d > %d", boundsArea, combinedArea); // Remove the next draw event PopNextEvent(); delete nextEvent; #if defined(DEBUG_ANDROID_EVENTS) ALOG("# Removing DRAW event (%d outstanding)", mNumDraws); #endif nextEvent = PeekNextEvent(); nextType = nextEvent->Type(); } // If the next type of event isn't the same as the current type, // we don't coalesce. if (nextType != curType) break; // Can only coalesce motion move events, for motion events if (curType != AndroidGeckoEvent::MOTION_EVENT) break; if (!(curEvent->Action() == AndroidMotionEvent::ACTION_MOVE && nextEvent->Action() == AndroidMotionEvent::ACTION_MOVE)) break; #if defined(DEBUG_ANDROID_EVENTS) ALOG("# Removing % 2d event", curType); #endif curEvent = PopNextEvent(); nextEvent = PeekNextEvent(); } EVLOG("nsAppShell: event %p %d [ndraws %d]", (void*)curEvent.get(), curEvent->Type(), mNumDraws); switch (curEvent->Type()) { case AndroidGeckoEvent::NATIVE_POKE: NativeEventCallback(); break; case AndroidGeckoEvent::ACCELERATION_EVENT: gDeviceMotionSystem->DeviceMotionChanged(nsIDeviceMotionData::TYPE_ACCELERATION, -curEvent->X(), curEvent->Y(), curEvent->Z()); break; case AndroidGeckoEvent::ORIENTATION_EVENT: gDeviceMotionSystem->DeviceMotionChanged(nsIDeviceMotionData::TYPE_ORIENTATION, -curEvent->Alpha(), curEvent->Beta(), curEvent->Gamma()); break; case AndroidGeckoEvent::LOCATION_EVENT: { if (!gLocationCallback) break; nsGeoPosition* p = curEvent->GeoPosition(); nsGeoPositionAddress* a = curEvent->GeoAddress(); if (p) { p->SetAddress(a); gLocationCallback->Update(curEvent->GeoPosition()); } else NS_WARNING("Received location event without geoposition!"); break; } case AndroidGeckoEvent::ACTIVITY_STOPPING: { nsCOMPtr<nsIObserverService> obsServ = mozilla::services::GetObserverService(); NS_NAMED_LITERAL_STRING(minimize, "heap-minimize"); obsServ->NotifyObservers(nsnull, "memory-pressure", minimize.get()); obsServ->NotifyObservers(nsnull, "application-background", nsnull); break; } case AndroidGeckoEvent::ACTIVITY_SHUTDOWN: { nsCOMPtr<nsIObserverService> obsServ = mozilla::services::GetObserverService(); NS_NAMED_LITERAL_STRING(context, "shutdown-persist"); obsServ->NotifyObservers(nsnull, "quit-application-granted", nsnull); obsServ->NotifyObservers(nsnull, "quit-application-forced", nsnull); obsServ->NotifyObservers(nsnull, "profile-change-net-teardown", context.get()); obsServ->NotifyObservers(nsnull, "profile-change-teardown", context.get()); obsServ->NotifyObservers(nsnull, "profile-before-change", context.get()); nsCOMPtr<nsIAppStartup> appSvc = do_GetService("@mozilla.org/toolkit/app-startup;1"); if (appSvc) appSvc->Quit(nsIAppStartup::eForceQuit); break; } case AndroidGeckoEvent::ACTIVITY_PAUSING: { // 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<nsIPrefBranch> prefBranch; prefs->GetBranch("browser.sessionstore.", getter_AddRefs(prefBranch)); if (prefBranch) prefBranch->SetIntPref("recent_crashes", 0); prefs->SavePrefFile(nsnull); } break; } case AndroidGeckoEvent::ACTIVITY_START: { nsCOMPtr<nsIObserverService> obsServ = mozilla::services::GetObserverService(); obsServ->NotifyObservers(nsnull, "application-foreground", nsnull); break; } case AndroidGeckoEvent::VIEWPORT: case AndroidGeckoEvent::BROADCAST: { if (curEvent->Characters().Length() == 0) break; nsCOMPtr<nsIObserverService> obsServ = mozilla::services::GetObserverService(); const NS_ConvertUTF16toUTF8 topic(curEvent->Characters()); const nsPromiseFlatString& data = PromiseFlatString(curEvent->CharactersExtra()); obsServ->NotifyObservers(nsnull, topic.get(), data.get()); break; } case AndroidGeckoEvent::LOAD_URI: { nsCOMPtr<nsICommandLineRunner> 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; const char *argv[3] = { "dummyappname", "-remote", uri }; nsresult rv = cmdline->Init(3, const_cast<char **>(argv), nsnull, nsICommandLine::STATE_REMOTE_AUTO); if (NS_SUCCEEDED(rv)) cmdline->Run(); nsMemory::Free(uri); 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; } default: nsWindow::OnGlobalAndroidEvent(curEvent); } EVLOG("nsAppShell: -- done event %p %d", (void*)curEvent.get(), curEvent->Type()); return true; }