/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "TestWRScrollData.h" #include "APZTestAccess.h" #include "gtest/gtest.h" #include "FrameMetrics.h" #include "gfxPlatform.h" #include "mozilla/layers/APZUpdater.h" #include "mozilla/layers/LayersTypes.h" #include "mozilla/layers/ScrollableLayerGuid.h" #include "mozilla/layers/WebRenderScrollDataWrapper.h" #include "mozilla/UniquePtr.h" #include "apz/src/APZCTreeManager.h" using mozilla::layers::APZCTreeManager; using mozilla::layers::APZUpdater; using mozilla::layers::LayersId; using mozilla::layers::ScrollableLayerGuid; using mozilla::layers::ScrollMetadata; using mozilla::layers::TestWRScrollData; using mozilla::layers::WebRenderLayerScrollData; using mozilla::layers::WebRenderScrollDataWrapper; /* static */ TestWRScrollData TestWRScrollData::Create(const char* aTreeShape, const APZUpdater& aUpdater, const LayerIntRegion* aVisibleRegions, const gfx::Matrix4x4* aTransforms) { // The WebRenderLayerScrollData tree needs to be created in a fairly // particular way (for example, each node needs to know the number of // descendants it has), so this function takes care to create the nodes // in the same order as WebRenderCommandBuilder would. TestWRScrollData result; const size_t len = strlen(aTreeShape); // "Layer index" in this function refers to the index by which a layer will // be accessible via TestWRScrollData::GetLayer(), and matches the order // in which the layer appears in |aTreeShape|. size_t currentLayerIndex = 0; struct LayerEntry { size_t mLayerIndex; int32_t mDescendantCount = 0; }; // Layers we have encountered in |aTreeShape|, but have not built a // WebRenderLayerScrollData for. (It can only be built after its // descendants have been encountered and counted.) std::stack pendingLayers; std::vector finishedLayers; // Tracks the level of nesting of '(' characters. Starts at 1 to account // for the root layer. size_t depth = 1; // Helper function for finishing a layer once all its descendants have been // encountered. auto finishLayer = [&] { MOZ_ASSERT(!pendingLayers.empty()); LayerEntry entry = pendingLayers.top(); WebRenderLayerScrollData layer; APZTestAccess::InitializeForTest(layer, entry.mDescendantCount); if (aVisibleRegions) { layer.SetVisibleRegion(aVisibleRegions[entry.mLayerIndex]); } if (aTransforms) { layer.SetTransform(aTransforms[entry.mLayerIndex]); } finishedLayers.push_back(std::move(layer)); // |finishedLayers| stores the layers in a different order than they // appeared in |aTreeShape|. To be able to access layers by their layer // index, keep a mapping from layer index to index in |finishedLayers|. result.mIndexMap.emplace(entry.mLayerIndex, finishedLayers.size() - 1); pendingLayers.pop(); // Keep track of descendant counts. The +1 is for the layer just finished. if (!pendingLayers.empty()) { pendingLayers.top().mDescendantCount += (entry.mDescendantCount + 1); } }; for (size_t i = 0; i < len; ++i) { if (aTreeShape[i] == '(') { ++depth; } else if (aTreeShape[i] == ')') { if (pendingLayers.size() <= 1) { printf("Invalid tree shape: too many ')'\n"); MOZ_CRASH(); } finishLayer(); // finish last layer at current depth --depth; } else { if (aTreeShape[i] != 'x') { printf("The only allowed character to represent a layer is 'x'\n"); MOZ_CRASH(); } if (depth == pendingLayers.size()) { // We have a previous layer at this same depth to finish. if (depth <= 1) { printf("The tree is only allowed to have one root\n"); MOZ_CRASH(); } finishLayer(); } MOZ_ASSERT(depth == pendingLayers.size() + 1); pendingLayers.push({currentLayerIndex}); ++currentLayerIndex; } } if (pendingLayers.size() != 1) { printf("Invalid tree shape: '(' and ')' not balanced\n"); MOZ_CRASH(); } finishLayer(); // finish root layer // As in WebRenderCommandBuilder, the layers need to be added to the // WebRenderScrollData in reverse of the order in which they were built. for (auto it = finishedLayers.rbegin(); it != finishedLayers.rend(); ++it) { result.AddLayerData(std::move(*it)); } // mIndexMap also needs to be adjusted to accout for the reversal above. for (auto& [layerIndex, storedIndex] : result.mIndexMap) { (void)layerIndex; // suppress -Werror=unused-variable storedIndex = result.GetLayerCount() - storedIndex - 1; } return result; } const WebRenderLayerScrollData* TestWRScrollData::operator[]( size_t aLayerIndex) const { auto it = mIndexMap.find(aLayerIndex); if (it == mIndexMap.end()) { return nullptr; } return GetLayerData(it->second); } WebRenderLayerScrollData* TestWRScrollData::operator[](size_t aLayerIndex) { auto it = mIndexMap.find(aLayerIndex); if (it == mIndexMap.end()) { return nullptr; } return GetLayerData(it->second); } void TestWRScrollData::SetScrollMetadata( size_t aLayerIndex, const nsTArray& aMetadata) { WebRenderLayerScrollData* layer = operator[](aLayerIndex); MOZ_ASSERT(layer); for (const ScrollMetadata& metadata : aMetadata) { layer->AppendScrollMetadata(*this, metadata); } } class WebRenderScrollDataWrapperTester : public ::testing::Test { protected: virtual void SetUp() { // This ensures ScrollMetadata::sNullMetadata is initialized. gfxPlatform::GetPlatform(); mManager = new APZCTreeManager(LayersId{0}); mUpdater = new APZUpdater(mManager, false); } RefPtr mManager; RefPtr mUpdater; }; TEST_F(WebRenderScrollDataWrapperTester, SimpleTree) { auto layers = TestWRScrollData::Create("x(x(x(xx)x(x)))", *mUpdater); WebRenderScrollDataWrapper w0(*mUpdater, &layers); ASSERT_EQ(layers[0], w0.GetLayer()); WebRenderScrollDataWrapper w1 = w0.GetLastChild(); ASSERT_EQ(layers[1], w1.GetLayer()); ASSERT_FALSE(w1.GetPrevSibling().IsValid()); WebRenderScrollDataWrapper w5 = w1.GetLastChild(); ASSERT_EQ(layers[5], w5.GetLayer()); WebRenderScrollDataWrapper w6 = w5.GetLastChild(); ASSERT_EQ(layers[6], w6.GetLayer()); ASSERT_FALSE(w6.GetLastChild().IsValid()); WebRenderScrollDataWrapper w2 = w5.GetPrevSibling(); ASSERT_EQ(layers[2], w2.GetLayer()); ASSERT_FALSE(w2.GetPrevSibling().IsValid()); WebRenderScrollDataWrapper w4 = w2.GetLastChild(); ASSERT_EQ(layers[4], w4.GetLayer()); ASSERT_FALSE(w4.GetLastChild().IsValid()); WebRenderScrollDataWrapper w3 = w4.GetPrevSibling(); ASSERT_EQ(layers[3], w3.GetLayer()); ASSERT_FALSE(w3.GetLastChild().IsValid()); ASSERT_FALSE(w3.GetPrevSibling().IsValid()); } static ScrollMetadata MakeMetadata(ScrollableLayerGuid::ViewID aId) { ScrollMetadata metadata; metadata.GetMetrics().SetScrollId(aId); return metadata; } TEST_F(WebRenderScrollDataWrapperTester, MultiFramemetricsTree) { auto layers = TestWRScrollData::Create("x(x(x(xx)x(x)))", *mUpdater); nsTArray metadata; metadata.InsertElementAt(0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 0)); // topmost of root layer metadata.InsertElementAt(0, MakeMetadata(ScrollableLayerGuid::NULL_SCROLL_ID)); metadata.InsertElementAt( 0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 1)); metadata.InsertElementAt( 0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 2)); metadata.InsertElementAt(0, MakeMetadata(ScrollableLayerGuid::NULL_SCROLL_ID)); metadata.InsertElementAt( 0, MakeMetadata( ScrollableLayerGuid::NULL_SCROLL_ID)); // bottom of root layer layers.SetScrollMetadata(0, metadata); metadata.Clear(); metadata.InsertElementAt( 0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 3)); layers.SetScrollMetadata(1, metadata); metadata.Clear(); metadata.InsertElementAt( 0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 4)); layers.SetScrollMetadata(2, metadata); metadata.Clear(); metadata.InsertElementAt( 0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 5)); layers.SetScrollMetadata(4, metadata); metadata.Clear(); metadata.InsertElementAt(0, MakeMetadata(ScrollableLayerGuid::NULL_SCROLL_ID)); metadata.InsertElementAt( 0, MakeMetadata(ScrollableLayerGuid::START_SCROLL_ID + 6)); layers.SetScrollMetadata(5, metadata); WebRenderScrollDataWrapper wrapper(*mUpdater, &layers); nsTArray expectedLayers; expectedLayers.AppendElement(layers[0]); expectedLayers.AppendElement(layers[0]); expectedLayers.AppendElement(layers[0]); expectedLayers.AppendElement(layers[0]); expectedLayers.AppendElement(layers[0]); expectedLayers.AppendElement(layers[0]); expectedLayers.AppendElement(layers[1]); expectedLayers.AppendElement(layers[5]); expectedLayers.AppendElement(layers[5]); expectedLayers.AppendElement(layers[6]); nsTArray expectedIds; expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 0); expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID); expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 1); expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 2); expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID); expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID); expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 3); expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID); expectedIds.AppendElement(ScrollableLayerGuid::START_SCROLL_ID + 6); expectedIds.AppendElement(ScrollableLayerGuid::NULL_SCROLL_ID); for (int i = 0; i < 10; i++) { ASSERT_EQ(expectedLayers[i], wrapper.GetLayer()); ASSERT_EQ(expectedIds[i], wrapper.Metrics().GetScrollId()); wrapper = wrapper.GetLastChild(); } ASSERT_FALSE(wrapper.IsValid()); }