cc: Allow the main thread to cancel commits

Add a new SetNeedsUpdateLayers that triggers the commit flow, but is
abortable if update layers doesn't actually make any changes.  This
allows the main thread to abort a begin frame.  This happens in the case
of scroll updates from the compositor thread or invalidations.

There was previously an abort begin frame call for when a visibility
message and a begin frame message were posted simultaneously, but it
incorrectly applied the scrolls and scales without informing the
compositor thread that these had already been consumed.  To fix this,
the abort message passes back a boolean about whether or not the
commit was aborted (and needed to be sent again) or was handled
(and the scrolls and scales processed).

To avoid a deluge of begin frames (in the commit sense) from the
scheduler, the scheduler has been adjusted to wait until the next begin
frame (in the vsync signal sense) so that these calls can be throttled.
Otherwise, the scheduler will just keep trying to begin frame.

[email protected], [email protected]
BUG=256381

Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=213338

Review URL: https://chromiumcodereview.appspot.com/19106007

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@214314 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index cdb5f61..0c82db8 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -469,6 +469,8 @@
   proxy_->SetNeedsAnimate();
 }
 
+void LayerTreeHost::SetNeedsUpdateLayers() { proxy_->SetNeedsUpdateLayers(); }
+
 void LayerTreeHost::SetNeedsCommit() {
   if (!prepaint_callback_.IsCancelled()) {
     TRACE_EVENT_INSTANT0("cc",
@@ -581,6 +583,11 @@
   SetNeedsCommit();
 }
 
+void LayerTreeHost::ApplyPageScaleDeltaFromImplSide(float page_scale_delta) {
+  DCHECK(CommitRequested());
+  page_scale_factor_ *= page_scale_delta;
+}
+
 void LayerTreeHost::SetPageScaleFactorAndLimits(float page_scale_factor,
                                                 float min_page_scale_factor,
                                                 float max_page_scale_factor) {
@@ -965,8 +972,8 @@
   if (!root_layer_.get())
     return;
 
-  Layer* root_scroll_layer = FindFirstScrollableLayer(root_layer_.get());
   gfx::Vector2d root_scroll_delta;
+  Layer* root_scroll_layer = FindFirstScrollableLayer(root_layer_.get());
 
   for (size_t i = 0; i < info.scrolls.size(); ++i) {
     Layer* layer =
@@ -981,8 +988,22 @@
                                          info.scrolls[i].scroll_delta);
     }
   }
-  if (!root_scroll_delta.IsZero() || info.page_scale_delta != 1.f)
+
+  if (!root_scroll_delta.IsZero() || info.page_scale_delta != 1.f) {
+    // SetScrollOffsetFromImplSide above could have destroyed the tree,
+    // so re-get this layer before doing anything to it.
+    root_scroll_layer = FindFirstScrollableLayer(root_layer_.get());
+
+    // Preemptively apply the scroll offset and scale delta here before sending
+    // it to the client.  If the client comes back and sets it to the same
+    // value, then the layer can early out without needing a full commit.
+    if (root_scroll_layer) {
+      root_scroll_layer->SetScrollOffsetFromImplSide(
+          root_scroll_layer->scroll_offset() + root_scroll_delta);
+    }
+    ApplyPageScaleDeltaFromImplSide(info.page_scale_delta);
     client_->ApplyScrollAndScale(root_scroll_delta, info.page_scale_delta);
+  }
 }
 
 void LayerTreeHost::StartRateLimiter(WebKit::WebGraphicsContext3D* context3d) {
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index f04871a..2ec3eac 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -165,6 +165,7 @@
   const RendererCapabilities& GetRendererCapabilities() const;
 
   void SetNeedsAnimate();
+  virtual void SetNeedsUpdateLayers();
   virtual void SetNeedsCommit();
   virtual void SetNeedsFullTreeSync();
   void SetNeedsRedraw();
@@ -189,6 +190,7 @@
   gfx::Size device_viewport_size() const { return device_viewport_size_; }
   float overdraw_bottom_height() const { return overdraw_bottom_height_; }
 
+  void ApplyPageScaleDeltaFromImplSide(float page_scale_delta);
   void SetPageScaleFactorAndLimits(float page_scale_factor,
                                    float min_page_scale_factor,
                                    float max_page_scale_factor);
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 629877f..f97eea0f5 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -1180,8 +1180,10 @@
   TRACE_EVENT0("cc", "LayerTreeHostImpl::DrawLayers");
   DCHECK(CanDraw());
 
-  if (frame->has_no_damage)
+  if (frame->has_no_damage) {
+    TRACE_EVENT0("cc", "EarlyOut_NoDamage");
     return;
+  }
 
   DCHECK(!frame->render_passes.empty());
 
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index 4d768205..e9435f7 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -1812,7 +1812,7 @@
   virtual void DidCommit() OVERRIDE {
     if (num_draw_layers_ == 2)
       return;
-    layer_tree_host()->root_layer()->SetNeedsDisplay();
+    layer_tree_host()->SetNeedsCommit();
   }
 
   virtual void CommitCompleteOnThread(LayerTreeHostImpl* impl) OVERRIDE {
@@ -3099,6 +3099,9 @@
 
   virtual void SetupTree() OVERRIDE {
     layer_ = FakePictureLayer::Create(&client_);
+    // Force commits to not be aborted so new frames get drawn, otherwise
+    // the renderer gets deferred initialized but nothing new needs drawing.
+    layer_->set_always_update_resources(true);
     layer_tree_host()->SetRootLayer(layer_);
     LayerTreeHostTest::SetupTree();
   }
@@ -3395,9 +3398,11 @@
         ++expected_push_properties_grandchild_;
         break;
       case 16:
+        // SetNeedsDisplay does not always set needs commit (so call it
+        // explicitly), but is a property change.
         child_->SetNeedsDisplay();
-        // The modified layer needs commit
         ++expected_push_properties_child_;
+        layer_tree_host()->SetNeedsCommit();
         break;
       case 17:
         EndTest();
diff --git a/cc/trees/layer_tree_host_unittest_context.cc b/cc/trees/layer_tree_host_unittest_context.cc
index ccb7afd..b014941 100644
--- a/cc/trees/layer_tree_host_unittest_context.cc
+++ b/cc/trees/layer_tree_host_unittest_context.cc
@@ -286,6 +286,7 @@
   virtual void InvalidateAndSetNeedsCommit() {
     // Cause damage so we try to draw.
     layer_tree_host()->root_layer()->SetNeedsDisplay();
+    layer_tree_host()->SetNeedsCommit();
   }
 
   bool NextTestCase() {
diff --git a/cc/trees/layer_tree_host_unittest_damage.cc b/cc/trees/layer_tree_host_unittest_damage.cc
index 523eecde..e8ae7a2 100644
--- a/cc/trees/layer_tree_host_unittest_damage.cc
+++ b/cc/trees/layer_tree_host_unittest_damage.cc
@@ -87,6 +87,7 @@
       case 3:
         // Cause non-visible damage.
         content_->SetNeedsDisplayRect(gfx::Rect(1990, 1990, 10, 10));
+        layer_tree_host()->SetNeedsCommit();
         break;
     }
   }
diff --git a/cc/trees/layer_tree_host_unittest_scroll.cc b/cc/trees/layer_tree_host_unittest_scroll.cc
index b07b3861..f0e1754 100644
--- a/cc/trees/layer_tree_host_unittest_scroll.cc
+++ b/cc/trees/layer_tree_host_unittest_scroll.cc
@@ -70,10 +70,8 @@
     }
   }
 
