// Copyright 2011 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/trees/tree_synchronizer.h" #include #include #include #include #include #include "base/containers/contains.h" #include "base/format_macros.h" #include "base/memory/ptr_util.h" #include "base/threading/thread_task_runner_handle.h" #include "cc/animation/animation_host.h" #include "cc/layers/layer.h" #include "cc/layers/layer_impl.h" #include "cc/layers/picture_layer.h" #include "cc/test/animation_test_common.h" #include "cc/test/fake_content_layer_client.h" #include "cc/test/fake_impl_task_runner_provider.h" #include "cc/test/fake_layer_tree_host.h" #include "cc/test/fake_rendering_stats_instrumentation.h" #include "cc/test/stub_layer_tree_host_single_thread_client.h" #include "cc/test/test_task_graph_runner.h" #include "cc/trees/compositor_commit_data.h" #include "cc/trees/effect_node.h" #include "cc/trees/scroll_node.h" #include "cc/trees/single_thread_proxy.h" #include "cc/trees/task_runner_provider.h" #include "testing/gtest/include/gtest/gtest.h" namespace cc { namespace { bool AreScrollOffsetsEqual(const SyncedScrollOffset* a, const SyncedScrollOffset* b) { return a->ActiveBase() == b->ActiveBase() && a->PendingBase() == b->PendingBase() && a->Delta() == b->Delta() && a->PendingDelta().get() == b->PendingDelta().get(); } class MockLayerImpl : public LayerImpl { public: static std::unique_ptr Create(LayerTreeImpl* tree_impl, int layer_id) { return base::WrapUnique(new MockLayerImpl(tree_impl, layer_id)); } ~MockLayerImpl() override { if (layer_impl_destruction_list_) layer_impl_destruction_list_->push_back(id()); } void SetLayerImplDestructionList(std::vector* list) { layer_impl_destruction_list_ = list; } private: MockLayerImpl(LayerTreeImpl* tree_impl, int layer_id) : LayerImpl(tree_impl, layer_id), layer_impl_destruction_list_(nullptr) {} std::vector* layer_impl_destruction_list_; }; class MockLayer : public Layer { public: static scoped_refptr Create( std::vector* layer_impl_destruction_list) { return base::WrapRefCounted(new MockLayer(layer_impl_destruction_list)); } std::unique_ptr CreateLayerImpl( LayerTreeImpl* tree_impl) override { return MockLayerImpl::Create(tree_impl, id()); } void PushPropertiesTo(LayerImpl* layer_impl) override { Layer::PushPropertiesTo(layer_impl); MockLayerImpl* mock_layer_impl = static_cast(layer_impl); mock_layer_impl->SetLayerImplDestructionList(layer_impl_destruction_list_); } private: explicit MockLayer(std::vector* layer_impl_destruction_list) : layer_impl_destruction_list_(layer_impl_destruction_list) {} ~MockLayer() override = default; std::vector* layer_impl_destruction_list_; }; void ExpectTreesAreIdentical(Layer* root_layer, LayerImpl* root_layer_impl, LayerTreeImpl* tree_impl) { auto layer_iter = root_layer->layer_tree_host()->begin(); auto layer_impl_iter = tree_impl->begin(); for (; layer_iter != root_layer->layer_tree_host()->end(); ++layer_iter, ++layer_impl_iter) { Layer* layer = *layer_iter; LayerImpl* layer_impl = *layer_impl_iter; ASSERT_TRUE(layer); ASSERT_TRUE(layer_impl); EXPECT_EQ(layer->id(), layer_impl->id()); EXPECT_EQ(layer_impl->layer_tree_impl(), tree_impl); EXPECT_EQ(layer->non_fast_scrollable_region(), layer_impl->non_fast_scrollable_region()); } } class TreeSynchronizerTest : public testing::Test { public: void ResetLayerTreeHost(const LayerTreeSettings& settings) { host_ = FakeLayerTreeHost::Create(&client_, &task_graph_runner_, animation_host_.get(), settings); host_->InitializeSingleThreaded(&single_thread_client_, base::ThreadTaskRunnerHandle::Get()); host_->host_impl()->CreatePendingTree(); } scoped_refptr SetupScrollLayer() { FakeLayerTreeHostImpl* host_impl = host_->host_impl(); scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr scroll_layer = Layer::Create(); ElementId scroll_element_id = ElementId(5); scroll_layer->SetElementId(scroll_element_id); layer_tree_root->AddChild(scroll_layer); scroll_layer->SetScrollable(gfx::Size(1, 1)); scroll_layer->SetBounds(gfx::Size(10, 10)); host_->SetRootLayer(layer_tree_root); host_->BuildPropertyTreesForTesting(); host_->CommitAndCreatePendingTree(); host_impl->ActivateSyncTree(); ExpectTreesAreIdentical(layer_tree_root.get(), host_impl->active_tree()->root_layer(), host_impl->active_tree()); return scroll_layer; } protected: TreeSynchronizerTest() : animation_host_(AnimationHost::CreateForTesting(ThreadInstance::MAIN)) { LayerTreeSettings settings; ResetLayerTreeHost(settings); } FakeLayerTreeHostClient client_; StubLayerTreeHostSingleThreadClient single_thread_client_; TestTaskGraphRunner task_graph_runner_; std::unique_ptr animation_host_; std::unique_ptr host_; }; // Attempts to synchronizes a null tree. This should not crash, and should // return a null tree. TEST_F(TreeSynchronizerTest, SyncNullTree) { TreeSynchronizer::SynchronizeTrees(static_cast(nullptr), host_->pending_tree()); EXPECT_TRUE(!host_->pending_tree()->root_layer()); } // Constructs a very simple tree and synchronizes it without trying to reuse any // preexisting layers. TEST_F(TreeSynchronizerTest, SyncSimpleTreeFromEmpty) { scoped_refptr layer_tree_root = Layer::Create(); layer_tree_root->AddChild(Layer::Create()); layer_tree_root->AddChild(Layer::Create()); host_->SetRootLayer(layer_tree_root); host_->BuildPropertyTreesForTesting(); TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(), host_->pending_tree()); LayerImpl* root = host_->pending_tree()->root_layer(); EXPECT_TRUE(base::Contains( host_->pending_tree()->LayersThatShouldPushProperties(), root)); ExpectTreesAreIdentical(layer_tree_root.get(), host_->pending_tree()->root_layer(), host_->pending_tree()); } // Constructs a very simple tree and synchronizes it without trying to reuse any // preexisting layers, and test that setting needs push properties happen with // tree sync. TEST_F(TreeSynchronizerTest, SyncSimpleTreeAndPushPropertiesFromEmpty) { scoped_refptr layer_tree_root = Layer::Create(); layer_tree_root->AddChild(Layer::Create()); layer_tree_root->AddChild(Layer::Create()); host_->SetRootLayer(layer_tree_root); host_->BuildPropertyTreesForTesting(); TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(), host_->pending_tree()); // First time the main thread layers are synced to pending tree, and all the // layers are created on pending tree and they all need to push properties to // active tree. LayerImpl* root = host_->pending_tree()->root_layer(); EXPECT_TRUE(base::Contains( host_->pending_tree()->LayersThatShouldPushProperties(), root)); ExpectTreesAreIdentical(layer_tree_root.get(), host_->pending_tree()->root_layer(), host_->pending_tree()); // Push properties to make pending tree have valid property tree index. TreeSynchronizer::PushLayerProperties(host_.get(), host_->pending_tree()); // Now sync from pending tree to active tree. This would clear the map of // layers that need push properties. TreeSynchronizer::SynchronizeTrees(host_->pending_tree(), host_->active_tree()); TreeSynchronizer::PushLayerProperties(host_->pending_tree(), host_->active_tree()); EXPECT_FALSE(base::Contains( host_->pending_tree()->LayersThatShouldPushProperties(), root)); // Set the main thread root layer needs push properties. layer_tree_root->SetNeedsPushProperties(); EXPECT_TRUE(base::Contains(host_->LayersThatShouldPushProperties(), layer_tree_root.get())); // When sync from main thread, the needs push properties status is carried // over to pending tree. TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(), host_->pending_tree()); TreeSynchronizer::PushLayerProperties(host_.get(), host_->pending_tree()); EXPECT_TRUE(base::Contains( host_->pending_tree()->LayersThatShouldPushProperties(), root)); } // Constructs a very simple tree and synchronizes it attempting to reuse some // layers TEST_F(TreeSynchronizerTest, SyncSimpleTreeReusingLayers) { std::vector layer_impl_destruction_list; scoped_refptr layer_tree_root = MockLayer::Create(&layer_impl_destruction_list); layer_tree_root->AddChild(MockLayer::Create(&layer_impl_destruction_list)); layer_tree_root->AddChild(MockLayer::Create(&layer_impl_destruction_list)); int second_layer_impl_id = layer_tree_root->children()[1]->id(); host_->SetRootLayer(layer_tree_root); host_->BuildPropertyTreesForTesting(); TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(), host_->pending_tree()); LayerImpl* layer_impl_tree_root = host_->pending_tree()->root_layer(); EXPECT_TRUE( base::Contains(host_->pending_tree()->LayersThatShouldPushProperties(), layer_impl_tree_root)); ExpectTreesAreIdentical(layer_tree_root.get(), layer_impl_tree_root, host_->pending_tree()); // We have to push properties to pick up the destruction list pointer. TreeSynchronizer::PushLayerProperties(host_.get(), host_->pending_tree()); // Add a new layer to the Layer side layer_tree_root->children()[0]->AddChild( MockLayer::Create(&layer_impl_destruction_list)); // Remove one. layer_tree_root->children()[1]->RemoveFromParent(); // Synchronize again. After the sync the trees should be equivalent and we // should have created and destroyed one LayerImpl. host_->BuildPropertyTreesForTesting(); TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(), host_->pending_tree()); layer_impl_tree_root = host_->pending_tree()->root_layer(); ExpectTreesAreIdentical(layer_tree_root.get(), layer_impl_tree_root, host_->pending_tree()); ASSERT_EQ(1u, layer_impl_destruction_list.size()); EXPECT_EQ(second_layer_impl_id, layer_impl_destruction_list[0]); host_->pending_tree()->DetachLayers(); } // Constructs a very simple tree and checks that a stacking-order change is // tracked properly. TEST_F(TreeSynchronizerTest, SyncSimpleTreeAndTrackStackingOrderChange) { std::vector layer_impl_destruction_list; // Set up the tree and sync once. child2 needs to be synced here, too, even // though we remove it to set up the intended scenario. scoped_refptr layer_tree_root = MockLayer::Create(&layer_impl_destruction_list); scoped_refptr child2 = MockLayer::Create(&layer_impl_destruction_list); layer_tree_root->AddChild(MockLayer::Create(&layer_impl_destruction_list)); layer_tree_root->AddChild(child2); int child1_id = layer_tree_root->children()[0]->id(); int child2_id = layer_tree_root->children()[1]->id(); host_->SetRootLayer(layer_tree_root); host_->BuildPropertyTreesForTesting(); TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(), host_->active_tree()); LayerImpl* layer_impl_tree_root = host_->active_tree()->root_layer(); ExpectTreesAreIdentical(layer_tree_root.get(), layer_impl_tree_root, host_->active_tree()); // We have to push properties to pick up the destruction list pointer. TreeSynchronizer::PushLayerProperties(layer_tree_root->layer_tree_host(), host_->active_tree()); host_->active_tree()->ResetAllChangeTracking(); // re-insert the layer and sync again. child2->RemoveFromParent(); layer_tree_root->AddChild(child2); host_->BuildPropertyTreesForTesting(); TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(), host_->active_tree()); layer_impl_tree_root = host_->active_tree()->root_layer(); ExpectTreesAreIdentical(layer_tree_root.get(), layer_impl_tree_root, host_->active_tree()); host_->active_tree()->SetPropertyTrees( layer_tree_root->layer_tree_host()->property_trees()); TreeSynchronizer::PushLayerProperties(layer_tree_root->layer_tree_host(), host_->active_tree()); // Check that the impl thread properly tracked the change. EXPECT_FALSE(layer_impl_tree_root->LayerPropertyChanged()); EXPECT_FALSE( host_->active_tree()->LayerById(child1_id)->LayerPropertyChanged()); EXPECT_TRUE( host_->active_tree()->LayerById(child2_id)->LayerPropertyChanged()); EXPECT_FALSE(host_->active_tree() ->LayerById(child2_id) ->LayerPropertyChangedFromPropertyTrees()); EXPECT_TRUE(host_->active_tree() ->LayerById(child2_id) ->LayerPropertyChangedNotFromPropertyTrees()); host_->active_tree()->DetachLayers(); } TEST_F(TreeSynchronizerTest, SyncSimpleTreeAndProperties) { scoped_refptr layer_tree_root = Layer::Create(); layer_tree_root->AddChild(Layer::Create()); layer_tree_root->AddChild(Layer::Create()); host_->SetRootLayer(layer_tree_root); // Pick some random properties to set. The values are not important, we're // just testing that at least some properties are making it through. gfx::Size root_bounds = gfx::Size(10, 12); layer_tree_root->SetBounds(root_bounds); gfx::Size second_child_bounds = gfx::Size(25, 53); layer_tree_root->children()[1]->SetBounds(second_child_bounds); int second_child_id = layer_tree_root->children()[1]->id(); host_->BuildPropertyTreesForTesting(); TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(), host_->active_tree()); LayerImpl* layer_impl_tree_root = host_->active_tree()->root_layer(); ExpectTreesAreIdentical(layer_tree_root.get(), layer_impl_tree_root, host_->active_tree()); TreeSynchronizer::PushLayerProperties(layer_tree_root->layer_tree_host(), host_->active_tree()); // Check that the property values we set on the Layer tree are reflected in // the LayerImpl tree. EXPECT_EQ(root_bounds, layer_impl_tree_root->bounds()); gfx::Size second_layer_impl_child_bounds = layer_impl_tree_root->layer_tree_impl() ->LayerById(second_child_id) ->bounds(); EXPECT_EQ(second_child_bounds.width(), second_layer_impl_child_bounds.width()); EXPECT_EQ(second_child_bounds.height(), second_layer_impl_child_bounds.height()); } TEST_F(TreeSynchronizerTest, ReuseLayerImplsAfterStructuralChange) { std::vector layer_impl_destruction_list; // Set up a tree with this sort of structure: // root --- A --- B ---+--- C // | // +--- D scoped_refptr layer_tree_root = MockLayer::Create(&layer_impl_destruction_list); layer_tree_root->AddChild(MockLayer::Create(&layer_impl_destruction_list)); scoped_refptr layer_a = layer_tree_root->children()[0]; layer_a->AddChild(MockLayer::Create(&layer_impl_destruction_list)); scoped_refptr layer_b = layer_a->children()[0]; layer_b->AddChild(MockLayer::Create(&layer_impl_destruction_list)); scoped_refptr layer_c = layer_b->children()[0]; layer_b->AddChild(MockLayer::Create(&layer_impl_destruction_list)); scoped_refptr layer_d = layer_b->children()[1]; host_->SetRootLayer(layer_tree_root); host_->BuildPropertyTreesForTesting(); TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(), host_->active_tree()); LayerImpl* layer_impl_tree_root = host_->active_tree()->root_layer(); ExpectTreesAreIdentical(layer_tree_root.get(), layer_impl_tree_root, host_->active_tree()); // We have to push properties to pick up the destruction list pointer. TreeSynchronizer::PushLayerProperties(layer_tree_root->layer_tree_host(), host_->active_tree()); // Now restructure the tree to look like this: // root --- D ---+--- A // | // +--- C --- B layer_tree_root->RemoveAllChildren(); layer_d->RemoveAllChildren(); layer_tree_root->AddChild(layer_d); layer_a->RemoveAllChildren(); layer_d->AddChild(layer_a); layer_c->RemoveAllChildren(); layer_d->AddChild(layer_c); layer_b->RemoveAllChildren(); layer_c->AddChild(layer_b); // After another synchronize our trees should match and we should not have // destroyed any LayerImpls host_->BuildPropertyTreesForTesting(); TreeSynchronizer::SynchronizeTrees(layer_tree_root.get(), host_->active_tree()); layer_impl_tree_root = host_->active_tree()->root_layer(); ExpectTreesAreIdentical(layer_tree_root.get(), layer_impl_tree_root, host_->active_tree()); EXPECT_EQ(0u, layer_impl_destruction_list.size()); host_->active_tree()->DetachLayers(); } // Constructs a very simple tree, synchronizes it, then synchronizes to a // totally new tree. All layers from the old tree should be deleted. TEST_F(TreeSynchronizerTest, SyncSimpleTreeThenDestroy) { std::vector layer_impl_destruction_list; scoped_refptr old_layer_tree_root = MockLayer::Create(&layer_impl_destruction_list); old_layer_tree_root->AddChild( MockLayer::Create(&layer_impl_destruction_list)); old_layer_tree_root->AddChild( MockLayer::Create(&layer_impl_destruction_list)); host_->SetRootLayer(old_layer_tree_root); int old_tree_root_layer_id = old_layer_tree_root->id(); int old_tree_first_child_layer_id = old_layer_tree_root->children()[0]->id(); int old_tree_second_child_layer_id = old_layer_tree_root->children()[1]->id(); host_->BuildPropertyTreesForTesting(); TreeSynchronizer::SynchronizeTrees(old_layer_tree_root.get(), host_->active_tree()); LayerImpl* layer_impl_tree_root = host_->active_tree()->root_layer(); ExpectTreesAreIdentical(old_layer_tree_root.get(), layer_impl_tree_root, host_->active_tree()); // We have to push properties to pick up the destruction list pointer. TreeSynchronizer::PushLayerProperties(old_layer_tree_root->layer_tree_host(), host_->active_tree()); // Remove all children on the Layer side. old_layer_tree_root->RemoveAllChildren(); // Synchronize again. After the sync all LayerImpls from the old tree should // be deleted. scoped_refptr new_layer_tree_root = Layer::Create(); host_->SetRootLayer(new_layer_tree_root); host_->BuildPropertyTreesForTesting(); TreeSynchronizer::SynchronizeTrees(new_layer_tree_root.get(), host_->active_tree()); layer_impl_tree_root = host_->active_tree()->root_layer(); ExpectTreesAreIdentical(new_layer_tree_root.get(), layer_impl_tree_root, host_->active_tree()); ASSERT_EQ(3u, layer_impl_destruction_list.size()); EXPECT_TRUE( base::Contains(layer_impl_destruction_list, old_tree_root_layer_id)); EXPECT_TRUE(base::Contains(layer_impl_destruction_list, old_tree_first_child_layer_id)); EXPECT_TRUE(base::Contains(layer_impl_destruction_list, old_tree_second_child_layer_id)); } // Constructs+syncs a tree with mask layer. TEST_F(TreeSynchronizerTest, SyncMaskLayer) { scoped_refptr layer_tree_root = Layer::Create(); layer_tree_root->AddChild(Layer::Create()); layer_tree_root->AddChild(Layer::Create()); layer_tree_root->AddChild(Layer::Create()); // First child gets a mask layer. FakeContentLayerClient client; scoped_refptr mask_layer = PictureLayer::Create(&client); layer_tree_root->children()[0]->SetMaskLayer(mask_layer); host_->SetRootLayer(layer_tree_root); host_->BuildPropertyTreesForTesting(); host_->CommitAndCreateLayerImplTree(); LayerImpl* layer_impl_tree_root = host_->active_tree()->root_layer(); ExpectTreesAreIdentical(layer_tree_root.get(), layer_impl_tree_root, host_->active_tree()); // Remove the mask layer. layer_tree_root->children()[0]->SetMaskLayer(nullptr); host_->BuildPropertyTreesForTesting(); host_->CommitAndCreateLayerImplTree(); layer_impl_tree_root = host_->active_tree()->root_layer(); ExpectTreesAreIdentical(layer_tree_root.get(), layer_impl_tree_root, host_->active_tree()); layer_impl_tree_root = host_->active_tree()->root_layer(); ExpectTreesAreIdentical(layer_tree_root.get(), layer_impl_tree_root, host_->active_tree()); layer_impl_tree_root = host_->active_tree()->root_layer(); ExpectTreesAreIdentical(layer_tree_root.get(), layer_impl_tree_root, host_->active_tree()); host_->active_tree()->DetachLayers(); } TEST_F(TreeSynchronizerTest, SynchronizeCurrentlyScrollingNode) { LayerTreeSettings settings; FakeLayerTreeHostImplClient client; FakeImplTaskRunnerProvider task_runner_provider; FakeRenderingStatsInstrumentation stats_instrumentation; TestTaskGraphRunner task_graph_runner; FakeLayerTreeHostImpl* host_impl = host_->host_impl(); scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr scroll_layer = Layer::Create(); scoped_refptr transient_scroll_layer = Layer::Create(); layer_tree_root->AddChild(transient_scroll_layer); transient_scroll_layer->AddChild(scroll_layer); ElementId scroll_element_id = ElementId(5); scroll_layer->SetElementId(scroll_element_id); transient_scroll_layer->SetScrollable(gfx::Size(1, 1)); scroll_layer->SetScrollable(gfx::Size(1, 1)); host_->SetRootLayer(layer_tree_root); host_->BuildPropertyTreesForTesting(); host_->CommitAndCreatePendingTree(); host_impl->ActivateSyncTree(); ExpectTreesAreIdentical(layer_tree_root.get(), host_impl->active_tree()->root_layer(), host_impl->active_tree()); ScrollNode* scroll_node = host_impl->active_tree()->property_trees()->scroll_tree.Node( scroll_layer->scroll_tree_index()); host_impl->active_tree()->SetCurrentlyScrollingNode(scroll_node); transient_scroll_layer->SetScrollable(gfx::Size(0, 0)); host_->BuildPropertyTreesForTesting(); host_impl->CreatePendingTree(); host_->CommitAndCreatePendingTree(); host_impl->ActivateSyncTree(); EXPECT_EQ(scroll_layer->scroll_tree_index(), host_impl->active_tree()->CurrentlyScrollingNode()->id); } TEST_F(TreeSynchronizerTest, SynchronizeScrollTreeScrollOffsetMap) { LayerTreeSettings settings; FakeLayerTreeHostImplClient client; FakeImplTaskRunnerProvider task_runner_provider; FakeRenderingStatsInstrumentation stats_instrumentation; TestTaskGraphRunner task_graph_runner; FakeLayerTreeHostImpl* host_impl = host_->host_impl(); scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr scroll_layer = Layer::Create(); scoped_refptr transient_scroll_layer = Layer::Create(); ElementId scroll_element_id = ElementId(5); scroll_layer->SetElementId(scroll_element_id); layer_tree_root->AddChild(transient_scroll_layer); transient_scroll_layer->AddChild(scroll_layer); ElementId transient_scroll_element_id = ElementId(7); transient_scroll_layer->SetElementId(transient_scroll_element_id); transient_scroll_layer->SetScrollable(gfx::Size(1, 1)); scroll_layer->SetScrollable(gfx::Size(1, 1)); transient_scroll_layer->SetScrollOffset(gfx::ScrollOffset(1, 2)); scroll_layer->SetScrollOffset(gfx::ScrollOffset(10, 20)); host_->SetRootLayer(layer_tree_root); host_->BuildPropertyTreesForTesting(); host_->CommitAndCreatePendingTree(); host_impl->ActivateSyncTree(); ExpectTreesAreIdentical(layer_tree_root.get(), host_impl->active_tree()->root_layer(), host_impl->active_tree()); // After the initial commit, scroll_offset_map in scroll_tree is expected to // have one entry for scroll_layer and one entry for transient_scroll_layer, // the pending base and active base must be the same at this stage. scoped_refptr scroll_layer_offset = new SyncedScrollOffset; scroll_layer_offset->PushMainToPending(scroll_layer->scroll_offset()); scroll_layer_offset->PushPendingToActive(); EXPECT_TRUE(AreScrollOffsetsEqual( scroll_layer_offset.get(), host_impl->active_tree() ->property_trees() ->scroll_tree.GetSyncedScrollOffset(scroll_layer->element_id()))); scoped_refptr transient_scroll_layer_offset = new SyncedScrollOffset; transient_scroll_layer_offset->PushMainToPending( transient_scroll_layer->scroll_offset()); transient_scroll_layer_offset->PushPendingToActive(); EXPECT_TRUE( AreScrollOffsetsEqual(transient_scroll_layer_offset.get(), host_impl->active_tree() ->property_trees() ->scroll_tree.GetSyncedScrollOffset( transient_scroll_layer->element_id()))); // Set ScrollOffset active delta: gfx::ScrollOffset(10, 10) LayerImpl* scroll_layer_impl = host_impl->active_tree()->LayerById(scroll_layer->id()); ScrollTree& scroll_tree = host_impl->active_tree()->property_trees()->scroll_tree; scroll_tree.SetScrollOffset(scroll_layer_impl->element_id(), gfx::ScrollOffset(20, 30)); // Pull ScrollOffset delta for main thread, and change offset on main thread std::unique_ptr commit_data(new CompositorCommitData()); scroll_tree.CollectScrollDeltas( commit_data.get(), ElementId(), settings.commit_fractional_scroll_deltas, base::flat_map()); host_->proxy()->SetNeedsCommit(); host_->ApplyCompositorChanges(commit_data.get()); EXPECT_EQ(gfx::ScrollOffset(20, 30), scroll_layer->scroll_offset()); scroll_layer->SetScrollOffset(gfx::ScrollOffset(100, 100)); // More update to ScrollOffset active delta: gfx::ScrollOffset(20, 20) scroll_tree.SetScrollOffset(scroll_layer_impl->element_id(), gfx::ScrollOffset(40, 50)); host_impl->active_tree()->SetCurrentlyScrollingNode( scroll_tree.Node(scroll_layer_impl->scroll_tree_index())); // Change the scroll tree topology by removing transient_scroll_layer. transient_scroll_layer->RemoveFromParent(); layer_tree_root->AddChild(scroll_layer); host_->BuildPropertyTreesForTesting(); host_impl->CreatePendingTree(); host_->CommitAndCreatePendingTree(); host_impl->ActivateSyncTree(); EXPECT_EQ(scroll_layer->scroll_tree_index(), host_impl->active_tree()->CurrentlyScrollingNode()->id); scroll_layer_offset->SetCurrent(gfx::ScrollOffset(20, 30)); scroll_layer_offset->PullDeltaForMainThread(); scroll_layer_offset->SetCurrent(gfx::ScrollOffset(40, 50)); scroll_layer_offset->PushMainToPending(gfx::ScrollOffset(100, 100)); scroll_layer_offset->PushPendingToActive(); EXPECT_TRUE(AreScrollOffsetsEqual( scroll_layer_offset.get(), host_impl->active_tree() ->property_trees() ->scroll_tree.GetSyncedScrollOffset(scroll_layer->element_id()))); EXPECT_EQ(nullptr, host_impl->active_tree() ->property_trees() ->scroll_tree.GetSyncedScrollOffset( transient_scroll_layer->element_id())); } TEST_F(TreeSynchronizerTest, RefreshPropertyTreesCachedData) { LayerTreeSettings settings; FakeLayerTreeHostImplClient client; FakeImplTaskRunnerProvider task_runner_provider; FakeRenderingStatsInstrumentation stats_instrumentation; TestTaskGraphRunner task_graph_runner; FakeLayerTreeHostImpl* host_impl = host_->host_impl(); scoped_refptr layer_tree_root = Layer::Create(); scoped_refptr transform_layer = Layer::Create(); gfx::Transform scale_transform; scale_transform.Scale3d(2.f, 2.f, 2.f); // Force adding a transform node for the layer. transform_layer->SetTransform(scale_transform); layer_tree_root->AddChild(transform_layer); host_->SetRootLayer(layer_tree_root); host_->BuildPropertyTreesForTesting(); host_->CommitAndCreatePendingTree(); host_impl->ActivateSyncTree(); // This arbitrarily set the animation scale for transform_layer and see if it // is refreshed when pushing layer trees. float maximum_animation_scale = 123.f; bool animation_affected_by_invalid_scale = true; host_impl->active_tree() ->property_trees() ->SetMaximumAnimationToScreenScaleForTesting( transform_layer->transform_tree_index(), maximum_animation_scale, animation_affected_by_invalid_scale); EXPECT_EQ( maximum_animation_scale, host_impl->active_tree()->property_trees()->MaximumAnimationToScreenScale( transform_layer->transform_tree_index())); EXPECT_TRUE(host_impl->active_tree() ->property_trees() ->AnimationAffectedByInvalidScale( transform_layer->transform_tree_index())); host_impl->CreatePendingTree(); host_->CommitAndCreatePendingTree(); host_impl->ActivateSyncTree(); EXPECT_EQ( 2.0f, host_impl->active_tree()->property_trees()->MaximumAnimationToScreenScale( transform_layer->transform_tree_index())); EXPECT_FALSE(host_impl->active_tree() ->property_trees() ->AnimationAffectedByInvalidScale( transform_layer->transform_tree_index())); } TEST_F(TreeSynchronizerTest, RoundedScrollDeltasOnCommit) { LayerTreeSettings settings; settings.commit_fractional_scroll_deltas = false; ResetLayerTreeHost(settings); FakeLayerTreeHostImpl* host_impl = host_->host_impl(); // Since this test simulates a scroll it needs an input handler. // TODO(bokan): Required because scroll commit is part of InputHandler - that // shouldn't be. See comment in ThreadedInputHandler::ProcessCommitDeltas. InputHandler::Create(static_cast(*host_impl)); scoped_refptr scroll_layer = SetupScrollLayer(); // Scroll the layer by a fractional amount. LayerImpl* scroll_layer_impl = host_impl->active_tree()->LayerById(scroll_layer->id()); scroll_layer_impl->ScrollBy(gfx::Vector2dF(0, 1.75f)); // When we collect the scroll deltas, we should have truncated the fractional // part because the commit_fractional_scroll_deltas setting is enabled. std::unique_ptr commit_data = host_impl->ProcessCompositorDeltas(); ASSERT_EQ(1u, commit_data->scrolls.size()); EXPECT_EQ(2.f, commit_data->scrolls[0].scroll_delta.y()); } TEST_F(TreeSynchronizerTest, PreserveFractionalScrollDeltasOnCommit) { LayerTreeSettings settings; settings.commit_fractional_scroll_deltas = true; ResetLayerTreeHost(settings); FakeLayerTreeHostImpl* host_impl = host_->host_impl(); // Since this test simulates a scroll it needs an input handler. // TODO(bokan): Required because scroll commit is part of InputHandler - that // shouldn't be. See comment in ThreadedInputHandler::ProcessCommitDeltas. InputHandler::Create(static_cast(*host_impl)); scoped_refptr scroll_layer = SetupScrollLayer(); // Scroll the layer by a fractional amount. LayerImpl* scroll_layer_impl = host_impl->active_tree()->LayerById(scroll_layer->id()); scroll_layer_impl->ScrollBy(gfx::Vector2dF(0, 1.75f)); // When we collect the scroll deltas, we should keep the fractional part // because the commit_fractional_scroll_deltas setting is disabled. std::unique_ptr commit_data = host_impl->ProcessCompositorDeltas(); ASSERT_EQ(1u, commit_data->scrolls.size()); EXPECT_EQ(1.75f, commit_data->scrolls[0].scroll_delta.y()); } } // namespace } // namespace cc