Mozart: Generalize frame scheduling.

Renamed SceneScheduler to FrameScheduler.

Added a SceneScheduler to the Renderer so that clients of the renderer
(like the view manager) can schedule frames more globally without
having to publish a scene.

Added some basic integration tests for the scheduling API.

BUG=
R=mikejurka@google.com

Review URL: https://codereview.chromium.org/1997513002 .
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/renderers.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/renderers.mojom.dart
index 37944ab..0b3eb61 100644
--- a/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/renderers.mojom.dart
+++ b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/renderers.mojom.dart
@@ -10,6 +10,7 @@
 import 'package:mojo_services/mojo/geometry.mojom.dart' as geometry_mojom;
 import 'package:mojo_services/mojo/gfx/composition/hit_tests.mojom.dart' as hit_tests_mojom;
 import 'package:mojo_services/mojo/gfx/composition/scene_token.mojom.dart' as scene_token_mojom;
+import 'package:mojo_services/mojo/gfx/composition/scheduling.mojom.dart' as scheduling_mojom;
 
 
 
@@ -173,6 +174,77 @@
 }
 
 
+class _RendererGetSchedulerParams extends bindings.Struct {
+  static const List<bindings.StructDataHeader> kVersions = const [
+    const bindings.StructDataHeader(16, 0)
+  ];
+  Object scheduler = null;
+
+  _RendererGetSchedulerParams() : super(kVersions.last.size);
+
+  static _RendererGetSchedulerParams deserialize(bindings.Message message) {
+    var decoder = new bindings.Decoder(message);
+    var result = decode(decoder);
+    if (decoder.excessHandles != null) {
+      decoder.excessHandles.forEach((h) => h.close());
+    }
+    return result;
+  }
+
+  static _RendererGetSchedulerParams decode(bindings.Decoder decoder0) {
+    if (decoder0 == null) {
+      return null;
+    }
+    _RendererGetSchedulerParams result = new _RendererGetSchedulerParams();
+
+    var mainDataHeader = decoder0.decodeStructDataHeader();
+    if (mainDataHeader.version <= kVersions.last.version) {
+      // Scan in reverse order to optimize for more recent versions.
+      for (int i = kVersions.length - 1; i >= 0; --i) {
+        if (mainDataHeader.version >= kVersions[i].version) {
+          if (mainDataHeader.size == kVersions[i].size) {
+            // Found a match.
+            break;
+          }
+          throw new bindings.MojoCodecError(
+              'Header size doesn\'t correspond to known version size.');
+        }
+      }
+    } else if (mainDataHeader.size < kVersions.last.size) {
+      throw new bindings.MojoCodecError(
+        'Message newer than the last known version cannot be shorter than '
+        'required by the last known version.');
+    }
+    if (mainDataHeader.version >= 0) {
+      
+      result.scheduler = decoder0.decodeInterfaceRequest(8, false, scheduling_mojom.FrameSchedulerStub.newFromEndpoint);
+    }
+    return result;
+  }
+
+  void encode(bindings.Encoder encoder) {
+    var encoder0 = encoder.getStructEncoderAtOffset(kVersions.last);
+    try {
+      encoder0.encodeInterfaceRequest(scheduler, 8, false);
+    } on bindings.MojoCodecError catch(e) {
+      e.message = "Error encountered while encoding field "
+          "scheduler of struct _RendererGetSchedulerParams: $e";
+      rethrow;
+    }
+  }
+
+  String toString() {
+    return "_RendererGetSchedulerParams("
+           "scheduler: $scheduler" ")";
+  }
+
+  Map toJson() {
+    throw new bindings.MojoCodecError(
+        'Object containing handles cannot be encoded to JSON.');
+  }
+}
+
+
 class _RendererGetHitTesterParams extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(16, 0)
@@ -245,7 +317,8 @@
 
 const int _rendererMethodSetRootSceneName = 0;
 const int _rendererMethodClearRootSceneName = 1;
-const int _rendererMethodGetHitTesterName = 2;
+const int _rendererMethodGetSchedulerName = 2;
+const int _rendererMethodGetHitTesterName = 3;
 
 class _RendererServiceDescription implements service_describer.ServiceDescription {
   dynamic getTopLevelInterface([Function responseFactory]) =>
@@ -262,6 +335,7 @@
   static const String serviceName = null;
   void setRootScene(scene_token_mojom.SceneToken sceneToken, int sceneVersion, geometry_mojom.Rect viewport);
   void clearRootScene();
+  void getScheduler(Object scheduler);
   void getHitTester(Object hitTester);
 }
 
@@ -345,6 +419,16 @@
     ctrl.sendMessage(params,
         _rendererMethodClearRootSceneName);
   }
+  void getScheduler(Object scheduler) {
+    if (!ctrl.isBound) {
+      ctrl.proxyError("The Proxy is closed.");
+      return;
+    }
+    var params = new _RendererGetSchedulerParams();
+    params.scheduler = scheduler;
+    ctrl.sendMessage(params,
+        _rendererMethodGetSchedulerName);
+  }
   void getHitTester(Object hitTester) {
     if (!ctrl.isBound) {
       ctrl.proxyError("The Proxy is closed.");
@@ -396,6 +480,11 @@
       case _rendererMethodClearRootSceneName:
         _impl.clearRootScene();
         break;
+      case _rendererMethodGetSchedulerName:
+        var params = _RendererGetSchedulerParams.deserialize(
+            message.payload);
+        _impl.getScheduler(params.scheduler);
+        break;
       case _rendererMethodGetHitTesterName:
         var params = _RendererGetHitTesterParams.deserialize(
             message.payload);
@@ -474,6 +563,9 @@
   void clearRootScene() {
     return impl.clearRootScene();
   }
+  void getScheduler(Object scheduler) {
+    return impl.getScheduler(scheduler);
+  }
   void getHitTester(Object hitTester) {
     return impl.getHitTester(hitTester);
   }
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scenes.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scenes.mojom.dart
index ea32f42..72e3cec 100644
--- a/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scenes.mojom.dart
+++ b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scenes.mojom.dart
@@ -549,7 +549,7 @@
     }
     if (mainDataHeader.version >= 0) {
       
-      result.scheduler = decoder0.decodeInterfaceRequest(8, false, scheduling_mojom.SceneSchedulerStub.newFromEndpoint);
+      result.scheduler = decoder0.decodeInterfaceRequest(8, false, scheduling_mojom.FrameSchedulerStub.newFromEndpoint);
     }
     return result;
   }
diff --git a/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scheduling.mojom.dart b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scheduling.mojom.dart
index b6d5908..b3128ce 100644
--- a/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scheduling.mojom.dart
+++ b/mojo/dart/packages/mojo_services/lib/mojo/gfx/composition/scheduling.mojom.dart
@@ -124,14 +124,14 @@
 }
 
 