-  virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, float scale)
-      OVERRIDE {
-    gfx::Vector2d offset = layer_tree_host()->root_layer()->scroll_offset();
-    layer_tree_host()->root_layer()->SetScrollOffset(offset + scroll_delta);
+  virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta,
+                                   float scale) OVERRIDE {
     num_scrolls_++;
   }
 
@@ -150,10 +148,8 @@
     }
   }
 
-  virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, float scale)
-      OVERRIDE {
-    gfx::Vector2d offset = layer_tree_host()->root_layer()->scroll_offset();
-    layer_tree_host()->root_layer()->SetScrollOffset(offset + scroll_delta);
+  virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta,
+                                   float scale) OVERRIDE {
     num_scrolls_++;
   }
 
@@ -167,6 +163,182 @@
 
 MULTI_THREAD_TEST_F(LayerTreeHostScrollTestScrollMultipleRedraw);
 
+class LayerTreeHostScrollTestScrollAbortedCommit
+    : public LayerTreeHostScrollTest {
+ public:
+  LayerTreeHostScrollTestScrollAbortedCommit()
+      : initial_scroll_(50, 60),
+        impl_scroll_(-3, 2),
+        second_main_scroll_(14, -3),
+        impl_scale_(2.f),
+        num_will_begin_frames_(0),
+        num_did_begin_frames_(0),
+        num_will_commits_(0),
+        num_did_commits_(0),
+        num_impl_commits_(0),
+        num_impl_scrolls_(0) {}
+
+  virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
+
+  virtual void SetupTree() OVERRIDE {
+    LayerTreeHostScrollTest::SetupTree();
+    scoped_refptr<Layer> root_scroll_layer = Layer::Create();
+    root_scroll_layer->SetScrollable(true);
+    root_scroll_layer->SetScrollOffset(initial_scroll_);
+    root_scroll_layer->SetBounds(gfx::Size(200, 200));
+    root_scroll_layer->SetMaxScrollOffset(gfx::Vector2d(100, 100));
+    root_scroll_layer->SetIsDrawable(true);
+    layer_tree_host()->root_layer()->AddChild(root_scroll_layer);
+
+    layer_tree_host()->SetPageScaleFactorAndLimits(1.f, 0.01f, 100.f);
+  }
+
+  virtual void WillBeginFrame() OVERRIDE {
+    num_will_begin_frames_++;
+    Layer* root_scroll_layer = layer_tree_host()->root_layer()->children()[0];
+    switch (num_will_begin_frames_) {
+      case 1:
+        // This will not be aborted because of the initial prop changes.
+        EXPECT_EQ(0, num_impl_scrolls_);
+        EXPECT_EQ(0, layer_tree_host()->source_frame_number());
+        EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(), initial_scroll_);
+        EXPECT_EQ(1.f, layer_tree_host()->page_scale_factor());
+        break;
+      case 2:
+        // This commit will be aborted, and another commit will be
+        // initiated from the redraw.
+        EXPECT_EQ(1, num_impl_scrolls_);
+        EXPECT_EQ(1, layer_tree_host()->source_frame_number());
+        EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(),
+                         initial_scroll_ + impl_scroll_);
+        EXPECT_EQ(impl_scale_, layer_tree_host()->page_scale_factor());
+        PostSetNeedsRedrawToMainThread();
+        break;
+      case 3:
+        // This commit will not be aborted because of the scroll change.
+        EXPECT_EQ(2, num_impl_scrolls_);
+        EXPECT_EQ(1, layer_tree_host()->source_frame_number());
+        EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(),
+                         initial_scroll_ + impl_scroll_ + impl_scroll_);
+        EXPECT_EQ(impl_scale_ * impl_scale_,
+                  layer_tree_host()->page_scale_factor());
+        root_scroll_layer->SetScrollOffset(root_scroll_layer->scroll_offset() +
+                                           second_main_scroll_);
+        break;
+      case 4:
+        // This commit will also be aborted.
+        EXPECT_EQ(3, num_impl_scrolls_);
+        EXPECT_EQ(2, layer_tree_host()->source_frame_number());
+        EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(),
+                         initial_scroll_ + impl_scroll_ + impl_scroll_ +
+                             impl_scroll_ + second_main_scroll_);
+        // End the test by drawing to verify this commit is also aborted.
+        PostSetNeedsRedrawToMainThread();
+        break;
+    }
+  }
+
+  virtual void DidBeginFrame() OVERRIDE { num_did_begin_frames_++; }
+
+  virtual void WillCommit() OVERRIDE { num_will_commits_++; }
+
+  virtual void DidCommit() OVERRIDE { num_did_commits_++; }
+
+  virtual void BeginCommitOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+    num_impl_commits_++;
+  }
+
+  virtual void DrawLayersOnThread(LayerTreeHostImpl* impl) OVERRIDE {
+    LayerImpl* root_scroll_layer =
+        impl->active_tree()->root_layer()->children()[0];
+
+    if (impl->active_tree()->source_frame_number() == 0 &&
+        impl->SourceAnimationFrameNumber() == 1) {
+      // First draw
+      EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), gfx::Vector2d());
+      root_scroll_layer->ScrollBy(impl_scroll_);
+      EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), impl_scroll_);
+      EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(), initial_scroll_);
+
+      EXPECT_EQ(1.f, impl->active_tree()->page_scale_delta());
+      EXPECT_EQ(1.f, impl->active_tree()->total_page_scale_factor());
+      impl->active_tree()->SetPageScaleDelta(impl_scale_);
+      EXPECT_EQ(impl_scale_, impl->active_tree()->page_scale_delta());
+      EXPECT_EQ(impl_scale_, impl->active_tree()->total_page_scale_factor());
+
+      // To simplify the testing flow, don't redraw here, just commit.
+      impl->SetNeedsCommit();
+    } else if (impl->active_tree()->source_frame_number() == 0 &&
+               impl->SourceAnimationFrameNumber() == 2) {
+      // Test a second draw after an aborted commit.
+      // The scroll/scale values should be baked into the offset/scale factor
+      // since the main thread consumed but aborted the begin frame.
+      EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), gfx::Vector2d());
+      root_scroll_layer->ScrollBy(impl_scroll_);
+      EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), impl_scroll_);
+      EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(),
+                       initial_scroll_ + impl_scroll_);
+
+      EXPECT_EQ(1.f, impl->active_tree()->page_scale_delta());
+      EXPECT_EQ(impl_scale_, impl->active_tree()->total_page_scale_factor());
+      impl->active_tree()->SetPageScaleDelta(impl_scale_);
+      EXPECT_EQ(impl_scale_, impl->active_tree()->page_scale_delta());
+      EXPECT_EQ(impl_scale_ * impl_scale_,
+                impl->active_tree()->total_page_scale_factor());
+
+      impl->SetNeedsCommit();
+    } else if (impl->active_tree()->source_frame_number() == 1 &&
+               impl->SourceAnimationFrameNumber() == 3) {
+      // Third draw after the second full commit.
+      EXPECT_EQ(root_scroll_layer->ScrollDelta(), gfx::Vector2d());
+      root_scroll_layer->ScrollBy(impl_scroll_);
+      impl->SetNeedsCommit();
+      EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), impl_scroll_);
+      EXPECT_VECTOR_EQ(
+          root_scroll_layer->scroll_offset(),
+          initial_scroll_ + impl_scroll_ + impl_scroll_ + second_main_scroll_);
+    } else if (impl->active_tree()->source_frame_number() == 1 &&
+               impl->SourceAnimationFrameNumber() == 4) {
+      // Final draw after the second aborted commit.
+      EXPECT_VECTOR_EQ(root_scroll_layer->ScrollDelta(), gfx::Vector2d());
+      EXPECT_VECTOR_EQ(root_scroll_layer->scroll_offset(),
+                       initial_scroll_ + impl_scroll_ + impl_scroll_ +
+                           impl_scroll_ + second_main_scroll_);
+      EndTest();
+    }
+  }
+
+  virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta,
+                                   float scale) OVERRIDE {
+    num_impl_scrolls_++;
+  }
+
+  virtual void AfterTest() OVERRIDE {
+    EXPECT_EQ(3, num_impl_scrolls_);
+    // Verify that the embedder sees aborted commits as real commits.
+    EXPECT_EQ(4, num_will_begin_frames_);
+    EXPECT_EQ(4, num_did_begin_frames_);
+    EXPECT_EQ(4, num_will_commits_);
+    EXPECT_EQ(4, num_did_commits_);
+    // ...but the compositor thread only sees two real ones.
+    EXPECT_EQ(2, num_impl_commits_);
+  }
+
+ private:
+  gfx::Vector2d initial_scroll_;
+  gfx::Vector2d impl_scroll_;
+  gfx::Vector2d second_main_scroll_;
+  float impl_scale_;
+  int num_will_begin_frames_;
+  int num_did_begin_frames_;
+  int num_will_commits_;
+  int num_did_commits_;
+  int num_impl_commits_;
+  int num_impl_scrolls_;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostScrollTestScrollAbortedCommit);
+
 class LayerTreeHostScrollTestFractionalScroll : public LayerTreeHostScrollTest {
  public:
   LayerTreeHostScrollTestFractionalScroll() : scroll_amount_(1.75, 0) {}
@@ -208,12 +380,6 @@
     root->ScrollBy(scroll_amount_);
   }
 
