File: AnchorPositioningUtils.h

package info (click to toggle)
firefox 147.0.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,683,484 kB
  • sloc: cpp: 7,607,246; javascript: 6,533,185; ansic: 3,775,227; python: 1,415,393; xml: 634,561; asm: 438,951; java: 186,241; sh: 62,752; makefile: 18,079; objc: 13,092; perl: 12,808; yacc: 4,583; cs: 3,846; pascal: 3,448; lex: 1,720; ruby: 1,003; php: 436; lisp: 258; awk: 247; sql: 66; sed: 54; csh: 10; exp: 6
file content (335 lines) | stat: -rw-r--r-- 12,847 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
/* -*- 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/. */

#ifndef AnchorPositioningUtils_h__
#define AnchorPositioningUtils_h__

#include "WritingModes.h"
#include "mozilla/Maybe.h"
#include "nsRect.h"
#include "nsTHashMap.h"

class nsAtom;
class nsIFrame;

template <class T>
class nsTArray;

template <class T>
class CopyableTArray;

namespace mozilla {

class nsDisplayListBuilder;

struct AnchorPosInfo {
  // Border-box of the anchor frame, offset against the positioned frame's
  // absolute containing block's padding box.
  nsRect mRect;
  // See `AnchorPosOffsetData::mCompensatesForScroll`.
  bool mCompensatesForScroll;
};

class DistanceToNearestScrollContainer {
 public:
  DistanceToNearestScrollContainer() = default;
  explicit DistanceToNearestScrollContainer(uint32_t aDistance)
      : mDistance{aDistance} {}

  bool Valid() const { return mDistance != kInvalid; }

  bool operator==(const DistanceToNearestScrollContainer&) const = default;
  bool operator!=(const DistanceToNearestScrollContainer&) const = default;

 private:
  // 0 is invalid - a frame itself cannot be its own nearest scroll container.
  static constexpr uint32_t kInvalid = 0;
  // Ancestor hops to the nearest scroll container. Note that scroll containers
  // between abspos/fixedpos frames and their containing blocks are irrelevant,
  // so the distance should be measured from the out-of-flow frame, not the
  // placeholder frame.
  uint32_t mDistance = kInvalid;
};

struct AnchorPosOffsetData {
  // Origin of the referenced anchor, w.r.t. containing block at the time of
  // resolution.
  nsPoint mOrigin;
  // Does this anchor's offset compensate for scroll?
  // https://drafts.csswg.org/css-anchor-position-1/#compensate-for-scroll
  bool mCompensatesForScroll = false;
  // Distance to this anchor's nearest scroll container.
  DistanceToNearestScrollContainer mDistanceToNearestScrollContainer;
};

// Resolved anchor positioning data.
struct AnchorPosResolutionData {
  // Size of the referenced anchor.
  nsSize mSize;
  // Offset resolution data. Nothing if the anchor did not resolve, or if the
  // anchor was only referred to by its size.
  Maybe<AnchorPosOffsetData> mOffsetData;
};

// Data required for an anchor positioned frame, including:
// * If valid anchors are found,
// * Cached offset/size resolution, if resolution was valid,
// * Compensating for scroll [1]
// * Default scroll shift [2]
//
// [1]: https://drafts.csswg.org/css-anchor-position-1/#compensate-for-scroll
// [2]: https://drafts.csswg.org/css-anchor-position-1/#default-scroll-shift
class AnchorPosReferenceData {
 private:
  using ResolutionMap =
      nsTHashMap<RefPtr<const nsAtom>, mozilla::Maybe<AnchorPosResolutionData>>;

 public:
  // Backup data for attempting a different `@position-try` style, when
  // the default anchor remains the same.
  // These entries correspond 1:1 to that of `AnchorPosReferenceData`.
  struct PositionTryBackup {
    mozilla::PhysicalAxes mCompensatingForScroll;
    nsPoint mDefaultScrollShift;
    nsRect mAdjustedContainingBlock;
  };
  using Value = mozilla::Maybe<AnchorPosResolutionData>;

  AnchorPosReferenceData() = default;
  AnchorPosReferenceData(const AnchorPosReferenceData&) = delete;
  AnchorPosReferenceData(AnchorPosReferenceData&&) = default;

  AnchorPosReferenceData& operator=(const AnchorPosReferenceData&) = delete;
  AnchorPosReferenceData& operator=(AnchorPosReferenceData&&) = default;

  struct Result {
    bool mAlreadyResolved;
    Value* mEntry;
  };

  Result InsertOrModify(const nsAtom* aAnchorName, bool aNeedOffset);
  const Value* Lookup(const nsAtom* aAnchorName) const;

  bool IsEmpty() const { return mMap.IsEmpty(); }

  ResolutionMap::const_iterator begin() const { return mMap.cbegin(); }
  ResolutionMap::const_iterator end() const { return mMap.cend(); }