-class _SceneSchedulerScheduleFrameParams extends bindings.Struct {
+class _FrameSchedulerScheduleFrameParams extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(8, 0)
   ];
 
-  _SceneSchedulerScheduleFrameParams() : super(kVersions.last.size);
+  _FrameSchedulerScheduleFrameParams() : super(kVersions.last.size);
 
-  static _SceneSchedulerScheduleFrameParams deserialize(bindings.Message message) {
+  static _FrameSchedulerScheduleFrameParams deserialize(bindings.Message message) {
     var decoder = new bindings.Decoder(message);
     var result = decode(decoder);
     if (decoder.excessHandles != null) {
@@ -140,11 +140,11 @@
     return result;
   }
 
-  static _SceneSchedulerScheduleFrameParams decode(bindings.Decoder decoder0) {
+  static _FrameSchedulerScheduleFrameParams decode(bindings.Decoder decoder0) {
     if (decoder0 == null) {
       return null;
     }
-    _SceneSchedulerScheduleFrameParams result = new _SceneSchedulerScheduleFrameParams();
+    _FrameSchedulerScheduleFrameParams result = new _FrameSchedulerScheduleFrameParams();
 
     var mainDataHeader = decoder0.decodeStructDataHeader();
     if (mainDataHeader.version <= kVersions.last.version) {
@@ -172,7 +172,7 @@
   }
 
   String toString() {
-    return "_SceneSchedulerScheduleFrameParams("")";
+    return "_FrameSchedulerScheduleFrameParams("")";
   }
 
   Map toJson() {
@@ -182,15 +182,15 @@
 }
 
 
-class SceneSchedulerScheduleFrameResponseParams extends bindings.Struct {
+class FrameSchedulerScheduleFrameResponseParams extends bindings.Struct {
   static const List<bindings.StructDataHeader> kVersions = const [
     const bindings.StructDataHeader(16, 0)
   ];
   FrameInfo frameInfo = null;
 
-  SceneSchedulerScheduleFrameResponseParams() : super(kVersions.last.size);
+  FrameSchedulerScheduleFrameResponseParams() : super(kVersions.last.size);
 
-  static SceneSchedulerScheduleFrameResponseParams deserialize(bindings.Message message) {
+  static FrameSchedulerScheduleFrameResponseParams deserialize(bindings.Message message) {
     var decoder = new bindings.Decoder(message);
     var result = decode(decoder);
     if (decoder.excessHandles != null) {
@@ -199,11 +199,11 @@
     return result;
   }
 
-  static SceneSchedulerScheduleFrameResponseParams decode(bindings.Decoder decoder0) {
+  static FrameSchedulerScheduleFrameResponseParams decode(bindings.Decoder decoder0) {
     if (decoder0 == null) {
       return null;
     }
-    SceneSchedulerScheduleFrameResponseParams result = new SceneSchedulerScheduleFrameResponseParams();
+    FrameSchedulerScheduleFrameResponseParams result = new FrameSchedulerScheduleFrameResponseParams();
 
     var mainDataHeader = decoder0.decodeStructDataHeader();
     if (mainDataHeader.version <= kVersions.last.version) {
@@ -237,13 +237,13 @@
       encoder0.encodeStruct(frameInfo, 8, false);
     } on bindings.MojoCodecError catch(e) {
       e.message = "Error encountered while encoding field "
-          "frameInfo of struct SceneSchedulerScheduleFrameResponseParams: $e";
+          "frameInfo of struct FrameSchedulerScheduleFrameResponseParams: $e";
       rethrow;
     }
   }
 
   String toString() {
-    return "SceneSchedulerScheduleFrameResponseParams("
+    return "FrameSchedulerScheduleFrameResponseParams("
            "frameInfo: $frameInfo" ")";
   }
 
@@ -254,9 +254,9 @@
   }
 }
 
-const int _sceneSchedulerMethodScheduleFrameName = 0;
+const int _frameSchedulerMethodScheduleFrameName = 0;
 
-class _SceneSchedulerServiceDescription implements service_describer.ServiceDescription {
+class _FrameSchedulerServiceDescription implements service_describer.ServiceDescription {
   dynamic getTopLevelInterface([Function responseFactory]) =>
       responseFactory(null);
 
@@ -267,31 +267,31 @@
       responseFactory(null);
 }
 
-abstract class SceneScheduler {
+abstract class FrameScheduler {
   static const String serviceName = null;
   dynamic scheduleFrame([Function responseFactory = null]);
 }
 
-class _SceneSchedulerProxyControl
+class _FrameSchedulerProxyControl
     extends bindings.ProxyMessageHandler
     implements bindings.ProxyControl {
-  _SceneSchedulerProxyControl.fromEndpoint(
+  _FrameSchedulerProxyControl.fromEndpoint(
       core.MojoMessagePipeEndpoint endpoint) : super.fromEndpoint(endpoint);
 
-  _SceneSchedulerProxyControl.fromHandle(
+  _FrameSchedulerProxyControl.fromHandle(
       core.MojoHandle handle) : super.fromHandle(handle);
 
-  _SceneSchedulerProxyControl.unbound() : super.unbound();
+  _FrameSchedulerProxyControl.unbound() : super.unbound();
 
   service_describer.ServiceDescription get serviceDescription =>
-      new _SceneSchedulerServiceDescription();
+      new _FrameSchedulerServiceDescription();
 
-  String get serviceName => SceneScheduler.serviceName;
+  String get serviceName => FrameScheduler.serviceName;
 
   void handleResponse(bindings.ServiceMessage message) {
     switch (message.header.type) {
-      case _sceneSchedulerMethodScheduleFrameName:
-        var r = SceneSchedulerScheduleFrameResponseParams.deserialize(
+      case _frameSchedulerMethodScheduleFrameName:
+        var r = FrameSchedulerScheduleFrameResponseParams.deserialize(
             message.payload);
         if (!message.header.hasRequestId) {
           proxyError("Expected a message with a valid request Id.");
@@ -320,69 +320,69 @@
   @override
   String toString() {
     var superString = super.toString();
-    return "_SceneSchedulerProxyControl($superString)";
+    return "_FrameSchedulerProxyControl($superString)";
   }
 }
 
-class SceneSchedulerProxy
+class FrameSchedulerProxy
     extends bindings.Proxy
-    implements SceneScheduler {
-  SceneSchedulerProxy.fromEndpoint(
+    implements FrameScheduler {
+  FrameSchedulerProxy.fromEndpoint(
       core.MojoMessagePipeEndpoint endpoint)
-      : super(new _SceneSchedulerProxyControl.fromEndpoint(endpoint));
+      : super(new _FrameSchedulerProxyControl.fromEndpoint(endpoint));
 
-  SceneSchedulerProxy.fromHandle(core.MojoHandle handle)
-      : super(new _SceneSchedulerProxyControl.fromHandle(handle));
+  FrameSchedulerProxy.fromHandle(core.MojoHandle handle)
+      : super(new _FrameSchedulerProxyControl.fromHandle(handle));
 
-  SceneSchedulerProxy.unbound()
-      : super(new _SceneSchedulerProxyControl.unbound());
+  FrameSchedulerProxy.unbound()
+      : super(new _FrameSchedulerProxyControl.unbound());
 
-  static SceneSchedulerProxy newFromEndpoint(
+  static FrameSchedulerProxy newFromEndpoint(
       core.MojoMessagePipeEndpoint endpoint) {
-    assert(endpoint.setDescription("For SceneSchedulerProxy"));
-    return new SceneSchedulerProxy.fromEndpoint(endpoint);
+    assert(endpoint.setDescription("For FrameSchedulerProxy"));
+    return new FrameSchedulerProxy.fromEndpoint(endpoint);
   }
 
-  factory SceneSchedulerProxy.connectToService(
+  factory FrameSchedulerProxy.connectToService(
       bindings.ServiceConnector s, String url, [String serviceName]) {
-    SceneSchedulerProxy p = new SceneSchedulerProxy.unbound();
+    FrameSchedulerProxy p = new FrameSchedulerProxy.unbound();
     s.connectToService(url, p, serviceName);
     return p;
   }
 
 
   dynamic scheduleFrame([Function responseFactory = null]) {
-    var params = new _SceneSchedulerScheduleFrameParams();
+    var params = new _FrameSchedulerScheduleFrameParams();
     return ctrl.sendMessageWithRequestId(
         params,
-        _sceneSchedulerMethodScheduleFrameName,
+        _frameSchedulerMethodScheduleFrameName,
         -1,
         bindings.MessageHeader.kMessageExpectsResponse);
   }
 }
 
-class _SceneSchedulerStubControl
+class _FrameSchedulerStubControl
     extends bindings.StubMessageHandler
-    implements bindings.StubControl<SceneScheduler> {
-  SceneScheduler _impl;
+    implements bindings.StubControl<FrameScheduler> {
+  FrameScheduler _impl;
 
-  _SceneSchedulerStubControl.fromEndpoint(
-      core.MojoMessagePipeEndpoint endpoint, [SceneScheduler impl])
+  _FrameSchedulerStubControl.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint, [FrameScheduler impl])
       : super.fromEndpoint(endpoint, autoBegin: impl != null) {
     _impl = impl;
   }
 
-  _SceneSchedulerStubControl.fromHandle(
-      core.MojoHandle handle, [SceneScheduler impl])
+  _FrameSchedulerStubControl.fromHandle(
+      core.MojoHandle handle, [FrameScheduler impl])
       : super.fromHandle(handle, autoBegin: impl != null) {
     _impl = impl;
   }
 
-  _SceneSchedulerStubControl.unbound([this._impl]) : super.unbound();
+  _FrameSchedulerStubControl.unbound([this._impl]) : super.unbound();
 
 
-  SceneSchedulerScheduleFrameResponseParams _sceneSchedulerScheduleFrameResponseParamsFactory(FrameInfo frameInfo) {
-    var result = new SceneSchedulerScheduleFrameResponseParams();
+  FrameSchedulerScheduleFrameResponseParams _frameSchedulerScheduleFrameResponseParamsFactory(FrameInfo frameInfo) {
+    var result = new FrameSchedulerScheduleFrameResponseParams();
     result.frameInfo = frameInfo;
     return result;
   }
@@ -397,14 +397,14 @@
       throw new core.MojoApiError("$this has no implementation set");
     }
     switch (message.header.type) {
-      case _sceneSchedulerMethodScheduleFrameName:
-        var response = _impl.scheduleFrame(_sceneSchedulerScheduleFrameResponseParamsFactory);
+      case _frameSchedulerMethodScheduleFrameName:
+        var response = _impl.scheduleFrame(_frameSchedulerScheduleFrameResponseParamsFactory);
         if (response is Future) {
           return response.then((response) {
             if (response != null) {
               return buildResponseWithId(
                   response,
-                  _sceneSchedulerMethodScheduleFrameName,
+                  _frameSchedulerMethodScheduleFrameName,
                   message.header.requestId,
                   bindings.MessageHeader.kMessageIsResponse);
             }
@@ -412,7 +412,7 @@
         } else if (response != null) {
           return buildResponseWithId(
               response,
-              _sceneSchedulerMethodScheduleFrameName,
+              _frameSchedulerMethodScheduleFrameName,
               message.header.requestId,
               bindings.MessageHeader.kMessageIsResponse);
         }
@@ -424,8 +424,8 @@
     return null;
   }
 
-  SceneScheduler get impl => _impl;
-  set impl(SceneScheduler d) {
+  FrameScheduler get impl => _impl;
+  set impl(FrameScheduler d) {
     if (d == null) {
       throw new core.MojoApiError("$this: Cannot set a null implementation");
     }
@@ -446,7 +446,7 @@
   @override
   String toString() {
     var superString = super.toString();
-    return "_SceneSchedulerStubControl($superString)";
+    return "_FrameSchedulerStubControl($superString)";
   }
 
   int get version => 0;
@@ -454,34 +454,34 @@
   static service_describer.ServiceDescription _cachedServiceDescription;
   static service_describer.ServiceDescription get serviceDescription {
     if (_cachedServiceDescription == null) {
-      _cachedServiceDescription = new _SceneSchedulerServiceDescription();
+      _cachedServiceDescription = new _FrameSchedulerServiceDescription();
     }
     return _cachedServiceDescription;
   }
 }
 
-class SceneSchedulerStub
-    extends bindings.Stub<SceneScheduler>
-    implements SceneScheduler {
-  SceneSchedulerStub.fromEndpoint(
-      core.MojoMessagePipeEndpoint endpoint, [SceneScheduler impl])
-      : super(new _SceneSchedulerStubControl.fromEndpoint(endpoint, impl));
+class FrameSchedulerStub
+    extends bindings.Stub<FrameScheduler>
+    implements FrameScheduler {
+  FrameSchedulerStub.fromEndpoint(
+      core.MojoMessagePipeEndpoint endpoint, [FrameScheduler impl])
+      : super(new _FrameSchedulerStubControl.fromEndpoint(endpoint, impl));
 
-  SceneSchedulerStub.fromHandle(
-      core.MojoHandle handle, [SceneScheduler impl])
-      : super(new _SceneSchedulerStubControl.fromHandle(handle, impl));
+  FrameSchedulerStub.fromHandle(
+      core.MojoHandle handle, [FrameScheduler impl])
+      : super(new _FrameSchedulerStubControl.fromHandle(handle, impl));
 
-  SceneSchedulerStub.unbound([SceneScheduler impl])
-      : super(new _SceneSchedulerStubControl.unbound(impl));
+  FrameSchedulerStub.unbound([FrameScheduler impl])
+      : super(new _FrameSchedulerStubControl.unbound(impl));
 
-  static SceneSchedulerStub newFromEndpoint(
+  static FrameSchedulerStub newFromEndpoint(
       core.MojoMessagePipeEndpoint endpoint) {
-    assert(endpoint.setDescription("For SceneSchedulerStub"));
-    return new SceneSchedulerStub.fromEndpoint(endpoint);
+    assert(endpoint.setDescription("For FrameSchedulerStub"));
+    return new FrameSchedulerStub.fromEndpoint(endpoint);
   }
 
   static service_describer.ServiceDescription get serviceDescription =>
-      _SceneSchedulerStubControl.serviceDescription;
+      _FrameSchedulerStubControl.serviceDescription;
 
 
   dynamic scheduleFrame([Function responseFactory = null]) {
diff --git a/mojo/services/gfx/composition/interfaces/renderers.mojom b/mojo/services/gfx/composition/interfaces/renderers.mojom
index a911d40..97f8d89 100644
--- a/mojo/services/gfx/composition/interfaces/renderers.mojom
+++ b/mojo/services/gfx/composition/interfaces/renderers.mojom
@@ -8,6 +8,7 @@
 import "mojo/services/geometry/interfaces/geometry.mojom";
 import "mojo/services/gfx/composition/interfaces/hit_tests.mojom";
 import "mojo/services/gfx/composition/interfaces/scene_token.mojom";
+import "mojo/services/gfx/composition/interfaces/scheduling.mojom";
 
 // The renderer is a service which renders a scene graph to a display.
 //
@@ -36,6 +37,9 @@
   // Dissociates the root scene from the renderer.
   ClearRootScene();
 
+  // Gets a scheduler to receive frame timing information for the renderer.
+  GetScheduler(FrameScheduler& scheduler);
+
   // Provides an interface which can be used to perform hit tests on the
   // contents of the renderer's scene graph.
   GetHitTester(HitTester& hit_tester);
diff --git a/mojo/services/gfx/composition/interfaces/scenes.mojom b/mojo/services/gfx/composition/interfaces/scenes.mojom
index 158f964..d4dadec 100644
--- a/mojo/services/gfx/composition/interfaces/scenes.mojom
+++ b/mojo/services/gfx/composition/interfaces/scenes.mojom
@@ -117,8 +117,8 @@
 //
 // SCHEDULING
 //
-// The scene provides support for scheduling update via its associated
-// |SceneScheduler| interface.  Use the scheduler to obtain frame timing
+// The scene provides support for scheduling frames via its associated
+// |FrameScheduler| interface.  Use the scheduler to obtain frame timing
 // information and to throttle updates to match the display refresh rate
 // (ie. with vsync).
 interface Scene {
@@ -155,8 +155,8 @@
   // introducing cycles in the scene's nodes; the connection will be closed.
   Publish(SceneMetadata? metadata);
 
-  // Gets a scheduler for scheduling upcoming scene operations.
-  GetScheduler(SceneScheduler& scheduler);
+  // Gets a scheduler to receive frame timing information for the scene.
+  GetScheduler(FrameScheduler& scheduler);
 };
 
 // An interface applications may implement to receive events from a scene.
diff --git a/mojo/services/gfx/composition/interfaces/scheduling.mojom b/mojo/services/gfx/composition/interfaces/scheduling.mojom
index 7b5dee1..91b09ae 100644
--- a/mojo/services/gfx/composition/interfaces/scheduling.mojom
+++ b/mojo/services/gfx/composition/interfaces/scheduling.mojom
@@ -5,26 +5,25 @@
 [DartPackage="mojo_services"]
 module mojo.gfx.composition;
 
-// Provides support for scheduling drawing and composition operations
-// for a particular scene.
+// Provides support for scheduling drawing and composition operations.
 //
-// Instances of this interface must be obtained from a |Scene|.  This interface
-// is modeled separately so as to allow applications to use different threads
-// for scheduling work as opposed to publishing scene updates.
-interface SceneScheduler {
+// Instances of this interface must be obtained from a |Scene| or |Renderer|.
+interface FrameScheduler {
   // Asks the compositor to invoke the callback when it is a good time to
   // draw the next frame.
   //
   // The rate at which the callback is invoked may depend on how the scene
   // has been embedded.  The scene will only receive frame callbacks while
   // it is attached to a scene graph which the compositor has been asked
-  // to renderer since timing information ultimately derives from the
+  // to render since timing information ultimately derives from the
   // renderer.  If the same scene is being rendered to multiple destinations
-  // with different timing requirements, the compositor will perform rate
-  // adaptation as required behind the scenes.
+  // with different timing requirements, the compositor will couple the
+  // scene scheduling to one of the renderers.
   //
   // The returned |frame_info| provides information about the frame to
-  // be drawn.
+  // be drawn.  This information should be passed to a
+  // |mojo::gfx::composition::FrameTracker| to apply compensation for
+  // skipped frames before using it.
   //
   // TODO(jeffbrown): Consider whether we should have the callback be invoked
   // immediately rather than on schedule, letting the client set its own
diff --git a/mojo/tools/data/apptests b/mojo/tools/data/apptests
index b02c076..6d1c170 100644
--- a/mojo/tools/data/apptests
+++ b/mojo/tools/data/apptests
@@ -15,6 +15,10 @@
     "test": "mojo:clipboard_apptests",
   },
   {
+    "test": "mojo:compositor_apptests",
+    "shell-args": ["--args-for=mojo:native_viewport_service --use-test-config"],
+  },
+  {
     "test": "mojo:example_apptests",
     # ExampleApplicationTest.CheckCommandLineArg checks --example_apptest_arg.
     "test-args": ["--example_apptest_arg"],
diff --git a/mojo/ui/choreographer.cc b/mojo/ui/choreographer.cc
index 758b70e..4267267 100644
--- a/mojo/ui/choreographer.cc
+++ b/mojo/ui/choreographer.cc
@@ -15,14 +15,14 @@
                              ChoreographerDelegate* delegate)
     : delegate_(delegate) {
   DCHECK(delegate_);
-  scene->GetScheduler(mojo::GetProxy(&scene_scheduler_));
+  scene->GetScheduler(mojo::GetProxy(&frame_scheduler_));
 }
 
 Choreographer::Choreographer(
-    mojo::gfx::composition::SceneSchedulerPtr scene_scheduler,
+    mojo::gfx::composition::FrameSchedulerPtr frame_scheduler,
     ChoreographerDelegate* delegate)
-    : scene_scheduler_(scene_scheduler.Pass()), delegate_(delegate) {
-  DCHECK(scene_scheduler_);
+    : frame_scheduler_(frame_scheduler.Pass()), delegate_(delegate) {
+  DCHECK(frame_scheduler_);
   DCHECK(delegate_);
 }
 
@@ -38,7 +38,7 @@
 void Choreographer::ScheduleFrame() {
   if (!frame_scheduled_) {
     frame_scheduled_ = true;
-    scene_scheduler_->ScheduleFrame(
+    frame_scheduler_->ScheduleFrame(
         base::Bind(&Choreographer::DoFrame, base::Unretained(this)));
   }
 }
diff --git a/mojo/ui/choreographer.h b/mojo/ui/choreographer.h
index 4e0cad5..a3844fd 100644
--- a/mojo/ui/choreographer.h
+++ b/mojo/ui/choreographer.h
@@ -43,13 +43,13 @@
  public:
   Choreographer(mojo::gfx::composition::Scene* scene,
                 ChoreographerDelegate* delegate);
-  Choreographer(mojo::gfx::composition::SceneSchedulerPtr scene_scheduler,
+  Choreographer(mojo::gfx::composition::FrameSchedulerPtr frame_scheduler,
                 ChoreographerDelegate* delegate);
   ~Choreographer();
 
   // Gets the scene scheduler.
-  mojo::gfx::composition::SceneScheduler* scene_scheduler() {
-    return scene_scheduler_.get();
+  mojo::gfx::composition::FrameScheduler* frame_scheduler() {
+    return frame_scheduler_.get();
   }
 
   // Gets the frame tracker.
@@ -57,11 +57,11 @@
     return frame_tracker_;
   }
 
-  // Schedules a call to the delegate's |OnDraw| using the scene scheduler.
+  // Schedules a call to the delegate's |OnDraw| using the frame scheduler.
   void ScheduleDraw();
 
  private:
-  mojo::gfx::composition::SceneSchedulerPtr scene_scheduler_;
+  mojo::gfx::composition::FrameSchedulerPtr frame_scheduler_;
   ChoreographerDelegate* delegate_;
   mojo::gfx::composition::FrameTracker frame_tracker_;
 
diff --git a/services/gfx/compositor/BUILD.gn b/services/gfx/compositor/BUILD.gn
index 40250f0..d03b6ce 100644
--- a/services/gfx/compositor/BUILD.gn
+++ b/services/gfx/compositor/BUILD.gn
@@ -22,6 +22,8 @@
     "compositor_engine.h",
     "compositor_impl.cc",
     "compositor_impl.h",
+    "frame_dispatcher.cc",
+    "frame_dispatcher.h",
     "graph/nodes.cc",
     "graph/nodes.h",
     "graph/resources.cc",
@@ -91,12 +93,15 @@
 
   sources = [
     "backend/vsync_scheduler_unittest.cc",
+    "tests/scheduling_apptest.cc",
   ]
 
   deps = [
     ":common",
     "//base/test:test_support",
     "//mojo/application:test_support",
+    "//mojo/services/gfx/composition/interfaces:interfaces_sync",
+    "//mojo/services/native_viewport/interfaces",
     "//testing/gtest",
   ]
 }
diff --git a/services/gfx/compositor/backend/gpu_output.cc b/services/gfx/compositor/backend/gpu_output.cc
index 531f2ee..d52ef5a 100644
--- a/services/gfx/compositor/backend/gpu_output.cc
+++ b/services/gfx/compositor/backend/gpu_output.cc
@@ -35,6 +35,7 @@
     : compositor_task_runner_(base::MessageLoop::current()->task_runner()),
       vsync_scheduler_(
           new VsyncScheduler(compositor_task_runner_, scheduler_callbacks)),
+      error_callback_(error_callback),
       rasterizer_thread_(new base::Thread("gpu_rasterizer")),
       rasterizer_initialized_(true, false) {
   DCHECK(context_provider);
diff --git a/services/gfx/compositor/compositor_engine.cc b/services/gfx/compositor/compositor_engine.cc
index bc05f2a..eade11f 100644
--- a/services/gfx/compositor/compositor_engine.cc
+++ b/services/gfx/compositor/compositor_engine.cc
@@ -215,10 +215,12 @@
 }
 
 void CompositorEngine::ScheduleFrame(SceneState* scene_state,
-                                     const SceneFrameCallback& callback) {
+                                     const FrameCallback& callback) {
   DCHECK(IsSceneStateRegisteredDebug(scene_state));
+  DVLOG(1) << "ScheduleFrame: scene=" << scene_state;
 
-  scene_state->AddSceneFrameCallback(callback);
+  if (!scene_state->frame_dispatcher().AddCallback(callback))
+    return;
 
   // TODO(jeffbrown): Be more selective and do this work only for scenes
   // which are strongly associated with the renderer so it doesn't receive
@@ -276,6 +278,18 @@
   }
 }
 
+void CompositorEngine::ScheduleFrame(RendererState* renderer_state,
+                                     const FrameCallback& callback) {
+  DCHECK(IsRendererStateRegisteredDebug(renderer_state));
+  DVLOG(1) << "ScheduleFrame: renderer=" << renderer_state;
+
+  if (!renderer_state->frame_dispatcher().AddCallback(callback))
+    return;
+
+  ScheduleFrameForRenderer(renderer_state,
+                           Scheduler::SchedulingMode::kUpdateAndSnapshot);
+}
+
 void CompositorEngine::HitTest(
     RendererState* renderer_state,
     mojo::PointFPtr point,
@@ -483,10 +497,12 @@
     return;
   DCHECK(IsRendererStateRegisteredDebug(renderer_state));
 
+  renderer_state->frame_dispatcher().DispatchCallbacks(frame_info);
+
   // TODO(jeffbrown): Be more selective and do this work only for scenes
   // associated with the renderer.
   for (auto& pair : scenes_by_token_) {
-    pair.second->DispatchSceneFrameCallbacks(frame_info);
+    pair.second->frame_dispatcher().DispatchCallbacks(frame_info);
   }
 }
 
diff --git a/services/gfx/compositor/compositor_engine.h b/services/gfx/compositor/compositor_engine.h
index e4abaad..4a64260 100644
--- a/services/gfx/compositor/compositor_engine.h
+++ b/services/gfx/compositor/compositor_engine.h
@@ -57,7 +57,7 @@
 
   // Schedules a frame callback.
   void ScheduleFrame(SceneState* scene_state,
-                     const SceneFrameCallback& callback);
+                     const FrameCallback& callback);
 
   // RENDERER REQUESTS
 
@@ -72,6 +72,10 @@
   // Destroys |renderer_state| if an error occurs.
   void ClearRootScene(RendererState* renderer_state);
 
+  // Schedules a frame callback.
+  void ScheduleFrame(RendererState* renderer_state,
+                     const FrameCallback& callback);
+
   // Performs a hit test.
   void HitTest(
       RendererState* renderer_state,
diff --git a/services/gfx/compositor/frame_dispatcher.cc b/services/gfx/compositor/frame_dispatcher.cc
new file mode 100644
index 0000000..0d04e3f
--- /dev/null
+++ b/services/gfx/compositor/frame_dispatcher.cc
@@ -0,0 +1,26 @@
+// 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 "services/gfx/compositor/frame_dispatcher.h"
+
+namespace compositor {
+
+FrameDispatcher::FrameDispatcher() {}
+
+FrameDispatcher::~FrameDispatcher() {}
+
+bool FrameDispatcher::AddCallback(const FrameCallback& callback) {
+  pending_callbacks_.emplace_back(callback);
+  return pending_callbacks_.size() == 1u;
+}
+
+void FrameDispatcher::DispatchCallbacks(
+    const mojo::gfx::composition::FrameInfo& frame_info) {
+  for (auto& callback : pending_callbacks_) {
+    callback.Run(frame_info.Clone());
+  }
+  pending_callbacks_.clear();
+}
+
+}  // namespace compositor
diff --git a/services/gfx/compositor/frame_dispatcher.h b/services/gfx/compositor/frame_dispatcher.h
new file mode 100644
index 0000000..6c9bc26
--- /dev/null
+++ b/services/gfx/compositor/frame_dispatcher.h
@@ -0,0 +1,39 @@
+// 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.
+
+#ifndef SERVICES_GFX_COMPOSITOR_FRAME_DISPATCHER_H_
+#define SERVICES_GFX_COMPOSITOR_FRAME_DISPATCHER_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "mojo/services/gfx/composition/interfaces/scheduling.mojom.h"
+
+namespace compositor {
+
+using FrameCallback =
+    base::Callback<void(mojo::gfx::composition::FrameInfoPtr)>;
+
+// Maintains a list of pending frame callbacks to be dispatched.
+class FrameDispatcher {
+ public:
+  FrameDispatcher();
+  ~FrameDispatcher();
+
+  // Adds a callback, returns true if it was the first pending callback.
+  bool AddCallback(const FrameCallback& callback);
+
+  // Dispatches all pending callbacks then clears the list.
+  void DispatchCallbacks(const mojo::gfx::composition::FrameInfo& frame_info);
+
+ private:
+  std::vector<FrameCallback> pending_callbacks_;
+
+  DISALLOW_COPY_AND_ASSIGN(FrameDispatcher);
+};
+
+}  // namespace compositor
+
+#endif  // SERVICES_GFX_COMPOSITOR_FRAME_DISPATCHER_H_
diff --git a/services/gfx/compositor/renderer_impl.cc b/services/gfx/compositor/renderer_impl.cc
index 1a0357a..547449c 100644
--- a/services/gfx/compositor/renderer_impl.cc
+++ b/services/gfx/compositor/renderer_impl.cc
@@ -8,6 +8,13 @@
 #include "base/bind_helpers.h"
 
 namespace compositor {
+namespace {
+void RunScheduleFrameCallback(
+    const RendererImpl::ScheduleFrameCallback& callback,
+    mojo::gfx::composition::FrameInfoPtr info) {
+  callback.Run(info.Pass());
+}
+}  // namespace
 
 RendererImpl::RendererImpl(
     CompositorEngine* engine,
@@ -37,6 +44,17 @@
   engine_->ClearRootScene(state_);
 }
 
+void RendererImpl::GetScheduler(
+    mojo::InterfaceRequest<mojo::gfx::composition::FrameScheduler>
+        scheduler_request) {
+  scheduler_bindings_.AddBinding(this, scheduler_request.Pass());
+}
+
+void RendererImpl::ScheduleFrame(const ScheduleFrameCallback& callback) {
+  engine_->ScheduleFrame(state_,
+                         base::Bind(&RunScheduleFrameCallback, callback));
+}
+
 void RendererImpl::HitTest(mojo::PointFPtr point,
                            const HitTestCallback& callback) {
   engine_->HitTest(state_, point.Pass(), callback);
diff --git a/services/gfx/compositor/renderer_impl.h b/services/gfx/compositor/renderer_impl.h
index 2571c33..bac5cf7 100644
--- a/services/gfx/compositor/renderer_impl.h
+++ b/services/gfx/compositor/renderer_impl.h
@@ -18,6 +18,7 @@
 // Renderer interface implementation.
 // This object is owned by its associated RendererState.
 class RendererImpl : public mojo::gfx::composition::Renderer,
+                     public mojo::gfx::composition::FrameScheduler,
                      public mojo::gfx::composition::HitTester {
  public:
   RendererImpl(CompositorEngine* engine,
@@ -36,15 +37,22 @@
                     uint32 scene_version,
                     mojo::RectPtr viewport) override;
   void ClearRootScene() override;
+  void GetScheduler(
+      mojo::InterfaceRequest<mojo::gfx::composition::FrameScheduler>
+          scheduler_request) override;
   void GetHitTester(mojo::InterfaceRequest<mojo::gfx::composition::HitTester>
                         hit_tester_request) override;
 
+  // |FrameScheduler|:
+  void ScheduleFrame(const ScheduleFrameCallback& callback) override;
+
   // |HitTester|:
   void HitTest(mojo::PointFPtr point, const HitTestCallback& callback) override;
 
   CompositorEngine* const engine_;
   RendererState* const state_;
   mojo::Binding<mojo::gfx::composition::Renderer> renderer_binding_;
+  mojo::BindingSet<mojo::gfx::composition::FrameScheduler> scheduler_bindings_;
   mojo::BindingSet<mojo::gfx::composition::HitTester> hit_tester_bindings;
 
   DISALLOW_COPY_AND_ASSIGN(RendererImpl);
diff --git a/services/gfx/compositor/renderer_state.h b/services/gfx/compositor/renderer_state.h
index 1cfbf4d..4c3c0d1 100644
--- a/services/gfx/compositor/renderer_state.h
+++ b/services/gfx/compositor/renderer_state.h
@@ -13,6 +13,7 @@
 #include "mojo/services/gfx/composition/cpp/formatting.h"
 #include "mojo/services/gfx/composition/interfaces/compositor.mojom.h"
 #include "services/gfx/compositor/backend/output.h"
+#include "services/gfx/compositor/frame_dispatcher.h"
 #include "services/gfx/compositor/graph/snapshot.h"
 #include "services/gfx/compositor/scene_state.h"
 
@@ -71,6 +72,8 @@
   // If the snapshot is not blocked, also updates |visible_snapshot()|.
   void SetSnapshot(const scoped_refptr<const Snapshot>& snapshot);
 
+  FrameDispatcher& frame_dispatcher() { return frame_dispatcher_; }
+
   const std::string& label() { return label_; }
   std::string FormattedLabel();
 
@@ -80,6 +83,7 @@
   const std::string label_;
   std::string formatted_label_cache_;
 
+  FrameDispatcher frame_dispatcher_;  // must be before renderer_impl_
   std::unique_ptr<mojo::gfx::composition::Renderer> renderer_impl_;
 
   SceneState* root_scene_ = nullptr;
diff --git a/services/gfx/compositor/scene_impl.cc b/services/gfx/compositor/scene_impl.cc
index 5db165c..64abed4 100644
--- a/services/gfx/compositor/scene_impl.cc
+++ b/services/gfx/compositor/scene_impl.cc
@@ -42,14 +42,14 @@
 }
 
 void SceneImpl::GetScheduler(
-    mojo::InterfaceRequest<mojo::gfx::composition::SceneScheduler>
+    mojo::InterfaceRequest<mojo::gfx::composition::FrameScheduler>
         scheduler_request) {
   scheduler_bindings_.AddBinding(this, scheduler_request.Pass());
 }
 
 void SceneImpl::ScheduleFrame(const ScheduleFrameCallback& callback) {
   engine_->ScheduleFrame(state_,
-                         base::Bind(&RunScheduleFrameCallback, callback));
+                         base::Bind(RunScheduleFrameCallback, callback));
 }
 
 }  // namespace compositor
diff --git a/services/gfx/compositor/scene_impl.h b/services/gfx/compositor/scene_impl.h
index f145c15..c7f7e8f 100644
--- a/services/gfx/compositor/scene_impl.h
+++ b/services/gfx/compositor/scene_impl.h
@@ -18,7 +18,7 @@
 // Scene interface implementation.
 // This object is owned by its associated SceneState.
 class SceneImpl : public mojo::gfx::composition::Scene,
-                  public mojo::gfx::composition::SceneScheduler {
+                  public mojo::gfx::composition::FrameScheduler {
  public:
   SceneImpl(
       CompositorEngine* engine,
@@ -37,16 +37,16 @@
   void Update(mojo::gfx::composition::SceneUpdatePtr update) override;
   void Publish(mojo::gfx::composition::SceneMetadataPtr metadata) override;
   void GetScheduler(
-      mojo::InterfaceRequest<mojo::gfx::composition::SceneScheduler>
+      mojo::InterfaceRequest<mojo::gfx::composition::FrameScheduler>
           scheduler_request) override;
 
-  // |SceneScheduler|:
+  // |FrameScheduler|:
   void ScheduleFrame(const ScheduleFrameCallback& callback) override;
 
   CompositorEngine* const engine_;
   SceneState* const state_;
   mojo::Binding<mojo::gfx::composition::Scene> scene_binding_;
-  mojo::BindingSet<mojo::gfx::composition::SceneScheduler> scheduler_bindings_;
+  mojo::BindingSet<mojo::gfx::composition::FrameScheduler> scheduler_bindings_;
 
   DISALLOW_COPY_AND_ASSIGN(SceneImpl);
 };
diff --git a/services/gfx/compositor/scene_state.cc b/services/gfx/compositor/scene_state.cc
index a5dab82..e54ce55 100644
--- a/services/gfx/compositor/scene_state.cc
+++ b/services/gfx/compositor/scene_state.cc
@@ -14,24 +14,7 @@
   DCHECK(scene_token_);
 }
 
-SceneState::~SceneState() {
-  // The scene implementation and all of its bindings must be destroyed
-  // before any pending callbacks are dropped on the floor.
-  scene_impl_.reset();
-  pending_frame_callbacks_.clear();
-}
-
-void SceneState::AddSceneFrameCallback(const SceneFrameCallback& callback) {
-  pending_frame_callbacks_.push_back(callback);
-}
-
-void SceneState::DispatchSceneFrameCallbacks(
-    const mojo::gfx::composition::FrameInfo& frame_info) {
-  for (auto& callback : pending_frame_callbacks_) {
-    callback.Run(frame_info.Clone());
-  }
-  pending_frame_callbacks_.clear();
-}
+SceneState::~SceneState() {}
 
 std::ostream& operator<<(std::ostream& os, SceneState* scene_state) {
   if (!scene_state)
diff --git a/services/gfx/compositor/scene_state.h b/services/gfx/compositor/scene_state.h
index c5f07ef..ec26485 100644
--- a/services/gfx/compositor/scene_state.h
+++ b/services/gfx/compositor/scene_state.h
@@ -8,20 +8,17 @@
 #include <memory>
 #include <string>
 #include <unordered_map>
-#include <vector>
 
 #include "base/callback.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "mojo/services/gfx/composition/cpp/formatting.h"
 #include "mojo/services/gfx/composition/interfaces/scenes.mojom.h"
+#include "services/gfx/compositor/frame_dispatcher.h"
 #include "services/gfx/compositor/graph/scene_def.h"
 
 namespace compositor {
 
-using SceneFrameCallback =
-    base::Callback<void(mojo::gfx::composition::FrameInfoPtr)>;
-
 // Describes the state of a particular scene.
 // This object is owned by the CompositorEngine that created it.
 class SceneState {
@@ -54,15 +51,15 @@
   // Gets the underlying scene definition, never null.
   SceneDef* scene_def() { return &scene_def_; }
 
-  void AddSceneFrameCallback(const SceneFrameCallback& callback);
-  void DispatchSceneFrameCallbacks(
-      const mojo::gfx::composition::FrameInfo& frame_info);
+  FrameDispatcher& frame_dispatcher() { return frame_dispatcher_; }
 
  private:
   mojo::gfx::composition::SceneTokenPtr scene_token_;
+
+  FrameDispatcher frame_dispatcher_;  // must be before scene_impl_
   std::unique_ptr<mojo::gfx::composition::Scene> scene_impl_;
+
   mojo::gfx::composition::SceneListenerPtr scene_listener_;
-  std::vector<SceneFrameCallback> pending_frame_callbacks_;
 
   SceneDef scene_def_;
 
diff --git a/services/gfx/compositor/tests/README.md b/services/gfx/compositor/tests/README.md
new file mode 100644
index 0000000..9188770
--- /dev/null
+++ b/services/gfx/compositor/tests/README.md
@@ -0,0 +1,10 @@
+# Mozart API Tests
+
+This directory contains test cases that exercise the Mozart Compositor APIs
+through IPC as a client would.
+
+These tests need a virtual framebuffer to execute.  Run them like this:
+
+$ testing/xvfb.py out/Debug mojo_run mojo:compositor_apptests
+  --shell-path out/Debug/mojo_shell \
+  --args-for="mojo:native_viewport_service --use-test-config"
diff --git a/services/gfx/compositor/tests/scheduling_apptest.cc b/services/gfx/compositor/tests/scheduling_apptest.cc
new file mode 100644
index 0000000..d116e61
--- /dev/null
+++ b/services/gfx/compositor/tests/scheduling_apptest.cc
@@ -0,0 +1,125 @@
+// 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 "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/application_test_base.h"
+#include "mojo/public/cpp/application/connect.h"
+#include "mojo/public/cpp/bindings/synchronous_interface_ptr.h"
+#include "mojo/public/cpp/system/macros.h"
+#include "mojo/services/gfx/composition/interfaces/compositor.mojom-sync.h"
+#include "mojo/services/gfx/composition/interfaces/scheduling.mojom-sync.h"
+#include "mojo/services/gpu/interfaces/context_provider.mojom.h"
+#include "mojo/services/native_viewport/interfaces/native_viewport.mojom.h"
+
+namespace test {
+
+using SynchronousCompositorPtr =
+    mojo::SynchronousInterfacePtr<mojo::gfx::composition::Compositor>;
+
+using SynchronousFrameSchedulerPtr =
+    mojo::SynchronousInterfacePtr<mojo::gfx::composition::FrameScheduler>;
+
+class SchedulingTest : public mojo::test::ApplicationTestBase {
+ public:
+  SchedulingTest() {}
+
+ protected:
+  void SetUp() override {
+    mojo::test::ApplicationTestBase::SetUp();
+
+    mojo::ConnectToService(shell(), "mojo:native_viewport_service",
+                           GetProxy(&viewport_));
+    auto size = mojo::Size::New();
+    size->width = 320;
+    size->height = 640;
+    auto configuration = mojo::SurfaceConfiguration::New();
+    viewport_->Create(size.Pass(), configuration.Pass(),
+                      [](mojo::ViewportMetricsPtr metrics) {
+
+                      });
+    viewport_->Show();
+
+    mojo::ContextProviderPtr context_provider;
+    viewport_->GetContextProvider(GetProxy(&context_provider));
+
+    mojo::ConnectToService(shell(), "mojo:compositor_service",
+                           mojo::GetSynchronousProxy(&compositor_));
+    compositor_->CreateRenderer(context_provider.Pass(),
+                                mojo::GetProxy(&renderer_), "SchedulingTest");
+  }
+
+  void TestScheduler(SynchronousFrameSchedulerPtr scheduler) {
+    mojo::gfx::composition::FrameInfoPtr frame_info1;
+    ASSERT_TRUE(scheduler->ScheduleFrame(&frame_info1));
+    AssertValidFrameInfo(frame_info1.get());
+
+    mojo::gfx::composition::FrameInfoPtr frame_info2;
+    ASSERT_TRUE(scheduler->ScheduleFrame(&frame_info2));
+    AssertValidFrameInfo(frame_info2.get());
+
+    EXPECT_GT(frame_info2->frame_time, frame_info1->frame_time);
+    EXPECT_GT(frame_info2->presentation_time, frame_info1->presentation_time);
+  }
+
+  void AssertValidFrameInfo(mojo::gfx::composition::FrameInfo* frame_info) {
+    ASSERT_NE(nullptr, frame_info);
+    EXPECT_LT(frame_info->frame_time, MojoGetTimeTicksNow());
+    EXPECT_GT(frame_info->frame_interval, 0u);
+    EXPECT_GT(frame_info->frame_deadline, frame_info->frame_time);
+    EXPECT_GT(frame_info->presentation_time, frame_info->frame_deadline);
+  }
+
+  mojo::NativeViewportPtr viewport_;
+  SynchronousCompositorPtr compositor_;
+  mojo::gfx::composition::RendererPtr renderer_;
+
+ private:
+  MOJO_DISALLOW_COPY_AND_ASSIGN(SchedulingTest);
+};
+
+namespace {
+
+TEST_F(SchedulingTest, RendererScheduler) {
+  SynchronousFrameSchedulerPtr scheduler;
+  renderer_->GetScheduler(mojo::GetSynchronousProxy(&scheduler));
+  TestScheduler(scheduler.Pass());
+}
+
+// Test what happens when a scene is not attached to a renderer.
+// It should still receive scheduled frame updates occasionally albeit
+// at some indeterminate rate (enough to keep the scene from hanging).
+TEST_F(SchedulingTest, OrphanedSceneScheduler) {
+  mojo::gfx::composition::ScenePtr scene;
+  mojo::gfx::composition::SceneTokenPtr scene_token;
+  compositor_->CreateScene(mojo::GetProxy(&scene), "SchedulingTest",
+                           &scene_token);
+
+  SynchronousFrameSchedulerPtr scheduler;
+  scene->GetScheduler(mojo::GetSynchronousProxy(&scheduler));
+  TestScheduler(scheduler.Pass());
+}
+
+// Test what happens when a scene is attached to a renderer.
+// It should receive scheduled frame updates at a rate determined
+// by the renderer.
+TEST_F(SchedulingTest, RootSceneScheduler) {
+  mojo::gfx::composition::ScenePtr scene;
+  mojo::gfx::composition::SceneTokenPtr scene_token;
+  compositor_->CreateScene(mojo::GetProxy(&scene), "SchedulingTest",
+                           &scene_token);
+
+  auto viewport = mojo::Rect::New();
+  viewport->width = 1;
+  viewport->height = 1;
+  renderer_->SetRootScene(scene_token.Pass(),
+                          mojo::gfx::composition::kSceneVersionNone,
+                          viewport.Pass());
+
+  SynchronousFrameSchedulerPtr scheduler;
+  scene->GetScheduler(mojo::GetSynchronousProxy(&scheduler));
+  TestScheduler(scheduler.Pass());
+}
+
+}  // namespace
+}  // namespace mojo
diff --git a/services/ui/launcher/launch_instance.cc b/services/ui/launcher/launch_instance.cc
index 82169cb..c8ed4f3 100644
--- a/services/ui/launcher/launch_instance.cc
+++ b/services/ui/launcher/launch_instance.cc
@@ -62,7 +62,7 @@
 
   auto requested_configuration = mojo::SurfaceConfiguration::New();
   viewport_->Create(
-      size.Clone(), requested_configuration.Pass(),
+      size.Pass(), requested_configuration.Pass(),
       base::Bind(&LaunchInstance::OnViewportCreated, base::Unretained(this)));
 }