-  virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, float scale)
-      OVERRIDE {
-    gfx::Vector2d offset = layer_tree_host()->root_layer()->scroll_offset();
-    layer_tree_host()->root_layer()->SetScrollOffset(offset + scroll_delta);
-  }
-
   virtual void AfterTest() OVERRIDE {}
 
  private:
@@ -285,14 +451,20 @@
 
   virtual void BeginTest() OVERRIDE { PostSetNeedsCommitToMainThread(); }
 
+  virtual void WillCommit() OVERRIDE {
+    // Keep the test committing (otherwise the early out for no update
+    // will stall the test).
+    if (layer_tree_host()->source_frame_number() < 2) {
+      layer_tree_host()->SetNeedsCommit();
+    }
+  }
+
   void DidScroll() {
     final_scroll_offset_ = expected_scroll_layer_->scroll_offset();
   }
 
-  virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, float scale)
-      OVERRIDE {
-    gfx::Vector2d offset = root_scroll_layer_->scroll_offset();
-    root_scroll_layer_->SetScrollOffset(offset + scroll_delta);
+  virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta,
+                                   float scale) OVERRIDE {
     num_scrolls_++;
   }
 
@@ -642,10 +814,8 @@
     }
   }
 
-  virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta, float scale)
-      OVERRIDE {
-    gfx::Vector2d offset = layer_tree_host()->root_layer()->scroll_offset();
-    layer_tree_host()->root_layer()->SetScrollOffset(offset + scroll_delta);
+  virtual void ApplyScrollAndScale(gfx::Vector2d scroll_delta,
+                                   float scale) OVERRIDE {
     num_scrolls_++;
   }
 
diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc
index 860f067..c160d97 100644
--- a/cc/trees/layer_tree_impl.cc
+++ b/cc/trees/layer_tree_impl.cc
@@ -231,6 +231,24 @@
   root_scroll_layer_->SetMaxScrollOffset(gfx::ToFlooredVector2d(max_scroll));
 }
 
