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 //
8 // Eric Vaughan
9 // Netscape Communications
10 //
11 // See documentation in associated header file
12 //
13 
14 #include "nsGrid.h"
15 #include "nsGridRowGroupLayout.h"
16 #include "nsBox.h"
17 #include "nsIScrollableFrame.h"
18 #include "nsSprocketLayout.h"
19 #include "nsGridLayout2.h"
20 #include "nsGridRow.h"
21 #include "nsGridCell.h"
22 #include "mozilla/ReflowInput.h"
23 
24 /*
25 The grid control expands the idea of boxes from 1 dimension to 2 dimensions.
26 It works by allowing the XUL to define a collection of rows and columns and then
27 stacking them on top of each other. Here is and example.
28 
29 Example 1:
30 
31 <grid>
32    <columns>
33       <column/>
34       <column/>
35    </columns>
36 
37    <rows>
38       <row/>
39       <row/>
40    </rows>
41 </grid>
42 
43 example 2:
44 
45 <grid>
46    <columns>
47       <column flex="1"/>
48       <column flex="1"/>
49    </columns>
50 
51    <rows>
52       <row>
53          <text value="hello"/>
54          <text value="there"/>
55       </row>
56    </rows>
57 </grid>
58 
59 example 3:
60 
61 <grid>
62 
63 <rows>
64       <row>
65          <text value="hello"/>
66          <text value="there"/>
67       </row>
68    </rows>
69 
70    <columns>
71       <column>
72          <text value="Hey I'm in the column and I'm on top!"/>
73       </column>
74       <column/>
75    </columns>
76 
77 </grid>
78 
79 Usually the columns are first and the rows are second, so the rows will be drawn
80 on top of the columns. You can reverse this by defining the rows first. Other
81 tags are then placed in the <row> or <column> tags causing the grid to
82 accommodate everyone. It does this by creating 3 things: A cellmap, a row list,
83 and a column list. The cellmap is a 2 dimensional array of nsGridCells. Each
84 cell contains 2 boxes.  One cell from the column list and one from the row list.
85 When a cell is asked for its size it returns that smallest size it can be to
86 accommodate the 2 cells. Row lists and Column lists use the same data structure:
87 nsGridRow. Essentially a row and column are the same except a row goes alone the
88 x axis and a column the y. To make things easier and save code everything is
89 written in terms of the x dimension. A flag is passed in called "isHorizontal"
90 that can flip the calculations to the y axis.
91 
92 Usually the number of cells in a row match the number of columns, but not
93 always. It is possible to define 5 columns for a grid but have 10 cells in one
94 of the rows. In this case 5 extra columns will be added to the column list to
95 handle the situation. These are called extraColumns/Rows.
96 */
97 
98 using namespace mozilla;
99 
nsGrid()100 nsGrid::nsGrid()
101     : mBox(nullptr),
102       mRowsBox(nullptr),
103       mColumnsBox(nullptr),
104       mNeedsRebuild(true),
105       mRowCount(0),
106       mColumnCount(0),
107       mExtraRowCount(0),
108       mExtraColumnCount(0),
109       mMarkingDirty(false) {
110   MOZ_COUNT_CTOR(nsGrid);
111 }
112 
~nsGrid()113 nsGrid::~nsGrid() {
114   FreeMap();
115   MOZ_COUNT_DTOR(nsGrid);
116 }
117 
118 /*
119  * This is called whenever something major happens in the grid. And example
120  * might be when many cells or row are added. It sets a flag signaling that
121  * all the grids caches information should be recalculated.
122  */
NeedsRebuild(nsBoxLayoutState & aState)123 void nsGrid::NeedsRebuild(nsBoxLayoutState& aState) {
124   if (mNeedsRebuild) return;
125 
126   // iterate through columns and rows and dirty them
127   mNeedsRebuild = true;
128 
129   // find the new row and column box. They could have
130   // been changed.
131   mRowsBox = nullptr;
132   mColumnsBox = nullptr;
133   FindRowsAndColumns(&mRowsBox, &mColumnsBox);
134 
135   // tell all the rows and columns they are dirty
136   DirtyRows(mRowsBox, aState);
137   DirtyRows(mColumnsBox, aState);
138 }
139 
140 /**
141  * If we are marked for rebuild. Then build everything
142  */
RebuildIfNeeded()143 void nsGrid::RebuildIfNeeded() {
144   if (!mNeedsRebuild) return;
145 
146   mNeedsRebuild = false;
147 
148   // find the row and columns frames
149   FindRowsAndColumns(&mRowsBox, &mColumnsBox);
150 
151   // count the rows and columns
152   int32_t computedRowCount = 0;
153   int32_t computedColumnCount = 0;
154   int32_t rowCount = 0;
155   int32_t columnCount = 0;
156 
157   CountRowsColumns(mRowsBox, rowCount, computedColumnCount);
158   CountRowsColumns(mColumnsBox, columnCount, computedRowCount);
159 
160   // computedRowCount are the actual number of rows as determined by the
161   // columns children.
162   // computedColumnCount are the number of columns as determined by the number
163   // of rows children.
164   // We can use this information to see how many extra columns or rows we need.
165   // This can happen if there are are more children in a row that number of
166   // columns defined. Example:
167   //
168   // <columns>
169   //   <column/>
170   // </columns>
171   //
172   // <rows>
173   //   <row>
174   //     <button/><button/>
175   //   </row>
176   // </rows>
177   //
178   // computedColumnCount = 2 // for the 2 buttons in the row tag
179   // computedRowCount = 0 // there is nothing in the  column tag
180   // mColumnCount = 1 // one column defined
181   // mRowCount = 1 // one row defined
182   //
183   // So in this case we need to make 1 extra column.
184   //
185 
186   // Make sure to update mExtraColumnCount no matter what, since it might
187   // happen that we now have as many columns as are defined, and we wouldn't
188   // want to have a positive mExtraColumnCount hanging about in that case!
189   mExtraColumnCount = computedColumnCount - columnCount;
190   if (computedColumnCount > columnCount) {
191     columnCount = computedColumnCount;
192   }
193 
194   // Same for rows.
195   mExtraRowCount = computedRowCount - rowCount;
196   if (computedRowCount > rowCount) {
197     rowCount = computedRowCount;
198   }
199 
200   // build and poplulate row and columns arrays
201   mRows = BuildRows(mRowsBox, rowCount, true);
202   mColumns = BuildRows(mColumnsBox, columnCount, false);
203 
204   // build and populate the cell map
205   mCellMap = BuildCellMap(rowCount, columnCount);
206 
207   mRowCount = rowCount;
208   mColumnCount = columnCount;
209 
210   // populate the cell map from column and row children
211   PopulateCellMap(mRows.get(), mColumns.get(), mRowCount, mColumnCount, true);
212   PopulateCellMap(mColumns.get(), mRows.get(), mColumnCount, mRowCount, false);
213 }
214 
FreeMap()215 void nsGrid::FreeMap() {
216   mRows = nullptr;
217   mColumns = nullptr;
218   mCellMap = nullptr;
219   mColumnCount = 0;
220   mRowCount = 0;
221   mExtraColumnCount = 0;
222   mExtraRowCount = 0;
223   mRowsBox = nullptr;
224   mColumnsBox = nullptr;
225 }
226 
227 /**
228  * finds the first <rows> and <columns> tags in the <grid> tag
229  */
FindRowsAndColumns(nsIFrame ** aRows,nsIFrame ** aColumns)230 void nsGrid::FindRowsAndColumns(nsIFrame** aRows, nsIFrame** aColumns) {
231   *aRows = nullptr;
232   *aColumns = nullptr;
233 
234   // find the boxes that contain our rows and columns
235   nsIFrame* child = nullptr;
236   // if we have <grid></grid> then mBox will be null (bug 125689)
237   if (mBox) child = nsBox::GetChildXULBox(mBox);
238 
239   while (child) {
240     nsIFrame* oldBox = child;
241     nsIScrollableFrame* scrollFrame = do_QueryFrame(child);
242     if (scrollFrame) {
243       nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame();
244       NS_ASSERTION(scrolledFrame, "Error no scroll frame!!");
245       child = do_QueryFrame(scrolledFrame);
246     }
247 
248     nsCOMPtr<nsIGridPart> monument = GetPartFromBox(child);
249     if (monument) {
250       nsGridRowGroupLayout* rowGroup = monument->CastToRowGroupLayout();
251       if (rowGroup) {
252         bool isHorizontal = !nsSprocketLayout::IsXULHorizontal(child);
253         if (isHorizontal)
254           *aRows = child;
255         else
256           *aColumns = child;
257 
258         if (*aRows && *aColumns) return;
259       }
260     }
261 
262     if (scrollFrame) {
263       child = oldBox;
264     }
265 
266     child = nsBox::GetNextXULBox(child);
267   }
268 }
269 
270 /**
271  * Count the number of rows and columns in the given box. aRowCount well become
272  * the actual number rows defined in the xul. aComputedColumnCount will become
273  * the number of columns by counting the number of cells in each row.
274  */
CountRowsColumns(nsIFrame * aRowBox,int32_t & aRowCount,int32_t & aComputedColumnCount)275 void nsGrid::CountRowsColumns(nsIFrame* aRowBox, int32_t& aRowCount,
276                               int32_t& aComputedColumnCount) {
277   aRowCount = 0;
278   aComputedColumnCount = 0;
279   // get the rowboxes layout manager. Then ask it to do the work for us
280   if (aRowBox) {
281     nsCOMPtr<nsIGridPart> monument = GetPartFromBox(aRowBox);
282     if (monument)
283       monument->CountRowsColumns(aRowBox, aRowCount, aComputedColumnCount);
284   }
285 }
286 
287 /**
288  * Given the number of rows create nsGridRow objects for them and full them out.
289  */
BuildRows(nsIFrame * aBox,int32_t aRowCount,bool aIsHorizontal)290 UniquePtr<nsGridRow[]> nsGrid::BuildRows(nsIFrame* aBox, int32_t aRowCount,
291                                          bool aIsHorizontal) {
292   // if no rows then return null
293   if (aRowCount == 0) {
294     return nullptr;
295   }
296 
297   // create the array
298   UniquePtr<nsGridRow[]> row;
299 
300   // only create new rows if we have to. Reuse old rows.
301   if (aIsHorizontal) {
302     if (aRowCount > mRowCount) {
303       row = MakeUnique<nsGridRow[]>(aRowCount);
304     } else {
305       for (int32_t i = 0; i < mRowCount; i++) mRows[i].Init(nullptr, false);
306 
307       row = Move(mRows);
308     }
309   } else {
310     if (aRowCount > mColumnCount) {
311       row = MakeUnique<nsGridRow[]>(aRowCount);
312     } else {
313       for (int32_t i = 0; i < mColumnCount; i++)
314         mColumns[i].Init(nullptr, false);
315 
316       row = Move(mColumns);
317     }
318   }
319 
320   // populate it if we can. If not it will contain only dynamic columns
321   if (aBox) {
322     nsCOMPtr<nsIGridPart> monument = GetPartFromBox(aBox);
323     if (monument) {
324       monument->BuildRows(aBox, row.get());
325     }
326   }
327 
328   return row;
329 }
330 
331 /**
332  * Given the number of rows and columns. Build a cellmap
333  */
BuildCellMap(int32_t aRows,int32_t aColumns)334 UniquePtr<nsGridCell[]> nsGrid::BuildCellMap(int32_t aRows, int32_t aColumns) {
335   int32_t size = aRows * aColumns;
336   int32_t oldsize = mRowCount * mColumnCount;
337   if (size == 0) {
338     return nullptr;
339   }
340 
341   if (size > oldsize) {
342     return MakeUnique<nsGridCell[]>(size);
343   }
344 
345   // clear out cellmap
346   for (int32_t i = 0; i < oldsize; i++) {
347     mCellMap[i].SetBoxInRow(nullptr);
348     mCellMap[i].SetBoxInColumn(nullptr);
349   }
350   return Move(mCellMap);
351 }
352 
353 /**
354  * Run through all the cells in the rows and columns and populate then with 2
355  * cells. One from the row and one from the column
356  */
PopulateCellMap(nsGridRow * aRows,nsGridRow * aColumns,int32_t aRowCount,int32_t aColumnCount,bool aIsHorizontal)357 void nsGrid::PopulateCellMap(nsGridRow* aRows, nsGridRow* aColumns,
358                              int32_t aRowCount, int32_t aColumnCount,
359                              bool aIsHorizontal) {
360   if (!aRows) return;
361 
362   // look through the columns
363   int32_t j = 0;
364 
365   for (int32_t i = 0; i < aRowCount; i++) {
366     nsIFrame* child = nullptr;
367     nsGridRow* row = &aRows[i];
368 
369     // skip bogus rows. They have no cells
370     if (row->mIsBogus) continue;
371 
372     child = row->mBox;
373     if (child) {
374       child = nsBox::GetChildXULBox(child);
375 
376       j = 0;
377 
378       while (child && j < aColumnCount) {
379         // skip bogus column. They have no cells
380         nsGridRow* column = &aColumns[j];
381         if (column->mIsBogus) {
382           j++;
383           continue;
384         }
385 
386         if (aIsHorizontal)
387           GetCellAt(j, i)->SetBoxInRow(child);
388         else
389           GetCellAt(i, j)->SetBoxInColumn(child);
390 
391         child = nsBox::GetNextXULBox(child);
392 
393         j++;
394       }
395     }
396   }
397 }
398 
399 /**
400  * Run through the rows in the given box and mark them dirty so they
401  * will get recalculated and get a layout.
402  */
DirtyRows(nsIFrame * aRowBox,nsBoxLayoutState & aState)403 void nsGrid::DirtyRows(nsIFrame* aRowBox, nsBoxLayoutState& aState) {
404   // make sure we prevent others from dirtying things.
405   mMarkingDirty = true;
406 
407   // if the box is a grid part have it recursively hand it.
408   if (aRowBox) {
409     nsCOMPtr<nsIGridPart> part = GetPartFromBox(aRowBox);
410     if (part) part->DirtyRows(aRowBox, aState);
411   }
412 
413   mMarkingDirty = false;
414 }
415 
GetColumnAt(int32_t aIndex,bool aIsHorizontal)416 nsGridRow* nsGrid::GetColumnAt(int32_t aIndex, bool aIsHorizontal) {
417   return GetRowAt(aIndex, !aIsHorizontal);
418 }
419 
GetRowAt(int32_t aIndex,bool aIsHorizontal)420 nsGridRow* nsGrid::GetRowAt(int32_t aIndex, bool aIsHorizontal) {
421   RebuildIfNeeded();
422 
423   if (aIsHorizontal) {
424     NS_ASSERTION(aIndex < mRowCount && aIndex >= 0, "Index out of range");
425     return &mRows[aIndex];
426   } else {
427     NS_ASSERTION(aIndex < mColumnCount && aIndex >= 0, "Index out of range");
428     return &mColumns[aIndex];
429   }
430 }
431 
GetCellAt(int32_t aX,int32_t aY)432 nsGridCell* nsGrid::GetCellAt(int32_t aX, int32_t aY) {
433   RebuildIfNeeded();
434 
435   NS_ASSERTION(aY < mRowCount && aY >= 0, "Index out of range");
436   NS_ASSERTION(aX < mColumnCount && aX >= 0, "Index out of range");
437   return &mCellMap[aY * mColumnCount + aX];
438 }
439 
GetExtraColumnCount(bool aIsHorizontal)440 int32_t nsGrid::GetExtraColumnCount(bool aIsHorizontal) {
441   return GetExtraRowCount(!aIsHorizontal);
442 }
443 
GetExtraRowCount(bool aIsHorizontal)444 int32_t nsGrid::GetExtraRowCount(bool aIsHorizontal) {
445   RebuildIfNeeded();
446 
447   if (aIsHorizontal)
448     return mExtraRowCount;
449   else
450     return mExtraColumnCount;
451 }
452 
453 /**
454  * These methods return the preferred, min, max sizes for a given row index.
455  * aIsHorizontal if aIsHorizontal is true. If you pass false you will get the
456  * inverse. As if you called GetPrefColumnSize(aState, index, aPref)
457  */
GetPrefRowSize(nsBoxLayoutState & aState,int32_t aRowIndex,bool aIsHorizontal)458 nsSize nsGrid::GetPrefRowSize(nsBoxLayoutState& aState, int32_t aRowIndex,
459                               bool aIsHorizontal) {
460   nsSize size(0, 0);
461   if (!(aRowIndex >= 0 && aRowIndex < GetRowCount(aIsHorizontal))) return size;
462 
463   nscoord height = GetPrefRowHeight(aState, aRowIndex, aIsHorizontal);
464   SetLargestSize(size, height, aIsHorizontal);
465 
466   return size;
467 }
468 
GetMinRowSize(nsBoxLayoutState & aState,int32_t aRowIndex,bool aIsHorizontal)469 nsSize nsGrid::GetMinRowSize(nsBoxLayoutState& aState, int32_t aRowIndex,
470                              bool aIsHorizontal) {
471   nsSize size(0, 0);
472   if (!(aRowIndex >= 0 && aRowIndex < GetRowCount(aIsHorizontal))) return size;
473 
474   nscoord height = GetMinRowHeight(aState, aRowIndex, aIsHorizontal);
475   SetLargestSize(size, height, aIsHorizontal);
476 
477   return size;
478 }
479 
GetMaxRowSize(nsBoxLayoutState & aState,int32_t aRowIndex,bool aIsHorizontal)480 nsSize nsGrid::GetMaxRowSize(nsBoxLayoutState& aState, int32_t aRowIndex,
481                              bool aIsHorizontal) {
482   nsSize size(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
483   if (!(aRowIndex >= 0 && aRowIndex < GetRowCount(aIsHorizontal))) return size;
484 
485   nscoord height = GetMaxRowHeight(aState, aRowIndex, aIsHorizontal);
486   SetSmallestSize(size, height, aIsHorizontal);
487 
488   return size;
489 }
490 
491 // static
GetPartFromBox(nsIFrame * aBox)492 nsIGridPart* nsGrid::GetPartFromBox(nsIFrame* aBox) {
493   if (!aBox) return nullptr;
494 
495   nsBoxLayout* layout = aBox->GetXULLayoutManager();
496   return layout ? layout->AsGridPart() : nullptr;
497 }
498 
GetBoxTotalMargin(nsIFrame * aBox,bool aIsHorizontal)499 nsMargin nsGrid::GetBoxTotalMargin(nsIFrame* aBox, bool aIsHorizontal) {
500   nsMargin margin(0, 0, 0, 0);
501   // walk the boxes parent chain getting the border/padding/margin of our parent
502   // rows
503 
504   // first get the layour manager
505   nsIGridPart* part = GetPartFromBox(aBox);
506   if (part) margin = part->GetTotalMargin(aBox, aIsHorizontal);
507 
508   return margin;
509 }
510 
511 /**
512  * The first and last rows can be affected by <rows> tags with borders or margin
513  * gets first and last rows and their indexes.
514  * If it fails because there are no rows then:
515  * FirstRow is nullptr
516  * LastRow is nullptr
517  * aFirstIndex = -1
518  * aLastIndex = -1
519  */
GetFirstAndLastRow(int32_t & aFirstIndex,int32_t & aLastIndex,nsGridRow * & aFirstRow,nsGridRow * & aLastRow,bool aIsHorizontal)520 void nsGrid::GetFirstAndLastRow(int32_t& aFirstIndex, int32_t& aLastIndex,
521                                 nsGridRow*& aFirstRow, nsGridRow*& aLastRow,
522                                 bool aIsHorizontal) {
523   aFirstRow = nullptr;
524   aLastRow = nullptr;
525   aFirstIndex = -1;
526   aLastIndex = -1;
527 
528   int32_t count = GetRowCount(aIsHorizontal);
529 
530   if (count == 0) return;
531 
532   // We could have collapsed columns either before or after our index.
533   // they should not count. So if we are the 5th row and the first 4 are
534   // collaped we become the first row. Or if we are the 9th row and
535   // 10 up to the last row are collapsed we then become the last.
536 
537   // see if we are first
538   int32_t i;
539   for (i = 0; i < count; i++) {
540     nsGridRow* row = GetRowAt(i, aIsHorizontal);
541     if (!row->IsXULCollapsed()) {
542       aFirstIndex = i;
543       aFirstRow = row;
544       break;
545     }
546   }
547 
548   // see if we are last
549   for (i = count - 1; i >= 0; i--) {
550     nsGridRow* row = GetRowAt(i, aIsHorizontal);
551     if (!row->IsXULCollapsed()) {
552       aLastIndex = i;
553       aLastRow = row;
554       break;
555     }
556   }
557 }
558 
559 /**
560  * A row can have a top and bottom offset. Usually this is just the top and
561  * bottom border/padding. However if the row is the first or last it could be
562  * affected by the fact a column or columns could have a top or bottom margin.
563  */
GetRowOffsets(int32_t aIndex,nscoord & aTop,nscoord & aBottom,bool aIsHorizontal)564 void nsGrid::GetRowOffsets(int32_t aIndex, nscoord& aTop, nscoord& aBottom,
565                            bool aIsHorizontal) {
566   RebuildIfNeeded();
567 
568   nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
569 
570   if (row->IsOffsetSet()) {
571     aTop = row->mTop;
572     aBottom = row->mBottom;
573     return;
574   }
575 
576   // first get the rows top and bottom border and padding
577   nsIFrame* box = row->GetBox();
578 
579   // add up all the padding
580   nsMargin margin(0, 0, 0, 0);
581   nsMargin border(0, 0, 0, 0);
582   nsMargin padding(0, 0, 0, 0);
583   nsMargin totalBorderPadding(0, 0, 0, 0);
584   nsMargin totalMargin(0, 0, 0, 0);
585 
586   // if there is a box and it's not bogus take its
587   // borders padding into account
588   if (box && !row->mIsBogus) {
589     if (!box->IsXULCollapsed()) {
590       // get real border and padding. GetXULBorderAndPadding
591       // is redefined on nsGridRowLeafFrame. If we called it here
592       // we would be in finite recurson.
593       box->GetXULBorder(border);
594       box->GetXULPadding(padding);
595 
596       totalBorderPadding += border;
597       totalBorderPadding += padding;
598     }
599 
600     // if we are the first or last row
601     // take into account <rows> tags around us
602     // that could have borders or margins.
603     // fortunately they only affect the first
604     // and last row inside the <rows> tag
605 
606     totalMargin = GetBoxTotalMargin(box, aIsHorizontal);
607   }
608 
609   if (aIsHorizontal) {
610     row->mTop = totalBorderPadding.top;
611     row->mBottom = totalBorderPadding.bottom;
612     row->mTopMargin = totalMargin.top;
613     row->mBottomMargin = totalMargin.bottom;
614   } else {
615     row->mTop = totalBorderPadding.left;
616     row->mBottom = totalBorderPadding.right;
617     row->mTopMargin = totalMargin.left;
618     row->mBottomMargin = totalMargin.right;
619   }
620 
621   // if we are the first or last row take into account the top and bottom
622   // borders of each columns.
623 
624   // If we are the first row then get the largest top border/padding in
625   // our columns. If that's larger than the rows top border/padding use it.
626 
627   // If we are the last row then get the largest bottom border/padding in
628   // our columns. If that's larger than the rows bottom border/padding use it.
629   int32_t firstIndex = 0;
630   int32_t lastIndex = 0;
631   nsGridRow* firstRow = nullptr;
632   nsGridRow* lastRow = nullptr;
633   GetFirstAndLastRow(firstIndex, lastIndex, firstRow, lastRow, aIsHorizontal);
634 
635   if (aIndex == firstIndex || aIndex == lastIndex) {
636     nscoord maxTop = 0;
637     nscoord maxBottom = 0;
638 
639     // run through the columns. Look at each column
640     // pick the largest top border or bottom border
641     int32_t count = GetColumnCount(aIsHorizontal);
642 
643     for (int32_t i = 0; i < count; i++) {
644       nsMargin totalChildBorderPadding(0, 0, 0, 0);
645 
646       nsGridRow* column = GetColumnAt(i, aIsHorizontal);
647       nsIFrame* box = column->GetBox();
648 
649       if (box) {
650         // ignore collapsed children
651         if (!box->IsXULCollapsed()) {
652           // include the margin of the columns. To the row
653           // at this point border/padding and margins all added
654           // up to more needed space.
655           margin = GetBoxTotalMargin(box, !aIsHorizontal);
656           // get real border and padding. GetXULBorderAndPadding
657           // is redefined on nsGridRowLeafFrame. If we called it here
658           // we would be in finite recurson.
659           box->GetXULBorder(border);
660           box->GetXULPadding(padding);
661           totalChildBorderPadding += border;
662           totalChildBorderPadding += padding;
663           totalChildBorderPadding += margin;
664         }
665 
666         nscoord top;
667         nscoord bottom;
668 
669         // pick the largest top margin
670         if (aIndex == firstIndex) {
671           if (aIsHorizontal) {
672             top = totalChildBorderPadding.top;
673           } else {
674             top = totalChildBorderPadding.left;
675           }
676           if (top > maxTop) maxTop = top;
677         }
678 
679         // pick the largest bottom margin
680         if (aIndex == lastIndex) {
681           if (aIsHorizontal) {
682             bottom = totalChildBorderPadding.bottom;
683           } else {
684             bottom = totalChildBorderPadding.right;
685           }
686           if (bottom > maxBottom) maxBottom = bottom;
687         }
688       }
689 
690       // If the biggest top border/padding the columns is larger than this rows
691       // top border/padding the use it.
692       if (aIndex == firstIndex) {
693         if (maxTop > (row->mTop + row->mTopMargin))
694           row->mTop = maxTop - row->mTopMargin;
695       }
696 
697       // If the biggest bottom border/padding the columns is larger than this
698       // rows bottom border/padding the use it.
699       if (aIndex == lastIndex) {
700         if (maxBottom > (row->mBottom + row->mBottomMargin))
701           row->mBottom = maxBottom - row->mBottomMargin;
702       }
703     }
704   }
705 
706   aTop = row->mTop;
707   aBottom = row->mBottom;
708 }
709 
710 /**
711  * These methods return the preferred, min, max coord for a given row index if
712  * aIsHorizontal is true. If you pass false you will get the inverse.
713  * As if you called GetPrefColumnHeight(aState, index, aPref).
714  */
GetPrefRowHeight(nsBoxLayoutState & aState,int32_t aIndex,bool aIsHorizontal)715 nscoord nsGrid::GetPrefRowHeight(nsBoxLayoutState& aState, int32_t aIndex,
716                                  bool aIsHorizontal) {
717   RebuildIfNeeded();
718 
719   nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
720 
721   if (row->IsXULCollapsed()) return 0;
722 
723   if (row->IsPrefSet()) return row->mPref;
724 
725   nsIFrame* box = row->mBox;
726 
727   // set in CSS?
728   if (box) {
729     bool widthSet, heightSet;
730     nsSize cssSize(-1, -1);
731     nsIFrame::AddXULPrefSize(box, cssSize, widthSet, heightSet);
732 
733     row->mPref = GET_HEIGHT(cssSize, aIsHorizontal);
734 
735     // yep do nothing.
736     if (row->mPref != -1) return row->mPref;
737   }
738 
739   // get the offsets so they are cached.
740   nscoord top;
741   nscoord bottom;
742   GetRowOffsets(aIndex, top, bottom, aIsHorizontal);
743 
744   // is the row bogus? If so then just ask it for its size
745   // it should not be affected by cells in the grid.
746   if (row->mIsBogus) {
747     nsSize size(0, 0);
748     if (box) {
749       size = box->GetXULPrefSize(aState);
750       nsBox::AddMargin(box, size);
751       nsGridLayout2::AddOffset(box, size);
752     }
753 
754     row->mPref = GET_HEIGHT(size, aIsHorizontal);
755     return row->mPref;
756   }
757 
758   nsSize size(0, 0);
759 
760   nsGridCell* child;
761 
762   int32_t count = GetColumnCount(aIsHorizontal);
763 
764   for (int32_t i = 0; i < count; i++) {
765     if (aIsHorizontal)
766       child = GetCellAt(i, aIndex);
767     else
768       child = GetCellAt(aIndex, i);
769 
770     // ignore collapsed children
771     if (!child->IsXULCollapsed()) {
772       nsSize childSize = child->GetXULPrefSize(aState);
773 
774       nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal);
775     }
776   }
777 
778   row->mPref = GET_HEIGHT(size, aIsHorizontal) + top + bottom;
779 
780   return row->mPref;
781 }
782 
GetMinRowHeight(nsBoxLayoutState & aState,int32_t aIndex,bool aIsHorizontal)783 nscoord nsGrid::GetMinRowHeight(nsBoxLayoutState& aState, int32_t aIndex,
784                                 bool aIsHorizontal) {
785   RebuildIfNeeded();
786 
787   nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
788 
789   if (row->IsXULCollapsed()) return 0;
790 
791   if (row->IsMinSet()) return row->mMin;
792 
793   nsIFrame* box = row->mBox;
794 
795   // set in CSS?
796   if (box) {
797     bool widthSet, heightSet;
798     nsSize cssSize(-1, -1);
799     nsIFrame::AddXULMinSize(aState, box, cssSize, widthSet, heightSet);
800 
801     row->mMin = GET_HEIGHT(cssSize, aIsHorizontal);
802 
803     // yep do nothing.
804     if (row->mMin != -1) return row->mMin;
805   }
806 
807   // get the offsets so they are cached.
808   nscoord top;
809   nscoord bottom;
810   GetRowOffsets(aIndex, top, bottom, aIsHorizontal);
811 
812   // is the row bogus? If so then just ask it for its size
813   // it should not be affected by cells in the grid.
814   if (row->mIsBogus) {
815     nsSize size(0, 0);
816     if (box) {
817       size = box->GetXULPrefSize(aState);
818       nsBox::AddMargin(box, size);
819       nsGridLayout2::AddOffset(box, size);
820     }
821 
822     row->mMin = GET_HEIGHT(size, aIsHorizontal) + top + bottom;
823     return row->mMin;
824   }
825 
826   nsSize size(0, 0);
827 
828   nsGridCell* child;
829 
830   int32_t count = GetColumnCount(aIsHorizontal);
831 
832   for (int32_t i = 0; i < count; i++) {
833     if (aIsHorizontal)
834       child = GetCellAt(i, aIndex);
835     else
836       child = GetCellAt(aIndex, i);
837 
838     // ignore collapsed children
839     if (!child->IsXULCollapsed()) {
840       nsSize childSize = child->GetXULMinSize(aState);
841 
842       nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal);
843     }
844   }
845 
846   row->mMin = GET_HEIGHT(size, aIsHorizontal);
847 
848   return row->mMin;
849 }
850 
GetMaxRowHeight(nsBoxLayoutState & aState,int32_t aIndex,bool aIsHorizontal)851 nscoord nsGrid::GetMaxRowHeight(nsBoxLayoutState& aState, int32_t aIndex,
852                                 bool aIsHorizontal) {
853   RebuildIfNeeded();
854 
855   nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
856 
857   if (row->IsXULCollapsed()) return 0;
858 
859   if (row->IsMaxSet()) return row->mMax;
860 
861   nsIFrame* box = row->mBox;
862 
863   // set in CSS?
864   if (box) {
865     bool widthSet, heightSet;
866     nsSize cssSize(-1, -1);
867     nsIFrame::AddXULMaxSize(box, cssSize, widthSet, heightSet);
868 
869     row->mMax = GET_HEIGHT(cssSize, aIsHorizontal);
870 
871     // yep do nothing.
872     if (row->mMax != -1) return row->mMax;
873   }
874 
875   // get the offsets so they are cached.
876   nscoord top;
877   nscoord bottom;
878   GetRowOffsets(aIndex, top, bottom, aIsHorizontal);
879 
880   // is the row bogus? If so then just ask it for its size
881   // it should not be affected by cells in the grid.
882   if (row->mIsBogus) {
883     nsSize size(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
884     if (box) {
885       size = box->GetXULPrefSize(aState);
886       nsBox::AddMargin(box, size);
887       nsGridLayout2::AddOffset(box, size);
888     }
889 
890     row->mMax = GET_HEIGHT(size, aIsHorizontal);
891     return row->mMax;
892   }
893 
894   nsSize size(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
895 
896   nsGridCell* child;
897 
898   int32_t count = GetColumnCount(aIsHorizontal);
899 
900   for (int32_t i = 0; i < count; i++) {
901     if (aIsHorizontal)
902       child = GetCellAt(i, aIndex);
903     else
904       child = GetCellAt(aIndex, i);
905 
906     // ignore collapsed children
907     if (!child->IsXULCollapsed()) {
908       nsSize min = child->GetXULMinSize(aState);
909       nsSize childSize =
910           nsBox::BoundsCheckMinMax(min, child->GetXULMaxSize(aState));
911       nsSprocketLayout::AddLargestSize(size, childSize, aIsHorizontal);
912     }
913   }
914 
915   row->mMax = GET_HEIGHT(size, aIsHorizontal) + top + bottom;
916 
917   return row->mMax;
918 }
919 
IsGrid(nsIFrame * aBox)920 bool nsGrid::IsGrid(nsIFrame* aBox) {
921   nsIGridPart* part = GetPartFromBox(aBox);
922   if (!part) return false;
923 
924   nsGridLayout2* grid = part->CastToGridLayout();
925 
926   if (grid) return true;
927 
928   return false;
929 }
930 
931 /**
932  * This get the flexibilty of the row at aIndex. It's not trivial. There are a
933  * few things we need to look at. Specifically we need to see if any <rows> or
934  * <columns> tags are around us. Their flexibilty will affect ours.
935  */
GetRowFlex(int32_t aIndex,bool aIsHorizontal)936 nscoord nsGrid::GetRowFlex(int32_t aIndex, bool aIsHorizontal) {
937   RebuildIfNeeded();
938 
939   nsGridRow* row = GetRowAt(aIndex, aIsHorizontal);
940 
941   if (row->IsFlexSet()) return row->mFlex;
942 
943   nsIFrame* box = row->mBox;
944   row->mFlex = 0;
945 
946   if (box) {
947     // We need our flex but a inflexible row could be around us. If so
948     // neither are we. However if its the row tag just inside the grid it won't
949     // affect us. We need to do this for this case:
950     // <grid>
951     //   <rows>
952     //     <rows> // this is not flexible. So our children should not be
953     //     flexible
954     //        <row flex="1"/>
955     //        <row flex="1"/>
956     //     </rows>
957     //        <row/>
958     //   </rows>
959     // </grid>
960     //
961     // or..
962     //
963     // <grid>
964     //  <rows>
965     //   <rows> // this is not flexible. So our children should not be flexible
966     //     <rows flex="1">
967     //        <row flex="1"/>
968     //        <row flex="1"/>
969     //     </rows>
970     //        <row/>
971     //   </rows>
972     //  </row>
973     // </grid>
974 
975     // So here is how it looks
976     //
977     // <grid>
978     //   <rows>   // parentsParent
979     //     <rows> // parent
980     //        <row flex="1"/>
981     //        <row flex="1"/>
982     //     </rows>
983     //        <row/>
984     //   </rows>
985     // </grid>
986 
987     // so the answer is simple: 1) Walk our parent chain. 2) If we find
988     // someone who is not flexible and they aren't the rows immediately in
989     // the grid. 3) Then we are not flexible
990 
991     box = GetScrollBox(box);
992     nsIFrame* parent = nsBox::GetParentXULBox(box);
993     nsIFrame* parentsParent = nullptr;
994 
995     while (parent) {
996       parent = GetScrollBox(parent);
997       parentsParent = nsBox::GetParentXULBox(parent);
998 
999       // if our parents parent is not a grid
1000       // the get its flex. If its 0 then we are
1001       // not flexible.
1002       if (parentsParent) {
1003         if (!IsGrid(parentsParent)) {
1004           nscoord flex = parent->GetXULFlex();
1005           nsIFrame::AddXULFlex(parent, flex);
1006           if (flex == 0) {
1007             row->mFlex = 0;
1008             return row->mFlex;
1009           }
1010         } else
1011           break;
1012       }
1013 
1014       parent = parentsParent;
1015     }
1016 
1017     // get the row flex.
1018     row->mFlex = box->GetXULFlex();
1019     nsIFrame::AddXULFlex(box, row->mFlex);
1020   }
1021 
1022   return row->mFlex;
1023 }
1024 
SetLargestSize(nsSize & aSize,nscoord aHeight,bool aIsHorizontal)1025 void nsGrid::SetLargestSize(nsSize& aSize, nscoord aHeight,
1026                             bool aIsHorizontal) {
1027   if (aIsHorizontal) {
1028     if (aSize.height < aHeight) aSize.height = aHeight;
1029   } else {
1030     if (aSize.width < aHeight) aSize.width = aHeight;
1031   }
1032 }
1033 
SetSmallestSize(nsSize & aSize,nscoord aHeight,bool aIsHorizontal)1034 void nsGrid::SetSmallestSize(nsSize& aSize, nscoord aHeight,
1035                              bool aIsHorizontal) {
1036   if (aIsHorizontal) {
1037     if (aSize.height > aHeight) aSize.height = aHeight;
1038   } else {
1039     if (aSize.width < aHeight) aSize.width = aHeight;
1040   }
1041 }
1042 
GetRowCount(int32_t aIsHorizontal)1043 int32_t nsGrid::GetRowCount(int32_t aIsHorizontal) {
1044   RebuildIfNeeded();
1045 
1046   if (aIsHorizontal)
1047     return mRowCount;
1048   else
1049     return mColumnCount;
1050 }
1051 
GetColumnCount(int32_t aIsHorizontal)1052 int32_t nsGrid::GetColumnCount(int32_t aIsHorizontal) {
1053   return GetRowCount(!aIsHorizontal);
1054 }
1055 
1056 /*
1057  * A cell in the given row or columns at the given index has had a child added
1058  * or removed
1059  */
CellAddedOrRemoved(nsBoxLayoutState & aState,int32_t aIndex,bool aIsHorizontal)1060 void nsGrid::CellAddedOrRemoved(nsBoxLayoutState& aState, int32_t aIndex,
1061                                 bool aIsHorizontal) {
1062   // TBD see if the cell will fit in our current row. If it will
1063   // just add it in.
1064   // but for now rebuild everything.
1065   if (mMarkingDirty) return;
1066 
1067   NeedsRebuild(aState);
1068 }
1069 
1070 /**
1071  * A row or columns at the given index had been added or removed
1072  */
RowAddedOrRemoved(nsBoxLayoutState & aState,int32_t aIndex,bool aIsHorizontal)1073 void nsGrid::RowAddedOrRemoved(nsBoxLayoutState& aState, int32_t aIndex,
1074                                bool aIsHorizontal) {
1075   // TBD see if we have extra room in the table and just add the new row in
1076   // for now rebuild the world
1077   if (mMarkingDirty) return;
1078 
1079   NeedsRebuild(aState);
1080 }
1081 
1082 /*
1083  * Scrollframes are tranparent. If this is given a scrollframe is will return
1084  * the frame inside. If there is no scrollframe it does nothing.
1085  */
GetScrolledBox(nsIFrame * aChild)1086 nsIFrame* nsGrid::GetScrolledBox(nsIFrame* aChild) {
1087   // first see if it is a scrollframe. If so walk down into it and get the
1088   // scrolled child
1089   nsIScrollableFrame* scrollFrame = do_QueryFrame(aChild);
1090   if (scrollFrame) {
1091     nsIFrame* scrolledFrame = scrollFrame->GetScrolledFrame();
1092     NS_ASSERTION(scrolledFrame, "Error no scroll frame!!");
1093     return scrolledFrame;
1094   }
1095 
1096   return aChild;
1097 }
1098 
1099 /*
1100  * Scrollframes are tranparent. If this is given a child in a scrollframe is
1101  * will return the scrollframe ourside it. If there is no scrollframe it does
1102  * nothing.
1103  */
GetScrollBox(nsIFrame * aChild)1104 nsIFrame* nsGrid::GetScrollBox(nsIFrame* aChild) {
1105   if (!aChild) return nullptr;
1106 
1107   // get parent
1108   nsIFrame* parent = nsBox::GetParentXULBox(aChild);
1109 
1110   // walk up until we find a scrollframe or a part
1111   // if it's a scrollframe return it.
1112   // if it's a parent then the child passed does not
1113   // have a scroll frame immediately wrapped around it.
1114   while (parent) {
1115     nsIScrollableFrame* scrollFrame = do_QueryFrame(parent);
1116     // scrollframe? Yep return it.
1117     if (scrollFrame) return parent;
1118 
1119     nsCOMPtr<nsIGridPart> parentGridRow = GetPartFromBox(parent);
1120     // if a part then just return the child
1121     if (parentGridRow) break;
1122 
1123     parent = nsBox::GetParentXULBox(parent);
1124   }
1125 
1126   return aChild;
1127 }
1128 
1129 #ifdef DEBUG_grid
PrintCellMap()1130 void nsGrid::PrintCellMap() {
1131   printf("-----Columns------\n");
1132   for (int x = 0; x < mColumnCount; x++) {
1133     nsGridRow* column = GetColumnAt(x);
1134     printf("%d(pf=%d, mn=%d, mx=%d) ", x, column->mPref, column->mMin,
1135            column->mMax);
1136   }
1137 
1138   printf("\n-----Rows------\n");
1139   for (x = 0; x < mRowCount; x++) {
1140     nsGridRow* column = GetRowAt(x);
1141     printf("%d(pf=%d, mn=%d, mx=%d) ", x, column->mPref, column->mMin,
1142            column->mMax);
1143   }
1144 
1145   printf("\n");
1146 }
1147 #endif
1148