HitTestInfoManager.cpp (5805B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "HitTestInfoManager.h" 8 #include "HitTestInfo.h" 9 10 #include "mozilla/gfx/CompositorHitTestInfo.h" 11 #include "mozilla/layers/ScrollableLayerGuid.h" 12 #include "nsDisplayList.h" 13 14 #define DEBUG_HITTEST_INFO 0 15 #if DEBUG_HITTEST_INFO 16 # define HITTEST_INFO_LOG(...) printf_stderr(__VA_ARGS__) 17 #else 18 # define HITTEST_INFO_LOG(...) 19 #endif 20 21 namespace mozilla::layers { 22 23 using ViewID = ScrollableLayerGuid::ViewID; 24 25 /** 26 * TODO(miko): This used to be a performance bottle-neck, but it does not show 27 * up in profiles anymore, see bugs 1424637 and 1424968. 28 * A better way of doing this would be to store current app units per dev pixel 29 * in wr::DisplayListBuilder, and update it whenever display items that separate 30 * presshell boundaries are encountered. 31 */ 32 static int32_t GetAppUnitsFromDisplayItem(nsDisplayItem* aItem) { 33 nsIFrame* frame = aItem->Frame(); 34 MOZ_ASSERT(frame); 35 return frame->PresContext()->AppUnitsPerDevPixel(); 36 } 37 38 static void CreateWebRenderCommands(wr::DisplayListBuilder& aBuilder, 39 nsDisplayItem* aItem, const nsRect& aArea, 40 const gfx::CompositorHitTestInfo& aFlags, 41 const ViewID& aViewId) { 42 const Maybe<SideBits> sideBits = 43 aBuilder.GetContainingFixedPosSideBits(aItem->GetActiveScrolledRoot()); 44 45 const LayoutDeviceRect devRect = 46 LayoutDeviceRect::FromAppUnits(aArea, GetAppUnitsFromDisplayItem(aItem)); 47 const wr::LayoutRect rect = wr::ToLayoutRect(devRect); 48 49 aBuilder.PushHitTest(rect, rect, !aItem->BackfaceIsHidden(), aViewId, aFlags, 50 sideBits.valueOr(SideBits::eNone)); 51 } 52 53 HitTestInfoManager::HitTestInfoManager() 54 : mFlags(gfx::CompositorHitTestInvisibleToHit), 55 mViewId(ScrollableLayerGuid::NULL_SCROLL_ID), 56 mSpaceAndClipChain(wr::InvalidScrollNodeWithChain()) {} 57 58 void HitTestInfoManager::Reset() { 59 mArea = nsRect(); 60 mFlags = gfx::CompositorHitTestInvisibleToHit; 61 mViewId = ScrollableLayerGuid::NULL_SCROLL_ID; 62 mSpaceAndClipChain = wr::InvalidScrollNodeWithChain(); 63 64 HITTEST_INFO_LOG("* HitTestInfoManager::Reset\n"); 65 } 66 67 bool HitTestInfoManager::ProcessItem( 68 nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder, 69 nsDisplayListBuilder* aDisplayListBuilder) { 70 MOZ_ASSERT(aItem); 71 72 HITTEST_INFO_LOG("* HitTestInfoManager::ProcessItem(%d, %s, has=%d)\n", 73 getpid(), aItem->Frame()->ListTag().get(), 74 aItem->HasHitTestInfo()); 75 76 if (MOZ_UNLIKELY(aItem->GetType() == DisplayItemType::TYPE_REMOTE)) { 77 // Remote frames might contain hit-test-info items inside (but those 78 // aren't processed by this process of course), so we can't optimize out the 79 // next hit-test info item because it might be on top of the iframe. 80 Reset(); 81 } 82 83 if (!aItem->HasHitTestInfo()) { 84 return false; 85 } 86 87 const HitTestInfo& hitTestInfo = aItem->GetHitTestInfo(); 88 const nsRect& area = hitTestInfo.Area(); 89 const gfx::CompositorHitTestInfo& flags = hitTestInfo.Info(); 90 91 if (flags == gfx::CompositorHitTestInvisibleToHit || area.IsEmpty()) { 92 return false; 93 } 94 95 const auto viewId = 96 hitTestInfo.GetViewId(aBuilder, aItem->GetActiveScrolledRoot()); 97 const auto spaceAndClipChain = aBuilder.CurrentSpaceAndClipChain(); 98 99 if (!Update(area, flags, viewId, spaceAndClipChain)) { 100 // The previous hit test information is still valid. 101 return false; 102 } 103 104 HITTEST_INFO_LOG("+ [%d, %d, %d, %d]: flags: 0x%x, viewId: %lu\n", area.x, 105 area.y, area.width, area.height, flags.serialize(), viewId); 106 107 CreateWebRenderCommands(aBuilder, aItem, area, flags, viewId); 108 109 return true; 110 } 111 112 void HitTestInfoManager::ProcessItemAsImage( 113 nsDisplayItem* aItem, const wr::LayoutRect& aRect, 114 wr::DisplayListBuilder& aBuilder, 115 nsDisplayListBuilder* aDisplayListBuilder) { 116 // Optimization: if the item has no children, we don't need to tell the 117 // compositor about it because there isn't going to be anything inside it 118 // that would produce a different hit-test result. 119 if (!aItem->HasChildren()) { 120 return; 121 } 122 const auto* asr = aItem->GetActiveScrolledRoot(); 123 SideBits sideBits = 124 aBuilder.GetContainingFixedPosSideBits(asr).valueOr(SideBits::eNone); 125 gfx::CompositorHitTestInfo hitInfo = mFlags; 126 if (hitInfo.contains(gfx::CompositorHitTestFlags::eVisibleToHitTest)) { 127 hitInfo += gfx::CompositorHitTestFlags::eIrregularArea; 128 } 129 aBuilder.PushHitTest(aRect, aRect, !aItem->BackfaceIsHidden(), mViewId, 130 hitInfo, sideBits); 131 } 132 133 /** 134 * Updates the current hit testing information if necessary. 135 * Returns true if the hit testing information was changed. 136 */ 137 bool HitTestInfoManager::Update(const nsRect& aArea, 138 const gfx::CompositorHitTestInfo& aFlags, 139 const ViewID& aViewId, 140 const wr::WrSpaceAndClipChain& aSpaceAndClip) { 141 if (mViewId == aViewId && mFlags == aFlags && mArea.Contains(aArea) && 142 mSpaceAndClipChain == aSpaceAndClip) { 143 // The previous hit testing information can be reused. 144 HITTEST_INFO_LOG("s [%d, %d, %d, %d]: flags: 0x%x, viewId: %lu\n", aArea.x, 145 aArea.y, aArea.width, aArea.height, aFlags.serialize(), 146 aViewId); 147 return false; 148 } 149 150 mArea = aArea; 151 mFlags = aFlags; 152 mViewId = aViewId; 153 mSpaceAndClipChain = aSpaceAndClip; 154 return true; 155 } 156 157 } // namespace mozilla::layers