+static void ApplySentScrollDeltasOn(LayerImpl* layer) {
+  layer->ApplySentScrollDeltas();
+}
+
+void LayerTreeImpl::ApplySentScrollAndScaleDeltas() {
+  DCHECK(IsActiveTree());
+
+  page_scale_factor_ *= sent_page_scale_delta_;
+  page_scale_delta_ /= sent_page_scale_delta_;
+  sent_page_scale_delta_ = 1.f;
+
+  if (!root_layer())
+    return;
+
+  LayerTreeHostCommon::CallFunctionForSubtree(
+      root_layer(), base::Bind(&ApplySentScrollDeltasOn));
+}
+
 void LayerTreeImpl::UpdateSolidColorScrollbars() {
   DCHECK(settings().solid_color_scrollbars);
 
diff --git a/cc/trees/layer_tree_impl.h b/cc/trees/layer_tree_impl.h
index aafaf2ac..27aa6fa 100644
--- a/cc/trees/layer_tree_impl.h
+++ b/cc/trees/layer_tree_impl.h
@@ -109,6 +109,7 @@
 
   void FindRootScrollLayer();
   void UpdateMaxScrollOffset();
+  void ApplySentScrollAndScaleDeltas();
 
   SkColor background_color() const { return background_color_; }
   void set_background_color(SkColor color) { background_color_ = color; }
diff --git a/cc/trees/proxy.h b/cc/trees/proxy.h
index e3b5f82..f4a1ef9 100644
--- a/cc/trees/proxy.h
+++ b/cc/trees/proxy.h
@@ -66,6 +66,7 @@
   virtual const RendererCapabilities& GetRendererCapabilities() const = 0;
 
   virtual void SetNeedsAnimate() = 0;
+  virtual void SetNeedsUpdateLayers() = 0;
   virtual void SetNeedsCommit() = 0;
   virtual void SetNeedsRedraw(gfx::Rect damage_rect) = 0;
 
diff --git a/cc/trees/single_thread_proxy.cc b/cc/trees/single_thread_proxy.cc
index 426b44f4..2c4d3a89a4 100644
--- a/cc/trees/single_thread_proxy.cc
+++ b/cc/trees/single_thread_proxy.cc
@@ -171,6 +171,11 @@
   NOTREACHED();
 }
 