  void AdjustCompensatingForScroll(const mozilla::PhysicalAxes& aAxes) {
    mCompensatingForScroll += aAxes;
  }

  mozilla::PhysicalAxes CompensatingForScrollAxes() const {
    return mCompensatingForScroll;
  }

  PositionTryBackup TryPositionWithSameDefaultAnchor() {
    auto compensatingForScroll = std::exchange(mCompensatingForScroll, {});
    auto defaultScrollShift = std::exchange(mDefaultScrollShift, {});
    auto adjustedContainingBlock = std::exchange(mAdjustedContainingBlock, {});
    return {compensatingForScroll, defaultScrollShift, adjustedContainingBlock};
  }

  void UndoTryPositionWithSameDefaultAnchor(PositionTryBackup&& aBackup) {
    mCompensatingForScroll = aBackup.mCompensatingForScroll;
    mDefaultScrollShift = aBackup.mDefaultScrollShift;
    mAdjustedContainingBlock = aBackup.mAdjustedContainingBlock;
  }

  // Distance from the default anchor to the nearest scroll container.
  DistanceToNearestScrollContainer mDistanceToDefaultScrollContainer;
  // https://drafts.csswg.org/css-anchor-position-1/#default-scroll-shift
  nsPoint mDefaultScrollShift;
  // Rect of the original containg block.
  nsRect mOriginalContainingBlockRect;
  // Adjusted containing block, by position-area or grid, as per
  // https://drafts.csswg.org/css-position/#original-cb
  // TODO(dshin, bug 2004596): "or" should be "and/or."
  nsRect mAdjustedContainingBlock;
  // TODO(dshin, bug 1987962): Remembered scroll offset
  // https://drafts.csswg.org/css-anchor-position-1/#remembered-scroll-offset
  // Name of the default used anchor. Not necessarily positioned frame's
  // style, because of fallbacks.
  RefPtr<const nsAtom> mDefaultAnchorName;

 private:
  ResolutionMap mMap;
  // Axes we need to compensate for scroll [1] in.
  // [1]: https://drafts.csswg.org/css-anchor-position-1/#compensate-for-scroll
  mozilla::PhysicalAxes mCompensatingForScroll;
};

struct LastSuccessfulPositionData {
  uint32_t mIndex = 0;
  bool mTriedAllFallbacks = false;
};

struct StylePositionArea;
class WritingMode;

struct AnchorPosDefaultAnchorCache {
  // Default anchor element's corresponding frame.
  const nsIFrame* mAnchor = nullptr;
  // Scroll container for the default anchor.
  const nsIFrame* mScrollContainer = nullptr;

  AnchorPosDefaultAnchorCache() = default;
  AnchorPosDefaultAnchorCache(const nsIFrame* aAnchor,
                              const nsIFrame* aScrollContainer);
};

// Cache data used by anchor resolution. To be populated on abspos reflow,
// whenever the frame makes any anchor reference.
struct AnchorPosResolutionCache {
  // Storage for referenced anchors. Designed to be long-lived (i.e. beyond
  // a reflow cycle).
  AnchorPosReferenceData* mReferenceData = nullptr;
  // Cached data for default anchor resolution. Designed to be short-lived,
  // so it can contain e.g. frame pointers.
  AnchorPosDefaultAnchorCache mDefaultAnchorCache;

  // Backup data for attempting a different `@position-try` style, when
  // the default anchor remains the same.
  using PositionTryBackup = AnchorPosReferenceData::PositionTryBackup;
  PositionTryBackup TryPositionWithSameDefaultAnchor() {
    return mReferenceData->TryPositionWithSameDefaultAnchor();
  }
  void UndoTryPositionWithSameDefaultAnchor(PositionTryBackup&& aBackup) {
    mReferenceData->UndoTryPositionWithSameDefaultAnchor(std::move(aBackup));
  }

  // Backup data for attempting a different `@position-try` style, when
  // the default anchor changes.
  using PositionTryFullBackup =
      std::pair<AnchorPosReferenceData, AnchorPosDefaultAnchorCache>;
  PositionTryFullBackup TryPositionWithDifferentDefaultAnchor() {
    auto referenceData = std::move(*mReferenceData);
    *mReferenceData = {};
    return std::make_pair(
        std::move(referenceData),
        std::exchange(mDefaultAnchorCache, AnchorPosDefaultAnchorCache{}));
  }
  void UndoTryPositionWithDifferentDefaultAnchor(
      PositionTryFullBackup&& aBackup) {
    *mReferenceData = std::move(aBackup.first);
    std::exchange(mDefaultAnchorCache, aBackup.second);
  }
};

enum class StylePositionTryFallbacksTryTacticKeyword : uint8_t;
using StylePositionTryFallbacksTryTactic =
    CopyableTArray<StylePositionTryFallbacksTryTacticKeyword>;

/**
 * AnchorPositioningUtils is a namespace class used for various anchor
 * positioning helper functions that are useful in multiple places.
 * The goal is to avoid code duplication and to avoid having too
 * many helpers in nsLayoutUtils.
 */
struct AnchorPositioningUtils {
  /**
   * Finds the first acceptable frame from the list of possible anchor frames
   * following https://drafts.csswg.org/css-anchor-position-1/#target
   */
  static nsIFrame* FindFirstAcceptableAnchor(
      const nsAtom* aName, const nsIFrame* aPositionedFrame,
      const nsTArray<nsIFrame*>& aPossibleAnchorFrames);

