Skip to content

Instantly share code, notes, and snippets.

@djg
Created August 12, 2019 02:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save djg/b57461f877732bc4c156e8eaedf01dea to your computer and use it in GitHub Desktop.
Save djg/b57461f877732bc4c156e8eaedf01dea to your computer and use it in GitHub Desktop.
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