+void SingleThreadProxy::SetNeedsUpdateLayers() {
+  DCHECK(Proxy::IsMainThread());
+  layer_tree_host_->ScheduleComposite();
+}
+
 void SingleThreadProxy::DoCommit(scoped_ptr<ResourceUpdateQueue> queue) {
   DCHECK(Proxy::IsMainThread());
   // Commit immediately.
@@ -203,11 +208,12 @@
     layer_tree_host_impl_->CommitComplete();
 
 #ifndef NDEBUG
-    // In the single-threaded case, the scroll deltas should never be
+    // In the single-threaded case, the scale and scroll deltas should never be
     // touched on the impl layer tree.
     scoped_ptr<ScrollAndScaleSet> scroll_info =
         layer_tree_host_impl_->ProcessScrollDeltas();
     DCHECK(!scroll_info->scrolls.size());
+    DCHECK_EQ(1.f, scroll_info->page_scale_delta);
 #endif
 
     base::TimeDelta duration = stats_instrumentation->EndRecording(start_time);
diff --git a/cc/trees/single_thread_proxy.h b/cc/trees/single_thread_proxy.h
index b344cc3..9f7eed4 100644
--- a/cc/trees/single_thread_proxy.h
+++ b/cc/trees/single_thread_proxy.h
@@ -32,6 +32,7 @@
   virtual void CreateAndInitializeOutputSurface() OVERRIDE;
   virtual const RendererCapabilities& GetRendererCapabilities() const OVERRIDE;
   virtual void SetNeedsAnimate() OVERRIDE;
+  virtual void SetNeedsUpdateLayers() OVERRIDE;
   virtual void SetNeedsCommit() OVERRIDE;
   virtual void SetNeedsRedraw(gfx::Rect damage_rect) OVERRIDE;
   virtual void SetDeferCommits(bool defer_commits) OVERRIDE;
diff --git a/cc/trees/thread_proxy.cc b/cc/trees/thread_proxy.cc
index 2bf82471..3d73b21 100644
--- a/cc/trees/thread_proxy.cc
+++ b/cc/trees/thread_proxy.cc
@@ -90,6 +90,7 @@
       using_synchronous_renderer_compositor_(
           layer_tree_host->settings().using_synchronous_renderer_compositor),
       inside_draw_(false),
+      can_cancel_commit_(true),
       defer_commits_(false),
       renew_tree_priority_on_impl_thread_pending_(false),
       draw_duration_history_(kDurationHistorySize),
@@ -128,10 +129,15 @@
                    &begin_frame_sent_to_main_thread_completion));
     begin_frame_sent_to_main_thread_completion.Wait();
   }
