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