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 #include "nsTableColGroupFrame.h"
6 
7 #include "mozilla/ComputedStyle.h"
8 #include "mozilla/PresShell.h"
9 #include "nsTableColFrame.h"
10 #include "nsTableFrame.h"
11 #include "nsStyleConsts.h"
12 #include "nsPresContext.h"
13 #include "nsHTMLParts.h"
14 #include "nsGkAtoms.h"
15 #include "nsCOMPtr.h"
16 #include "nsCSSRendering.h"
17 
18 using namespace mozilla;
19 
20 #define COLGROUP_SYNTHETIC_BIT NS_FRAME_STATE_BIT(30)
21 
IsSynthetic() const22 bool nsTableColGroupFrame::IsSynthetic() const {
23   return HasAnyStateBits(COLGROUP_SYNTHETIC_BIT);
24 }
25 
SetIsSynthetic()26 void nsTableColGroupFrame::SetIsSynthetic() {
27   AddStateBits(COLGROUP_SYNTHETIC_BIT);
28 }
29 
ResetColIndices(nsIFrame * aFirstColGroup,int32_t aFirstColIndex,nsIFrame * aStartColFrame)30 void nsTableColGroupFrame::ResetColIndices(nsIFrame* aFirstColGroup,
31                                            int32_t aFirstColIndex,
32                                            nsIFrame* aStartColFrame) {
33   nsTableColGroupFrame* colGroupFrame = (nsTableColGroupFrame*)aFirstColGroup;
34   int32_t colIndex = aFirstColIndex;
35   while (colGroupFrame) {
36     if (colGroupFrame->IsTableColGroupFrame()) {
37       // reset the starting col index for the first cg only if we should reset
38       // the whole colgroup (aStartColFrame defaults to nullptr) or if
39       // aFirstColIndex is smaller than the existing starting col index
40       if ((colIndex != aFirstColIndex) ||
41           (colIndex < colGroupFrame->GetStartColumnIndex()) ||
42           !aStartColFrame) {
43         colGroupFrame->SetStartColumnIndex(colIndex);
44       }
45       nsIFrame* colFrame = aStartColFrame;
46       if (!colFrame || (colIndex != aFirstColIndex)) {
47         colFrame = colGroupFrame->PrincipalChildList().FirstChild();
48       }
49       while (colFrame) {
50         if (colFrame->IsTableColFrame()) {
51           ((nsTableColFrame*)colFrame)->SetColIndex(colIndex);
52           colIndex++;
53         }
54         colFrame = colFrame->GetNextSibling();
55       }
56     }
57     colGroupFrame =
58         static_cast<nsTableColGroupFrame*>(colGroupFrame->GetNextSibling());
59   }
60 }
61 
AddColsToTable(int32_t aFirstColIndex,bool aResetSubsequentColIndices,const nsFrameList::Slice & aCols)62 nsresult nsTableColGroupFrame::AddColsToTable(int32_t aFirstColIndex,
63                                               bool aResetSubsequentColIndices,
64                                               const nsFrameList::Slice& aCols) {
65   nsTableFrame* tableFrame = GetTableFrame();
66 
67   tableFrame->InvalidateFrameSubtree();
68 
69   // set the col indices of the col frames and and add col info to the table
70   int32_t colIndex = aFirstColIndex;
71   nsFrameList::Enumerator e(aCols);
72   for (; !e.AtEnd(); e.Next()) {
73     ((nsTableColFrame*)e.get())->SetColIndex(colIndex);
74     mColCount++;
75     tableFrame->InsertCol((nsTableColFrame&)*e.get(), colIndex);
76     colIndex++;
77   }
78 
79   for (nsFrameList::Enumerator eTail = e.GetUnlimitedEnumerator();
80        !eTail.AtEnd(); eTail.Next()) {
81     ((nsTableColFrame*)eTail.get())->SetColIndex(colIndex);
82     colIndex++;
83   }
84 
85   // We have already set the colindex for all the colframes in this
86   // colgroup that come after the first inserted colframe, but there could
87   // be other colgroups following this one and their colframes need
88   // correct colindices too.
89   if (aResetSubsequentColIndices && GetNextSibling()) {
90     ResetColIndices(GetNextSibling(), colIndex);
91   }
92 
93   return NS_OK;
94 }
95 
GetLastRealColGroup(nsTableFrame * aTableFrame)96 nsTableColGroupFrame* nsTableColGroupFrame::GetLastRealColGroup(
97     nsTableFrame* aTableFrame) {
98   nsFrameList colGroups = aTableFrame->GetColGroups();
99 
100   auto lastColGroup = static_cast<nsTableColGroupFrame*>(colGroups.LastChild());
101   if (!lastColGroup) {
102     return nullptr;
103   }
104 
105   if (!lastColGroup->IsSynthetic()) {
106     return lastColGroup;
107   }
108 
109   return static_cast<nsTableColGroupFrame*>(lastColGroup->GetPrevSibling());
110 }
111 
112 // don't set mColCount here, it is done in AddColsToTable
SetInitialChildList(ChildListID aListID,nsFrameList & aChildList)113 void nsTableColGroupFrame::SetInitialChildList(ChildListID aListID,
114                                                nsFrameList& aChildList) {
115   MOZ_ASSERT(mFrames.IsEmpty(),
116              "unexpected second call to SetInitialChildList");
117   MOZ_ASSERT(aListID == kPrincipalList, "unexpected child list");
118 #ifdef DEBUG
119   for (nsIFrame* f : aChildList) {
120     MOZ_ASSERT(f->GetParent() == this, "Unexpected parent");
121   }
122 #endif
123   if (aChildList.IsEmpty()) {
124     GetTableFrame()->AppendAnonymousColFrames(this, GetSpan(),
125                                               eColAnonymousColGroup, false);
126     return;
127   }
128 
129   mFrames.AppendFrames(this, aChildList);
130 }
131 
132 /* virtual */
DidSetComputedStyle(ComputedStyle * aOldComputedStyle)133 void nsTableColGroupFrame::DidSetComputedStyle(
134     ComputedStyle* aOldComputedStyle) {
135   nsContainerFrame::DidSetComputedStyle(aOldComputedStyle);
136 
137   if (!aOldComputedStyle)  // avoid this on init
138     return;
139 
140   nsTableFrame* tableFrame = GetTableFrame();
141   if (tableFrame->IsBorderCollapse() &&
142       tableFrame->BCRecalcNeeded(aOldComputedStyle, Style())) {
143     int32_t colCount = GetColCount();
144     if (!colCount) return;  // this is a degenerated colgroup
145     TableArea damageArea(GetFirstColumn()->GetColIndex(), 0, colCount,
146                          tableFrame->GetRowCount());
147     tableFrame->AddBCDamageArea(damageArea);
148   }
149 }
150 
AppendFrames(ChildListID aListID,nsFrameList & aFrameList)151 void nsTableColGroupFrame::AppendFrames(ChildListID aListID,
152                                         nsFrameList& aFrameList) {
153   NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
154 
155   nsTableColFrame* col = GetFirstColumn();
156   nsTableColFrame* nextCol;
157   while (col && col->GetColType() == eColAnonymousColGroup) {
158     // this colgroup spans one or more columns but now that there is a
159     // real column below, spanned anonymous columns should be removed,
160     // since the HTML spec says to ignore the span of a colgroup if it
161     // has content columns in it.
162     nextCol = col->GetNextCol();
163     RemoveFrame(kPrincipalList, col);
164     col = nextCol;
165   }
166 
167   // Our next colframe should be an eColContent.  We've removed all the
168   // eColAnonymousColGroup colframes, eColAnonymousCol colframes always follow
169   // eColContent ones, and eColAnonymousCell colframes only appear in a
170   // synthetic colgroup, which never gets AppendFrames() called on it.
171   MOZ_ASSERT(!col || col->GetColType() == eColContent,
172              "What's going on with our columns?");
173 
174   const nsFrameList::Slice& newFrames = mFrames.AppendFrames(this, aFrameList);
175   InsertColsReflow(GetStartColumnIndex() + mColCount, newFrames);
176 }
177 
InsertFrames(ChildListID aListID,nsIFrame * aPrevFrame,const nsLineList::iterator * aPrevFrameLine,nsFrameList & aFrameList)178 void nsTableColGroupFrame::InsertFrames(
179     ChildListID aListID, nsIFrame* aPrevFrame,
180     const nsLineList::iterator* aPrevFrameLine, nsFrameList& aFrameList) {
181   NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
182   NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
183                "inserting after sibling frame with different parent");
184 
185   nsTableColFrame* col = GetFirstColumn();
186   nsTableColFrame* nextCol;
187   while (col && col->GetColType() == eColAnonymousColGroup) {
188     // this colgroup spans one or more columns but now that there is a
189     // real column below, spanned anonymous columns should be removed,
190     // since the HTML spec says to ignore the span of a colgroup if it
191     // has content columns in it.
192     nextCol = col->GetNextCol();
193     if (col == aPrevFrame) {
194       // This can happen when we're being appended to
195       NS_ASSERTION(!nextCol || nextCol->GetColType() != eColAnonymousColGroup,
196                    "Inserting in the middle of our anonymous cols?");
197       // We'll want to insert at the beginning
198       aPrevFrame = nullptr;
199     }
200     RemoveFrame(kPrincipalList, col);
201     col = nextCol;
202   }
203 
204   // Our next colframe should be an eColContent.  We've removed all the
205   // eColAnonymousColGroup colframes, eColAnonymousCol colframes always follow
206   // eColContent ones, and eColAnonymousCell colframes only appear in a
207   // synthetic colgroup, which never gets InsertFrames() called on it.
208   MOZ_ASSERT(!col || col->GetColType() == eColContent,
209              "What's going on with our columns?");
210 
211   NS_ASSERTION(!aPrevFrame || aPrevFrame == aPrevFrame->LastContinuation(),
212                "Prev frame should be last in continuation chain");
213   NS_ASSERTION(!aPrevFrame || !GetNextColumn(aPrevFrame) ||
214                    GetNextColumn(aPrevFrame)->GetColType() != eColAnonymousCol,
215                "Shouldn't be inserting before a spanned colframe");
216 
217   const nsFrameList::Slice& newFrames =
218       mFrames.InsertFrames(this, aPrevFrame, aFrameList);
219   nsIFrame* prevFrame = nsTableFrame::GetFrameAtOrBefore(
220       this, aPrevFrame, LayoutFrameType::TableCol);
221 
222   int32_t colIndex = (prevFrame)
223                          ? ((nsTableColFrame*)prevFrame)->GetColIndex() + 1
224                          : GetStartColumnIndex();
225   InsertColsReflow(colIndex, newFrames);
226 }
227 
InsertColsReflow(int32_t aColIndex,const nsFrameList::Slice & aCols)228 void nsTableColGroupFrame::InsertColsReflow(int32_t aColIndex,
229                                             const nsFrameList::Slice& aCols) {
230   AddColsToTable(aColIndex, true, aCols);
231 
232   PresShell()->FrameNeedsReflow(this, IntrinsicDirty::TreeChange,
233                                 NS_FRAME_HAS_DIRTY_CHILDREN);
234 }
235 
RemoveChild(nsTableColFrame & aChild,bool aResetSubsequentColIndices)236 void nsTableColGroupFrame::RemoveChild(nsTableColFrame& aChild,
237                                        bool aResetSubsequentColIndices) {
238   int32_t colIndex = 0;
239   nsIFrame* nextChild = nullptr;
240   if (aResetSubsequentColIndices) {
241     colIndex = aChild.GetColIndex();
242     nextChild = aChild.GetNextSibling();
243   }
244   mFrames.DestroyFrame(&aChild);
245   mColCount--;
246   if (aResetSubsequentColIndices) {
247     if (nextChild) {  // reset inside this and all following colgroups
248       ResetColIndices(this, colIndex, nextChild);
249     } else {
250       nsIFrame* nextGroup = GetNextSibling();
251       if (nextGroup)  // reset next and all following colgroups
252         ResetColIndices(nextGroup, colIndex);
253     }
254   }
255 
256   PresShell()->FrameNeedsReflow(this, IntrinsicDirty::TreeChange,
257                                 NS_FRAME_HAS_DIRTY_CHILDREN);
258 }
259 
RemoveFrame(ChildListID aListID,nsIFrame * aOldFrame)260 void nsTableColGroupFrame::RemoveFrame(ChildListID aListID,
261                                        nsIFrame* aOldFrame) {
262   NS_ASSERTION(aListID == kPrincipalList, "unexpected child list");
263 
264   if (!aOldFrame) {
265     return;
266   }
267   bool contentRemoval = false;
268 
269   if (aOldFrame->IsTableColFrame()) {
270     nsTableColFrame* colFrame = (nsTableColFrame*)aOldFrame;
271     if (colFrame->GetColType() == eColContent) {
272       contentRemoval = true;
273       // Remove any anonymous column frames this <col> produced via a colspan
274       nsTableColFrame* col = colFrame->GetNextCol();
275       nsTableColFrame* nextCol;
276       while (col && col->GetColType() == eColAnonymousCol) {
277 #ifdef DEBUG
278 #endif
279         nextCol = col->GetNextCol();
280         RemoveFrame(kPrincipalList, col);
281         col = nextCol;
282       }
283     }
284 
285     int32_t colIndex = colFrame->GetColIndex();
286     // The RemoveChild call handles calling FrameNeedsReflow on us.
287     RemoveChild(*colFrame, true);
288 
289     nsTableFrame* tableFrame = GetTableFrame();
290     tableFrame->RemoveCol(this, colIndex, true, true);
291     if (mFrames.IsEmpty() && contentRemoval && !IsSynthetic()) {
292       tableFrame->AppendAnonymousColFrames(this, GetSpan(),
293                                            eColAnonymousColGroup, true);
294     }
295   } else {
296     mFrames.DestroyFrame(aOldFrame);
297   }
298 }
299 
GetLogicalSkipSides() const300 nsIFrame::LogicalSides nsTableColGroupFrame::GetLogicalSkipSides() const {
301   LogicalSides skip(mWritingMode);
302   if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak ==
303                    StyleBoxDecorationBreak::Clone)) {
304     return skip;
305   }
306 
307   if (GetPrevInFlow()) {
308     skip |= eLogicalSideBitsBStart;
309   }
310   if (GetNextInFlow()) {
311     skip |= eLogicalSideBitsBEnd;
312   }
313   return skip;
314 }
315 
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)316 void nsTableColGroupFrame::Reflow(nsPresContext* aPresContext,
317                                   ReflowOutput& aDesiredSize,
318                                   const ReflowInput& aReflowInput,
319                                   nsReflowStatus& aStatus) {
320   MarkInReflow();
321   DO_GLOBAL_REFLOW_COUNT("nsTableColGroupFrame");
322   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
323   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
324   NS_ASSERTION(nullptr != mContent, "bad state -- null content for frame");
325 
326   const nsStyleVisibility* groupVis = StyleVisibility();
327   bool collapseGroup = StyleVisibility::Collapse == groupVis->mVisible;
328   if (collapseGroup) {
329     GetTableFrame()->SetNeedToCollapse(true);
330   }
331   // for every content child that (is a column thingy and does not already have
332   // a frame) create a frame and adjust it's style
333 
334   for (nsIFrame* kidFrame = mFrames.FirstChild(); kidFrame;
335        kidFrame = kidFrame->GetNextSibling()) {
336     // Give the child frame a chance to reflow, even though we know it'll have 0
337     // size
338     ReflowOutput kidSize(aReflowInput);
339     ReflowInput kidReflowInput(aPresContext, aReflowInput, kidFrame,
340                                LogicalSize(kidFrame->GetWritingMode()));
341 
342     nsReflowStatus status;
343     ReflowChild(kidFrame, aPresContext, kidSize, kidReflowInput, 0, 0,
344                 ReflowChildFlags::Default, status);
345     FinishReflowChild(kidFrame, aPresContext, kidSize, &kidReflowInput, 0, 0,
346                       ReflowChildFlags::Default);
347   }
348 
349   aDesiredSize.ClearSize();
350   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
351 }
352 
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)353 void nsTableColGroupFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
354                                             const nsDisplayListSet& aLists) {
355   // Per https://drafts.csswg.org/css-tables-3/#global-style-overrides:
356   // "All css properties of table-column and table-column-group boxes are
357   // ignored, except when explicitly specified by this specification."
358   // CSS outlines and box-shadows fall into this category, so we skip them
359   // on these boxes.
360   MOZ_ASSERT_UNREACHABLE("Colgroups don't paint themselves");
361 }
362 
GetFirstColumn()363 nsTableColFrame* nsTableColGroupFrame::GetFirstColumn() {
364   return GetNextColumn(nullptr);
365 }
366 
GetNextColumn(nsIFrame * aChildFrame)367 nsTableColFrame* nsTableColGroupFrame::GetNextColumn(nsIFrame* aChildFrame) {
368   nsTableColFrame* result = nullptr;
369   nsIFrame* childFrame = aChildFrame;
370   if (!childFrame) {
371     childFrame = mFrames.FirstChild();
372   } else {
373     childFrame = childFrame->GetNextSibling();
374   }
375   while (childFrame) {
376     if (mozilla::StyleDisplay::TableColumn ==
377         childFrame->StyleDisplay()->mDisplay) {
378       result = (nsTableColFrame*)childFrame;
379       break;
380     }
381     childFrame = childFrame->GetNextSibling();
382   }
383   return result;
384 }
385 
GetSpan()386 int32_t nsTableColGroupFrame::GetSpan() { return StyleTable()->mXSpan; }
387 
SetContinuousBCBorderWidth(LogicalSide aForSide,BCPixelSize aPixelValue)388 void nsTableColGroupFrame::SetContinuousBCBorderWidth(LogicalSide aForSide,
389                                                       BCPixelSize aPixelValue) {
390   switch (aForSide) {
391     case eLogicalSideBStart:
392       mBStartContBorderWidth = aPixelValue;
393       return;
394     case eLogicalSideBEnd:
395       mBEndContBorderWidth = aPixelValue;
396       return;
397     default:
398       NS_ERROR("invalid side arg");
399   }
400 }
401 
GetContinuousBCBorderWidth(WritingMode aWM,LogicalMargin & aBorder)402 void nsTableColGroupFrame::GetContinuousBCBorderWidth(WritingMode aWM,
403                                                       LogicalMargin& aBorder) {
404   int32_t d2a = PresContext()->AppUnitsPerDevPixel();
405   nsTableColFrame* col =
406       GetTableFrame()->GetColFrame(mStartColIndex + mColCount - 1);
407   col->GetContinuousBCBorderWidth(aWM, aBorder);
408   aBorder.BStart(aWM) = BC_BORDER_END_HALF_COORD(d2a, mBStartContBorderWidth);
409   aBorder.BEnd(aWM) = BC_BORDER_START_HALF_COORD(d2a, mBEndContBorderWidth);
410 }
411 
412 /* ----- global methods ----- */
413 
NS_NewTableColGroupFrame(PresShell * aPresShell,ComputedStyle * aStyle)414 nsTableColGroupFrame* NS_NewTableColGroupFrame(PresShell* aPresShell,
415                                                ComputedStyle* aStyle) {
416   return new (aPresShell)
417       nsTableColGroupFrame(aStyle, aPresShell->GetPresContext());
418 }
419 
NS_IMPL_FRAMEARENA_HELPERS(nsTableColGroupFrame)420 NS_IMPL_FRAMEARENA_HELPERS(nsTableColGroupFrame)
421 
422 void nsTableColGroupFrame::InvalidateFrame(uint32_t aDisplayItemKey,
423                                            bool aRebuildDisplayItems) {
424   nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
425   if (GetTableFrame()->IsBorderCollapse()) {
426     const bool rebuild = StaticPrefs::layout_display_list_retain_sc();
427     GetParent()->InvalidateFrameWithRect(InkOverflowRect() + GetPosition(),
428                                          aDisplayItemKey, rebuild);
429   }
430 }
431 
InvalidateFrameWithRect(const nsRect & aRect,uint32_t aDisplayItemKey,bool aRebuildDisplayItems)432 void nsTableColGroupFrame::InvalidateFrameWithRect(const nsRect& aRect,
433                                                    uint32_t aDisplayItemKey,
434                                                    bool aRebuildDisplayItems) {
435   nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
436                                     aRebuildDisplayItems);
437   // If we have filters applied that would affects our bounds, then
438   // we get an inactive layer created and this is computed
439   // within FrameLayerBuilder
440   GetParent()->InvalidateFrameWithRect(aRect + GetPosition(), aDisplayItemKey,
441                                        aRebuildDisplayItems);
442 }
443 
444 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const445 nsresult nsTableColGroupFrame::GetFrameName(nsAString& aResult) const {
446   return MakeFrameName(u"TableColGroup"_ns, aResult);
447 }
448 
Dump(int32_t aIndent)449 void nsTableColGroupFrame::Dump(int32_t aIndent) {
450   char* indent = new char[aIndent + 1];
451   if (!indent) return;
452   for (int32_t i = 0; i < aIndent + 1; i++) {
453     indent[i] = ' ';
454   }
455   indent[aIndent] = 0;
456 
457   printf(
458       "%s**START COLGROUP DUMP**\n%s startcolIndex=%d  colcount=%d span=%d "
459       "isSynthetic=%s",
460       indent, indent, GetStartColumnIndex(), GetColCount(), GetSpan(),
461       IsSynthetic() ? "true" : "false");
462 
463   // verify the colindices
464   int32_t j = GetStartColumnIndex();
465   nsTableColFrame* col = GetFirstColumn();
466   while (col) {
467     NS_ASSERTION(j == col->GetColIndex(), "wrong colindex on col frame");
468     col = col->GetNextCol();
469     j++;
470   }
471   NS_ASSERTION((j - GetStartColumnIndex()) == GetColCount(),
472                "number of cols out of sync");
473   printf("\n%s**END COLGROUP DUMP** ", indent);
474   delete[] indent;
475 }
476 #endif
477