Bug 569520 part 2. Implement a mozRequestAnimationFrame/mozAnimationStartTime API. r=roc, a=joe

This commit is contained in:
Boris Zbarsky 2010-08-11 17:05:28 -04:00
parent 9c767adbfb
commit 456e9e5548
9 changed files with 205 additions and 1 deletions

View File

@ -1408,6 +1408,12 @@ public:
*/
virtual mozilla::dom::Element* GetElementById(const nsAString& aElementId) = 0;
void ScheduleBeforePaintEvent();
void BeforePaintEventFiring()
{
mHavePendingPaint = PR_FALSE;
}
protected:
~nsIDocument()
{
@ -1547,6 +1553,9 @@ protected:
// True if document has ever had script handling object.
PRPackedBool mHasHadScriptHandlingObject;
// True if we're waiting for a before-paint event.
PRPackedBool mHavePendingPaint;
// The document's script global object, the object from which the
// document can get its script context and scope. This is the
// *inner* window object.

View File

@ -3074,6 +3074,11 @@ nsDocument::doCreateShell(nsPresContext* aContext,
mExternalResourceMap.ShowViewers();
if (mHavePendingPaint) {
mPresShell->GetPresContext()->RefreshDriver()->
ScheduleBeforePaintEvent(this);
}
shell.swap(*aInstancePtrResult);
return NS_OK;
@ -3083,6 +3088,9 @@ void
nsDocument::DeleteShell()
{
mExternalResourceMap.HideViewers();
if (mHavePendingPaint) {
mPresShell->GetPresContext()->RefreshDriver()->RevokeBeforePaintEvent(this);
}
mPresShell = nsnull;
}
@ -7857,3 +7865,17 @@ nsIDocument::CreateStaticClone(nsISupports* aCloneContainer)
mCreatingStaticClone = PR_FALSE;
return clonedDoc.forget();
}
void
nsIDocument::ScheduleBeforePaintEvent()
{
if (!mHavePendingPaint) {
// We don't want to use GetShell() here, because we want to schedule the
// paint even if we're frozen. Either we'll get unfrozen and then the
// event will fire, or we'll quietly go away at some point.
mHavePendingPaint =
!mPresShell ||
mPresShell->GetPresContext()->RefreshDriver()->
ScheduleBeforePaintEvent(this);
}
}

View File

@ -3509,6 +3509,38 @@ nsGlobalWindow::GetMozPaintCount(PRUint64* aResult)
return NS_OK;
}
NS_IMETHODIMP
nsGlobalWindow::MozRequestAnimationFrame()
{
FORWARD_TO_INNER(MozRequestAnimationFrame, (), NS_ERROR_NOT_INITIALIZED);
if (!mDoc) {
return NS_OK;
}
mDoc->ScheduleBeforePaintEvent();
return NS_OK;
}
NS_IMETHODIMP
nsGlobalWindow::GetMozAnimationStartTime(PRInt64 *aTime)
{
FORWARD_TO_INNER(GetMozAnimationStartTime, (aTime), NS_ERROR_NOT_INITIALIZED);
if (mDoc) {
nsIPresShell* presShell = mDoc->GetShell();
if (presShell) {
*aTime = presShell->GetPresContext()->RefreshDriver()->
MostRecentRefreshEpochTime() / PR_USEC_PER_MSEC;
return NS_OK;
}
}
// If all else fails, just be compatible with Date.now()
*aTime = JS_Now() / PR_USEC_PER_MSEC;
return NS_OK;
}
NS_IMETHODIMP
nsGlobalWindow::SetScreenX(PRInt32 aScreenX)
{

View File

@ -220,4 +220,14 @@ interface nsIDOMWindowInternal : nsIDOMWindow2
* been painted to the screen.
*/
readonly attribute unsigned long long mozPaintCount;
/**
* Request a refresh of this browser window.
*/
void mozRequestAnimationFrame();
/**
* The current animation start time in milliseconds since the epoch.
*/
readonly attribute long long mozAnimationStartTime;
};

View File

@ -66,7 +66,6 @@
#include "mozFlushType.h"
#include "nsWeakReference.h"
#include <stdio.h> // for FILE definition
#include "nsRefreshDriver.h"
#include "nsChangeHint.h"
class nsIContent;
@ -102,6 +101,8 @@ struct nsPoint;
struct nsIntPoint;
struct nsRect;
struct nsIntRect;
class nsRefreshDriver;
class nsARefreshObserver;
typedef short SelectionType;
typedef PRUint64 nsFrameState;

View File

