blob: 9ac7b526814cb3265ac96b8b050a33221939d986 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/svg/graphics/SVGImage.h"
#include "core/svg/graphics/SVGImageChromeClient.h"
#include "platform/SharedBuffer.h"
#include "platform/Timer.h"
#include "platform/geometry/FloatRect.h"
#include "platform/testing/UnitTestHelpers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/utils/SkNullCanvas.h"
#include "wtf/PtrUtil.h"
namespace blink {
class SVGImageTest : public ::testing::Test {
public:
SVGImage& image() { return *m_image; }
void load(const char* data, bool shouldPause) {
m_observer = new PauseControlImageObserver(shouldPause);
m_image = SVGImage::create(m_observer);
m_image->setData(SharedBuffer::create(data, strlen(data)), true);
}
void pumpFrame() {
Image* image = m_image.get();
sk_sp<SkCanvas> nullCanvas(SkCreateNullCanvas());
SkPaint paint;
FloatRect dummyRect(0, 0, 100, 100);
image->draw(nullCanvas.get(), paint, dummyRect, dummyRect,
DoNotRespectImageOrientation,
Image::DoNotClampImageToSourceRect);
}
private:
class PauseControlImageObserver
: public GarbageCollectedFinalized<PauseControlImageObserver>,
public ImageObserver {
USING_GARBAGE_COLLECTED_MIXIN(PauseControlImageObserver);
public:
PauseControlImageObserver(bool shouldPause) : m_shouldPause(shouldPause) {}
void decodedSizeChangedTo(const Image*, size_t newSize) override {}
void didDraw(const Image*) override {}
bool shouldPauseAnimation(const Image*) override { return m_shouldPause; }
void animationAdvanced(const Image*) override {}
void changedInRect(const Image*, const IntRect&) override {}
DEFINE_INLINE_VIRTUAL_TRACE() { ImageObserver::trace(visitor); }
private:
bool m_shouldPause;
};
Persistent<PauseControlImageObserver> m_observer;
RefPtr<SVGImage> m_image;
};
const char kAnimatedDocument[] =
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'>"
"<style>"
"@keyframes rot {"
" from { transform: rotate(0deg); } to { transform: rotate(-360deg); }"
"}"
".spinner {"
" transform-origin: 50%% 50%%;"
" animation-name: rot;"
" animation-duration: 4s;"
" animation-iteration-count: infinite;"
" animation-timing-function: linear;"
"}"
"</style>"
"<path class='spinner' fill='none' d='M 8,1.125 A 6.875,6.875 0 1 1 "
"1.125,8' stroke-width='2' stroke='blue'/>"
"</svg>";
TEST_F(SVGImageTest, TimelineSuspendAndResume) {
const bool shouldPause = true;
load(kAnimatedDocument, shouldPause);
SVGImageChromeClient& chromeClient = image().chromeClientForTesting();
Timer<SVGImageChromeClient>* timer = new Timer<SVGImageChromeClient>(
&chromeClient, &SVGImageChromeClient::animationTimerFired);
chromeClient.setTimer(wrapUnique(timer));
// Simulate a draw. Cause a frame (timer) to be scheduled.
pumpFrame();
EXPECT_TRUE(image().hasAnimations());
EXPECT_TRUE(timer->isActive());
// Fire the timer/trigger a frame update. Since the observer always returns
// true for shouldPauseAnimation, this will result in the timeline being
// suspended.
// TODO(alexclarke): Move over to using base::TimeDelta and base::TimeTicks so
// we can avoid computations like this.
testing::runDelayedTasks(1.0 + timer->nextFireInterval() * 1000.0);
EXPECT_TRUE(chromeClient.isSuspended());
EXPECT_FALSE(timer->isActive());
// Simulate a draw. This should resume the animation again.
pumpFrame();
EXPECT_TRUE(timer->isActive());
EXPECT_FALSE(chromeClient.isSuspended());
}
TEST_F(SVGImageTest, ResetAnimation) {
const bool shouldPause = false;
load(kAnimatedDocument, shouldPause);
SVGImageChromeClient& chromeClient = image().chromeClientForTesting();
Timer<SVGImageChromeClient>* timer = new Timer<SVGImageChromeClient>(
&chromeClient, &SVGImageChromeClient::animationTimerFired);
chromeClient.setTimer(wrapUnique(timer));
// Simulate a draw. Cause a frame (timer) to be scheduled.
pumpFrame();
EXPECT_TRUE(image().hasAnimations());
EXPECT_TRUE(timer->isActive());
// Reset the animation. This will suspend the timeline but not cancel the
// timer.
image().resetAnimation();
EXPECT_TRUE(chromeClient.isSuspended());
EXPECT_TRUE(timer->isActive());
// Fire the timer/trigger a frame update. The timeline will remain
// suspended and no frame will be scheduled.
// TODO(alexclarke): Move over to using base::TimeDelta and base::TimeTicks so
// we can avoid computations like this.
testing::runDelayedTasks(1.0 + timer->nextFireInterval() * 1000.0);
EXPECT_TRUE(chromeClient.isSuspended());
EXPECT_FALSE(timer->isActive());
// Simulate a draw. This should resume the animation again.
pumpFrame();
EXPECT_FALSE(chromeClient.isSuspended());
EXPECT_TRUE(timer->isActive());
}
} // namespace blink