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