+
   in_composite_and_readback_ = true;
   BeginFrameOnMainThread(scoped_ptr<BeginFrameAndCommitState>());
   in_composite_and_readback_ = false;
 
+  // Composite and readback requires a second commit to undo any changes
+  // that it made.
+  can_cancel_commit_ = false;
+
   // Perform a synchronous readback.
   ReadbackRequest request;
   request.rect = rect;
@@ -301,6 +307,17 @@
   }
 }
 
+void ThreadProxy::SendCommitRequestToImplThreadIfNeeded() {
+  DCHECK(IsMainThread());
+  if (commit_request_sent_to_impl_thread_)
+    return;
+  commit_request_sent_to_impl_thread_ = true;
+  Proxy::ImplThreadTaskRunner()->PostTask(
+      FROM_HERE,
+      base::Bind(&ThreadProxy::SetNeedsCommitOnImplThread,
+                 impl_thread_weak_ptr_));
+}
+
 const RendererCapabilities& ThreadProxy::GetRendererCapabilities() const {
   DCHECK(IsMainThread());
   DCHECK(!layer_tree_host_->output_surface_lost());
@@ -314,30 +331,26 @@
 
   TRACE_EVENT0("cc", "ThreadProxy::SetNeedsAnimate");
   animate_requested_ = true;
+  can_cancel_commit_ = false;
+  SendCommitRequestToImplThreadIfNeeded();
+}
 
