tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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__