Commit b9d011c6 authored by fs@opera.com's avatar fs@opera.com

Drive SVG Animations via requestAnimationFrame

Start using FrameView::scheduleAnimation instead of a Timer for driving
the animations in a SMILTimeContainer.
The timer however remains to use when the next animation event is further
into the future than within the (~) next frame interval. Hence
SMILTimeContainer::m_timer is renamed to m_wakeupTimer.

The animation time from the compositor is plumbed through the layers, but
is not yet used as the "global" clock. That will have to wait for a later
CL.

For the SVG-in-<img> (and similar) case(s),
SVGImageChromeClient::scheduleAnimation is modified to use a fixed frame
interval.
In SVGImage, the initialization of m_page is moved later, to disable any
methods that depend on accessing the SVG root element (prompted by the
need to query if the image has any animations) until the actual document
is loaded/being loaded.

BUG=231576

Review URL: https://codereview.chromium.org/179293004

git-svn-id: svn://svn.chromium.org/blink/trunk@168631 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 4312e7df
......@@ -53,6 +53,7 @@ public:
void updateTime(double time);
double currentTime();
void unfreeze() { m_frozen = false; }
bool isFrozen() const { return m_frozen; }
void resetTimeForTesting() { m_time = 0; m_frozen = true; }
private:
......
......@@ -102,6 +102,7 @@ private:
void wake();
friend class SMILTimeContainer;
static const double s_minimumDelay;
OwnPtr<PlatformTiming> m_timing;
......
......@@ -12,6 +12,7 @@
#include "core/page/Chrome.h"
#include "core/page/ChromeClient.h"
#include "core/page/Page.h"
#include "core/svg/SVGDocumentExtensions.h"
namespace WebCore {
......@@ -30,6 +31,7 @@ void PageAnimator::serviceScriptedAnimations(double monotonicAnimationStartTime)
for (RefPtr<LocalFrame> frame = m_page->mainFrame(); frame; frame = frame->tree().traverseNext()) {
frame->view()->serviceScrollAnimations();
DocumentAnimations::updateAnimationTimingForAnimationFrame(*frame->document(), monotonicAnimationStartTime);
SVGDocumentExtensions::serviceOnAnimationFrame(*frame->document(), monotonicAnimationStartTime);
}
Vector<RefPtr<Document> > documents;
......
......@@ -83,6 +83,22 @@ RenderSVGResourceContainer* SVGDocumentExtensions::resourceById(const AtomicStri
return m_resources.get(id);
}
void SVGDocumentExtensions::serviceOnAnimationFrame(Document& document, double monotonicAnimationStartTime)
{
if (!document.svgExtensions())
return;
document.accessSVGExtensions().serviceAnimations(monotonicAnimationStartTime);
}
void SVGDocumentExtensions::serviceAnimations(double monotonicAnimationStartTime)
{
Vector<RefPtr<SVGSVGElement> > timeContainers;
timeContainers.appendRange(m_timeContainers.begin(), m_timeContainers.end());
Vector<RefPtr<SVGSVGElement> >::iterator end = timeContainers.end();
for (Vector<RefPtr<SVGSVGElement> >::iterator itr = timeContainers.begin(); itr != end; ++itr)
(*itr)->timeContainer()->serviceAnimations(monotonicAnimationStartTime);
}
void SVGDocumentExtensions::startAnimations()
{
// FIXME: Eventually every "Time Container" will need a way to latch on to some global timer
......
......@@ -54,6 +54,8 @@ public:
void removeResource(const AtomicString& id);
RenderSVGResourceContainer* resourceById(const AtomicString& id) const;
static void serviceOnAnimationFrame(Document&, double monotonicAnimationStartTime);
void startAnimations();
void pauseAnimations();
void unpauseAnimations();
......@@ -109,6 +111,8 @@ public:
void removeElementFromPendingResources(Element*);
PassOwnPtr<SVGPendingElements> removePendingResource(const AtomicString& id);
void serviceAnimations(double monotonicAnimationStartTime);
// The following two functions are used for scheduling a pending resource to be removed.
void markPendingResourcesForRemoval(const AtomicString&);
Element* removeElementFromPendingResourcesForRemoval(const AtomicString&);
......
......@@ -26,17 +26,17 @@
#include "config.h"
#include "core/svg/animation/SMILTimeContainer.h"
#include "core/animation/AnimationClock.h"
#include "core/animation/DocumentTimeline.h"
#include "core/dom/ElementTraversal.h"
#include "core/frame/FrameView.h"
#include "core/svg/SVGSVGElement.h"
#include "core/svg/animation/SVGSMILElement.h"
#include "wtf/CurrentTime.h"
using namespace std;
namespace WebCore {
static const double animationFrameDelay = 0.025;
SMILTimeContainer::SMILTimeContainer(SVGSVGElement* owner)
: m_beginTime(0)
, m_pauseTime(0)
......@@ -44,7 +44,9 @@ SMILTimeContainer::SMILTimeContainer(SVGSVGElement* owner)
, m_accumulatedActiveTime(0)
, m_presetStartTime(0)
, m_documentOrderIndexesDirty(false)
, m_timer(this, &SMILTimeContainer::timerFired)
, m_framePending(false)
, m_animationClock(AnimationClock::create())
, m_wakeupTimer(this, &SMILTimeContainer::wakeupTimerFired)
, m_ownerSVGElement(owner)
#ifndef NDEBUG
, m_preventScheduledAnimationsChanges(false)
......@@ -55,7 +57,7 @@ SMILTimeContainer::SMILTimeContainer(SVGSVGElement* owner)
SMILTimeContainer::~SMILTimeContainer()
{
cancelAnimationFrame();
ASSERT(!m_timer.isActive());
ASSERT(!m_wakeupTimer.isActive());
#ifndef NDEBUG
ASSERT(!m_preventScheduledAnimationsChanges);
#endif
......@@ -99,6 +101,11 @@ void SMILTimeContainer::unschedule(SVGSMILElement* animation, SVGElement* target
scheduled->remove(idx);
}
bool SMILTimeContainer::hasAnimations() const
{
return !m_scheduledAnimations.isEmpty();
}
void SMILTimeContainer::notifyIntervalsChanged()
{
// Schedule updateAnimations() to be called asynchronously so multiple intervals
......@@ -114,7 +121,7 @@ SMILTime SMILTimeContainer::elapsed() const
if (isPaused())
return m_accumulatedActiveTime;
return currentTime() + m_accumulatedActiveTime - lastResumeTime();
return m_animationClock->currentTime() + m_accumulatedActiveTime - lastResumeTime();
}
bool SMILTimeContainer::isPaused() const
......@@ -130,7 +137,7 @@ bool SMILTimeContainer::isStarted() const
void SMILTimeContainer::begin()
{
ASSERT(!m_beginTime);
double now = currentTime();
double now = m_animationClock->currentTime();
// If 'm_presetStartTime' is set, the timeline was modified via setElapsed() before the document began.
// In this case pass on 'seekToTime=true' to updateAnimations().
......@@ -141,28 +148,33 @@ void SMILTimeContainer::begin()
if (m_pauseTime) {
m_pauseTime = now;
cancelAnimationFrame();
} else {
// Latch the clock to this time (0 or the preset start time).
m_animationClock->updateTime(now);
}
}
void SMILTimeContainer::pause()
{
ASSERT(!isPaused());
m_pauseTime = currentTime();
m_pauseTime = m_animationClock->currentTime();
if (m_beginTime) {
m_accumulatedActiveTime += m_pauseTime - lastResumeTime();
cancelAnimationFrame();
}
m_resumeTime = 0;
m_animationClock->unfreeze();
}
void SMILTimeContainer::resume()
{
ASSERT(isPaused());
m_resumeTime = currentTime();
m_resumeTime = m_animationClock->currentTime();
m_pauseTime = 0;
scheduleAnimationFrame();
m_animationClock->unfreeze();
}
void SMILTimeContainer::setElapsed(SMILTime time)
......@@ -173,10 +185,11 @@ void SMILTimeContainer::setElapsed(SMILTime time)
return;
}
if (m_beginTime)
cancelAnimationFrame();
m_animationClock->unfreeze();
double now = currentTime();
cancelAnimationFrame();
double now = m_animationClock->currentTime();
m_beginTime = now - time.value();
m_resumeTime = 0;
if (m_pauseTime) {
......@@ -201,6 +214,8 @@ void SMILTimeContainer::setElapsed(SMILTime time)
#endif
updateAnimations(time, true);
// Latch the clock to wait for this frame to be sampled by the frame interval.
m_animationClock->updateTime(now);
}
bool SMILTimeContainer::isTimelineRunning() const
......@@ -216,8 +231,11 @@ void SMILTimeContainer::scheduleAnimationFrame(SMILTime fireTime)
if (!fireTime.isFinite())
return;
SMILTime delay = max(fireTime - elapsed(), SMILTime(animationFrameDelay));
m_timer.startOneShot(delay.value());
SMILTime delay = fireTime - elapsed();
if (delay.value() < DocumentTimeline::s_minimumDelay)
serviceOnNextFrame();
else
m_wakeupTimer.startOneShot(delay.value() - DocumentTimeline::s_minimumDelay);
}
void SMILTimeContainer::scheduleAnimationFrame()
......@@ -225,18 +243,21 @@ void SMILTimeContainer::scheduleAnimationFrame()
if (!isTimelineRunning())
return;
m_timer.startOneShot(0);
// Could also schedule a wakeup at +0 seconds, but that could still
// potentially race with the servicing of the next frame.
serviceOnNextFrame();
}
void SMILTimeContainer::cancelAnimationFrame()
{
m_timer.stop();
m_framePending = false;
m_wakeupTimer.stop();
}
void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*)
void SMILTimeContainer::wakeupTimerFired(Timer<SMILTimeContainer>*)
{
ASSERT(isTimelineRunning());
updateAnimations(elapsed());
serviceOnNextFrame();
}
void SMILTimeContainer::updateDocumentOrderIndexes()
......@@ -266,6 +287,35 @@ struct PriorityCompare {
SMILTime m_elapsed;
};
Document& SMILTimeContainer::document() const
{
ASSERT(m_ownerSVGElement);
return m_ownerSVGElement->document();
}
void SMILTimeContainer::serviceOnNextFrame()
{
if (document().view()) {
document().view()->scheduleAnimation();
m_framePending = true;
}
}
void SMILTimeContainer::serviceAnimations(double monotonicAnimationStartTime)
{
if (!m_framePending)
return;
m_framePending = false;
// If the clock is frozen at this point, it means the timeline has been
// started, but the first animation frame hasn't yet been serviced. If so,
// then just keep the clock frozen for this update.
if (!m_animationClock->isFrozen())
m_animationClock->updateTime(monotonicAnimationStartTime);
updateAnimations(elapsed());
m_animationClock->unfreeze();
}
void SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime)
{
SMILTime earliestFireTime = SMILTime::unresolved();
......
......@@ -38,6 +38,8 @@
namespace WebCore {
class AnimationClock;
class Document;
class SVGElement;
class SVGSMILElement;
class SVGSVGElement;
......@@ -61,6 +63,9 @@ public:
void resume();
void setElapsed(SMILTime);
void serviceAnimations(double monotonicAnimationStartTime);
bool hasAnimations() const;
void setDocumentOrderIndexesDirty() { m_documentOrderIndexesDirty = true; }
private:
......@@ -70,12 +75,15 @@ private:
void scheduleAnimationFrame(SMILTime fireTime);
void scheduleAnimationFrame();
void cancelAnimationFrame();
void timerFired(Timer<SMILTimeContainer>*);
void wakeupTimerFired(Timer<SMILTimeContainer>*);
void updateAnimations(SMILTime elapsed, bool seekToTime = false);
void serviceOnNextFrame();
void updateDocumentOrderIndexes();
double lastResumeTime() const { return m_resumeTime ? m_resumeTime : m_beginTime; }
Document& document() const;
double m_beginTime;
double m_pauseTime;
double m_resumeTime;
......@@ -83,8 +91,10 @@ private:
double m_presetStartTime;
bool m_documentOrderIndexesDirty;
bool m_framePending;
Timer<SMILTimeContainer> m_timer;
OwnPtr<AnimationClock> m_animationClock;
Timer<SMILTimeContainer> m_wakeupTimer;
typedef pair<SVGElement*, QualifiedName> ElementAttributePair;
typedef Vector<SVGSMILElement*> AnimationsVector;
......
......@@ -42,6 +42,7 @@
#include "core/svg/SVGFEImageElement.h"
#include "core/svg/SVGImageElement.h"
#include "core/svg/SVGSVGElement.h"
#include "core/svg/animation/SMILTimeContainer.h"
#include "core/svg/graphics/SVGImageChromeClient.h"
#include "platform/LengthFunctions.h"
#include "platform/geometry/IntRect.h"
......@@ -416,6 +417,17 @@ void SVGImage::resetAnimation()
stopAnimation();
}
bool SVGImage::hasAnimations() const
{
if (!m_page)
return false;
LocalFrame* frame = m_page->mainFrame();
SVGSVGElement* rootElement = toSVGDocument(frame->document())->rootElement();
if (!rootElement)
return false;
return rootElement->timeContainer()->hasAnimations();
}
bool SVGImage::dataChanged(bool allDataReceived)
{
TRACE_EVENT0("webkit", "SVGImage::dataChanged");
......@@ -438,12 +450,12 @@ bool SVGImage::dataChanged(bool allDataReceived)
// This will become an issue when SVGImage will be able to load other
// SVGImage objects, but we're safe now, because SVGImage can only be
// loaded by a top-level document.
m_page = adoptPtr(new Page(pageClients));
m_page->settings().setScriptEnabled(false);
m_page->settings().setPluginsEnabled(false);
m_page->settings().setAcceleratedCompositingEnabled(false);
OwnPtr<Page> page = adoptPtr(new Page(pageClients));
page->settings().setScriptEnabled(false);
page->settings().setPluginsEnabled(false);
page->settings().setAcceleratedCompositingEnabled(false);
RefPtr<LocalFrame> frame = LocalFrame::create(FrameInit::create(&m_page->frameHost(), dummyFrameLoaderClient));
RefPtr<LocalFrame> frame = LocalFrame::create(FrameInit::create(&page->frameHost(), dummyFrameLoaderClient));
frame->setView(FrameView::create(frame.get()));
frame->init();
FrameLoader& loader = frame->loader();
......@@ -453,6 +465,8 @@ bool SVGImage::dataChanged(bool allDataReceived)
frame->view()->setCanHaveScrollbars(false); // SVG Images will always synthesize a viewBox, if it's not available, and thus never see scrollbars.
frame->view()->setTransparent(true); // SVG Images are transparent.
m_page = page.release();
loader.load(FrameLoadRequest(0, blankURL(), SubstituteData(data(), "image/svg+xml", "UTF-8", KURL(), ForceSynchronousLoad)));
// Set the intrinsic size before a container size is available.
m_intrinsicSize = containerSize();
......
......@@ -67,6 +67,9 @@ public:
// Returns the SVG image document's frame.
FrameView* frameView() const;
// Does the SVG image/document contain any animations?
bool hasAnimations() const;
private:
friend class AXRenderObject;
friend class SVGImageChromeClient;
......
......@@ -32,9 +32,12 @@
#include "core/frame/FrameView.h"
#include "core/svg/graphics/SVGImage.h"
#include "platform/graphics/ImageObserver.h"
#include "wtf/CurrentTime.h"
namespace WebCore {
static const double animationFrameDelay = 0.025;
SVGImageChromeClient::SVGImageChromeClient(SVGImage* image)
: m_image(image)
, m_animationTimer(this, &SVGImageChromeClient::animationTimerFired)
......@@ -67,7 +70,12 @@ void SVGImageChromeClient::scheduleAnimation()
// approach.
if (m_animationTimer.isActive())
return;
m_animationTimer.startOneShot(0);
// Schedule the 'animation' ASAP if the image does not contain any
// animations, but prefer a fixed, jittery, frame-delay if there're any
// animations. Checking for pending/active animations could be more
// stringent.
double fireTime = m_image->hasAnimations() ? animationFrameDelay : 0;
m_animationTimer.startOneShot(fireTime);
}
void SVGImageChromeClient::animationTimerFired(Timer<SVGImageChromeClient>*)
......@@ -75,7 +83,7 @@ void SVGImageChromeClient::animationTimerFired(Timer<SVGImageChromeClient>*)
// In principle, we should call requestAnimationFrame callbacks here, but
// we know there aren't any because script is forbidden inside SVGImages.
if (m_image) {
m_image->frameView()->page()->animator().serviceScriptedAnimations(0);
m_image->frameView()->page()->animator().serviceScriptedAnimations(currentTime());
m_image->frameView()->updateLayoutAndStyleIfNeededRecursive();
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment