[go: nahoru, domu]

cc: Glue LTHI and Scheduler changes for checker-imaging.

This change glues together the scheduling and tile management changes
for checker-imaging via ProxyImpl and SingleThreadProxy for enabling
checker-imaging. And adds integration LayerTreeTests, including a
scroll test to ensure that an impl-side pending tree does not affect
SyncedProperty state synchronization.

BUG=686267, 691041
CQ_INCLUDE_TRYBOTS=master.tryserver.blink:linux_trusty_blink_rel

Review-Url: https://codereview.chromium.org/2717553005
Cr-Commit-Position: refs/heads/master@{#453508}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index e0d8f256..a966184 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -853,6 +853,7 @@
     "trees/layer_tree_host_pixeltest_tiles.cc",
     "trees/layer_tree_host_unittest.cc",
     "trees/layer_tree_host_unittest_animation.cc",
+    "trees/layer_tree_host_unittest_checkerimaging.cc",
     "trees/layer_tree_host_unittest_context.cc",
     "trees/layer_tree_host_unittest_copyrequest.cc",
     "trees/layer_tree_host_unittest_damage.cc",
diff --git a/cc/playback/raster_source.cc b/cc/playback/raster_source.cc
index 9daa8f3..e53daf1 100644
--- a/cc/playback/raster_source.cc
+++ b/cc/playback/raster_source.cc
@@ -247,6 +247,8 @@
 }
 
 gfx::Rect RasterSource::GetRectForImage(ImageId image_id) const {
+  if (!display_list_)
+    return gfx::Rect();
   return display_list_->GetRectForImage(image_id);
 }
 
diff --git a/cc/scheduler/scheduler_state_machine.cc b/cc/scheduler/scheduler_state_machine.cc
index ae1991b..6dd7c39 100644
--- a/cc/scheduler/scheduler_state_machine.cc
+++ b/cc/scheduler/scheduler_state_machine.cc
@@ -526,7 +526,8 @@
 
   // We must not finish the commit until the pending tree is free.
   if (has_pending_tree_) {
-    DCHECK(settings_.main_frame_before_activation_enabled);
+    DCHECK(settings_.main_frame_before_activation_enabled ||
+           current_pending_tree_is_impl_side_);
     return false;
   }
 
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index e3dc1e1..bb90d2c 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -93,10 +93,12 @@
       LayerTreeHostImplClient* host_impl_client,
       TaskRunnerProvider* task_runner_provider,
       TaskGraphRunner* task_graph_runner,
-      RenderingStatsInstrumentation* stats_instrumentation) {
+      RenderingStatsInstrumentation* stats_instrumentation,
+      scoped_refptr<base::SequencedTaskRunner> image_worker_task_runner) {
     return base::WrapUnique(new LayerTreeHostImplForTesting(
         test_hooks, settings, host_impl_client, task_runner_provider,
-        task_graph_runner, stats_instrumentation));
+        task_graph_runner, stats_instrumentation,
+        std::move(image_worker_task_runner)));
   }
 
  protected:
@@ -106,7 +108,8 @@
       LayerTreeHostImplClient* host_impl_client,
       TaskRunnerProvider* task_runner_provider,
       TaskGraphRunner* task_graph_runner,
-      RenderingStatsInstrumentation* stats_instrumentation)
+      RenderingStatsInstrumentation* stats_instrumentation,
+      scoped_refptr<base::SequencedTaskRunner> image_worker_task_runner)
       : LayerTreeHostImpl(settings,
                           host_impl_client,
                           task_runner_provider,
@@ -114,10 +117,8 @@
                           task_graph_runner,
                           AnimationHost::CreateForTesting(ThreadInstance::IMPL),
                           0,
-                          nullptr),
-        test_hooks_(test_hooks),
-        block_notify_ready_to_activate_for_testing_(false),
-        notify_ready_to_activate_was_blocked_(false) {}
+                          std::move(image_worker_task_runner)),
+        test_hooks_(test_hooks) {}
 
   void CreateResourceAndRasterBufferProvider(
       std::unique_ptr<RasterBufferProvider>* raster_buffer_provider,
@@ -136,6 +137,11 @@
     test_hooks_->DidFinishImplFrameOnThread(this);
   }
 
+  void DidSendBeginMainFrame() override {
+    LayerTreeHostImpl::DidSendBeginMainFrame();
+    test_hooks_->DidSendBeginMainFrameOnThread(this);
+  }
+
   void BeginMainFrameAborted(
       CommitEarlyOutReason reason,
       std::vector<std::unique_ptr<SwapPromise>> swap_promises) override {
@@ -208,6 +214,13 @@
     }
   }
 
+  void BlockImplSideInvalidationRequestsForTesting(bool block) override {
+    block_impl_side_invalidation_ = block;
+    if (!block_impl_side_invalidation_ && impl_side_invalidation_was_blocked_) {
+      RequestImplSideInvalidation();
+    }
+  }
+
   void ActivateSyncTree() override {
     test_hooks_->WillActivateTreeOnThread(this);
     LayerTreeHostImpl::ActivateSyncTree();
@@ -250,14 +263,33 @@
     test_hooks_->NotifyTileStateChangedOnThread(this, tile);
   }
 
+  void InvalidateContentOnImplSide() override {
+    LayerTreeHostImpl::InvalidateContentOnImplSide();
+    test_hooks_->DidInvalidateContentOnImplSide(this);
+  }
+
+  void RequestImplSideInvalidation() override {
+    if (block_impl_side_invalidation_) {
+      impl_side_invalidation_was_blocked_ = true;
+      return;
+    }
+
+    impl_side_invalidation_was_blocked_ = false;
+    LayerTreeHostImpl::RequestImplSideInvalidation();
+    test_hooks_->DidRequestImplSideInvalidation(this);
+  }
+
   AnimationHost* animation_host() const {
     return static_cast<AnimationHost*>(mutator_host());
   }
 
  private:
   TestHooks* test_hooks_;
-  bool block_notify_ready_to_activate_for_testing_;
-  bool notify_ready_to_activate_was_blocked_;
+  bool block_notify_ready_to_activate_for_testing_ = false;
+  bool notify_ready_to_activate_was_blocked_ = false;
+
+  bool block_impl_side_invalidation_ = false;
+  bool impl_side_invalidation_was_blocked_ = false;
 };
 
 // Implementation of LayerTreeHost callback interface.
@@ -342,12 +374,14 @@
       const LayerTreeSettings& settings,
       scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
       scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner,
+      scoped_refptr<base::SequencedTaskRunner> image_worker_task_runner,
       MutatorHost* mutator_host) {
     LayerTreeHost::InitParams params;
     params.client = client;
     params.task_graph_runner = task_graph_runner;
     params.settings = &settings;
     params.mutator_host = mutator_host;
+    params.image_worker_task_runner = std::move(image_worker_task_runner);
 
     std::unique_ptr<LayerTreeHostForTesting> layer_tree_host(
         new LayerTreeHostForTesting(test_hooks, &params, mode));
@@ -377,7 +411,7 @@
         LayerTreeHostImplForTesting::Create(
             test_hooks_, GetSettings(), host_impl_client,
             GetTaskRunnerProvider(), task_graph_runner(),
-            rendering_stats_instrumentation());
+            rendering_stats_instrumentation(), image_worker_task_runner_);
     input_handler_weak_ptr_ = host_impl->AsWeakPtr();
     return host_impl;
   }
@@ -595,7 +629,8 @@
 
   layer_tree_host_ = LayerTreeHostForTesting::Create(
       this, mode_, client_.get(), client_.get(), task_graph_runner_.get(),
-      settings_, main_task_runner, impl_task_runner, animation_host_.get());
+      settings_, main_task_runner, impl_task_runner,
+      image_worker_->task_runner(), animation_host_.get());
   ASSERT_TRUE(layer_tree_host_);
 
   main_task_runner_ =
@@ -741,6 +776,9 @@
     ASSERT_TRUE(impl_thread_->Start());
   }
 
+  image_worker_ = base::MakeUnique<base::Thread>("ImageWorker");
+  ASSERT_TRUE(image_worker_->Start());
+
   shared_bitmap_manager_.reset(new TestSharedBitmapManager);
   gpu_memory_buffer_manager_.reset(new TestGpuMemoryBufferManager);
   task_graph_runner_.reset(new TestTaskGraphRunner);
diff --git a/cc/test/layer_tree_test.h b/cc/test/layer_tree_test.h
index 625f47b..3b1f5439 100644
--- a/cc/test/layer_tree_test.h
+++ b/cc/test/layer_tree_test.h
@@ -149,6 +149,10 @@
 
   gfx::Vector2dF ScrollDelta(LayerImpl* layer_impl);
 
+  base::SingleThreadTaskRunner* image_worker_task_runner() const {
+    return image_worker_->task_runner().get();
+  }
+
  private:
   virtual void DispatchAddAnimationToPlayer(
       AnimationPlayer* player_to_receive_animation,
@@ -186,6 +190,7 @@
   scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
   scoped_refptr<base::SingleThreadTaskRunner> impl_task_runner_;
   std::unique_ptr<base::Thread> impl_thread_;
+  std::unique_ptr<base::Thread> image_worker_;
   std::unique_ptr<SharedBitmapManager> shared_bitmap_manager_;
   std::unique_ptr<TestGpuMemoryBufferManager> gpu_memory_buffer_manager_;
   std::unique_ptr<TestTaskGraphRunner> task_graph_runner_;
diff --git a/cc/test/test_hooks.h b/cc/test/test_hooks.h
index d54bbcc..3102137 100644
--- a/cc/test/test_hooks.h
+++ b/cc/test/test_hooks.h
@@ -28,6 +28,7 @@
   virtual void WillBeginImplFrameOnThread(LayerTreeHostImpl* host_impl,
                                           const BeginFrameArgs& args) {}
   virtual void DidFinishImplFrameOnThread(LayerTreeHostImpl* host_impl) {}
+  virtual void DidSendBeginMainFrameOnThread(LayerTreeHostImpl* host_impl) {}
   virtual void BeginMainFrameAbortedOnThread(LayerTreeHostImpl* host_impl,
                                              CommitEarlyOutReason reason) {}
   virtual void ReadyToCommitOnThread(LayerTreeHostImpl* host_impl) {}
@@ -58,6 +59,8 @@
                                     bool has_unfinished_animation) {}
   virtual void WillAnimateLayers(LayerTreeHostImpl* host_impl,
                                  base::TimeTicks monotonic_time) {}
+  virtual void DidInvalidateContentOnImplSide(LayerTreeHostImpl* host_impl) {}
+  virtual void DidRequestImplSideInvalidation(LayerTreeHostImpl* host_impl) {}
 
   // Asynchronous compositor thread hooks.
   // These are called asynchronously from the LayerTreeHostImpl performing its
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index 09eae2b..4ad7918 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -95,6 +95,7 @@
 
 LayerTreeHost::LayerTreeHost(InitParams* params, CompositorMode mode)
     : micro_benchmark_controller_(this),
+      image_worker_task_runner_(params->image_worker_task_runner),
       compositor_mode_(mode),
       ui_resource_manager_(base::MakeUnique<UIResourceManager>()),
       client_(params->client),
@@ -104,9 +105,11 @@
       id_(s_layer_tree_host_sequence_number.GetNext() + 1),
       task_graph_runner_(params->task_graph_runner),
       event_listener_properties_(),
-      mutator_host_(params->mutator_host),
-      image_worker_task_runner_(params->image_worker_task_runner) {
+      mutator_host_(params->mutator_host) {
   DCHECK(task_graph_runner_);
+  DCHECK(!settings_.enable_checker_imaging || image_worker_task_runner_);
+  DCHECK(!settings_.enable_checker_imaging ||
+         settings_.image_decode_tasks_enabled);
 
   mutator_host_->SetMutatorHostClient(this);
 
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index 2b33aac..025336d 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -479,6 +479,8 @@
 
   base::WeakPtr<InputHandler> input_handler_weak_ptr_;
 
+  scoped_refptr<base::SequencedTaskRunner> image_worker_task_runner_;
+
  private:
   friend class LayerTreeHostSerializationTest;
 
@@ -599,8 +601,6 @@
 
   MutatorHost* mutator_host_;
 
-  scoped_refptr<base::SequencedTaskRunner> image_worker_task_runner_;
-
   DISALLOW_COPY_AND_ASSIGN(LayerTreeHost);
 };
 
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index f99af61..fa252c5a0 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -1212,6 +1212,11 @@
   NOTREACHED();
 }
 
+void LayerTreeHostImpl::BlockImplSideInvalidationRequestsForTesting(
+    bool block) {
+  NOTREACHED();
+}
+
 void LayerTreeHostImpl::ResetTreesForTesting() {
   if (active_tree_)
     active_tree_->DetachLayers();
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index 0787f7f4..4f62793 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -235,6 +235,7 @@
     DISALLOW_COPY_AND_ASSIGN(FrameData);
   };
 
+  virtual void DidSendBeginMainFrame() {}
   virtual void BeginMainFrameAborted(
       CommitEarlyOutReason reason,
       std::vector<std::unique_ptr<SwapPromise>> swap_promises);
@@ -253,7 +254,8 @@
 
   // Analogous to a commit, this function is used to create a sync tree and
   // add impl-side invalidations to it.
-  void InvalidateContentOnImplSide();
+  // virtual for testing.
+  virtual void InvalidateContentOnImplSide();
 
   void SetTreeLayerFilterMutated(ElementId element_id,
                                  LayerTreeImpl* tree,
@@ -318,6 +320,10 @@
   // immediately if any notifications had been blocked while blocking.
   virtual void BlockNotifyReadyToActivateForTesting(bool block);
 
+  // Prevents notifying the |client_| when an impl side invalidation request is
+  // made. When unblocked, the disabled request will immediately be called.
+  virtual void BlockImplSideInvalidationRequestsForTesting(bool block);
+
   // Resets all of the trees to an empty state.
   void ResetTreesForTesting();
 
diff --git a/cc/trees/layer_tree_host_unittest_checkerimaging.cc b/cc/trees/layer_tree_host_unittest_checkerimaging.cc
new file mode 100644
index 0000000..580fa3e
--- /dev/null
+++ b/cc/trees/layer_tree_host_unittest_checkerimaging.cc
@@ -0,0 +1,196 @@
+// Copyright 2017 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 "cc/base/completion_event.h"
+#include "cc/test/begin_frame_args_test.h"
+#include "cc/test/fake_content_layer_client.h"
+#include "cc/test/fake_external_begin_frame_source.h"
+#include "cc/test/fake_picture_layer.h"
+#include "cc/test/layer_tree_test.h"
+#include "cc/test/skia_common.h"
+#include "cc/test/test_compositor_frame_sink.h"
+#include "cc/trees/layer_tree_impl.h"
+
+namespace cc {
+namespace {
+
+class LayerTreeHostCheckerImagingTest : public LayerTreeTest {
+ public:
+  void BeginTest() override { PostSetNeedsCommitToMainThread(); }
+  void AfterTest() override {}
+
+  void InitializeSettings(LayerTreeSettings* settings) override {
+    settings->image_decode_tasks_enabled = true;
+    settings->enable_checker_imaging = true;
+  }
+
+  void SetupTree() override {
+    // Set up a content client which creates the following tiling, x denoting
+    // the image to checker:
+    // |---|---|---|---|
+    // | x | x |   |   |
+    // |---|---|---|---|
+    // | x | x |   |   |
+    // |---|---|---|---|
+    gfx::Size layer_size(1000, 500);
+    content_layer_client_.set_bounds(layer_size);
+    content_layer_client_.set_fill_with_nonsolid_color(true);
+    sk_sp<SkImage> checkerable_image =
+        CreateDiscardableImage(gfx::Size(450, 450));
+    content_layer_client_.add_draw_image(checkerable_image, gfx::Point(0, 0),
+                                         PaintFlags());
+
+    layer_tree_host()->SetRootLayer(
+        FakePictureLayer::Create(&content_layer_client_));
+    layer_tree_host()->root_layer()->SetBounds(layer_size);
+    LayerTreeTest::SetupTree();
+  }
+
+  void FlushImageDecodeTasks() {
+    CompletionEvent completion_event;
+    image_worker_task_runner()->PostTask(
+        FROM_HERE,
+        base::Bind([](CompletionEvent* event) { event->Signal(); },
+                   base::Unretained(&completion_event)));
+    completion_event.Wait();
+  }
+
+ private:
+  // Accessed only on the main thread.
+  FakeContentLayerClient content_layer_client_;
+};
+
+class LayerTreeHostCheckerImagingTestMergeWithMainFrame
+    : public LayerTreeHostCheckerImagingTest {
+  void BeginMainFrame(const BeginFrameArgs& args) override {
+    if (layer_tree_host()->SourceFrameNumber() == 1) {
+      // The first commit has happened, invalidate a tile outside the region
+      // for the image to ensure that the final invalidation on the pending
+      // tree is the union of this and impl-side invalidation.
+      layer_tree_host()->root_layer()->SetNeedsDisplayRect(
+          gfx::Rect(600, 0, 50, 500));
+      layer_tree_host()->SetNeedsCommit();
+    }
+  }
+
+  void ReadyToCommitOnThread(LayerTreeHostImpl* host_impl) override {
+    if (num_of_commits_ == 1) {
+      // Send the blocked invalidation request before notifying that we're ready
+      // to commit, since the invalidation will be merged with the commit.
+      host_impl->BlockImplSideInvalidationRequestsForTesting(false);
+    }
+  }
+
+  void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) override {
+    switch (++num_of_commits_) {
+      case 1: {
+        // The first commit has happened. Run all tasks on the image worker to
+        // ensure that the decode completion triggers an impl-side invalidation
+        // request.
+        FlushImageDecodeTasks();
+
+        // Block notifying the scheduler of this request until we get a commit.
+        host_impl->BlockImplSideInvalidationRequestsForTesting(true);
+        host_impl->SetNeedsCommit();
+      } break;
+      case 2: {
+        // Ensure that the expected tiles are invalidated on the sync tree.
+        PictureLayerImpl* sync_layer_impl = static_cast<PictureLayerImpl*>(
+            host_impl->sync_tree()->root_layer_for_testing());
+        PictureLayerTiling* sync_tiling =
+            sync_layer_impl->picture_layer_tiling_set()
+                ->FindTilingWithResolution(TileResolution::HIGH_RESOLUTION);
+
+        for (int i = 0; i < 4; i++) {
+          for (int j = 0; j < 2; j++) {
+            Tile* tile =
+                sync_tiling->TileAt(i, j) ? sync_tiling->TileAt(i, j) : nullptr;
+
+            // If this is the pending tree, then only the invalidated tiles
+            // exist and have a raster task. If its the active tree, then only
+            // the invalidated tiles have a raster task.
+            if (i < 3) {
+              EXPECT_TRUE(tile->HasRasterTask());
+            } else if (host_impl->pending_tree()) {
+              EXPECT_EQ(tile, nullptr);
+            } else {
+              EXPECT_FALSE(tile->HasRasterTask());
+            }
+          }
+        }
+        EndTest();
+      } break;
+      default:
+        NOTREACHED();
+    }
+  }
+
+  void AfterTest() override { EXPECT_EQ(num_of_commits_, 2); }
+
+  int num_of_commits_ = 0;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(
+    LayerTreeHostCheckerImagingTestMergeWithMainFrame);
+
+class LayerTreeHostCheckerImagingTestImplSideTree
+    : public LayerTreeHostCheckerImagingTest {
+  void DidInvalidateContentOnImplSide(LayerTreeHostImpl* host_impl) override {
+    ++num_of_impl_side_invalidations_;
+
+    // The source_frame_number of the sync tree should be from the first main
+    // frame, since this is an impl-side sync tree.
+    EXPECT_EQ(host_impl->sync_tree()->source_frame_number(), 0);
+
+    // Ensure that the expected tiles are invalidated on the sync tree.
+    PictureLayerImpl* sync_layer_impl = static_cast<PictureLayerImpl*>(
+        host_impl->sync_tree()->root_layer_for_testing());
+    PictureLayerTiling* sync_tiling =
+        sync_layer_impl->picture_layer_tiling_set()->FindTilingWithResolution(
+            TileResolution::HIGH_RESOLUTION);
+
+    for (int i = 0; i < 4; i++) {
+      for (int j = 0; j < 2; j++) {
+        Tile* tile =
+            sync_tiling->TileAt(i, j) ? sync_tiling->TileAt(i, j) : nullptr;
+
+        // If this is the pending tree, then only the invalidated tiles
+        // exist and have a raster task. If its the active tree, then only
+        // the invalidated tiles have a raster task.
+        if (i < 2) {
+          EXPECT_TRUE(tile->HasRasterTask());
+        } else if (host_impl->pending_tree()) {
+          EXPECT_EQ(tile, nullptr);
+        } else {
+          EXPECT_FALSE(tile->HasRasterTask());
+        }
+      }
+    }
+  }
+
+  void CommitCompleteOnThread(LayerTreeHostImpl* host_impl) override {
+    num_of_commits_++;
+  }
+
+  void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) override {
+    num_of_activations_++;
+    if (num_of_activations_ == 2)
+      EndTest();
+  }
+
+  void AfterTest() override {
+    EXPECT_EQ(num_of_activations_, 2);
+    EXPECT_EQ(num_of_commits_, 1);
+    EXPECT_EQ(num_of_impl_side_invalidations_, 1);
+  }
+
+  int num_of_activations_ = 0;
+  int num_of_commits_ = 0;
+  int num_of_impl_side_invalidations_ = 0;
+};
+
+SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostCheckerImagingTestImplSideTree);
+
+}  // namespace
+}  // namespace cc
diff --git a/cc/trees/layer_tree_host_unittest_scroll.cc b/cc/trees/layer_tree_host_unittest_scroll.cc
index 9f545a5..151eb26 100644
--- a/cc/trees/layer_tree_host_unittest_scroll.cc
+++ b/cc/trees/layer_tree_host_unittest_scroll.cc
@@ -10,6 +10,7 @@
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "cc/animation/animation_host.h"
+#include "cc/base/completion_event.h"
 #include "cc/input/main_thread_scrolling_reason.h"
 #include "cc/input/scroll_elasticity_helper.h"
 #include "cc/layers/layer.h"
@@ -2070,5 +2071,175 @@
 
 SINGLE_AND_MULTI_THREAD_TEST_F(LayerTreeHostScrollTestPropertyTreeUpdate);
 
+class LayerTreeHostScrollTestImplSideInvalidation
+    : public LayerTreeHostScrollTest {
+  void BeginTest() override {
+    layer_tree_host()->outer_viewport_scroll_layer()->set_did_scroll_callback(
+        base::Bind(&LayerTreeHostScrollTestImplSideInvalidation::
+                       DidScrollOuterViewport,
+                   base::Unretained(this)));
+    PostSetNeedsCommitToMainThread();
+  }
+
+  void DidScrollOuterViewport(const gfx::ScrollOffset& offset) {
+    // Defer responding to the main frame until an impl-side pending tree is
+    // created for the invalidation request.
+    {
+      CompletionEvent completion;
+      task_runner_provider()->ImplThreadTaskRunner()->PostTask(
+          FROM_HERE,
+          base::Bind(&LayerTreeHostScrollTestImplSideInvalidation::
+                         WaitForInvalidationOnImplThread,
+                     base::Unretained(this), &completion));
+      completion.Wait();
+    }
+
+    switch (++num_of_deltas_) {
+      case 1: {
+        // First set of deltas is here. The impl thread will scroll to the
+        // second case on activation, so add a delta from the main thread that
+        // takes us to the final value.
+        Layer* outer_viewport_layer =
+            layer_tree_host()->outer_viewport_scroll_layer();
+        gfx::ScrollOffset delta_to_send =
+            outer_viewport_offsets_[2] - outer_viewport_offsets_[1];
+        outer_viewport_layer->SetScrollOffset(
+            outer_viewport_layer->scroll_offset() + delta_to_send);
+      } break;
+      case 2:
+        // Let the commit abort for the second set of deltas.
+        break;
+      default:
+        NOTREACHED();
+    }
+  }
+
+  void WaitForInvalidationOnImplThread(CompletionEvent* completion) {
+    impl_side_invalidation_event_ = completion;
+    SignalCompletionIfPossible();
+  }
+
+  void DidInvalidateContentOnImplSide(LayerTreeHostImpl* host_impl) override {
+    invalidated_on_impl_thread_ = true;
+    SignalCompletionIfPossible();
+  }
+
+  void SignalCompletionIfPossible() {
+    if (!invalidated_on_impl_thread_ || !impl_side_invalidation_event_)
+      return;
+
+    impl_side_invalidation_event_->Signal();
+    impl_side_invalidation_event_ = nullptr;
+    invalidated_on_impl_thread_ = false;
+  }
+
+  void DidSendBeginMainFrameOnThread(LayerTreeHostImpl* host_impl) override {
+    switch (++num_of_main_frames_) {
+      case 1:
+        // Do nothing for the first BeginMainFrame.
+        break;
+      case 2:
+        // Add some more delta to the active tree state of the scroll offset and
+        // a commit to send this additional delta to the main thread.
+        host_impl->active_tree()
+            ->OuterViewportScrollLayer()
+            ->SetCurrentScrollOffset(outer_viewport_offsets_[1]);
+        host_impl->SetNeedsCommit();
+
+        // Request an impl-side invalidation to create an impl-side pending
+        // tree.
+        host_impl->RequestImplSideInvalidation();
+        break;
+      case 3:
+        // Request another impl-side invalidation so the aborted commit comes
+        // after this tree is activated.
+        host_impl->RequestImplSideInvalidation();
+        break;
+      default:
+        NOTREACHED();
+    }
+  }
+
+  void BeginMainFrameAbortedOnThread(LayerTreeHostImpl* host_impl,
+                                     CommitEarlyOutReason reason) override {
+    // The aborted main frame is bound to come after the fourth activation,
+    // since the activation should occur synchronously after the impl-side
+    // invalidation, and the main thread is released after this activation. It
+    // should leave the scroll offset unchanged.
+    EXPECT_EQ(reason, CommitEarlyOutReason::FINISHED_NO_UPDATES);
+    EXPECT_EQ(num_of_activations_, 4);
+    EXPECT_EQ(num_of_main_frames_, 3);
+    EXPECT_EQ(host_impl->active_tree()
+                  ->OuterViewportScrollLayer()
+                  ->CurrentScrollOffset(),
+              outer_viewport_offsets_[2]);
+    EndTest();
+  }
+
+  void DidActivateTreeOnThread(LayerTreeHostImpl* host_impl) override {
+    switch (++num_of_activations_) {
+      case 1:
+        // Now that we have the active tree, scroll a layer and ask for a commit
+        // to send a BeginMainFrame with the scroll delta to the main thread.
+        host_impl->active_tree()
+            ->OuterViewportScrollLayer()
+            ->SetCurrentScrollOffset(outer_viewport_offsets_[0]);
+        host_impl->SetNeedsCommit();
+        break;
+      case 2:
+        // The second activation is from an impl-side pending tree so the source
+        // frame number on the active tree remains unchanged, and the scroll
+        // offset on the active tree should also remain unchanged.
+        EXPECT_EQ(host_impl->active_tree()->source_frame_number(), 0);
+        EXPECT_EQ(host_impl->active_tree()
+                      ->OuterViewportScrollLayer()
+                      ->CurrentScrollOffset(),
+                  outer_viewport_offsets_[1]);
+        break;
+      case 3:
+        // The third activation is from a commit. The scroll offset on the
+        // active tree should include deltas sent from the main thread.
+        EXPECT_EQ(host_impl->active_tree()->source_frame_number(), 1);
+        EXPECT_EQ(host_impl->active_tree()
+                      ->OuterViewportScrollLayer()
+                      ->CurrentScrollOffset(),
+                  outer_viewport_offsets_[2]);
+        break;
+      case 4:
+        // The fourth activation is from an impl-side pending tree, which should
+        // leave the scroll offset unchanged.
+        EXPECT_EQ(host_impl->active_tree()->source_frame_number(), 1);
+        EXPECT_EQ(host_impl->active_tree()
+                      ->OuterViewportScrollLayer()
+                      ->CurrentScrollOffset(),
+                  outer_viewport_offsets_[2]);
+        break;
+      default:
+        NOTREACHED();
+    }
+  }
+
+  void AfterTest() override {
+    EXPECT_EQ(num_of_activations_, 4);
+    EXPECT_EQ(num_of_deltas_, 2);
+    EXPECT_EQ(num_of_main_frames_, 3);
+  }
+
+  const gfx::ScrollOffset outer_viewport_offsets_[3] = {
+      gfx::ScrollOffset(20, 20), gfx::ScrollOffset(50, 50),
+      gfx::ScrollOffset(70, 70)};
+
+  // Impl thread.
+  int num_of_activations_ = 0;
+  int num_of_main_frames_ = 0;
+  bool invalidated_on_impl_thread_ = false;
+  CompletionEvent* impl_side_invalidation_event_ = nullptr;
+
+  // Main thread.
+  int num_of_deltas_ = 0;
+};
+
+MULTI_THREAD_TEST_F(LayerTreeHostScrollTestImplSideInvalidation);
+
 }  // namespace
 }  // namespace cc
diff --git a/cc/trees/layer_tree_settings.h b/cc/trees/layer_tree_settings.h
index b25a3cd8..d0083b3 100644
--- a/cc/trees/layer_tree_settings.h
+++ b/cc/trees/layer_tree_settings.h
@@ -82,7 +82,11 @@
   size_t scheduled_raster_task_limit = 32;
   bool use_occlusion_for_tile_prioritization = false;
   bool verify_clip_tree_calculations = false;
+
+  // TODO(khushalsagar): Enable for all client and remove this flag if possible.
+  // See crbug/com/696864.
   bool image_decode_tasks_enabled = false;
+
   bool use_layer_lists = false;
   int max_staging_buffer_usage_in_bytes = 32 * 1024 * 1024;
   ManagedMemoryPolicy gpu_memory_policy;
diff --git a/cc/trees/proxy_impl.cc b/cc/trees/proxy_impl.cc
index 7c2cb9fb2..5691956 100644
--- a/cc/trees/proxy_impl.cc
+++ b/cc/trees/proxy_impl.cc
@@ -434,9 +434,7 @@
 
 void ProxyImpl::NeedsImplSideInvalidation() {
   DCHECK(IsImplThread());
-  // TODO(khushalsagar): Plumb this to the scheduler when
-  // https://codereview.chromium.org/2659123004/ lands. See crbug.com/686267.
-  NOTIMPLEMENTED();
+  scheduler_->SetNeedsImplSideInvalidation();
 }
 
 void ProxyImpl::WillBeginImplFrame(const BeginFrameArgs& args) {
@@ -467,6 +465,7 @@
   MainThreadTaskRunner()->PostTask(
       FROM_HERE, base::Bind(&ProxyMain::BeginMainFrame, proxy_main_weak_ptr_,
                             base::Passed(&begin_main_frame_state)));
+  layer_tree_host_impl_->DidSendBeginMainFrame();
   devtools_instrumentation::DidRequestMainThreadFrame(layer_tree_host_id_);
 }
 
@@ -556,7 +555,9 @@
 }
 
 void ProxyImpl::ScheduledActionPerformImplSideInvalidation() {
-  NOTIMPLEMENTED();
+  TRACE_EVENT0("cc", "ProxyImpl::ScheduledActionPerformImplSideInvalidation");
+  DCHECK(IsImplThread());
+  layer_tree_host_impl_->InvalidateContentOnImplSide();
 }
 
 void ProxyImpl::SendBeginMainFrameNotExpectedSoon() {
diff --git a/cc/trees/single_thread_proxy.cc b/cc/trees/single_thread_proxy.cc
index 7469f7cd..33ed54a 100644
--- a/cc/trees/single_thread_proxy.cc
+++ b/cc/trees/single_thread_proxy.cc
@@ -63,6 +63,9 @@
   DebugScopedSetImplThread impl(task_runner_provider_);
 
   const LayerTreeSettings& settings = layer_tree_host_->GetSettings();
+  DCHECK(settings.single_thread_proxy_scheduler ||
+         !settings.enable_checker_imaging)
+      << "Checker-imaging is not supported in synchronous single threaded mode";
   if (settings.single_thread_proxy_scheduler && !scheduler_on_impl_thread_) {
     SchedulerSettings scheduler_settings(settings.ToSchedulerSettings());
     scheduler_settings.commit_to_active_tree = CommitToActiveTree();
@@ -425,9 +428,8 @@
 }
 
 void SingleThreadProxy::NeedsImplSideInvalidation() {
-  // TODO(khushalsagar): Plumb this to the scheduler when
-  // https://codereview.chromium.org/2659123004/ lands. See crbug.com/686267.
-  NOTIMPLEMENTED();
+  DCHECK(scheduler_on_impl_thread_);
+  scheduler_on_impl_thread_->SetNeedsImplSideInvalidation();
 }
 
 void SingleThreadProxy::CompositeImmediately(base::TimeTicks frame_begin_time) {
@@ -604,6 +606,7 @@
   task_runner_provider_->MainThreadTaskRunner()->PostTask(
       FROM_HERE, base::Bind(&SingleThreadProxy::BeginMainFrame,
                             weak_factory_.GetWeakPtr(), begin_frame_args));
+  layer_tree_host_impl_->DidSendBeginMainFrame();
 }
 
 void SingleThreadProxy::SendBeginMainFrameNotExpectedSoon() {
@@ -732,7 +735,19 @@
 }
 
 void SingleThreadProxy::ScheduledActionPerformImplSideInvalidation() {
-  NOTIMPLEMENTED();
+  DCHECK(scheduler_on_impl_thread_);
+
+  DebugScopedSetImplThread impl(task_runner_provider_);
+  commit_blocking_task_runner_.reset(new BlockingTaskRunner::CapturePostTasks(
+      task_runner_provider_->blocking_main_thread_task_runner()));
+  layer_tree_host_impl_->InvalidateContentOnImplSide();
+
+  // Invalidations go directly to the active tree, so we synchronously call
+  // NotifyReadyToActivate to update the scheduler and LTHI state correctly.
+  // Since in single-threaded mode the scheduler will wait for a ready to draw
+  // signal from LTHI, the draw will remain blocked till the invalidated tiles
+  // are ready.
+  NotifyReadyToActivate();
 }
 
 void SingleThreadProxy::UpdateBrowserControlsState(