@ -47,6 +47,10 @@
#include "prlog.h"
#include "nsAutoPtr.h"
#include "nsCSSFrameConstructor.h"
#include "nsIDocument.h"
#include "nsGUIEvent.h"
#include "nsEventDispatcher.h"
#include "jsapi.h"
/*
* TODO:
@ -80,6 +84,14 @@ nsRefreshDriver::MostRecentRefresh() const
return mMostRecentRefresh;
}
PRInt64
nsRefreshDriver::MostRecentRefreshEpochTime() const
{
const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted();
return mMostRecentRefreshEpochTime;
}
PRBool
nsRefreshDriver::AddRefreshObserver(nsARefreshObserver *aObserver,
mozFlushType aFlushType)
@ -143,12 +155,15 @@ nsRefreshDriver::ObserverCount() const
}
sum += mStyleFlushObservers.Length();
sum += mLayoutFlushObservers.Length();
sum += mBeforePaintTargets.Length();
return sum;
}
void
nsRefreshDriver::UpdateMostRecentRefresh()
{
// Call JS_Now first, since that can have nonzero latency in some rare cases.
mMostRecentRefreshEpochTime = JS_Now();
mMostRecentRefresh = TimeStamp::Now();
}
@ -217,6 +232,21 @@ nsRefreshDriver::Notify(nsITimer * /* unused */)
}
}
if (i == 0) {
// Don't just loop while we have things in mBeforePaintTargets,
// the whole point is that event handlers should readd the
// target as needed.
nsTArray<nsIDocument*> targets;
targets.SwapElements(mBeforePaintTargets);
PRInt64 eventTime = mMostRecentRefreshEpochTime / PR_USEC_PER_MSEC;
for (PRUint32 i = 0; i < targets.Length(); ++i) {
targets[i]->BeforePaintEventFiring();
}
for (PRUint32 i = 0; i < targets.Length(); ++i) {
nsEvent ev(PR_TRUE, NS_BEFOREPAINT);
ev.time = eventTime;
nsEventDispatcher::Dispatch(targets[i], nsnull, &ev);
}
// This is the Flush_Style case.
while (!mStyleFlushObservers.IsEmpty() &&
mPresContext && mPresContext->GetPresShell()) {
@ -280,3 +310,20 @@ nsRefreshDriver::IsRefreshObserver(nsARefreshObserver *aObserver,
return array.Contains(aObserver);
}
#endif
PRBool
nsRefreshDriver::ScheduleBeforePaintEvent(nsIDocument* aDocument)
{
NS_ASSERTION(mBeforePaintTargets.IndexOf(aDocument) ==
mBeforePaintTargets.NoIndex,
"Shouldn't have a paint event posted for this document");
PRBool appended = mBeforePaintTargets.AppendElement(aDocument) != nsnull;
EnsureTimerStarted();
return appended;
}
void
nsRefreshDriver::RevokeBeforePaintEvent(nsIDocument* aDocument)
{
mBeforePaintTargets.RemoveElement(aDocument);
}

View File

@ -49,9 +49,11 @@
#include "nsCOMPtr.h"
#include "nsTObserverArray.h"
#include "nsTArray.h"
#include "nsAutoPtr.h"
class nsPresContext;
class nsIPresShell;
class nsIDocument;
/**
* An abstract base class to be implemented by callers wanting to be
@ -91,6 +93,10 @@ public:
* the main event loop have the same start time.)
*/
mozilla::TimeStamp MostRecentRefresh() const;
/**
* Same thing, but in microseconds since the epoch.
*/
PRInt64 MostRecentRefreshEpochTime() const;
/**
* Add / remove refresh observers. Returns whether the operation
@ -140,6 +146,16 @@ public:
return mLayoutFlushObservers.Contains(aShell);
}
/**
* Add a document for which we should fire a MozBeforePaint event.
*/
PRBool ScheduleBeforePaintEvent(nsIDocument* aDocument);
/**
* Remove a document for which we should fire a MozBeforePaint event.
*/
void RevokeBeforePaintEvent(nsIDocument* aDocument);
/**
* Tell the refresh driver that it is done driving refreshes and
* should stop its timer and forget about its pres context. This may
@ -188,6 +204,8 @@ private:
nsCOMPtr<nsITimer> mTimer;
mozilla::TimeStamp mMostRecentRefresh; // only valid when mTimer non-null
PRInt64 mMostRecentRefreshEpochTime; // same thing as mMostRecentRefresh,
// but in microseconds since the epoch.
nsPresContext *mPresContext; // weak; pres context passed in constructor
// and unset in Disconnect
@ -198,6 +216,8 @@ private:
ObserverArray mObservers[3];
nsAutoTArray<nsIPresShell*, 16> mStyleFlushObservers;
nsAutoTArray<nsIPresShell*, 16> mLayoutFlushObservers;
// nsTArray on purpose, because we want to be able to swap.
nsTArray<nsIDocument*> mBeforePaintTargets;
};
#endif /* !defined(nsRefreshDriver_h_) */

View File

@ -196,6 +196,7 @@ _TEST_FILES += \
bug467672-5.html \
bug467672-5-ref.html \
test_bug499538-1.html \
test_bug569520.html \
test_bug570378-arabic-1a.html \
test_bug570378-arabic-1b.html \
test_bug570378-arabic-1c.html \

View File

@ -0,0 +1,62 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=569520
-->
<head>
<title>Test for Bug 569520</title>
<script type="application/javascript" src="/MochiKit/packed.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=569520">Mozilla Bug 569520</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 569520 **/
SimpleTest.waitForExplicitFinish();
var startNow = Date.now();
var start = window.mozAnimationStartTime;
var firstListenerTime;
var secondListenerTime;
function secondListener(ev) {
secondListenerTime = ev.timeStamp;
window.removeEventListener("MozBeforePaint", secondListener, false);
// They really shouldn't be more than 100ms apart, but we can get weird
// effects on slow machines. 5 minutes is our test timeout, though.
ok(Math.abs(startNow - start) <= 5 * 60 * 1000, "Bogus animation start time");
ok(firstListenerTime >= start, "First listener should fire after start");
ok(secondListenerTime > firstListenerTime,
"Second listener should fire after first listener");
SimpleTest.finish();
}
function firstListener(ev) {
firstListenerTime = ev.timeStamp;
window.removeEventListener("MozBeforePaint", firstListener, false);
window.addEventListener("MozBeforePaint", secondListener, false);
mozRequestAnimationFrame();
}
addLoadEvent(function() {
setTimeout(function() {
window.addEventListener("MozBeforePaint", firstListener, false);
mozRequestAnimationFrame();
}, 100);
});
</script>
</pre>
</body>
</html>