Diff
Modified: trunk/LayoutTests/ChangeLog (213168 => 213169)
--- trunk/LayoutTests/ChangeLog 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/LayoutTests/ChangeLog 2017-02-28 21:26:27 UTC (rev 213169)
@@ -1,3 +1,19 @@
+2017-02-28 Chris Dumez <cdu...@apple.com>
+
+ [iOS] Throttle requestAnimationFrame to 30fps in low power mode
+ https://bugs.webkit.org/show_bug.cgi?id=168837
+ <rdar://problem/30700929>
+
+ Reviewed by Simon Fraser.
+
+ Add layout test coverage.
+
+ * fast/animation/request-animation-frame-throttling-detached-iframe-expected.txt: Added.
+ * fast/animation/request-animation-frame-throttling-detached-iframe.html: Added.
+ * fast/animation/request-animation-frame-throttling-lowPowerMode-expected.txt: Added.
+ * fast/animation/request-animation-frame-throttling-lowPowerMode.html: Added.
+ * fast/animation/resources/frame-with-animation.html: Added.
+
2017-02-28 Myles C. Maxfield <mmaxfi...@apple.com>
[macOS] Migrate off of CTFontCreateForCSS
Added: trunk/LayoutTests/fast/animation/request-animation-frame-throttling-detached-iframe-expected.txt (0 => 213169)
--- trunk/LayoutTests/fast/animation/request-animation-frame-throttling-detached-iframe-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/animation/request-animation-frame-throttling-detached-iframe-expected.txt 2017-02-28 21:26:27 UTC (rev 213169)
@@ -0,0 +1,31 @@
+Test that requestAnimationFrame gets the right throttling in an iframe when inserted into a document.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS internals.isRequestAnimationFrameThrottled() is false
+PASS internals.requestAnimationFrameInterval is 0.015
+PASS frame.contentWindow.internals.isRequestAnimationFrameThrottled() is false
+PASS frame.contentWindow.internals.requestAnimationFrameInterval is 0.015
+internals.setLowPowerModeEnabled(true);
+PASS internals.isRequestAnimationFrameThrottled() is true
+PASS internals.requestAnimationFrameInterval is 0.030
+PASS frame.contentWindow.internals.isRequestAnimationFrameThrottled() is true
+PASS frame.contentWindow.internals.requestAnimationFrameInterval is 0.030
+frame.remove()
+document.body.appendChild(frame)
+PASS internals.isRequestAnimationFrameThrottled() is true
+PASS internals.requestAnimationFrameInterval is 0.030
+PASS frame.contentWindow.internals.isRequestAnimationFrameThrottled() is true
+PASS frame.contentWindow.internals.requestAnimationFrameInterval is 0.030
+frame.remove()
+internals.setLowPowerModeEnabled(false);
+PASS internals.isRequestAnimationFrameThrottled() is false
+PASS internals.requestAnimationFrameInterval is 0.015
+document.body.appendChild(frame)
+PASS frame.contentWindow.internals.isRequestAnimationFrameThrottled() is false
+PASS frame.contentWindow.internals.requestAnimationFrameInterval is 0.015
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/fast/animation/request-animation-frame-throttling-detached-iframe.html (0 => 213169)
--- trunk/LayoutTests/fast/animation/request-animation-frame-throttling-detached-iframe.html (rev 0)
+++ trunk/LayoutTests/fast/animation/request-animation-frame-throttling-detached-iframe.html 2017-02-28 21:26:27 UTC (rev 213169)
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<script>
+description("Test that requestAnimationFrame gets the right throttling in an iframe when inserted into a document.");
+jsTestIsAsync = true;
+
+let i = 0;
+function doWork()
+{
+ i++;
+ requestAnimationFrame(doWork);
+}
+
+requestAnimationFrame(doWork);
+
+const frame = document.createElement("iframe");
+frame.src = ""
+frame._onload_ = function() {
+ shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
+ shouldBe("internals.requestAnimationFrameInterval", "0.015");
+ shouldBeFalse("frame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+ shouldBe("frame.contentWindow.internals.requestAnimationFrameInterval", "0.015");
+
+ evalAndLog("internals.setLowPowerModeEnabled(true);");
+ shouldBeTrue("internals.isRequestAnimationFrameThrottled()");
+ shouldBe("internals.requestAnimationFrameInterval", "0.030");
+ shouldBeTrue("frame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+ shouldBe("frame.contentWindow.internals.requestAnimationFrameInterval", "0.030");
+ evalAndLog("frame.remove()");
+
+ evalAndLog("document.body.appendChild(frame)");
+ frame._onload_ = function() {
+ shouldBeTrue("internals.isRequestAnimationFrameThrottled()");
+ shouldBe("internals.requestAnimationFrameInterval", "0.030");
+ shouldBeTrue("frame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+ shouldBe("frame.contentWindow.internals.requestAnimationFrameInterval", "0.030");
+
+ evalAndLog("frame.remove()");
+ evalAndLog("internals.setLowPowerModeEnabled(false);");
+ shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
+ shouldBe("internals.requestAnimationFrameInterval", "0.015");
+
+ evalAndLog("document.body.appendChild(frame)");
+ frame._onload_ = function() {
+ shouldBeFalse("frame.contentWindow.internals.isRequestAnimationFrameThrottled()");
+ shouldBe("frame.contentWindow.internals.requestAnimationFrameInterval", "0.015");
+ finishJSTest();
+ }
+ }
+};
+document.body.appendChild(frame);
+</script>
+<script src=""
+</body>
+</html>
Added: trunk/LayoutTests/fast/animation/request-animation-frame-throttling-lowPowerMode-expected.txt (0 => 213169)
--- trunk/LayoutTests/fast/animation/request-animation-frame-throttling-lowPowerMode-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/animation/request-animation-frame-throttling-lowPowerMode-expected.txt 2017-02-28 21:26:27 UTC (rev 213169)
@@ -0,0 +1,26 @@
+Test that requestAnimationFrame gets throttled in low power mode.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS internals.isRequestAnimationFrameThrottled() is false
+PASS internals.requestAnimationFrameInterval is Infinity
+rAFHandle = requestAnimationFrame(doWork);
+PASS internals.isRequestAnimationFrameThrottled() is false
+PASS internals.requestAnimationFrameInterval is 0.015
+internals.setLowPowerModeEnabled(true);
+PASS internals.isRequestAnimationFrameThrottled() is true
+PASS internals.requestAnimationFrameInterval is 0.030
+cancelAnimationFrame(rAFHandle);
+PASS internals.isRequestAnimationFrameThrottled() is true
+PASS internals.requestAnimationFrameInterval is 0.030
+rAFHandle = requestAnimationFrame(doWork);
+PASS internals.isRequestAnimationFrameThrottled() is true
+PASS internals.requestAnimationFrameInterval is 0.030
+internals.setLowPowerModeEnabled(false);
+PASS internals.isRequestAnimationFrameThrottled() is false
+PASS internals.requestAnimationFrameInterval is 0.015
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/fast/animation/request-animation-frame-throttling-lowPowerMode.html (0 => 213169)
--- trunk/LayoutTests/fast/animation/request-animation-frame-throttling-lowPowerMode.html (rev 0)
+++ trunk/LayoutTests/fast/animation/request-animation-frame-throttling-lowPowerMode.html 2017-02-28 21:26:27 UTC (rev 213169)
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<script>
+description("Test that requestAnimationFrame gets throttled in low power mode.");
+
+let rAFHandle;
+let i = 0;
+function doWork()
+{
+ i++;
+ rAFHandle = requestAnimationFrame(doWork);
+}
+
+shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
+shouldBe("internals.requestAnimationFrameInterval", "Infinity");
+evalAndLog("rAFHandle = requestAnimationFrame(doWork);");
+shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
+shouldBe("internals.requestAnimationFrameInterval", "0.015");
+evalAndLog("internals.setLowPowerModeEnabled(true);");
+shouldBeTrue("internals.isRequestAnimationFrameThrottled()");
+shouldBe("internals.requestAnimationFrameInterval", "0.030");
+evalAndLog("cancelAnimationFrame(rAFHandle);");
+shouldBeTrue("internals.isRequestAnimationFrameThrottled()");
+shouldBe("internals.requestAnimationFrameInterval", "0.030");
+evalAndLog("rAFHandle = requestAnimationFrame(doWork);");
+shouldBeTrue("internals.isRequestAnimationFrameThrottled()");
+shouldBe("internals.requestAnimationFrameInterval", "0.030");
+evalAndLog("internals.setLowPowerModeEnabled(false);");
+shouldBeFalse("internals.isRequestAnimationFrameThrottled()");
+shouldBe("internals.requestAnimationFrameInterval", "0.015");
+</script>
+<script src=""
+</body>
+</html>
Added: trunk/LayoutTests/fast/animation/resources/frame-with-animation.html (0 => 213169)
--- trunk/LayoutTests/fast/animation/resources/frame-with-animation.html (rev 0)
+++ trunk/LayoutTests/fast/animation/resources/frame-with-animation.html 2017-02-28 21:26:27 UTC (rev 213169)
@@ -0,0 +1,10 @@
+<script>
+let i = 0;
+function doWork()
+{
+ i++;
+ requestAnimationFrame(doWork);
+}
+
+requestAnimationFrame(doWork);
+</script>
Modified: trunk/Source/WTF/ChangeLog (213168 => 213169)
--- trunk/Source/WTF/ChangeLog 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Source/WTF/ChangeLog 2017-02-28 21:26:27 UTC (rev 213169)
@@ -1,3 +1,19 @@
+2017-02-28 Chris Dumez <cdu...@apple.com>
+
+ [iOS] Throttle requestAnimationFrame to 30fps in low power mode
+ https://bugs.webkit.org/show_bug.cgi?id=168837
+ <rdar://problem/30700929>
+
+ Reviewed by Simon Fraser.
+
+ Add support for operator -= on WTF::OptionSet for convenience:
+ set -= Enum::A;
+ looks much better than:
+ set = set - Enum::A;
+
+ * wtf/OptionSet.h:
+ (WTF::OptionSet::operator-=):
+
2017-02-28 Michael Saboff <msab...@apple.com>
Add ability to configure JSC options from a file
Modified: trunk/Source/WTF/wtf/OptionSet.h (213168 => 213169)
--- trunk/Source/WTF/wtf/OptionSet.h 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Source/WTF/wtf/OptionSet.h 2017-02-28 21:26:27 UTC (rev 213169)
@@ -125,6 +125,13 @@
return lhs;
}
+ friend OptionSet& operator-=(OptionSet& lhs, OptionSet rhs)
+ {
+ lhs.m_storage &= ~rhs.m_storage;
+
+ return lhs;
+ }
+
constexpr friend OptionSet operator-(OptionSet lhs, OptionSet rhs)
{
return OptionSet::fromRaw(lhs.m_storage & ~rhs.m_storage);
Modified: trunk/Source/WebCore/ChangeLog (213168 => 213169)
--- trunk/Source/WebCore/ChangeLog 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Source/WebCore/ChangeLog 2017-02-28 21:26:27 UTC (rev 213169)
@@ -1,3 +1,60 @@
+2017-02-28 Chris Dumez <cdu...@apple.com>
+
+ [iOS] Throttle requestAnimationFrame to 30fps in low power mode
+ https://bugs.webkit.org/show_bug.cgi?id=168837
+ <rdar://problem/30700929>
+
+ Reviewed by Simon Fraser.
+
+ Throttle requestAnimationFrame to 30fps in low power mode on iOS to save battery.
+
+ ScriptedAnimationController now maintains an OptionSet of throttling reasons.
+ Throttling reasons for now are: OutsideViewport, VisuallyIdle, and LowPowerMode.
+ The requestAnimationFrame interval is then determined based on those throttling
+ reasons:
+ - OutsideViewport or VisuallyIdle: 10 seconds (very aggressive throttling)
+ - LowPowerMode: 30fps
+ - No reasons: 60fps
+
+ The Page now keeps track of low power mode state using a LowPowerModeNotifier.
+ Whenever low power mode changes, it updates the throttling reasons in all the
+ documents' ScriptedAnimationControllers in the frame tree.
+
+ Tests: fast/animation/request-animation-frame-throttling-detached-iframe.html
+ fast/animation/request-animation-frame-throttling-lowPowerMode.html
+
+ * dom/Document.cpp:
+ (WebCore::Document::requestAnimationFrame):
+ * dom/Document.h:
+ * dom/ScriptedAnimationController.cpp:
+ (WebCore::ScriptedAnimationController::ScriptedAnimationController):
+ (WebCore::throttlingReasonToString):
+ (WebCore::throttlingReasonsToString):
+ (WebCore::ScriptedAnimationController::addThrottlingReason):
+ (WebCore::ScriptedAnimationController::removeThrottlingReason):
+ (WebCore::ScriptedAnimationController::isThrottled):
+ (WebCore::ScriptedAnimationController::interval):
+ (WebCore::ScriptedAnimationController::page):
+ (WebCore::ScriptedAnimationController::scheduleAnimation):
+ * dom/ScriptedAnimationController.h:
+ (WebCore::ScriptedAnimationController::create):
+ * page/FrameView.cpp:
+ (WebCore::FrameView::updateScriptedAnimationsAndTimersThrottlingState):
+ * page/Page.cpp:
+ (WebCore::Page::Page):
+ (WebCore::Page::isLowPowerModeEnabled):
+ (WebCore::Page::setLowPowerModeEnabledOverrideForTesting):
+ (WebCore::updateScriptedAnimationsThrottlingReason):
+ (WebCore::Page::setIsVisuallyIdleInternal):
+ (WebCore::Page::handleLowModePowerChange):
+ * page/Page.h:
+ * testing/Internals.cpp:
+ (WebCore::Internals::resetToConsistentState):
+ (WebCore::Internals::requestAnimationFrameInterval):
+ (WebCore::Internals::setLowPowerModeEnabled):
+ * testing/Internals.h:
+ * testing/Internals.idl:
+
2017-02-28 Youenn Fablet <you...@apple.com>
[WebRTC] Limit libwebrtc logging in Debug build
Modified: trunk/Source/WebCore/dom/Document.cpp (213168 => 213169)
--- trunk/Source/WebCore/dom/Document.cpp 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Source/WebCore/dom/Document.cpp 2017-02-28 21:26:27 UTC (rev 213169)
@@ -5481,12 +5481,6 @@
m_scriptedAnimationController->resume();
}
-void Document::scriptedAnimationControllerSetThrottled(bool isThrottled)
-{
- if (m_scriptedAnimationController)
- m_scriptedAnimationController->setThrottled(isThrottled);
-}
-
void Document::windowScreenDidChange(PlatformDisplayID displayID)
{
if (m_scriptedAnimationController)
@@ -6058,9 +6052,9 @@
{
if (!m_scriptedAnimationController) {
#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
- m_scriptedAnimationController = ScriptedAnimationController::create(this, page() ? page()->chrome().displayID() : 0);
+ m_scriptedAnimationController = ScriptedAnimationController::create(*this, page() ? page()->chrome().displayID() : 0);
#else
- m_scriptedAnimationController = ScriptedAnimationController::create(this, 0);
+ m_scriptedAnimationController = ScriptedAnimationController::create(*this, 0);
#endif
// It's possible that the Page may have suspended scripted animations before
// we were created. We need to make sure that we don't start up the animation
Modified: trunk/Source/WebCore/dom/Document.h (213168 => 213169)
--- trunk/Source/WebCore/dom/Document.h 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Source/WebCore/dom/Document.h 2017-02-28 21:26:27 UTC (rev 213169)
@@ -958,7 +958,6 @@
ScriptedAnimationController* scriptedAnimationController() { return m_scriptedAnimationController.get(); }
void suspendScriptedAnimationControllerCallbacks();
void resumeScriptedAnimationControllerCallbacks();
- void scriptedAnimationControllerSetThrottled(bool);
void windowScreenDidChange(PlatformDisplayID);
Modified: trunk/Source/WebCore/dom/ScriptedAnimationController.cpp (213168 => 213169)
--- trunk/Source/WebCore/dom/ScriptedAnimationController.cpp 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Source/WebCore/dom/ScriptedAnimationController.cpp 2017-02-28 21:26:27 UTC (rev 213169)
@@ -40,6 +40,7 @@
#include "Settings.h"
#include <wtf/Ref.h>
#include <wtf/SystemTracing.h>
+#include <wtf/text/StringBuilder.h>
#if USE(REQUEST_ANIMATION_FRAME_TIMER)
#include <algorithm>
@@ -46,19 +47,27 @@
#include <wtf/CurrentTime.h>
// Allow a little more than 60fps to make sure we can at least hit that frame rate.
-#define MinimumAnimationInterval 0.015
-#define MinimumThrottledAnimationInterval 10
+static const Seconds fullSpeedAnimationInterval { 0.015 };
+// Allow a little more than 30fps to make sure we can at least hit that frame rate.
+static const Seconds halfSpeedThrottlingAnimationInterval { 0.030 };
+static const Seconds aggressiveThrottlingAnimationInterval { 10 };
#endif
+#define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(page() && page()->isAlwaysOnLoggingAllowed(), PerformanceLogging, "%p - ScriptedAnimationController::" fmt, this, ##__VA_ARGS__)
+
namespace WebCore {
-ScriptedAnimationController::ScriptedAnimationController(Document* document, PlatformDisplayID displayID)
- : m_document(document)
+ScriptedAnimationController::ScriptedAnimationController(Document& document, PlatformDisplayID displayID)
+ : m_document(&document)
#if USE(REQUEST_ANIMATION_FRAME_TIMER)
, m_animationTimer(*this, &ScriptedAnimationController::animationTimerFired)
#endif
{
windowScreenDidChange(displayID);
+
+ auto* page = document.page();
+ if (page && page->isLowPowerModeEnabled())
+ addThrottlingReason(ThrottlingReason::LowPowerMode);
}
ScriptedAnimationController::~ScriptedAnimationController()
@@ -86,28 +95,78 @@
scheduleAnimation();
}
-void ScriptedAnimationController::setThrottled(bool isThrottled)
+#if USE(REQUEST_ANIMATION_FRAME_TIMER) && USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR) && !RELEASE_LOG_DISABLED
+
+static const char* throttlingReasonToString(ScriptedAnimationController::ThrottlingReason reason)
{
+ switch (reason) {
+ case ScriptedAnimationController::ThrottlingReason::VisuallyIdle:
+ return "VisuallyIdle";
+ case ScriptedAnimationController::ThrottlingReason::OutsideViewport:
+ return "OutsideViewport";
+ case ScriptedAnimationController::ThrottlingReason::LowPowerMode:
+ return "LowPowerMode";
+ }
+}
+
+static String throttlingReasonsToString(OptionSet<ScriptedAnimationController::ThrottlingReason> reasons)
+{
+ if (reasons.isEmpty())
+ return ASCIILiteral("[Unthrottled]");
+
+ StringBuilder builder;
+ for (auto reason : reasons) {
+ if (!builder.isEmpty())
+ builder.append('|');
+ builder.append(throttlingReasonToString(reason));
+ }
+ return builder.toString();
+}
+
+#endif
+
+void ScriptedAnimationController::addThrottlingReason(ThrottlingReason reason)
+{
#if USE(REQUEST_ANIMATION_FRAME_TIMER) && USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
- if (m_isThrottled == isThrottled)
+ if (m_throttlingReasons.contains(reason))
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_throttlingReasons |= reason;
- m_isThrottled = isThrottled;
+ RELEASE_LOG_IF_ALLOWED("addThrottlingReason(%s) -> %s", throttlingReasonToString(reason), throttlingReasonsToString(m_throttlingReasons).utf8().data());
+
if (m_animationTimer.isActive()) {
m_animationTimer.stop();
scheduleAnimation();
}
#else
- UNUSED_PARAM(isThrottled);
+ UNUSED_PARAM(reason);
#endif
}
+void ScriptedAnimationController::removeThrottlingReason(ThrottlingReason reason)
+{
+#if USE(REQUEST_ANIMATION_FRAME_TIMER) && USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+ if (!m_throttlingReasons.contains(reason))
+ return;
+
+ m_throttlingReasons -= reason;
+
+ RELEASE_LOG_IF_ALLOWED("removeThrottlingReason(%s) -> %s", throttlingReasonToString(reason), throttlingReasonsToString(m_throttlingReasons).utf8().data());
+
+ if (m_animationTimer.isActive()) {
+ m_animationTimer.stop();
+ scheduleAnimation();
+ }
+#else
+ UNUSED_PARAM(reason);
+#endif
+}
+
bool ScriptedAnimationController::isThrottled() const
{
#if USE(REQUEST_ANIMATION_FRAME_TIMER) && USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
- return m_isThrottled;
+ return !m_throttlingReasons.isEmpty();
#else
return false;
#endif
@@ -192,6 +251,23 @@
#endif
}
+Seconds ScriptedAnimationController::interval() const
+{
+#if USE(REQUEST_ANIMATION_FRAME_TIMER) && USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
+ if (m_throttlingReasons.contains(ThrottlingReason::VisuallyIdle) || m_throttlingReasons.contains(ThrottlingReason::OutsideViewport))
+ return aggressiveThrottlingAnimationInterval;
+ if (m_throttlingReasons.contains(ThrottlingReason::LowPowerMode))
+ return halfSpeedThrottlingAnimationInterval;
+ ASSERT(m_throttlingReasons.isEmpty());
+#endif
+ return fullSpeedAnimationInterval;
+}
+
+Page* ScriptedAnimationController::page() const
+{
+ return m_document ? m_document->page() : nullptr;
+}
+
void ScriptedAnimationController::scheduleAnimation()
{
if (!requestAnimationFrameEnabled())
@@ -199,7 +275,7 @@
#if USE(REQUEST_ANIMATION_FRAME_TIMER)
#if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
- if (!m_isUsingTimer && !m_isThrottled) {
+ if (!m_isUsingTimer && !isThrottled()) {
if (DisplayRefreshMonitorManager::sharedManager().scheduleAnimation(*this))
return;
@@ -209,13 +285,8 @@
if (m_animationTimer.isActive())
return;
- double animationInterval = MinimumAnimationInterval;
-#if USE(REQUEST_ANIMATION_FRAME_TIMER) && USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR)
- if (m_isThrottled)
- animationInterval = MinimumThrottledAnimationInterval;
-#endif
-
- double scheduleDelay = std::max<double>(animationInterval - (m_document->domWindow()->nowTimestamp() - m_lastAnimationFrameTimestamp), 0);
+ Seconds animationInterval = interval();
+ double scheduleDelay = std::max<double>(animationInterval.value() - (m_document->domWindow()->nowTimestamp() - m_lastAnimationFrameTimestamp), 0);
m_animationTimer.startOneShot(scheduleDelay);
#else
if (FrameView* frameView = m_document->view())
Modified: trunk/Source/WebCore/dom/ScriptedAnimationController.h (213168 => 213169)
--- trunk/Source/WebCore/dom/ScriptedAnimationController.h 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Source/WebCore/dom/ScriptedAnimationController.h 2017-02-28 21:26:27 UTC (rev 213169)
@@ -27,6 +27,7 @@
#include "DOMTimeStamp.h"
#include "PlatformScreen.h"
+#include <wtf/OptionSet.h>
#include <wtf/RefCounted.h>
#include <wtf/RefPtr.h>
#include <wtf/Vector.h>
@@ -44,6 +45,7 @@
namespace WebCore {
class Document;
+class Page;
class RequestAnimationFrameCallback;
class ScriptedAnimationController : public RefCounted<ScriptedAnimationController>
@@ -52,7 +54,7 @@
#endif
{
public:
- static Ref<ScriptedAnimationController> create(Document* document, PlatformDisplayID displayID)
+ static Ref<ScriptedAnimationController> create(Document& document, PlatformDisplayID displayID)
{
return adoptRef(*new ScriptedAnimationController(document, displayID));
}
@@ -68,14 +70,24 @@
void suspend();
void resume();
- void setThrottled(bool);
+
+ enum class ThrottlingReason {
+ VisuallyIdle = 1 << 0,
+ OutsideViewport = 1 << 1,
+ LowPowerMode = 1 << 2,
+ };
+ void addThrottlingReason(ThrottlingReason);
+ void removeThrottlingReason(ThrottlingReason);
WEBCORE_EXPORT bool isThrottled() const;
+ WEBCORE_EXPORT Seconds interval() const;
void windowScreenDidChange(PlatformDisplayID);
private:
- ScriptedAnimationController(Document*, PlatformDisplayID);
+ ScriptedAnimationController(Document&, PlatformDisplayID);
+ Page* page() const;
+
typedef Vector<RefPtr<RequestAnimationFrameCallback>> CallbackList;
CallbackList m_callbacks;
@@ -97,7 +109,8 @@
RefPtr<DisplayRefreshMonitor> createDisplayRefreshMonitor(PlatformDisplayID) const override;
bool m_isUsingTimer { false };
- bool m_isThrottled { false };
+
+ OptionSet<ThrottlingReason> m_throttlingReasons;
#endif
};
Modified: trunk/Source/WebCore/page/FrameView.cpp (213168 => 213169)
--- trunk/Source/WebCore/page/FrameView.cpp 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Source/WebCore/page/FrameView.cpp 2017-02-28 21:26:27 UTC (rev 213169)
@@ -2563,8 +2563,12 @@
// We don't throttle zero-size or display:none frames because those are usually utility frames.
bool shouldThrottle = visibleRect.isEmpty() && !m_size.isEmpty() && frame().ownerRenderer();
- if (auto* scriptedAnimationController = document->scriptedAnimationController())
- scriptedAnimationController->setThrottled(shouldThrottle);
+ if (auto* scriptedAnimationController = document->scriptedAnimationController()) {
+ if (shouldThrottle)
+ scriptedAnimationController->addThrottlingReason(ScriptedAnimationController::ThrottlingReason::OutsideViewport);
+ else
+ scriptedAnimationController->removeThrottlingReason(ScriptedAnimationController::ThrottlingReason::OutsideViewport);
+ }
document->setTimerThrottlingEnabled(shouldThrottle);
}
Modified: trunk/Source/WebCore/page/Page.cpp (213168 => 213169)
--- trunk/Source/WebCore/page/Page.cpp 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Source/WebCore/page/Page.cpp 2017-02-28 21:26:27 UTC (rev 213169)
@@ -57,6 +57,7 @@
#include "InspectorInstrumentation.h"
#include "LibWebRTCProvider.h"
#include "Logging.h"
+#include "LowPowerModeNotifier.h"
#include "MainFrame.h"
#include "MediaCanStartListener.h"
#include "Navigator.h"
@@ -84,6 +85,7 @@
#include "SVGDocumentExtensions.h"
#include "SchemeRegistry.h"
#include "ScriptController.h"
+#include "ScriptedAnimationController.h"
#include "ScrollingCoordinator.h"
#include "Settings.h"
#include "SharedBuffer.h"
@@ -263,6 +265,7 @@
, m_isClosing(false)
, m_isUtilityPage(isUtilityPageChromeClient(chrome().client()))
, m_performanceMonitor(isUtilityPage() ? nullptr : std::make_unique<PerformanceMonitor>(*this))
+ , m_lowPowerModeNotifier(std::make_unique<LowPowerModeNotifier>([this](bool isLowPowerModeEnabled) { handleLowModePowerChange(isLowPowerModeEnabled); }))
{
updateTimerThrottlingState();
@@ -954,6 +957,20 @@
return !isUtilityPage() && nonUtilityPageCount == 1;
}
+bool Page::isLowPowerModeEnabled() const
+{
+ if (m_lowPowerModeEnabledOverrideForTesting)
+ return m_lowPowerModeEnabledOverrideForTesting.value();
+
+ return m_lowPowerModeNotifier->isLowPowerModeEnabled();
+}
+
+void Page::setLowPowerModeEnabledOverrideForTesting(std::optional<bool> isEnabled)
+{
+ m_lowPowerModeEnabledOverrideForTesting = isEnabled;
+ handleLowModePowerChange(m_lowPowerModeEnabledOverrideForTesting.value_or(false));
+}
+
void Page::setTopContentInset(float contentInset)
{
if (m_topContentInset == contentInset)
@@ -1094,14 +1111,34 @@
}
}
-void Page::setIsVisuallyIdleInternal(bool isVisuallyIdle)
+enum class ThrottlingReasonOperation { Add, Remove };
+static void updateScriptedAnimationsThrottlingReason(Page& page, ThrottlingReasonOperation operation, ScriptedAnimationController::ThrottlingReason reason)
{
- for (Frame* frame = &mainFrame(); frame; frame = frame->tree().traverseNext()) {
- if (frame->document())
- frame->document()->scriptedAnimationControllerSetThrottled(isVisuallyIdle);
+ for (Frame* frame = &page.mainFrame(); frame; frame = frame->tree().traverseNext()) {
+ auto* document = frame->document();
+ if (!document)
+ continue;
+ auto* scriptedAnimationController = document->scriptedAnimationController();
+ if (!scriptedAnimationController)
+ continue;
+
+ if (operation == ThrottlingReasonOperation::Add)
+ scriptedAnimationController->addThrottlingReason(reason);
+ else
+ scriptedAnimationController->removeThrottlingReason(reason);
}
}
+void Page::setIsVisuallyIdleInternal(bool isVisuallyIdle)
+{
+ updateScriptedAnimationsThrottlingReason(*this, isVisuallyIdle ? ThrottlingReasonOperation::Add : ThrottlingReasonOperation::Remove, ScriptedAnimationController::ThrottlingReason::VisuallyIdle);
+}
+
+void Page::handleLowModePowerChange(bool isLowPowerModeEnabled)
+{
+ updateScriptedAnimationsThrottlingReason(*this, isLowPowerModeEnabled ? ThrottlingReasonOperation::Add : ThrottlingReasonOperation::Remove, ScriptedAnimationController::ThrottlingReason::LowPowerMode);
+}
+
void Page::userStyleSheetLocationChanged()
{
// FIXME: Eventually we will move to a model of just being handed the sheet
Modified: trunk/Source/WebCore/page/Page.h (213168 => 213169)
--- trunk/Source/WebCore/page/Page.h 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Source/WebCore/page/Page.h 2017-02-28 21:26:27 UTC (rev 213169)
@@ -99,6 +99,7 @@
class InspectorClient;
class InspectorController;
class LibWebRTCProvider;
+class LowPowerModeNotifier;
class MainFrame;
class MediaCanStartListener;
class MediaPlaybackTarget;
@@ -576,6 +577,9 @@
WEBCORE_EXPORT bool hasSelectionAtPosition(const FloatPoint&) const;
#endif
+ bool isLowPowerModeEnabled() const;
+ WEBCORE_EXPORT void setLowPowerModeEnabledOverrideForTesting(std::optional<bool>);
+
private:
WEBCORE_EXPORT void initGroup();
@@ -598,6 +602,8 @@
Vector<Ref<PluginViewBase>> pluginViews();
+ void handleLowModePowerChange(bool);
+
enum class TimerThrottlingState { Disabled, Enabled, EnabledIncreasing };
void hiddenPageDOMTimerThrottlingStateChanged();
void setTimerThrottlingState(TimerThrottlingState);
@@ -780,6 +786,8 @@
std::optional<EventThrottlingBehavior> m_eventThrottlingBehaviorOverride;
std::unique_ptr<PerformanceMonitor> m_performanceMonitor;
+ std::unique_ptr<LowPowerModeNotifier> m_lowPowerModeNotifier;
+ std::optional<bool> m_lowPowerModeEnabledOverrideForTesting;
bool m_isRunningUserScripts { false };
};
Modified: trunk/Source/WebCore/testing/Internals.cpp (213168 => 213169)
--- trunk/Source/WebCore/testing/Internals.cpp 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Source/WebCore/testing/Internals.cpp 2017-02-28 21:26:27 UTC (rev 213169)
@@ -436,6 +436,7 @@
#endif
page.setShowAllPlugins(false);
+ page.setLowPowerModeEnabledOverrideForTesting(std::nullopt);
#if USE(QUICK_LOOK)
MockQuickLookHandleClient::singleton().setPassword("");
@@ -1019,6 +1020,14 @@
return scriptedAnimationController->isThrottled();
}
+double Internals::requestAnimationFrameInterval() const
+{
+ auto* scriptedAnimationController = contextDocument()->scriptedAnimationController();
+ if (!scriptedAnimationController)
+ return INFINITY;
+ return scriptedAnimationController->interval().value();
+}
+
bool Internals::areTimersThrottled() const
{
return contextDocument()->isTimerThrottlingEnabled();
@@ -1290,6 +1299,19 @@
FontCache::singleton().invalidate();
}
+ExceptionOr<void> Internals::setLowPowerModeEnabled(bool isEnabled)
+{
+ auto* document = contextDocument();
+ if (!document)
+ return Exception { INVALID_ACCESS_ERR };
+ auto* page = document->page();
+ if (!page)
+ return Exception { INVALID_ACCESS_ERR };
+
+ page->setLowPowerModeEnabledOverrideForTesting(isEnabled);
+ return { };
+}
+
ExceptionOr<void> Internals::setScrollViewPosition(int x, int y)
{
Document* document = contextDocument();
Modified: trunk/Source/WebCore/testing/Internals.h (213168 => 213169)
--- trunk/Source/WebCore/testing/Internals.h 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Source/WebCore/testing/Internals.h 2017-02-28 21:26:27 UTC (rev 213169)
@@ -130,6 +130,7 @@
// DOMTimers throttling testing.
ExceptionOr<bool> isTimerThrottled(int timeoutId);
bool isRequestAnimationFrameThrottled() const;
+ double requestAnimationFrameInterval() const;
bool areTimersThrottled() const;
enum EventThrottlingBehavior { Responsive, Unresponsive };
@@ -174,6 +175,7 @@
ExceptionOr<void> setMarkedTextMatchesAreHighlighted(bool);
void invalidateFontCache();
+ ExceptionOr<void> setLowPowerModeEnabled(bool);
ExceptionOr<void> setScrollViewPosition(int x, int y);
Modified: trunk/Source/WebCore/testing/Internals.idl (213168 => 213169)
--- trunk/Source/WebCore/testing/Internals.idl 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Source/WebCore/testing/Internals.idl 2017-02-28 21:26:27 UTC (rev 213169)
@@ -350,6 +350,9 @@
boolean isRequestAnimationFrameThrottled();
boolean areTimersThrottled();
+ [MayThrowException] void setLowPowerModeEnabled(boolean enabled);
+ readonly attribute double requestAnimationFrameInterval;
+
// Override the behavior of WebPage::eventThrottlingDelay(), which only affects iOS.
attribute EventThrottlingBehavior? eventThrottlingBehaviorOverride;
Modified: trunk/Tools/ChangeLog (213168 => 213169)
--- trunk/Tools/ChangeLog 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Tools/ChangeLog 2017-02-28 21:26:27 UTC (rev 213169)
@@ -1,3 +1,16 @@
+2017-02-28 Chris Dumez <cdu...@apple.com>
+
+ [iOS] Throttle requestAnimationFrame to 30fps in low power mode
+ https://bugs.webkit.org/show_bug.cgi?id=168837
+ <rdar://problem/30700929>
+
+ Reviewed by Simon Fraser.
+
+ Add unit test for -= operator on WTF::OptionSet.
+
+ * TestWebKitAPI/Tests/WTF/OptionSet.cpp:
+ (TestWebKitAPI::TEST):
+
2017-02-28 Jonathan Bedard <jbed...@apple.com>
webkitpy: Regular _expression_ for parsing simctl device information is wrong for iPad Pro
Modified: trunk/Tools/TestWebKitAPI/Tests/WTF/OptionSet.cpp (213168 => 213169)
--- trunk/Tools/TestWebKitAPI/Tests/WTF/OptionSet.cpp 2017-02-28 21:24:50 UTC (rev 213168)
+++ trunk/Tools/TestWebKitAPI/Tests/WTF/OptionSet.cpp 2017-02-28 21:26:27 UTC (rev 213169)
@@ -86,6 +86,18 @@
EXPECT_TRUE((set - set).isEmpty());
}
+TEST(WTF_OptionSet, MinusEqual)
+{
+ OptionSet<ExampleFlags> set { ExampleFlags::A, ExampleFlags::B, ExampleFlags::C };
+
+ EXPECT_TRUE(((set -= ExampleFlags::A) == OptionSet<ExampleFlags> { ExampleFlags::B, ExampleFlags::C }));
+ EXPECT_TRUE((set == OptionSet<ExampleFlags> { ExampleFlags::B, ExampleFlags::C }));
+ EXPECT_TRUE(((set -= ExampleFlags::D) == OptionSet<ExampleFlags> { ExampleFlags::B, ExampleFlags::C }));
+ EXPECT_TRUE((set == OptionSet<ExampleFlags> { ExampleFlags::B, ExampleFlags::C }));
+ EXPECT_TRUE((set -= set).isEmpty());
+ EXPECT_TRUE(set.isEmpty());
+}
+
TEST(WTF_OptionSet, ContainsTwoFlags)
{
OptionSet<ExampleFlags> set { ExampleFlags::A, ExampleFlags::B };