AnchorPositioningUtils.h (14373B)
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 #ifndef AnchorPositioningUtils_h__ 8 #define AnchorPositioningUtils_h__ 9 10 #include "WritingModes.h" 11 #include "mozilla/Maybe.h" 12 #include "nsRect.h" 13 #include "nsTHashMap.h" 14 15 class nsAtom; 16 class nsIFrame; 17 18 template <class T> 19 class nsTArray; 20 21 template <class T> 22 class CopyableTArray; 23 24 namespace mozilla { 25 26 class nsDisplayListBuilder; 27 28 struct AnchorPosInfo { 29 // Border-box of the anchor frame, offset against the positioned frame's 30 // absolute containing block's padding box. 31 nsRect mRect; 32 // See `AnchorPosOffsetData::mCompensatesForScroll`. 33 bool mCompensatesForScroll; 34 }; 35 36 class DistanceToNearestScrollContainer { 37 public: 38 DistanceToNearestScrollContainer() = default; 39 explicit DistanceToNearestScrollContainer(uint32_t aDistance) 40 : mDistance{aDistance} {} 41 42 bool Valid() const { return mDistance != kInvalid; } 43 44 bool operator==(const DistanceToNearestScrollContainer&) const = default; 45 bool operator!=(const DistanceToNearestScrollContainer&) const = default; 46 47 private: 48 // 0 is invalid - a frame itself cannot be its own nearest scroll container. 49 static constexpr uint32_t kInvalid = 0; 50 // Ancestor hops to the nearest scroll container. Note that scroll containers 51 // between abspos/fixedpos frames and their containing blocks are irrelevant, 52 // so the distance should be measured from the out-of-flow frame, not the 53 // placeholder frame. 54 uint32_t mDistance = kInvalid; 55 }; 56 57 struct AnchorPosOffsetData { 58 // Origin of the referenced anchor, w.r.t. containing block at the time of 59 // resolution. 60 nsPoint mOrigin; 61 // Does this anchor's offset compensate for scroll? 62 // https://drafts.csswg.org/css-anchor-position-1/#compensate-for-scroll 63 bool mCompensatesForScroll = false; 64 // Distance to this anchor's nearest scroll container. 65 DistanceToNearestScrollContainer mDistanceToNearestScrollContainer; 66 }; 67 68 // Resolved anchor positioning data. 69 struct AnchorPosResolutionData { 70 // Size of the referenced anchor. 71 nsSize mSize; 72 // Offset resolution data. Nothing if the anchor did not resolve, or if the 73 // anchor was only referred to by its size. 74 Maybe<AnchorPosOffsetData> mOffsetData; 75 }; 76 77 // Data required for an anchor positioned frame, including: 78 // * If valid anchors are found, 79 // * Cached offset/size resolution, if resolution was valid, 80 // * Compensating for scroll [1] 81 // * Default scroll shift [2] 82 // 83 // [1]: https://drafts.csswg.org/css-anchor-position-1/#compensate-for-scroll 84 // [2]: https://drafts.csswg.org/css-anchor-position-1/#default-scroll-shift 85 class AnchorPosReferenceData { 86 private: 87 using ResolutionMap = 88 nsTHashMap<RefPtr<const nsAtom>, mozilla::Maybe<AnchorPosResolutionData>>; 89 90 public: 91 // Backup data for attempting a different `@position-try` style, when 92 // the default anchor remains the same. 93 // These entries correspond 1:1 to that of `AnchorPosReferenceData`. 94 struct PositionTryBackup { 95 mozilla::PhysicalAxes mCompensatingForScroll; 96 nsPoint mDefaultScrollShift; 97 nsRect mAdjustedContainingBlock; 98 SideBits mScrollCompensatedSides; 99 nsMargin mInsets; 100 }; 101 using Value = mozilla::Maybe<AnchorPosResolutionData>; 102 103 AnchorPosReferenceData() = default; 104 AnchorPosReferenceData(const AnchorPosReferenceData&) = delete; 105 AnchorPosReferenceData(AnchorPosReferenceData&&) = default; 106 107 AnchorPosReferenceData& operator=(const AnchorPosReferenceData&) = delete; 108 AnchorPosReferenceData& operator=(AnchorPosReferenceData&&) = default; 109 110 struct Result { 111 bool mAlreadyResolved; 112 Value* mEntry; 113 }; 114 115 Result InsertOrModify(const nsAtom* aAnchorName, bool aNeedOffset); 116 const Value* Lookup(const nsAtom* aAnchorName) const; 117 118 bool IsEmpty() const { return mMap.IsEmpty(); } 119 120 ResolutionMap::const_iterator begin() const { return mMap.cbegin(); } 121 ResolutionMap::const_iterator end() const { return mMap.cend(); } 122 123 void AdjustCompensatingForScroll(const mozilla::PhysicalAxes& aAxes) { 124 mCompensatingForScroll += aAxes; 125 } 126 127 mozilla::PhysicalAxes CompensatingForScrollAxes() const { 128 return mCompensatingForScroll; 129 } 130 131 PositionTryBackup TryPositionWithSameDefaultAnchor() { 132 auto compensatingForScroll = std::exchange(mCompensatingForScroll, {}); 133 auto defaultScrollShift = std::exchange(mDefaultScrollShift, {}); 134 auto adjustedContainingBlock = std::exchange(mAdjustedContainingBlock, {}); 135 auto containingBlockSidesAttachedToAnchor = 136 std::exchange(mScrollCompensatedSides, SideBits::eNone); 137 auto insets = std::exchange(mInsets, nsMargin{}); 138 return {compensatingForScroll, defaultScrollShift, adjustedContainingBlock, 139 containingBlockSidesAttachedToAnchor, insets}; 140 } 141 142 void UndoTryPositionWithSameDefaultAnchor(PositionTryBackup&& aBackup) { 143 mCompensatingForScroll = aBackup.mCompensatingForScroll; 144 mDefaultScrollShift = aBackup.mDefaultScrollShift; 145 mAdjustedContainingBlock = aBackup.mAdjustedContainingBlock; 146 mScrollCompensatedSides = aBackup.mScrollCompensatedSides; 147 mInsets = aBackup.mInsets; 148 } 149 150 // Distance from the default anchor to the nearest scroll container. 151 DistanceToNearestScrollContainer mDistanceToDefaultScrollContainer; 152 // https://drafts.csswg.org/css-anchor-position-1/#default-scroll-shift 153 nsPoint mDefaultScrollShift; 154 // Rect of the original containg block. 155 nsRect mOriginalContainingBlockRect; 156 // Adjusted containing block, by position-area or grid, as per 157 // https://drafts.csswg.org/css-position/#original-cb 158 // TODO(dshin, bug 2004596): "or" should be "and/or." 159 nsRect mAdjustedContainingBlock; 160 // TODO(dshin, bug 1987962): Remembered scroll offset 161 // https://drafts.csswg.org/css-anchor-position-1/#remembered-scroll-offset 162 // Name of the default used anchor. Not necessarily positioned frame's 163 // style, because of fallbacks. 164 RefPtr<const nsAtom> mDefaultAnchorName; 165 // Flag indicating which sides of the containing block attach to the 166 // scroll-compensated anchor. Whenever a scroll-compensated anchor scrolls, it 167 // effectively moves around w.r.t. its absolute containing block. This 168 // effectively changes the size of the containing block. For example, given: 169 // 170 // * Absolute containing block of 50px height, 171 // * Scroller, under the abs CB, with the scrolled content height of 100px, 172 // * Anchor element, under the scroller, of 30px height, and 173 // * Positioned element of 30px height, attached to anchor at the bottom. 174 // 175 // The positioned element would overflow the abs CB, until the scroller moves 176 // down by 10px. We address this by defining sides of the CB that scrolls 177 // with the anchor, so that whenever we carry out an overflow check, we move 178 // those sides by the scroll offset, while pinning the rest of the sides to 179 // the original containing block. 180 SideBits mScrollCompensatedSides = SideBits::eNone; 181 // Resolved insets for this positioned element. Modifies the adjusted & 182 // scrolled containing block. 183 nsMargin mInsets; 184 185 private: 186 ResolutionMap mMap; 187 // Axes we need to compensate for scroll [1] in. 188 // [1]: https://drafts.csswg.org/css-anchor-position-1/#compensate-for-scroll 189 mozilla::PhysicalAxes mCompensatingForScroll; 190 }; 191 192 struct LastSuccessfulPositionData { 193 RefPtr<const ComputedStyle> mStyle; 194 uint32_t mIndex = 0; 195 bool mTriedAllFallbacks = false; 196 }; 197 198 struct StylePositionArea; 199 class WritingMode; 200 201 struct AnchorPosDefaultAnchorCache { 202 // Default anchor element's corresponding frame. 203 const nsIFrame* mAnchor = nullptr; 204 // Scroll container for the default anchor. 205 const nsIFrame* mScrollContainer = nullptr; 206 207 AnchorPosDefaultAnchorCache() = default; 208 AnchorPosDefaultAnchorCache(const nsIFrame* aAnchor, 209 const nsIFrame* aScrollContainer); 210 }; 211 212 // Cache data used by anchor resolution. To be populated on abspos reflow, 213 // whenever the frame makes any anchor reference. 214 struct AnchorPosResolutionCache { 215 // Storage for referenced anchors. Designed to be long-lived (i.e. beyond 216 // a reflow cycle). 217 AnchorPosReferenceData* mReferenceData = nullptr; 218 // Cached data for default anchor resolution. Designed to be short-lived, 219 // so it can contain e.g. frame pointers. 220 AnchorPosDefaultAnchorCache mDefaultAnchorCache; 221 222 // Backup data for attempting a different `@position-try` style, when 223 // the default anchor remains the same. 224 using PositionTryBackup = AnchorPosReferenceData::PositionTryBackup; 225 PositionTryBackup TryPositionWithSameDefaultAnchor() { 226 return mReferenceData->TryPositionWithSameDefaultAnchor(); 227 } 228 void UndoTryPositionWithSameDefaultAnchor(PositionTryBackup&& aBackup) { 229 mReferenceData->UndoTryPositionWithSameDefaultAnchor(std::move(aBackup)); 230 } 231 232 // Backup data for attempting a different `@position-try` style, when 233 // the default anchor changes. 234 using PositionTryFullBackup = 235 std::pair<AnchorPosReferenceData, AnchorPosDefaultAnchorCache>; 236 PositionTryFullBackup TryPositionWithDifferentDefaultAnchor() { 237 auto referenceData = std::move(*mReferenceData); 238 *mReferenceData = {}; 239 return std::make_pair( 240 std::move(referenceData), 241 std::exchange(mDefaultAnchorCache, AnchorPosDefaultAnchorCache{})); 242 } 243 void UndoTryPositionWithDifferentDefaultAnchor( 244 PositionTryFullBackup&& aBackup) { 245 *mReferenceData = std::move(aBackup.first); 246 std::exchange(mDefaultAnchorCache, aBackup.second); 247 } 248 }; 249 250 enum class StylePositionTryFallbacksTryTacticKeyword : uint8_t; 251 using StylePositionTryFallbacksTryTactic = 252 CopyableTArray<StylePositionTryFallbacksTryTacticKeyword>; 253 254 /** 255 * AnchorPositioningUtils is a namespace class used for various anchor 256 * positioning helper functions that are useful in multiple places. 257 * The goal is to avoid code duplication and to avoid having too 258 * many helpers in nsLayoutUtils. 259 */ 260 struct AnchorPositioningUtils { 261 /** 262 * Finds the first acceptable frame from the list of possible anchor frames 263 * following https://drafts.csswg.org/css-anchor-position-1/#target 264 */ 265 static nsIFrame* FindFirstAcceptableAnchor( 266 const nsAtom* aName, const nsIFrame* aPositionedFrame, 267 const nsTArray<nsIFrame*>& aPossibleAnchorFrames); 268 269 static Maybe<nsRect> GetAnchorPosRect( 270 const nsIFrame* aAbsoluteContainingBlock, const nsIFrame* aAnchor, 271 bool aCBRectIsvalid); 272 273 static Maybe<AnchorPosInfo> ResolveAnchorPosRect( 274 const nsIFrame* aPositioned, const nsIFrame* aAbsoluteContainingBlock, 275 const nsAtom* aAnchorName, bool aCBRectIsvalid, 276 AnchorPosResolutionCache* aResolutionCache); 277 278 static Maybe<nsSize> ResolveAnchorPosSize( 279 const nsIFrame* aPositioned, const nsAtom* aAnchorName, 280 AnchorPosResolutionCache* aResolutionCache); 281 282 /** 283 * Adjust the containing block rect for the 'position-area' property. 284 * https://drafts.csswg.org/css-anchor-position-1/#position-area 285 */ 286 static nsRect AdjustAbsoluteContainingBlockRectForPositionArea( 287 const nsRect& aAnchorRect, const nsRect& aCBRect, 288 WritingMode aPositionedWM, WritingMode aCBWM, 289 const StylePositionArea& aPosArea, StylePositionArea* aOutResolvedArea); 290 291 /** 292 * Gets the used anchor name for an anchor positioned frame. 293 * 294 * @param aPositioned The anchor positioned frame. 295 * @param aAnchorName The anchor name specified in the anchor function, 296 * or nullptr if not specified. 297 * 298 * If `aAnchorName` is not specified, then this function will return the 299 * default anchor name, if the `position-anchor` property specified one. 300 * Otherwise it will return `nsGkAtoms::AnchorPosImplicitAnchor` if the 301 * element has an implicit anchor, or a nullptr. 302 */ 303 static const nsAtom* GetUsedAnchorName(const nsIFrame* aPositioned, 304 const nsAtom* aAnchorName); 305 306 /** 307 * Get the implicit anchor of the frame. 308 * 309 * @param aFrame The anchor positioned frame. 310 * 311 * For pseudo-elements, this returns the parent frame of the originating 312 * element. For popovers, this returns the primary frame of the invoker. In 313 * all other cases, returns null. 314 */ 315 static nsIFrame* GetAnchorPosImplicitAnchor(const nsIFrame* aFrame); 316 317 struct NearestScrollFrameInfo { 318 const nsIFrame* mScrollContainer = nullptr; 319 DistanceToNearestScrollContainer mDistance; 320 }; 321 static NearestScrollFrameInfo GetNearestScrollFrame(const nsIFrame* aFrame); 322 323 static nsPoint GetScrollOffsetFor( 324 PhysicalAxes aAxes, const nsIFrame* aPositioned, 325 const AnchorPosDefaultAnchorCache& aDefaultAnchorCache); 326 327 struct ContainingBlockInfo { 328 // Provide an explicit containing block size, for during reflow when 329 // its `mRect` has not yet been set. 330 static ContainingBlockInfo ExplicitCBFrameSize( 331 const nsRect& aContainingBlockRect); 332 // Provide the positioned frame, to query its containing block rect. 333 static ContainingBlockInfo UseCBFrameSize(const nsIFrame* aPositioned); 334 335 nsRect GetContainingBlockRect() const { return mRect; } 336 337 private: 338 explicit ContainingBlockInfo(const nsRect& aRect) : mRect{aRect} {} 339 nsRect mRect; 340 }; 341 342 static bool FitsInContainingBlock(const nsIFrame* aPositioned, 343 const AnchorPosReferenceData&); 344 345 /** 346 * If aFrame is positioned using CSS anchor positioning, and it scrolls with 347 * its anchor this function returns the anchor. Otherwise null. 348 * Note that this function has different behaviour if it called during paint 349 * (ie aBuilder not null) or not during painting (aBuilder null). 350 */ 351 static nsIFrame* GetAnchorThatFrameScrollsWith(nsIFrame* aFrame, 352 nsDisplayListBuilder* aBuilder, 353 bool aSkipAsserts = false); 354 355 // Trigger a layout for positioned items that are currently overflowing their 356 // abs-cb and that have available fallbacks to try. 357 static bool TriggerLayoutOnOverflow(PresShell* aPresShell, 358 bool aEvaluateAllFallbacksIfNeeded); 359 }; 360 361 } // namespace mozilla 362 363 #endif // AnchorPositioningUtils_h__