Created
August 12, 2019 02:11
-
-
Save djg/b57461f877732bc4c156e8eaedf01dea to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime) { | |
MOZ_ASSERT(!nsContentUtils::GetCurrentJSContext(), | |
"Shouldn't have a JSContext on the stack"); | |
if (nsNPAPIPluginInstance::InPluginCallUnsafeForReentry()) { | |
NS_ERROR("Refresh driver should not run during plugin call!"); | |
// Try to survive this by just ignoring the refresh tick. | |
return; | |
} | |
#if defined(MOZ_WIDGET_ANDROID) | |
gfx::VRManager* vm = gfx::VRManager::Get(); | |
if (vm->IsPresenting()) { | |
RunFrameRequestCallbacks(aNowTime); | |
return; | |
} | |
#endif // defined(MOZ_WIDGET_ANDROID) | |
AUTO_PROFILER_LABEL("nsRefreshDriver::Tick", LAYOUT); | |
// We're either frozen or we were disconnected (likely in the middle | |
// of a tick iteration). Just do nothing here, since our | |
// prescontext went away. | |
if (IsFrozen() || !mPresContext) { | |
return; | |
} | |
// We can have a race condition where the vsync timestamp | |
// is before the most recent refresh due to a forced refresh. | |
// The underlying assumption is that the refresh driver tick can only | |
// go forward in time, not backwards. To prevent the refresh | |
// driver from going back in time, just skip this tick and | |
// wait until the next tick. | |
if ((aNowTime <= mMostRecentRefresh) && !mTestControllingRefreshes) { | |
return; | |
} | |
if (IsWaitingForPaint(aNowTime)) { | |
// We're currently suspended waiting for earlier Tick's to | |
// be completed (on the Compositor). Mark that we missed the paint | |
// and keep waiting. | |
PROFILER_ADD_MARKER("nsRefreshDriver::Tick waiting for paint", LAYOUT); | |
return; | |
} | |
TimeStamp previousRefresh = mMostRecentRefresh; | |
mMostRecentRefresh = aNowTime; | |
if (mRootRefresh) { | |
mRootRefresh->RemoveRefreshObserver(this, FlushType::Style); | |
mRootRefresh = nullptr; | |
} | |
mSkippedPaints = false; | |
mWarningThreshold = 1; | |
RefPtr<PresShell> presShell = mPresContext->GetPresShell(); | |
if (!presShell || | |
(!HasObservers() && !HasImageRequests() && | |
mVisualViewportResizeEvents.IsEmpty() && mScrollEvents.IsEmpty() && | |
mVisualViewportScrollEvents.IsEmpty())) { | |
// Things are being destroyed, or we no longer have any observers. | |
// We don't want to stop the timer when observers are initially | |
// removed, because sometimes observers can be added and removed | |
// often depending on what other things are going on and in that | |
// situation we don't want to thrash our timer. So instead we | |
// wait until we get a Notify() call when we have no observers | |
// before stopping the timer. | |
// On top level content pages keep the timer running initially so that we | |
// paint the page soon enough. | |
if (presShell && !mThrottled && !mTestControllingRefreshes && | |
XRE_IsContentProcess() && | |
mPresContext->Document()->IsTopLevelContentDocument() && | |
!gfxPlatform::IsInLayoutAsapMode() && | |
!mPresContext->HadContentfulPaint() && | |
mPresContext->Document()->GetReadyStateEnum() < | |
Document::READYSTATE_COMPLETE) { | |
if (mInitialTimerRunningLimit.IsNull()) { | |
mInitialTimerRunningLimit = | |
TimeStamp::Now() + TimeDuration::FromSeconds(4.0f); | |
// Don't let the timer to run forever, so limit to 4s for now. | |
} else if (mInitialTimerRunningLimit < TimeStamp::Now()) { | |
StopTimer(); | |
} | |
} else { | |
StopTimer(); | |
} | |
return; | |
} | |
mResizeSuppressed = false; | |
AutoRestore<bool> restoreInRefresh(mInRefresh); | |
mInRefresh = true; | |
AutoRestore<TimeStamp> restoreTickStart(mTickStart); | |
mTickStart = TimeStamp::Now(); | |
mTickVsyncId = aId; | |
mTickVsyncTime = aNowTime; | |
gfxPlatform::GetPlatform()->SchedulePaintIfDeviceReset(); | |
// We want to process any pending APZ metrics ahead of their positions | |
// in the queue. This will prevent us from spending precious time | |
// painting a stale displayport. | |
if (StaticPrefs::apz_peek_messages_enabled()) { | |
nsLayoutUtils::UpdateDisplayPortMarginsFromPendingMessages(); | |
} | |
AutoTArray<nsCOMPtr<nsIRunnable>, 16> earlyRunners; | |
earlyRunners.SwapElements(mEarlyRunners); | |
for (auto& runner : earlyRunners) { | |
runner->Run(); | |
} | |
// Resize events should be fired before layout flushes or | |
// calling animation frame callbacks. | |
AutoTArray<PresShell*, 16> observers; | |
observers.AppendElements(mResizeEventFlushObservers); | |
for (PresShell* shell : Reversed(observers)) { | |
if (!mPresContext || !mPresContext->GetPresShell()) { | |
StopTimer(); | |
return; | |
} | |
// Make sure to not process observers which might have been removed | |
// during previous iterations. | |
if (!mResizeEventFlushObservers.RemoveElement(shell)) { | |
continue; | |
} | |
shell->FireResizeEvent(); | |
} | |
DispatchVisualViewportResizeEvents(); | |
double phaseMetrics[MOZ_ARRAY_LENGTH(mObservers)] = { | |
0.0, | |
}; | |
/* | |
* The timer holds a reference to |this| while calling |Notify|. | |
* However, implementations of |WillRefresh| are permitted to destroy | |
* the pres context, which will cause our |mPresContext| to become | |
* null. If this happens, we must stop notifying observers. | |
*/ | |
for (uint32_t i = 0; i < ArrayLength(mObservers); ++i) { | |
AutoRecordPhase phaseRecord(&phaseMetrics[i]); | |
ObserverArray::EndLimitedIterator etor(mObservers[i]); | |
while (etor.HasMore()) { | |
RefPtr<nsARefreshObserver> obs = etor.GetNext(); | |
obs->WillRefresh(aNowTime); | |
if (!mPresContext || !mPresContext->GetPresShell()) { | |
StopTimer(); | |
return; | |
} | |
} | |
// Any animation timelines updated above may cause animations to queue | |
// Promise resolution microtasks. We shouldn't run these, however, until we | |
// have fully updated the animation state. | |
// | |
// As per the "update animations and send events" procedure[1], we should | |
// remove replaced animations and then run these microtasks before | |
// dispatching the corresponding animation events. | |
// | |
// [1] | |
// https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events | |
if (i == 1) { | |
nsAutoMicroTask mt; | |
ReduceAnimations(mPresContext->Document(), nullptr); | |
} | |
// Check if running the microtask checkpoint caused the pres context to | |
// be destroyed. | |
if (i == 1 && (!mPresContext || !mPresContext->GetPresShell())) { | |
StopTimer(); | |
return; | |
} | |
if (i == 1) { | |
// This is the FlushType::Style case. | |
DispatchScrollEvents(); | |
DispatchVisualViewportScrollEvents(); | |
DispatchAnimationEvents(); | |
RunFullscreenSteps(); | |
RunFrameRequestCallbacks(aNowTime); | |
if (mPresContext && mPresContext->GetPresShell()) { | |
AutoTArray<PresShell*, 16> observers; | |
observers.AppendElements(mStyleFlushObservers); | |
for (uint32_t j = observers.Length(); | |
j && mPresContext && mPresContext->GetPresShell(); --j) { | |
// Make sure to not process observers which might have been removed | |
// during previous iterations. | |
PresShell* rawPresShell = observers[j - 1]; | |
if (!mStyleFlushObservers.RemoveElement(rawPresShell)) { | |
continue; | |
} | |
RefPtr<PresShell> presShell = rawPresShell; | |
presShell->mObservingStyleFlushes = false; | |
presShell->FlushPendingNotifications( | |
ChangesToFlush(FlushType::Style, false)); | |
// Inform the FontFaceSet that we ticked, so that it can resolve its | |
// ready promise if it needs to (though it might still be waiting on | |
// a layout flush). | |
presShell->NotifyFontFaceSetOnRefresh(); | |
mNeedToRecomputeVisibility = true; | |
// Record the telemetry for the # of flushes that occured between | |
// ticks. | |
printf_stderr("*** i = 1\n"); | |
presShell->PingFlushPerTickTelemetry(FlushType::Style); | |
} | |
} | |
} else if (i == 2) { | |
// This is the FlushType::Layout case. | |
AutoTArray<PresShell*, 16> observers; | |
observers.AppendElements(mLayoutFlushObservers); | |
for (uint32_t j = observers.Length(); | |
j && mPresContext && mPresContext->GetPresShell(); --j) { | |
// Make sure to not process observers which might have been removed | |
// during previous iterations. | |
PresShell* rawPresShell = observers[j - 1]; | |
if (!mLayoutFlushObservers.RemoveElement(rawPresShell)) { | |
continue; | |
} | |
RefPtr<PresShell> presShell = rawPresShell; | |
presShell->mObservingLayoutFlushes = false; | |
presShell->mWasLastReflowInterrupted = false; | |
FlushType flushType = HasPendingAnimations(presShell) | |
? FlushType::Layout | |
: FlushType::InterruptibleLayout; | |
presShell->FlushPendingNotifications(ChangesToFlush(flushType, false)); | |
// Inform the FontFaceSet that we ticked, so that it can resolve its | |
// ready promise if it needs to. | |
presShell->NotifyFontFaceSetOnRefresh(); | |
mNeedToRecomputeVisibility = true; | |
// Record the telemetry for the # of flushes that occured between ticks. | |
printf_stderr("*** i = 2\n"); | |
presShell->PingFlushPerTickTelemetry(FlushType::Layout); | |
} | |
} | |
// The pres context may be destroyed during we do the flushing. | |
if (!mPresContext || !mPresContext->GetPresShell()) { | |
StopTimer(); | |
return; | |
} | |
} | |
// Recompute approximate frame visibility if it's necessary and enough time | |
// has passed since the last time we did it. | |
if (mNeedToRecomputeVisibility && !mThrottled && | |
aNowTime >= mNextRecomputeVisibilityTick && | |
!presShell->IsPaintingSuppressed()) { | |
mNextRecomputeVisibilityTick = aNowTime + mMinRecomputeVisibilityInterval; | |
mNeedToRecomputeVisibility = false; | |
presShell->ScheduleApproximateFrameVisibilityUpdateNow(); | |
} | |
#ifdef MOZ_XUL | |
// Update any popups that may need to be moved or hidden due to their | |
// anchor changing. | |
if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) { | |
pm->UpdatePopupPositions(this); | |
} | |
#endif | |
UpdateIntersectionObservations(); | |
/* | |
* Perform notification to imgIRequests subscribed to listen | |
* for refresh events. | |
*/ | |
for (auto iter = mStartTable.Iter(); !iter.Done(); iter.Next()) { | |
const uint32_t& delay = iter.Key(); | |
ImageStartData* data = iter.UserData(); | |
if (data->mStartTime) { | |
TimeStamp& start = *data->mStartTime; | |
TimeDuration prev = previousRefresh - start; | |
TimeDuration curr = aNowTime - start; | |
uint32_t prevMultiple = uint32_t(prev.ToMilliseconds()) / delay; | |
// We want to trigger images' refresh if we've just crossed over a | |
// multiple of the first image's start time. If so, set the animation | |
// start time to the nearest multiple of the delay and move all the | |
// images in this table to the main requests table. | |
if (prevMultiple != uint32_t(curr.ToMilliseconds()) / delay) { | |
mozilla::TimeStamp desired = | |
start + TimeDuration::FromMilliseconds(prevMultiple * delay); | |
BeginRefreshingImages(data->mEntries, desired); | |
} | |
} else { | |
// This is the very first time we've drawn images with this time delay. | |
// Set the animation start time to "now" and move all the images in this | |
// table to the main requests table. | |
mozilla::TimeStamp desired = aNowTime; | |
BeginRefreshingImages(data->mEntries, desired); | |
data->mStartTime.emplace(aNowTime); | |
} | |
} | |
if (mRequests.Count()) { | |
// RequestRefresh may run scripts, so it's not safe to directly call it | |
// while using a hashtable enumerator to enumerate mRequests in case | |
// script modifies the hashtable. Instead, we build a (local) array of | |
// images to refresh, and then we refresh each image in that array. | |
nsCOMArray<imgIContainer> imagesToRefresh(mRequests.Count()); | |
for (auto iter = mRequests.Iter(); !iter.Done(); iter.Next()) { | |
nsISupportsHashKey* entry = iter.Get(); | |
auto req = static_cast<imgIRequest*>(entry->GetKey()); | |
MOZ_ASSERT(req, "Unable to retrieve the image request"); | |
nsCOMPtr<imgIContainer> image; | |
if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) { | |
imagesToRefresh.AppendElement(image.forget()); | |
} | |
} | |
for (uint32_t i = 0; i < imagesToRefresh.Length(); i++) { | |
imagesToRefresh[i]->RequestRefresh(aNowTime); | |
} | |
} | |
double phasePaint = 0.0; | |
bool dispatchRunnablesAfterTick = false; | |
if (mViewManagerFlushIsPending) { | |
AutoRecordPhase paintRecord(&phasePaint); | |
RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); | |
nsTArray<nsDocShell*> profilingDocShells; | |
GetProfileTimelineSubDocShells(GetDocShell(mPresContext), | |
profilingDocShells); | |
for (nsDocShell* docShell : profilingDocShells) { | |
// For the sake of the profile timeline's simplicity, this is flagged as | |
// paint even if it includes creating display lists | |
MOZ_ASSERT(timelines); | |
MOZ_ASSERT(timelines->HasConsumer(docShell)); | |
timelines->AddMarkerForDocShell(docShell, "Paint", | |
MarkerTracingType::START); | |
} | |
#ifdef MOZ_DUMP_PAINTING | |
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { | |
printf_stderr("Starting ProcessPendingUpdates\n"); | |
} | |
#endif | |
mViewManagerFlushIsPending = false; | |
RefPtr<nsViewManager> vm = mPresContext->GetPresShell()->GetViewManager(); | |
{ | |
PaintTelemetry::AutoRecordPaint record; | |
vm->ProcessPendingUpdates(); | |
} | |
#ifdef MOZ_DUMP_PAINTING | |
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { | |
printf_stderr("Ending ProcessPendingUpdates\n"); | |
} | |
#endif | |
for (nsDocShell* docShell : profilingDocShells) { | |
MOZ_ASSERT(timelines); | |
MOZ_ASSERT(timelines->HasConsumer(docShell)); | |
timelines->AddMarkerForDocShell(docShell, "Paint", | |
MarkerTracingType::END); | |
} | |
dispatchRunnablesAfterTick = true; | |
mHasScheduleFlush = false; | |
} | |
double totalMs = (TimeStamp::Now() - mTickStart).ToMilliseconds(); | |
#ifndef ANDROID /* bug 1142079 */ | |
mozilla::Telemetry::Accumulate(mozilla::Telemetry::REFRESH_DRIVER_TICK, | |
static_cast<uint32_t>(totalMs)); | |
#endif | |
// Bug 1568107: If the totalMs is greater than 1/60th second (ie. 1000/60 ms) | |
// then record, via telemetry, the percentage of time spent in each | |
// sub-system. | |
if (totalMs > 1000.0 / 60.0) { | |
auto record = [=](const nsCString& aKey, double aDurationMs) -> void { | |
MOZ_ASSERT(aDurationMs <= totalMs); | |
auto phasePercent = static_cast<uint32_t>(aDurationMs * 100.0 / totalMs); | |
Telemetry::Accumulate(Telemetry::REFRESH_DRIVER_TICK_PHASE_WEIGHT, aKey, | |
phasePercent); | |
}; | |
record(NS_LITERAL_CSTRING("Event"), phaseMetrics[0]); | |
record(NS_LITERAL_CSTRING("Style"), phaseMetrics[1]); | |
record(NS_LITERAL_CSTRING("Reflow"), phaseMetrics[2]); | |
record(NS_LITERAL_CSTRING("Display"), phaseMetrics[3]); | |
record(NS_LITERAL_CSTRING("Paint"), phasePaint); | |
// Explicitly record the time unaccounted for. | |
double other = totalMs - | |
std::accumulate(phaseMetrics, ArrayEnd(phaseMetrics), 0.0) - | |
phasePaint; | |
record(NS_LITERAL_CSTRING("Other"), other); | |
} | |
if (mNotifyDOMContentFlushed) { | |
mNotifyDOMContentFlushed = false; | |
mPresContext->NotifyDOMContentFlushed(); | |
} | |
nsTObserverArray<nsAPostRefreshObserver*>::ForwardIterator iter( | |
mPostRefreshObservers); | |
while (iter.HasMore()) { | |
nsAPostRefreshObserver* observer = iter.GetNext(); | |
observer->DidRefresh(); | |
} | |
NS_ASSERTION(mInRefresh, "Still in refresh"); | |
if (mPresContext->IsRoot() && XRE_IsContentProcess() && | |
StaticPrefs::gfx_content_always_paint()) { | |
ScheduleViewManagerFlush(); | |
} | |
if (dispatchRunnablesAfterTick && sPendingIdleRunnables) { | |
AutoTArray<RunnableWithDelay, 8>* runnables = sPendingIdleRunnables; | |
sPendingIdleRunnables = nullptr; | |
for (RunnableWithDelay& runnableWithDelay : *runnables) { | |
NS_DispatchToCurrentThreadQueue(runnableWithDelay.mRunnable.forget(), | |
runnableWithDelay.mDelay, | |
EventQueuePriority::Idle); | |
} | |
delete runnables; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment