diff --git a/examples/fiber/index.html b/examples/fiber/index.html
index d824f2a6311af..7a56ca0b9da22 100644
--- a/examples/fiber/index.html
+++ b/examples/fiber/index.html
@@ -90,7 +90,7 @@
Fiber Example
return r;
}
var newSize = s / 2;
- var slowDown = false;
+ var slowDown = true;
if (slowDown) {
var e = performance.now() + 0.8;
while (performance.now() < e) {
diff --git a/src/renderers/art/ReactARTFiber.js b/src/renderers/art/ReactARTFiber.js
index ee9aa7d85915b..955a3648f9c84 100644
--- a/src/renderers/art/ReactARTFiber.js
+++ b/src/renderers/art/ReactARTFiber.js
@@ -21,6 +21,7 @@ const invariant = require('fbjs/lib/invariant');
const emptyObject = require('emptyObject');
const React = require('React');
const ReactFiberReconciler = require('ReactFiberReconciler');
+const ReactDOMFrameScheduling = require('ReactDOMFrameScheduling');
const { Component } = React;
@@ -507,9 +508,9 @@ const ARTRenderer = ReactFiberReconciler({
return emptyObject;
},
- scheduleAnimationCallback: window.requestAnimationFrame,
+ scheduleAnimationCallback: ReactDOMFrameScheduling.rAF,
- scheduleDeferredCallback: window.requestIdleCallback,
+ scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,
shouldSetTextContent(props) {
return (
diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js
index fc7cb30e83a96..60ed4718ce981 100644
--- a/src/renderers/dom/fiber/ReactDOMFiber.js
+++ b/src/renderers/dom/fiber/ReactDOMFiber.js
@@ -20,6 +20,7 @@ var ReactControlledComponent = require('ReactControlledComponent');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
var ReactDOMFiberComponent = require('ReactDOMFiberComponent');
+var ReactDOMFrameScheduling = require('ReactDOMFrameScheduling');
var ReactDOMInjection = require('ReactDOMInjection');
var ReactGenericBatching = require('ReactGenericBatching');
var ReactFiberReconciler = require('ReactFiberReconciler');
@@ -296,9 +297,9 @@ var DOMRenderer = ReactFiberReconciler({
parentInstance.removeChild(child);
},
- scheduleAnimationCallback: window.requestAnimationFrame,
+ scheduleAnimationCallback: ReactDOMFrameScheduling.rAF,
- scheduleDeferredCallback: window.requestIdleCallback,
+ scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,
useSyncScheduling: true,
diff --git a/src/renderers/dom/fiber/ReactDOMFrameScheduling.js b/src/renderers/dom/fiber/ReactDOMFrameScheduling.js
new file mode 100644
index 0000000000000..2ef1d331e3098
--- /dev/null
+++ b/src/renderers/dom/fiber/ReactDOMFrameScheduling.js
@@ -0,0 +1,149 @@
+/**
+ * Copyright 2013-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @providesModule ReactDOMFrameScheduling
+ * @flow
+ */
+
+'use strict';
+
+// This a built-in polyfill for requestIdleCallback. It works by scheduling
+// a requestAnimationFrame, store the time for the start of the frame, then
+// schedule a postMessage which gets scheduled after paint. Within the
+// postMessage handler do as much work as possible until time + frame rate.
+// By separating the idle call into a separate event tick we ensure that
+// layout, paint and other browser work is counted against the available time.
+// The frame rate is dynamically adjusted.
+
+import type { Deadline } from 'ReactFiberReconciler';
+
+var invariant = require('invariant');
+
+// TODO: There's no way to cancel these, because Fiber doesn't atm.
+let rAF : (callback : (time : number) => void) => number;
+let rIC : (callback : (deadline : Deadline) => void) => number;
+if (typeof requestAnimationFrame !== 'function') {
+ invariant(
+ false,
+ 'React depends on requestAnimationFrame. Make sure that you load a ' +
+ 'polyfill in older browsers.'
+ );
+} else if (typeof requestIdleCallback !== 'function') {
+ // Wrap requestAnimationFrame and polyfill requestIdleCallback.
+
+ var scheduledRAFCallback = null;
+ var scheduledRICCallback = null;
+
+ var isIdleScheduled = false;
+ var isAnimationFrameScheduled = false;
+
+ var frameDeadline = 0;
+ // We start out assuming that we run at 30fps but then the heuristic tracking
+ // will adjust this value to a faster fps if we get more frequent animation
+ // frames.
+ var previousFrameTime = 33;
+ var activeFrameTime = 33;
+
+ var frameDeadlineObject = {
+ timeRemaining: (
+ typeof performance === 'object' &&
+ typeof performance.now === 'function' ? function() {
+ // We assume that if we have a performance timer that the rAF callback
+ // gets a performance timer value. Not sure if this is always true.
+ return frameDeadline - performance.now();
+ } : function() {
+ // As a fallback we use Date.now.
+ return frameDeadline - Date.now();
+ }
+ ),
+ };
+
+ // We use the postMessage trick to defer idle work until after the repaint.
+ var messageKey =
+ '__reactIdleCallback$' + Math.random().toString(36).slice(2);
+ var idleTick = function(event) {
+ if (event.source !== window || event.data !== messageKey) {
+ return;
+ }
+ isIdleScheduled = false;
+ var callback = scheduledRICCallback;
+ scheduledRICCallback = null;
+ if (callback) {
+ callback(frameDeadlineObject);
+ }
+ };
+ // Assumes that we have addEventListener in this environment. Might need
+ // something better for old IE.
+ window.addEventListener('message', idleTick, false);
+
+ var animationTick = function(rafTime) {
+ isAnimationFrameScheduled = false;
+ var nextFrameTime = rafTime - frameDeadline + activeFrameTime;
+ if (nextFrameTime < activeFrameTime && previousFrameTime < activeFrameTime) {
+ if (nextFrameTime < 8) {
+ // Defensive coding. We don't support higher frame rates than 120hz.
+ // If we get lower than that, it is probably a bug.
+ nextFrameTime = 8;
+ }
+ // If one frame goes long, then the next one can be short to catch up.
+ // If two frames are short in a row, then that's an indication that we
+ // actually have a higher frame rate than what we're currently optimizing.
+ // We adjust our heuristic dynamically accordingly. For example, if we're
+ // running on 120hz display or 90hz VR display.
+ // Take the max of the two in case one of them was an anomaly due to
+ // missed frame deadlines.
+ activeFrameTime = nextFrameTime < previousFrameTime ?
+ previousFrameTime : nextFrameTime;
+ } else {
+ previousFrameTime = nextFrameTime;
+ }
+ frameDeadline = rafTime + activeFrameTime;
+ if (!isIdleScheduled) {
+ isIdleScheduled = true;
+ window.postMessage(messageKey, '*');
+ }
+ var callback = scheduledRAFCallback;
+ scheduledRAFCallback = null;
+ if (callback) {
+ callback(rafTime);
+ }
+ };
+
+ rAF = function(callback : (time : number) => void) : number {
+ // This assumes that we only schedule one callback at a time because that's
+ // how Fiber uses it.
+ scheduledRAFCallback = callback;
+ if (!isAnimationFrameScheduled) {
+ // If rIC didn't already schedule one, we need to schedule a frame.
+ isAnimationFrameScheduled = true;
+ requestAnimationFrame(animationTick);
+ }
+ return 0;
+ };
+
+ rIC = function(callback : (deadline : Deadline) => void) : number {
+ // This assumes that we only schedule one callback at a time because that's
+ // how Fiber uses it.
+ scheduledRICCallback = callback;
+ if (!isAnimationFrameScheduled) {
+ // If rAF didn't already schedule one, we need to schedule a frame.
+ // TODO: If this rAF doesn't materialize because the browser throttles, we
+ // might want to still have setTimeout trigger rIC as a backup to ensure
+ // that we keep performing work.
+ isAnimationFrameScheduled = true;
+ requestAnimationFrame(animationTick);
+ }
+ return 0;
+ };
+} else {
+ rAF = requestAnimationFrame;
+ rIC = requestIdleCallback;
+}
+
+exports.rAF = rAF;
+exports.rIC = rIC;
diff --git a/src/renderers/shared/fiber/ReactFiberReconciler.js b/src/renderers/shared/fiber/ReactFiberReconciler.js
index 52cc90f57d362..b87f2bca33c8e 100644
--- a/src/renderers/shared/fiber/ReactFiberReconciler.js
+++ b/src/renderers/shared/fiber/ReactFiberReconciler.js
@@ -67,8 +67,8 @@ export type HostConfig = {
insertBefore(parentInstance : I | C, child : I | TI, beforeChild : I | TI) : void,
removeChild(parentInstance : I | C, child : I | TI) : void,
- scheduleAnimationCallback(callback : () => void) : void,
- scheduleDeferredCallback(callback : (deadline : Deadline) => void) : void,
+ scheduleAnimationCallback(callback : () => void) : number | void,
+ scheduleDeferredCallback(callback : (deadline : Deadline) => void) : number | void,
prepareForCommit() : void,
resetAfterCommit() : void,