  static Maybe<nsRect> GetAnchorPosRect(
      const nsIFrame* aAbsoluteContainingBlock, const nsIFrame* aAnchor,
      bool aCBRectIsvalid);

  static Maybe<AnchorPosInfo> ResolveAnchorPosRect(
      const nsIFrame* aPositioned, const nsIFrame* aAbsoluteContainingBlock,
      const nsAtom* aAnchorName, bool aCBRectIsvalid,
      AnchorPosResolutionCache* aResolutionCache);

  static Maybe<nsSize> ResolveAnchorPosSize(
      const nsIFrame* aPositioned, const nsAtom* aAnchorName,
      AnchorPosResolutionCache* aResolutionCache);

  /**
   * Adjust the containing block rect for the 'position-area' property.
   * https://drafts.csswg.org/css-anchor-position-1/#position-area
   */
  static nsRect AdjustAbsoluteContainingBlockRectForPositionArea(
      const nsRect& aAnchorRect, const nsRect& aCBRect,
      WritingMode aPositionedWM, WritingMode aCBWM,
      const StylePositionArea& aPosArea, StylePositionArea* aOutResolvedArea);

  /**
   * Gets the used anchor name for an anchor positioned frame.
   *
   * @param aPositioned The anchor positioned frame.
   * @param aAnchorName The anchor name specified in the anchor function,
   *   or nullptr if not specified.
   *
   * If `aAnchorName` is not specified, then this function will return the
   * default anchor name, if the `position-anchor` property specified one.
   * Otherwise it will return `nsGkAtoms::AnchorPosImplicitAnchor` if the
   * element has an implicit anchor, or a nullptr.
   */
  static const nsAtom* GetUsedAnchorName(const nsIFrame* aPositioned,
                                         const nsAtom* aAnchorName);

  /**
   * Get the implicit anchor of the frame.
   *
   * @param aFrame The anchor positioned frame.
   *
   * For pseudo-elements, this returns the parent frame of the originating
   * element. For popovers, this returns the primary frame of the invoker. In
   * all other cases, returns null.
   */
  static nsIFrame* GetAnchorPosImplicitAnchor(const nsIFrame* aFrame);

  struct NearestScrollFrameInfo {
    const nsIFrame* mScrollContainer = nullptr;
    DistanceToNearestScrollContainer mDistance;
  };
  static NearestScrollFrameInfo GetNearestScrollFrame(const nsIFrame* aFrame);

  static nsPoint GetScrollOffsetFor(
      PhysicalAxes aAxes, const nsIFrame* aPositioned,
      const AnchorPosDefaultAnchorCache& aDefaultAnchorCache);

  struct ContainingBlockInfo {
    // Provide an explicit containing block size, for during reflow when
    // its `mRect` has not yet been set.
    static ContainingBlockInfo ExplicitCBFrameSize(
        const nsRect& aContainingBlockRect);
    // Provide the positioned frame, to query  its containing block rect.
    static ContainingBlockInfo UseCBFrameSize(const nsIFrame* aPositioned);

    nsRect GetContainingBlockRect() const { return mRect; }

   private:
    explicit ContainingBlockInfo(const nsRect& aRect) : mRect{aRect} {}
    nsRect mRect;
  };

  static bool FitsInContainingBlock(const nsIFrame* aPositioned,
                                    const AnchorPosReferenceData&);

  /**
   * If aFrame is positioned using CSS anchor positioning, and it scrolls with
   * its anchor this function returns the anchor. Otherwise null.
   * Note that this function has different behaviour if it called during paint
   * (ie aBuilder not null) or not during painting (aBuilder null).
   */
  static nsIFrame* GetAnchorThatFrameScrollsWith(nsIFrame* aFrame,
                                                 nsDisplayListBuilder* aBuilder,
                                                 bool aSkipAsserts = false);

  // Trigger a layout for positioned items that are currently overflowing their
  // abs-cb and that have available fallbacks to try.
  static bool TriggerLayoutOnOverflow(PresShell* aPresShell,
                                      bool aEvaluateAllFallbacksIfNeeded);
};

}  // namespace mozilla

#endif  // AnchorPositioningUtils_h__