-  if (commit_request_sent_to_impl_thread_)
-    return;
-  commit_request_sent_to_impl_thread_ = true;
-  Proxy::ImplThreadTaskRunner()->PostTask(
-      FROM_HERE,
-      base::Bind(&ThreadProxy::SetNeedsCommitOnImplThread,
-                 impl_thread_weak_ptr_));
+void ThreadProxy::SetNeedsUpdateLayers() {
+  DCHECK(IsMainThread());
+  SendCommitRequestToImplThreadIfNeeded();
 }
 
 void ThreadProxy::SetNeedsCommit() {
   DCHECK(IsMainThread());
+  // Unconditionally set here to handle SetNeedsCommit calls during a commit.
+  can_cancel_commit_ = false;
+
   if (commit_requested_)
     return;
   TRACE_EVENT0("cc", "ThreadProxy::SetNeedsCommit");
   commit_requested_ = true;
 
-  if (commit_request_sent_to_impl_thread_)
-    return;
-  commit_request_sent_to_impl_thread_ = true;
-  Proxy::ImplThreadTaskRunner()->PostTask(
-      FROM_HERE,
-      base::Bind(&ThreadProxy::SetNeedsCommitOnImplThread,
-                 impl_thread_weak_ptr_));
+  SendCommitRequestToImplThreadIfNeeded();
 }
 
 void ThreadProxy::DidLoseOutputSurfaceOnImplThread() {
@@ -680,21 +693,23 @@
   // callbacks will trigger another frame.
   animate_requested_ = false;
 
-  if (begin_frame_state)
-    layer_tree_host_->ApplyScrollAndScale(*begin_frame_state->scroll_info);
-
   if (!in_composite_and_readback_ && !layer_tree_host_->visible()) {
     commit_requested_ = false;
     commit_request_sent_to_impl_thread_ = false;
 
     TRACE_EVENT0("cc", "EarlyOut_NotVisible");
+    bool did_handle = false;
     Proxy::ImplThreadTaskRunner()->PostTask(
         FROM_HERE,
         base::Bind(&ThreadProxy::BeginFrameAbortedByMainThreadOnImplThread,
-                   impl_thread_weak_ptr_));
+                   impl_thread_weak_ptr_,
+                   did_handle));
     return;
   }
 
+  if (begin_frame_state)
+    layer_tree_host_->ApplyScrollAndScale(*begin_frame_state->scroll_info);
+
   layer_tree_host_->WillBeginFrame();
 
   if (begin_frame_state) {
@@ -718,24 +733,44 @@
   // UpdateLayers.
   commit_requested_ = false;
   commit_request_sent_to_impl_thread_ = false;
+  bool can_cancel_this_commit =
+      can_cancel_commit_ && !in_composite_and_readback_;
+  can_cancel_commit_ = true;
 
   scoped_ptr<ResourceUpdateQueue> queue =
       make_scoped_ptr(new ResourceUpdateQueue);
-  layer_tree_host_->UpdateLayers(
+  bool updated = layer_tree_host_->UpdateLayers(
       queue.get(),
-      begin_frame_state ?
-          begin_frame_state->memory_allocation_limit_bytes : 0u);
+      begin_frame_state ? begin_frame_state->memory_allocation_limit_bytes
+                        : 0u);
 
   // Once single buffered layers are committed, they cannot be modified until
   // they are drawn by the impl thread.
   textures_acquired_ = false;
 
   layer_tree_host_->WillCommit();
-  // Before applying scrolls and calling animate, we set animate_requested_ to
-  // false. If it is true now, it means SetNeedAnimate was called again, but
-  // during a state when commit_request_sent_to_impl_thread_ = true. We need to
-  // force that call to happen again now so that the commit request is sent to
-  // the impl thread.
+
+  if (!updated && can_cancel_this_commit) {
+    TRACE_EVENT0("cc", "EarlyOut_NoUpdates");
+    bool did_handle = true;
+    Proxy::ImplThreadTaskRunner()->PostTask(
+        FROM_HERE,
+        base::Bind(&ThreadProxy::BeginFrameAbortedByMainThreadOnImplThread,
+                   impl_thread_weak_ptr_,
+                   did_handle));
+
+    // Although the commit is internally aborted, this is because it has been
+    // detected to be a no-op.  From the perspective of an embedder, this commit
+    // went through, and input should no longer be throttled, etc.
+    layer_tree_host_->CommitComplete();
+    layer_tree_host_->DidBeginFrame();
+    return;
+  }
+
+  // Before calling animate, we set animate_requested_ to false. If it is true
+  // now, it means SetNeedAnimate was called again, but during a state when
+  // commit_request_sent_to_impl_thread_ = true. We need to force that call to
+  // happen again now so that the commit request is sent to the impl thread.
   if (animate_requested_) {
     // Forces SetNeedsAnimate to consider posting a commit task.
     animate_requested_ = false;
@@ -831,13 +866,19 @@
       scheduler_on_impl_thread_->AnticipatedDrawTime());
 }
 
