https://bugs.webkit.org/show_bug.cgi?id=144718
<rdar://problem/
20688782>
Reviewed by Simon Fraser.
Source/WebCore:
Throttle RequestAnimationFrame in subframes that are outside the
viewport or have "display: none" for performance and power.
Tests: fast/animation/request-animation-frame-throttle-subframe-display-none.html
fast/animation/request-animation-frame-throttle-subframe.html
* dom/Document.h:
(WebCore::Document::scriptedAnimationController):
* dom/ScriptedAnimationController.cpp:
(WebCore::ScriptedAnimationController::setThrottled):
(WebCore::ScriptedAnimationController::isThrottled):
* dom/ScriptedAnimationController.h:
* loader/FrameLoader.cpp:
(WebCore::FrameLoader::open):
Call FrameView::setFrameRect() only *after* the view has been
set on the Frame. Otherwise, setFrameRect() ends up calling
viewportContentsChanged() and we hit the
ASSERT(frame().view() == this) assertion in windowClipRect()
because the Frame still has its old FrameView. This is covered
by loader/go-back-to-different-window-size.html layout test.
* page/FrameView.cpp:
(WebCore::FrameView::viewportContentsChanged):
(WebCore::FrameView::applyRecursivelyWithVisibleRect):
(WebCore::FrameView::resumeVisibleImageAnimations):
(WebCore::FrameView::updateScriptedAnimationsThrottlingState):
(WebCore::FrameView::resumeVisibleImageAnimationsIncludingSubframes):
(WebCore::FrameView::updateThrottledDOMTimersState):
(WebCore::FrameView::scrollPositionChanged): Deleted.
(WebCore::FrameView::sendResizeEventIfNeeded): Deleted.
* page/FrameView.h:
* testing/Internals.cpp:
(WebCore::Internals::isRequestAnimationFrameThrottled):
(WebCore::Internals::isTimerThrottled): Deleted.
* testing/Internals.h:
* testing/Internals.idl:
LayoutTests:
* fast/animation/request-animation-frame-throttle-subframe-display-none-expected.txt: Added.
* fast/animation/request-animation-frame-throttle-subframe-display-none.html: Added.
Add layout test to test that RequestAnimationFrame is properly throttled
in "display: none" subframes.
* fast/animation/request-animation-frame-throttle-subframe-expected.txt: Added.
* fast/animation/request-animation-frame-throttle-subframe.html: Added.
Add layout test to test that RequestAnimationFrame is properly throttled
in frames that are outside the viewport.
* fast/animation/resources/requestAnimationFrame-frame-2.html: Added.
* fast/animation/resources/requestAnimationFrame-frame.html: Added.
* platform/win/TestExpectations:
Skip the 2 new tests on Windows as requestAnimationFrame throttling is
only supported on Cocoa.
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@183998
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2015-05-08 Chris Dumez <cdumez@apple.com>
+
+ Throttle RequestAnimationFrame in subframes that are outside the viewport
+ https://bugs.webkit.org/show_bug.cgi?id=144718
+ <rdar://problem/20688782>
+
+ Reviewed by Simon Fraser.
+
+ * fast/animation/request-animation-frame-throttle-subframe-display-none-expected.txt: Added.
+ * fast/animation/request-animation-frame-throttle-subframe-display-none.html: Added.
+ Add layout test to test that RequestAnimationFrame is properly throttled
+ in "display: none" subframes.
+
+ * fast/animation/request-animation-frame-throttle-subframe-expected.txt: Added.
+ * fast/animation/request-animation-frame-throttle-subframe.html: Added.
+ Add layout test to test that RequestAnimationFrame is properly throttled
+ in frames that are outside the viewport.
+
+ * fast/animation/resources/requestAnimationFrame-frame-2.html: Added.
+ * fast/animation/resources/requestAnimationFrame-frame.html: Added.
+
+ * platform/win/TestExpectations:
+ Skip the 2 new tests on Windows as requestAnimationFrame throttling is
+ only supported on Cocoa.
+
2015-05-08 Carlos Garcia Campos <cgarcia@igalia.com>
[GTK] WTR doesn't correctly handle the Escape key
--- /dev/null
+Tests that requestAnimationFrame is throttled in subframes that are display:none
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Frame is initially visibile so requestAnimationFrame should not be throttled
+PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() became false
+PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is false
+Hiding subframe.
+testFrame.style.display = 'none';
+PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() became true
+PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is true
+Show subframe again
+testFrame.style.display = 'block';
+PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() became false
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+description("Tests that requestAnimationFrame is throttled in subframes that are display:none");
+window.jsTestIsAsync = true;
+
+function checkSubframeThrottled()
+{
+ shouldBeTrue("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+
+ debug("Show subframe again");
+ evalAndLog("testFrame.style.display = 'block';");
+
+ shouldBecomeEqual("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()", "false", finishJSTest);
+}
+
+function hideFrame()
+{
+ shouldBeFalse("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+
+ debug("Hiding subframe.");
+ evalAndLog("testFrame.style.display = 'none';");
+
+ shouldBecomeEqual("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()", "true", checkSubframeThrottled);
+}
+
+function runTest()
+{
+ testFrame = document.getElementById("testFrame");
+ debug("Frame is initially visibile so requestAnimationFrame should not be throttled");
+ shouldBecomeEqual("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()", "false", hideFrame);
+}
+
+var i = 0;
+requestAnimationFrame(function() {
+ i++;
+});
+</script>
+<iframe id="testFrame" src="resources/requestAnimationFrame-frame.html" onload="runTest()"></iframe>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
--- /dev/null
+Tests that requestAnimationFrame is throttled in subframes that are outside the viewport
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Frame is initially outside the viewport so requestAnimationFrame should be throttled
+PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() became true
+PASS internals.isRequestAnimationFrameThrottled() is false
+PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is true
+PASS grandChildFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is true
+Scrolling frame into view.
+RequestAnimationFrame should no longer be throttled
+PASS internals.isRequestAnimationFrameThrottled() is false
+PASS grandChildFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is false
+PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is false
+Scrolling frame out of view again.
+PASS internals.isRequestAnimationFrameThrottled() is false
+PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() became true
+PASS testFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is true
+PASS grandChildFrame.contentWindow.internals.isRequestAnimationFrameThrottled() is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+description("Tests that requestAnimationFrame is throttled in subframes that are outside the viewport");
+window.jsTestIsAsync = true;
+
+function checkSubframesThrottled()
+{
+ shouldBeTrue("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+ shouldBeTrue("grandChildFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+
+ finishJSTest();
+}
+
+function scrollFrameOutOfView()
+{
+ debug("Scrolling frame out of view again.");
+ window.scroll(0, 0);
+
+ shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
+ shouldBecomeEqual("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()", "true", checkSubframesThrottled);
+}
+
+function scrollFrameIntoView()
+{
+ shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
+ shouldBeTrue("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+ shouldBeTrue("grandChildFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+
+ debug("Scrolling frame into view.");
+ window.internals.scrollElementToRect(testFrame, 0, 0, 300, 300);
+
+ debug("RequestAnimationFrame should no longer be throttled");
+ shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
+ shouldBeFalse("grandChildFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+ shouldBeFalse("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+
+ scrollFrameOutOfView();
+}
+
+function runTest()
+{
+ testFrame = document.getElementById("testFrame");
+ grandChildFrame = testFrame.contentDocument.getElementById("grandChildFrame");
+ debug("Frame is initially outside the viewport so requestAnimationFrame should be throttled");
+ shouldBecomeEqual("testFrame.contentWindow.internals.isRequestAnimationFrameThrottled()", "true", scrollFrameIntoView);
+}
+
+var i = 0;
+requestAnimationFrame(function() {
+ i++;
+});
+</script>
+<div style="position: relative; width: 1600px; height: 2400px; background-color: green;">
+ <iframe id="testFrame" src="resources/requestAnimationFrame-frame.html" style="position:absolute; left: 600px; top: 800px;" onload="runTest()"></iframe>
+</div>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+var i = 0;
+requestAnimationFrame(function() {
+ i++;
+});
+</script>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html>
+<body>
+<iframe id="grandChildFrame" src="requestAnimationFrame-frame-2.html"></iframe>
+<script>
+var i = 0;
+requestAnimationFrame(function() {
+ i++;
+});
+</script>
+</body>
+</html>
webkit.org/b/138676 imported/w3c/canvas/2d.line.join.parallel.html [ Failure ]
webkit.org/b/138676 imported/w3c/canvas/2d.strokeRect.zero.5.html [ Failure ]
+# requestAnimationFrame throttling does not work on Windows because USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR) is disabled.
+webkit.org/b/144718 fast/animation/request-animation-frame-throttle-subframe-display-none.html [ Skip ]
+webkit.org/b/144718 fast/animation/request-animation-frame-throttle-subframe.html [ Skip ]
+
################################################################################
######################### Start list of UNREVIEWED failures ########################
################################################################################
+2015-05-08 Chris Dumez <cdumez@apple.com>
+
+ Throttle RequestAnimationFrame in subframes that are outside the viewport
+ https://bugs.webkit.org/show_bug.cgi?id=144718
+ <rdar://problem/20688782>
+
+ Reviewed by Simon Fraser.
+
+ Throttle RequestAnimationFrame in subframes that are outside the
+ viewport or have "display: none" for performance and power.
+
+ Tests: fast/animation/request-animation-frame-throttle-subframe-display-none.html
+ fast/animation/request-animation-frame-throttle-subframe.html
+
+ * dom/Document.h:
+ (WebCore::Document::scriptedAnimationController):
+ * dom/ScriptedAnimationController.cpp:
+ (WebCore::ScriptedAnimationController::setThrottled):
+ (WebCore::ScriptedAnimationController::isThrottled):
+ * dom/ScriptedAnimationController.h:
+ * loader/FrameLoader.cpp:
+ (WebCore::FrameLoader::open):
+ Call FrameView::setFrameRect() only *after* the view has been
+ set on the Frame. Otherwise, setFrameRect() ends up calling
+ viewportContentsChanged() and we hit the
+ ASSERT(frame().view() == this) assertion in windowClipRect()
+ because the Frame still has its old FrameView. This is covered
+ by loader/go-back-to-different-window-size.html layout test.
+
+ * page/FrameView.cpp:
+ (WebCore::FrameView::viewportContentsChanged):
+ (WebCore::FrameView::applyRecursivelyWithVisibleRect):
+ (WebCore::FrameView::resumeVisibleImageAnimations):
+ (WebCore::FrameView::updateScriptedAnimationsThrottlingState):
+ (WebCore::FrameView::resumeVisibleImageAnimationsIncludingSubframes):
+ (WebCore::FrameView::updateThrottledDOMTimersState):
+ (WebCore::FrameView::scrollPositionChanged): Deleted.
+ (WebCore::FrameView::sendResizeEventIfNeeded): Deleted.
+ * page/FrameView.h:
+ * testing/Internals.cpp:
+ (WebCore::Internals::isRequestAnimationFrameThrottled):
+ (WebCore::Internals::isTimerThrottled): Deleted.
+ * testing/Internals.h:
+ * testing/Internals.idl:
+
2015-05-08 Daniel Bates <dabates@apple.com>
Fix the iOS Simulator external SDK build following <http://trac.webkit.org/changeset/181918>
virtual void postTask(Task) override final; // Executes the task on context's thread asynchronously.
+#if ENABLE(REQUEST_ANIMATION_FRAME)
+ ScriptedAnimationController* scriptedAnimationController() { return m_scriptedAnimationController.get(); }
+#endif
void suspendScriptedAnimationControllerCallbacks();
void resumeScriptedAnimationControllerCallbacks();
void scriptedAnimationControllerSetThrottled(bool);
#include "DocumentLoader.h"
#include "FrameView.h"
#include "InspectorInstrumentation.h"
+#include "Logging.h"
+#include "MainFrame.h"
#include "RequestAnimationFrameCallback.h"
#include "Settings.h"
#include <wtf/Ref.h>
if (m_isThrottled == isThrottled)
return;
+ LOG(Animations, "%p - Setting RequestAnimationFrame throttling state to %d in frame %p (isMainFrame: %d)", this, isThrottled, m_document->frame(), m_document->frame() ? m_document->frame()->isMainFrame() : 0);
+
m_isThrottled = isThrottled;
if (m_animationTimer.isActive()) {
m_animationTimer.stop();
#endif
}
+bool ScriptedAnimationController::isThrottled() const
+{
+#if USE(REQUEST_ANIMATION_FRAME_TIMER) && USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+ return m_isThrottled;
+#else
+ return false;
+#endif
+}
+
ScriptedAnimationController::CallbackId ScriptedAnimationController::registerCallback(PassRefPtr<RequestAnimationFrameCallback> callback)
{
ScriptedAnimationController::CallbackId id = ++m_nextCallbackId;
void suspend();
void resume();
void setThrottled(bool);
+ WEBCORE_EXPORT bool isThrottled() const;
void windowScreenDidChange(PlatformDisplayID);
ASSERT(view);
view->setWasScrolledByUser(false);
- // Use the current ScrollView's frame rect.
- if (m_frame.view())
- view->setFrameRect(m_frame.view()->frameRect());
+ Optional<IntRect> previousViewFrameRect = m_frame.view() ? m_frame.view()->frameRect() : Optional<IntRect>(Nullopt);
m_frame.setView(view);
+
+ // Use the previous ScrollView's frame rect.
+ if (previousViewFrameRect)
+ view->setFrameRect(previousViewFrameRect.value());
m_frame.setDocument(document);
document->domWindow()->resumeFromPageCache();
#include "RenderWidget.h"
#include "SVGDocument.h"
#include "SVGSVGElement.h"
+#include "ScriptedAnimationController.h"
#include "ScrollAnimator.h"
#include "ScrollingCoordinator.h"
#include "Settings.h"
void FrameView::viewportContentsChanged()
{
+ if (!frame().view()) {
+ // The frame is being destroyed.
+ return;
+ }
+
// When the viewport contents changes (scroll, resize, style recalc, layout, ...),
// check if we should resume animated images or unthrottle DOM timers.
- resumeVisibleImageAnimationsIncludingSubframes();
- updateThrottledDOMTimersState();
+ applyRecursivelyWithVisibleRect([] (FrameView& frameView, const IntRect& visibleRect) {
+ frameView.resumeVisibleImageAnimations(visibleRect);
+ frameView.updateThrottledDOMTimersState(visibleRect);
+ frameView.updateScriptedAnimationsThrottlingState(visibleRect);
+ });
}
bool FrameView::fixedElementsLayoutRelativeToFrame() const
viewportContentsChanged();
}
-void FrameView::resumeVisibleImageAnimationsIncludingSubframes()
+void FrameView::applyRecursivelyWithVisibleRect(const std::function<void (FrameView& frameView, const IntRect& visibleRect)>& apply)
{
- auto* renderView = frame().contentRenderer();
- if (!renderView)
- return;
-
IntRect windowClipRect = this->windowClipRect();
auto visibleRect = windowToContents(windowClipRect);
- if (visibleRect.isEmpty())
- return;
-
- // Resume paused image animations in this frame.
- renderView->resumePausedImageAnimationsIfNeeded(visibleRect);
+ apply(*this, visibleRect);
// Recursive call for subframes. We cache the current FrameView's windowClipRect to avoid recomputing it for every subframe.
TemporaryChange<IntRect*> windowClipRectCache(m_cachedWindowClipRect, &windowClipRect);
for (Frame* childFrame = frame().tree().firstChild(); childFrame; childFrame = childFrame->tree().nextSibling()) {
if (auto* childView = childFrame->view())
- childView->resumeVisibleImageAnimationsIncludingSubframes();
+ childView->applyRecursivelyWithVisibleRect(apply);
}
}
+void FrameView::resumeVisibleImageAnimations(const IntRect& visibleRect)
+{
+ if (visibleRect.isEmpty())
+ return;
+
+ if (auto* renderView = frame().contentRenderer())
+ renderView->resumePausedImageAnimationsIfNeeded(visibleRect);
+}
+
+void FrameView::updateScriptedAnimationsThrottlingState(const IntRect& visibleRect)
+{
+#if ENABLE(REQUEST_ANIMATION_FRAME)
+ if (frame().isMainFrame())
+ return;
+
+ auto* document = frame().document();
+ if (!document)
+ return;
+
+ auto* scriptedAnimationController = document->scriptedAnimationController();
+ if (!scriptedAnimationController)
+ return;
+
+ // FIXME: This doesn't work for subframes of a "display: none" frame because
+ // they have a non-null ownerRenderer.
+ bool shouldThrottle = !frame().ownerRenderer() || visibleRect.isEmpty();
+ scriptedAnimationController->setThrottled(shouldThrottle);
+#else
+ UNUSED_PARAM(visibleRect);
+#endif
+}
+
+
+void FrameView::resumeVisibleImageAnimationsIncludingSubframes()
+{
+ applyRecursivelyWithVisibleRect([] (FrameView& frameView, const IntRect& visibleRect) {
+ frameView.resumeVisibleImageAnimations(visibleRect);
+ });
+}
+
void FrameView::updateLayerPositionsAfterScrolling()
{
// If we're scrolling as a result of updating the view size after layout, we'll update widgets and layer positions soon anyway.
m_throttledTimers.remove(timer);
}
-void FrameView::updateThrottledDOMTimersState()
+void FrameView::updateThrottledDOMTimersState(const IntRect& visibleRect)
{
if (m_throttledTimers.isEmpty())
return;
- IntRect visibleRect = windowToContents(windowClipRect());
-
// Do not iterate over the HashSet because calling DOMTimer::updateThrottlingStateAfterViewportChange()
// may cause timers to remove themselves from it while we are iterating.
Vector<DOMTimer*> timers;
void forceLayoutParentViewIfNeeded();
void performPostLayoutTasks();
void autoSizeIfEnabled();
- void updateThrottledDOMTimersState();
+
+ void applyRecursivelyWithVisibleRect(const std::function<void (FrameView& frameView, const IntRect& visibleRect)>&);
+ void updateThrottledDOMTimersState(const IntRect& visibleRect);
+ void resumeVisibleImageAnimations(const IntRect& visibleRect);
+ void updateScriptedAnimationsThrottlingState(const IntRect& visibleRect);
void updateLayerFlushThrottling();
WEBCORE_EXPORT void adjustTiledBackingCoverage();
#include "RenderedDocumentMarker.h"
#include "RuntimeEnabledFeatures.h"
#include "SchemeRegistry.h"
+#include "ScriptedAnimationController.h"
#include "ScrollingCoordinator.h"
#include "SerializedScriptValue.h"
#include "Settings.h"
return timer->m_throttleState == DOMTimer::ShouldThrottle;
}
+bool Internals::isRequestAnimationFrameThrottled() const
+{
+#if ENABLE(REQUEST_ANIMATION_FRAME)
+ auto* scriptedAnimationController = contextDocument()->scriptedAnimationController();
+ if (!scriptedAnimationController)
+ return false;
+ return scriptedAnimationController->isThrottled();
+#else
+ return false;
+#endif
+}
+
String Internals::visiblePlaceholder(Element* element)
{
if (is<HTMLTextFormControlElement>(element)) {
// DOMTimers throttling testing.
bool isTimerThrottled(int timeoutId, ExceptionCode&);
+ bool isRequestAnimationFrameThrottled() const;
// Spatial Navigation testing.
unsigned lastSpatialNavigationCandidateCount(ExceptionCode&) const;
// Query if a timer is currently throttled, to debug timer throttling.
[RaisesException] boolean isTimerThrottled(long timerHandle);
+ boolean isRequestAnimationFrameThrottled();
+
[RaisesException] void startTrackingStyleRecalcs();
[RaisesException] unsigned long styleRecalcCount();