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 "GridLines.h"
8 
9 #include "GridDimension.h"
10 #include "GridLine.h"
11 #include "mozilla/dom/GridBinding.h"
12 #include "mozilla/dom/GridArea.h"
13 #include "nsGridContainerFrame.h"
14 
15 namespace mozilla::dom {
16 
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(GridLines,mParent,mLines)17 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(GridLines, mParent, mLines)
18 NS_IMPL_CYCLE_COLLECTING_ADDREF(GridLines)
19 NS_IMPL_CYCLE_COLLECTING_RELEASE(GridLines)
20 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GridLines)
21   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
22   NS_INTERFACE_MAP_ENTRY(nsISupports)
23 NS_INTERFACE_MAP_END
24 
25 GridLines::GridLines(GridDimension* aParent) : mParent(aParent) {
26   MOZ_ASSERT(aParent, "Should never be instantiated with a null GridDimension");
27 }
28 
29 GridLines::~GridLines() = default;
30 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)31 JSObject* GridLines::WrapObject(JSContext* aCx,
32                                 JS::Handle<JSObject*> aGivenProto) {
33   return GridLines_Binding::Wrap(aCx, this, aGivenProto);
34 }
35 
Length() const36 uint32_t GridLines::Length() const { return mLines.Length(); }
37 
Item(uint32_t aIndex)38 GridLine* GridLines::Item(uint32_t aIndex) {
39   return mLines.SafeElementAt(aIndex);
40 }
41 
IndexedGetter(uint32_t aIndex,bool & aFound)42 GridLine* GridLines::IndexedGetter(uint32_t aIndex, bool& aFound) {
43   aFound = aIndex < mLines.Length();
44   if (!aFound) {
45     return nullptr;
46   }
47   return mLines[aIndex];
48 }
49 
AddLineNameIfNotPresent(nsTArray<RefPtr<nsAtom>> & aLineNames,nsAtom * aName)50 static void AddLineNameIfNotPresent(nsTArray<RefPtr<nsAtom>>& aLineNames,
51                                     nsAtom* aName) {
52   if (!aLineNames.Contains(aName)) {
53     aLineNames.AppendElement(aName);
54   }
55 }
56 
AddLineNamesIfNotPresent(nsTArray<RefPtr<nsAtom>> & aLineNames,const nsTArray<RefPtr<nsAtom>> & aNames)57 static void AddLineNamesIfNotPresent(nsTArray<RefPtr<nsAtom>>& aLineNames,
58                                      const nsTArray<RefPtr<nsAtom>>& aNames) {
59   for (const auto& name : aNames) {
60     AddLineNameIfNotPresent(aLineNames, name);
61   }
62 }
63 
SetLineInfo(const ComputedGridTrackInfo * aTrackInfo,const ComputedGridLineInfo * aLineInfo,const nsTArray<RefPtr<GridArea>> & aAreas,bool aIsRow)64 void GridLines::SetLineInfo(const ComputedGridTrackInfo* aTrackInfo,
65                             const ComputedGridLineInfo* aLineInfo,
66                             const nsTArray<RefPtr<GridArea>>& aAreas,
67                             bool aIsRow) {
68   MOZ_ASSERT(aLineInfo);
69   mLines.Clear();
70 
71   if (!aTrackInfo) {
72     return;
73   }
74 
75   uint32_t lineCount =
76       aTrackInfo->mEndFragmentTrack - aTrackInfo->mStartFragmentTrack + 1;
77 
78   // If there is at least one track, line count is one more
79   // than the number of tracks.
80   if (lineCount > 0) {
81     nscoord lastTrackEdge = 0;
82     nscoord startOfNextTrack;
83     uint32_t repeatIndex = 0;
84     uint32_t numRepeatTracks = aTrackInfo->mRemovedRepeatTracks.Length();
85     uint32_t numAddedLines = 0;
86 
87     // For the calculation of negative line numbers, we need to know
88     // the total number of leading implicit and explicit tracks.
89     // This might be different from the number of tracks sizes in
90     // aTrackInfo, because some of those tracks may be auto-fits that
91     // have been removed.
92     uint32_t leadingTrackCount =
93         aTrackInfo->mNumLeadingImplicitTracks + aTrackInfo->mNumExplicitTracks;
94     if (numRepeatTracks > 0) {
95       for (auto& removedTrack : aTrackInfo->mRemovedRepeatTracks) {
96         if (removedTrack) {
97           ++leadingTrackCount;
98         }
99       }
100     }
101 
102     for (uint32_t i = aTrackInfo->mStartFragmentTrack;
103          i < aTrackInfo->mEndFragmentTrack + 1; i++) {
104       // Since line indexes are 1-based, calculate a 1-based value
105       // for this track to simplify some calculations.
106       const uint32_t line1Index = i + 1;
107 
108       startOfNextTrack = (i < aTrackInfo->mEndFragmentTrack)
109                              ? aTrackInfo->mPositions[i]
110                              : lastTrackEdge;
111 
112       // Get the line names for the current line. aLineInfo->mNames
113       // may contain duplicate names. This is intentional, since grid
114       // layout works fine with duplicate names, and we don't want to
115       // detect and remove duplicates in layout since it is an O(n^2)
116       // problem. We do the work here since this is only run when
117       // requested by devtools, and slowness here will not affect
118       // normal browsing.
119       const nsTArray<RefPtr<nsAtom>>& possiblyDuplicateLineNames(
120           aLineInfo->mNames.SafeElementAt(i, nsTArray<RefPtr<nsAtom>>()));
121 
122       nsTArray<RefPtr<nsAtom>> lineNames;
123       AddLineNamesIfNotPresent(lineNames, possiblyDuplicateLineNames);
124 
125       // Add in names from grid areas where this line is used as a boundary.
126       for (auto area : aAreas) {
127         // We specifically ignore line names from implicitly named areas,
128         // because it can be confusing for designers who might naturally use
129         // a named line of "-start" or "-end" and create an implicit named
130         // area without meaning to.
131         if (area->Type() == GridDeclaration::Implicit) {
132           continue;
133         }
134 
135         bool haveNameToAdd = false;
136         nsAutoString nameToAdd;
137         area->GetName(nameToAdd);
138         if (aIsRow) {
139           if (area->RowStart() == line1Index) {
140             haveNameToAdd = true;
141             nameToAdd.AppendLiteral("-start");
142           } else if (area->RowEnd() == line1Index) {
143             haveNameToAdd = true;
144             nameToAdd.AppendLiteral("-end");
145           }
146         } else {
147           if (area->ColumnStart() == line1Index) {
148             haveNameToAdd = true;
149             nameToAdd.AppendLiteral("-start");
150           } else if (area->ColumnEnd() == line1Index) {
151             haveNameToAdd = true;
152             nameToAdd.AppendLiteral("-end");
153           }
154         }
155 
156         if (haveNameToAdd) {
157           RefPtr<nsAtom> name = NS_Atomize(nameToAdd);
158           AddLineNameIfNotPresent(lineNames, name);
159         }
160       }
161 
162       if (i >= (aTrackInfo->mRepeatFirstTrack +
163                 aTrackInfo->mNumLeadingImplicitTracks) &&
164           repeatIndex < numRepeatTracks) {
165         numAddedLines += AppendRemovedAutoFits(
166             aTrackInfo, aLineInfo, lastTrackEdge, repeatIndex, numRepeatTracks,
167             leadingTrackCount, lineNames);
168       }
169 
170       // If this line is the one that ends a repeat, then add
171       // in the mNamesFollowingRepeat names from aLineInfo.
172       if (numRepeatTracks > 0 && i == (aTrackInfo->mRepeatFirstTrack +
173                                        aTrackInfo->mNumLeadingImplicitTracks +
174                                        numRepeatTracks - numAddedLines)) {
175         AddLineNamesIfNotPresent(lineNames, aLineInfo->mNamesFollowingRepeat);
176       }
177 
178       RefPtr<GridLine> line = new GridLine(this);
179       mLines.AppendElement(line);
180       MOZ_ASSERT(line1Index > 0, "line1Index must be positive.");
181       bool isBeforeFirstExplicit =
182           (line1Index <= aTrackInfo->mNumLeadingImplicitTracks);
183       bool isAfterLastExplicit = line1Index > (leadingTrackCount + 1);
184       // Calculate an actionable line number for this line, that could be used
185       // in a css grid property to align a grid item or area at that line.
186       // For implicit lines that appear before line 1, report a number of 0.
187       // We can't report negative indexes, because those have a different
188       // meaning in the css grid spec (negative indexes are negative-1-based
189       // from the end of the grid decreasing towards the front).
190       uint32_t lineNumber = isBeforeFirstExplicit
191                                 ? 0
192                                 : (line1Index + numAddedLines -
193                                    aTrackInfo->mNumLeadingImplicitTracks);
194 
195       // The negativeNumber is counted back from the leadingTrackCount.
196       int32_t lineNegativeNumber =
197           isAfterLastExplicit
198               ? 0
199               : (line1Index + numAddedLines - (leadingTrackCount + 2));
200       GridDeclaration lineType = (isBeforeFirstExplicit || isAfterLastExplicit)
201                                      ? GridDeclaration::Implicit
202                                      : GridDeclaration::Explicit;
203       line->SetLineValues(
204           lineNames, nsPresContext::AppUnitsToDoubleCSSPixels(lastTrackEdge),
205           nsPresContext::AppUnitsToDoubleCSSPixels(startOfNextTrack -
206                                                    lastTrackEdge),
207           lineNumber, lineNegativeNumber, lineType);
208 
209       if (i < aTrackInfo->mEndFragmentTrack) {
210         lastTrackEdge = aTrackInfo->mPositions[i] + aTrackInfo->mSizes[i];
211       }
212     }
213 
214     // Define a function that gets the mLines index for a given line number.
215     // This is necessary since it's possible for a line number to not be
216     // represented in mLines. If this is the case, then return  -1.
217     const int32_t lineCount = mLines.Length();
218     const uint32_t lastLineNumber = mLines[lineCount - 1]->Number();
219     auto IndexForLineNumber =
220         [lineCount, lastLineNumber](uint32_t aLineNumber) -> int32_t {
221       if (lastLineNumber == 0) {
222         // None of the lines have addressable numbers, so none of them can have
223         // aLineNumber
224         return -1;
225       }
226 
227       int32_t possibleIndex = (int32_t)aLineNumber - 1;
228       if (possibleIndex < 0 || possibleIndex > lineCount - 1) {
229         // aLineNumber is not represented in mLines.
230         return -1;
231       }
232 
233       return possibleIndex;
234     };
235 
236     // Post-processing loop for implicit grid areas.
237     for (const auto& area : aAreas) {
238       if (area->Type() == GridDeclaration::Implicit) {
239         // Get the appropriate indexes for the area's start and end lines as
240         // they are represented in mLines.
241         int32_t startIndex =
242             IndexForLineNumber(aIsRow ? area->RowStart() : area->ColumnStart());
243         int32_t endIndex =
244             IndexForLineNumber(aIsRow ? area->RowEnd() : area->ColumnEnd());
245 
246         // If both start and end indexes are -1, then stop here since we cannot
247         // reason about the naming for either lines.
248         if (startIndex < 0 && endIndex < 0) {
249           break;
250         }
251 
252         // Get the "-start" and "-end" line names of the grid area.
253         nsAutoString startLineName;
254         area->GetName(startLineName);
255         startLineName.AppendLiteral("-start");
256         nsAutoString endLineName;
257         area->GetName(endLineName);
258         endLineName.AppendLiteral("-end");
259 
260         // Get the list of existing line names for the start and end of the grid
261         // area. In the case where one of the start or end indexes are -1, use a
262         // dummy line as a substitute for the start/end line.
263         RefPtr<GridLine> dummyLine = new GridLine(this);
264         RefPtr<GridLine> areaStartLine =
265             startIndex > -1 ? mLines[startIndex] : dummyLine;
266         nsTArray<RefPtr<nsAtom>> startLineNames(areaStartLine->Names().Clone());
267 
268         RefPtr<GridLine> areaEndLine =
269             endIndex > -1 ? mLines[endIndex] : dummyLine;
270         nsTArray<RefPtr<nsAtom>> endLineNames(areaEndLine->Names().Clone());
271 
272         RefPtr<nsAtom> start = NS_Atomize(startLineName);
273         RefPtr<nsAtom> end = NS_Atomize(endLineName);
274         if (startLineNames.Contains(end) || endLineNames.Contains(start)) {
275           // Add the reversed line names.
276           AddLineNameIfNotPresent(startLineNames, end);
277           AddLineNameIfNotPresent(endLineNames, start);
278         } else {
279           // Add the normal line names.
280           AddLineNameIfNotPresent(startLineNames, start);
281           AddLineNameIfNotPresent(endLineNames, end);
282         }
283 
284         areaStartLine->SetLineNames(startLineNames);
285         areaEndLine->SetLineNames(endLineNames);
286       }
287     }
288   }
289 }
290 
AppendRemovedAutoFits(const ComputedGridTrackInfo * aTrackInfo,const ComputedGridLineInfo * aLineInfo,nscoord aLastTrackEdge,uint32_t & aRepeatIndex,uint32_t aNumRepeatTracks,uint32_t aNumLeadingTracks,nsTArray<RefPtr<nsAtom>> & aLineNames)291 uint32_t GridLines::AppendRemovedAutoFits(
292     const ComputedGridTrackInfo* aTrackInfo,
293     const ComputedGridLineInfo* aLineInfo, nscoord aLastTrackEdge,
294     uint32_t& aRepeatIndex, uint32_t aNumRepeatTracks,
295     uint32_t aNumLeadingTracks, nsTArray<RefPtr<nsAtom>>& aLineNames) {
296   bool extractedExplicitLineNames = false;
297   nsTArray<RefPtr<nsAtom>> explicitLineNames;
298   uint32_t linesAdded = 0;
299   while (aRepeatIndex < aNumRepeatTracks &&
300          aTrackInfo->mRemovedRepeatTracks[aRepeatIndex]) {
301     // If this is not the very first call to this function, and if we
302     // haven't already added a line this call, pull all the explicit
303     // names to pass along to the next line that will be added after
304     // this function completes.
305     if (aRepeatIndex > 0 && linesAdded == 0) {
306       // Find the names that didn't match the before or after names,
307       // and extract them.
308       for (const auto& name : aLineNames) {
309         if (!aLineInfo->mNamesBefore.Contains(name) &&
310             !aLineInfo->mNamesAfter.Contains(name)) {
311           explicitLineNames.AppendElement(name);
312         }
313       }
314       for (const auto& extractedName : explicitLineNames) {
315         aLineNames.RemoveElement(extractedName);
316       }
317       extractedExplicitLineNames = true;
318     }
319 
320     AddLineNamesIfNotPresent(aLineNames, aLineInfo->mNamesBefore);
321 
322     RefPtr<GridLine> line = new GridLine(this);
323     mLines.AppendElement(line);
324 
325     // Time to calculate the line numbers. For the positive numbers
326     // we count with a 1-based index from mRepeatFirstTrack. Although
327     // this number is the index of the first repeat track AFTER all
328     // the leading implicit tracks, that's still what we want since
329     // all those leading implicit tracks have line number 0.
330     uint32_t lineNumber = aTrackInfo->mRepeatFirstTrack + aRepeatIndex + 1;
331 
332     // The negative number does have to account for the leading
333     // implicit tracks. We've been passed aNumLeadingTracks which is
334     // the total of the leading implicit tracks plus the explicit
335     // tracks. So all we have to do is subtract that number plus one
336     // from the 0-based index of this track.
337     int32_t lineNegativeNumber =
338         (aTrackInfo->mNumLeadingImplicitTracks + aTrackInfo->mRepeatFirstTrack +
339          aRepeatIndex) -
340         (aNumLeadingTracks + 1);
341     line->SetLineValues(
342         aLineNames, nsPresContext::AppUnitsToDoubleCSSPixels(aLastTrackEdge),
343         nsPresContext::AppUnitsToDoubleCSSPixels(0), lineNumber,
344         lineNegativeNumber, GridDeclaration::Explicit);
345 
346     // No matter what, the next line should have the after names associated
347     // with it. If we go through the loop again, the before names will also
348     // be added.
349     aLineNames = aLineInfo->mNamesAfter.Clone();
350     aRepeatIndex++;
351 
352     linesAdded++;
353   }
354 
355   aRepeatIndex++;
356 
357   if (extractedExplicitLineNames) {
358     // Pass on the explicit names we saved to the next explicit line.
359     AddLineNamesIfNotPresent(aLineNames, explicitLineNames);
360   }
361 
362   // If we haven't finished adding auto-repeated tracks, then we need to put
363   // back the before names, in case we cleared them above.
364   if (aRepeatIndex < aNumRepeatTracks) {
365     AddLineNamesIfNotPresent(aLineNames, aLineInfo->mNamesBefore);
366   }
367 
368   return linesAdded;
369 }
370 
371 }  // namespace mozilla::dom
372