-void ThreadProxy::BeginFrameAbortedByMainThreadOnImplThread() {
+void ThreadProxy::BeginFrameAbortedByMainThreadOnImplThread(bool did_handle) {
   TRACE_EVENT0("cc", "ThreadProxy::BeginFrameAbortedByMainThreadOnImplThread");
   DCHECK(IsImplThread());
   DCHECK(scheduler_on_impl_thread_);
   DCHECK(scheduler_on_impl_thread_->CommitPending());
+  DCHECK(!layer_tree_host_impl_->pending_tree());
 
-  scheduler_on_impl_thread_->BeginFrameAbortedByMainThread();
+  // If the begin frame data was handled, then scroll and scale set was applied
+  // by the main thread, so the active tree needs to be updated as if these sent
+  // values were applied and committed.
+  if (did_handle)
+    layer_tree_host_impl_->active_tree()->ApplySentScrollAndScaleDeltas();
+  scheduler_on_impl_thread_->BeginFrameAbortedByMainThread(did_handle);
 }
 
 void ThreadProxy::ScheduledActionCommit() {
@@ -905,7 +946,8 @@
 
 ScheduledActionDrawAndSwapResult
 ThreadProxy::ScheduledActionDrawAndSwapInternal(bool forced_draw) {
-  TRACE_EVENT0("cc", "ThreadProxy::ScheduledActionDrawAndSwap");
+  TRACE_EVENT1(
+      "cc", "ThreadProxy::ScheduledActionDrawAndSwap", "forced", forced_draw);
 
   ScheduledActionDrawAndSwapResult result;
   result.did_draw = false;
@@ -1066,6 +1108,7 @@
   completion.Wait();
 
   textures_acquired_ = true;
+  can_cancel_commit_ = false;
 }
 
 void ThreadProxy::AcquireLayerTexturesForMainThreadOnImplThread(
diff --git a/cc/trees/thread_proxy.h b/cc/trees/thread_proxy.h
index eb2ef7e..c9d5b47 100644
--- a/cc/trees/thread_proxy.h
+++ b/cc/trees/thread_proxy.h
@@ -49,6 +49,7 @@
   virtual void CreateAndInitializeOutputSurface() OVERRIDE;
   virtual const RendererCapabilities& GetRendererCapabilities() const OVERRIDE;
   virtual void SetNeedsAnimate() OVERRIDE;
+  virtual void SetNeedsUpdateLayers() OVERRIDE;
   virtual void SetNeedsCommit() OVERRIDE;
   virtual void SetNeedsRedraw(gfx::Rect damage_rect) OVERRIDE;
   virtual void SetDeferCommits(bool defer_commits) OVERRIDE;
@@ -135,6 +136,7 @@
   void OnOutputSurfaceInitializeAttempted(
       bool success,
       const RendererCapabilities& capabilities);
+  void SendCommitRequestToImplThreadIfNeeded();
 
   // Called on impl thread.
   struct ReadbackRequest;
@@ -146,7 +148,7 @@
       CompletionEvent* completion,
       ResourceUpdateQueue* queue,
       scoped_refptr<cc::ContextProvider> offscreen_context_provider);
-  void BeginFrameAbortedByMainThreadOnImplThread();
+  void BeginFrameAbortedByMainThreadOnImplThread(bool did_handle);
   void RequestReadbackOnImplThread(ReadbackRequest* request);
   void FinishAllRenderingOnImplThread(CompletionEvent* completion);
   void InitializeImplOnImplThread(CompletionEvent* completion);
@@ -241,6 +243,8 @@
 
   bool inside_draw_;
 
+  bool can_cancel_commit_;
+
   bool defer_commits_;
   scoped_ptr<BeginFrameAndCommitState> pending_deferred_commit_;