1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "gfxContext.h"
8 #include "nsMathMLmtableFrame.h"
9 #include "nsPresContext.h"
10 #include "nsStyleConsts.h"
11 #include "nsNameSpaceManager.h"
12 #include "nsCSSRendering.h"
13 #include "mozilla/dom/MathMLElement.h"
14 
15 #include "nsCRT.h"
16 #include "nsTArray.h"
17 #include "nsTableFrame.h"
18 #include "celldata.h"
19 
20 #include "mozilla/PresShell.h"
21 #include "mozilla/RestyleManager.h"
22 #include <algorithm>
23 
24 #include "nsIScriptError.h"
25 #include "nsContentUtils.h"
26 #include "nsLayoutUtils.h"
27 
28 using namespace mozilla;
29 using namespace mozilla::image;
30 using mozilla::dom::Element;
31 
32 //
33 // <mtable> -- table or matrix - implementation
34 //
35 
ParseStyleValue(nsAtom * aAttribute,const nsAString & aAttributeValue)36 static int8_t ParseStyleValue(nsAtom* aAttribute,
37                               const nsAString& aAttributeValue) {
38   if (aAttribute == nsGkAtoms::rowalign_) {
39     if (aAttributeValue.EqualsLiteral("top")) {
40       return static_cast<int8_t>(StyleVerticalAlignKeyword::Top);
41     }
42     if (aAttributeValue.EqualsLiteral("bottom")) {
43       return static_cast<int8_t>(StyleVerticalAlignKeyword::Bottom);
44     }
45     if (aAttributeValue.EqualsLiteral("center")) {
46       return static_cast<int8_t>(StyleVerticalAlignKeyword::Middle);
47     }
48     return static_cast<int8_t>(StyleVerticalAlignKeyword::Baseline);
49   }
50 
51   if (aAttribute == nsGkAtoms::columnalign_) {
52     if (aAttributeValue.EqualsLiteral("left")) {
53       return int8_t(StyleTextAlign::Left);
54     }
55     if (aAttributeValue.EqualsLiteral("right")) {
56       return int8_t(StyleTextAlign::Right);
57     }
58     return int8_t(StyleTextAlign::Center);
59   }
60 
61   if (aAttribute == nsGkAtoms::rowlines_ ||
62       aAttribute == nsGkAtoms::columnlines_) {
63     if (aAttributeValue.EqualsLiteral("solid")) {
64       return static_cast<int8_t>(StyleBorderStyle::Solid);
65     }
66     if (aAttributeValue.EqualsLiteral("dashed")) {
67       return static_cast<int8_t>(StyleBorderStyle::Dashed);
68     }
69     return static_cast<int8_t>(StyleBorderStyle::None);
70   }
71 
72   MOZ_CRASH("Unrecognized attribute.");
73   return -1;
74 }
75 
ExtractStyleValues(const nsAString & aString,nsAtom * aAttribute,bool aAllowMultiValues)76 static nsTArray<int8_t>* ExtractStyleValues(const nsAString& aString,
77                                             nsAtom* aAttribute,
78                                             bool aAllowMultiValues) {
79   nsTArray<int8_t>* styleArray = nullptr;
80 
81   const char16_t* start = aString.BeginReading();
82   const char16_t* end = aString.EndReading();
83 
84   int32_t startIndex = 0;
85   int32_t count = 0;
86 
87   while (start < end) {
88     // Skip leading spaces.
89     while ((start < end) && nsCRT::IsAsciiSpace(*start)) {
90       start++;
91       startIndex++;
92     }
93 
94     // Look for the end of the string, or another space.
95     while ((start < end) && !nsCRT::IsAsciiSpace(*start)) {
96       start++;
97       count++;
98     }
99 
100     // Grab the value found and process it.
101     if (count > 0) {
102       if (!styleArray) styleArray = new nsTArray<int8_t>();
103 
104       // We want to return a null array if an attribute gives multiple values,
105       // but multiple values aren't allowed.
106       if (styleArray->Length() > 1 && !aAllowMultiValues) {
107         delete styleArray;
108         return nullptr;
109       }
110 
111       nsDependentSubstring valueString(aString, startIndex, count);
112       int8_t styleValue = ParseStyleValue(aAttribute, valueString);
113       styleArray->AppendElement(styleValue);
114 
115       startIndex += count;
116       count = 0;
117     }
118   }
119   return styleArray;
120 }
121 
ReportParseError(nsIFrame * aFrame,const char16_t * aAttribute,const char16_t * aValue)122 static nsresult ReportParseError(nsIFrame* aFrame, const char16_t* aAttribute,
123                                  const char16_t* aValue) {
124   nsIContent* content = aFrame->GetContent();
125 
126   AutoTArray<nsString, 3> params;
127   params.AppendElement(aValue);
128   params.AppendElement(aAttribute);
129   params.AppendElement(nsDependentAtomString(content->NodeInfo()->NameAtom()));
130 
131   return nsContentUtils::ReportToConsole(
132       nsIScriptError::errorFlag, "Layout: MathML"_ns, content->OwnerDoc(),
133       nsContentUtils::eMATHML_PROPERTIES, "AttributeParsingError", params);
134 }
135 
136 // Each rowalign='top bottom' or columnalign='left right center' (from
137 // <mtable> or <mtr>) is split once into an nsTArray<int8_t> which is
138 // stored in the property table. Row/Cell frames query the property table
139 // to see what values apply to them.
140 
NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowAlignProperty,nsTArray<int8_t>)141 NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowAlignProperty, nsTArray<int8_t>)
142 NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowLinesProperty, nsTArray<int8_t>)
143 NS_DECLARE_FRAME_PROPERTY_DELETABLE(ColumnAlignProperty, nsTArray<int8_t>)
144 NS_DECLARE_FRAME_PROPERTY_DELETABLE(ColumnLinesProperty, nsTArray<int8_t>)
145 
146 static const FramePropertyDescriptor<nsTArray<int8_t>>* AttributeToProperty(
147     nsAtom* aAttribute) {
148   if (aAttribute == nsGkAtoms::rowalign_) return RowAlignProperty();
149   if (aAttribute == nsGkAtoms::rowlines_) return RowLinesProperty();
150   if (aAttribute == nsGkAtoms::columnalign_) return ColumnAlignProperty();
151   NS_ASSERTION(aAttribute == nsGkAtoms::columnlines_, "Invalid attribute");
152   return ColumnLinesProperty();
153 }
154 
155 /* This method looks for a property that applies to a cell, but it looks
156  * recursively because some cell properties can come from the cell, a row,
157  * a table, etc. This function searches through the hierarchy for a property
158  * and returns its value. The function stops searching after checking a <mtable>
159  * frame.
160  */
FindCellProperty(const nsIFrame * aCellFrame,const FramePropertyDescriptor<nsTArray<int8_t>> * aFrameProperty)161 static nsTArray<int8_t>* FindCellProperty(
162     const nsIFrame* aCellFrame,
163     const FramePropertyDescriptor<nsTArray<int8_t>>* aFrameProperty) {
164   const nsIFrame* currentFrame = aCellFrame;
165   nsTArray<int8_t>* propertyData = nullptr;
166 
167   while (currentFrame) {
168     propertyData = currentFrame->GetProperty(aFrameProperty);
169     bool frameIsTable = (currentFrame->IsTableFrame());
170 
171     if (propertyData || frameIsTable)
172       currentFrame = nullptr;  // A null frame pointer exits the loop
173     else
174       currentFrame = currentFrame->GetParent();  // Go to the parent frame
175   }
176 
177   return propertyData;
178 }
179 
ApplyBorderToStyle(const nsMathMLmtdFrame * aFrame,nsStyleBorder & aStyleBorder)180 static void ApplyBorderToStyle(const nsMathMLmtdFrame* aFrame,
181                                nsStyleBorder& aStyleBorder) {
182   uint32_t rowIndex = aFrame->RowIndex();
183   uint32_t columnIndex = aFrame->ColIndex();
184 
185   nscoord borderWidth = nsPresContext::CSSPixelsToAppUnits(1);
186 
187   nsTArray<int8_t>* rowLinesList = FindCellProperty(aFrame, RowLinesProperty());
188 
189   nsTArray<int8_t>* columnLinesList =
190       FindCellProperty(aFrame, ColumnLinesProperty());
191 
192   // We don't place a row line on top of the first row
193   if (rowIndex > 0 && rowLinesList) {
194     // If the row number is greater than the number of provided rowline
195     // values, we simply repeat the last value.
196     uint32_t listLength = rowLinesList->Length();
197     if (rowIndex < listLength) {
198       aStyleBorder.SetBorderStyle(
199           eSideTop,
200           static_cast<StyleBorderStyle>(rowLinesList->ElementAt(rowIndex - 1)));
201     } else {
202       aStyleBorder.SetBorderStyle(eSideTop,
203                                   static_cast<StyleBorderStyle>(
204                                       rowLinesList->ElementAt(listLength - 1)));
205     }
206     aStyleBorder.SetBorderWidth(eSideTop, borderWidth);
207   }
208 
209   // We don't place a column line on the left of the first column.
210   if (columnIndex > 0 && columnLinesList) {
211     // If the column number is greater than the number of provided columline
212     // values, we simply repeat the last value.
213     uint32_t listLength = columnLinesList->Length();
214     if (columnIndex < listLength) {
215       aStyleBorder.SetBorderStyle(
216           eSideLeft, static_cast<StyleBorderStyle>(
217                          columnLinesList->ElementAt(columnIndex - 1)));
218     } else {
219       aStyleBorder.SetBorderStyle(
220           eSideLeft, static_cast<StyleBorderStyle>(
221                          columnLinesList->ElementAt(listLength - 1)));
222     }
223     aStyleBorder.SetBorderWidth(eSideLeft, borderWidth);
224   }
225 }
226 
ComputeBorderOverflow(nsMathMLmtdFrame * aFrame,const nsStyleBorder & aStyleBorder)227 static nsMargin ComputeBorderOverflow(nsMathMLmtdFrame* aFrame,
228                                       const nsStyleBorder& aStyleBorder) {
229   nsMargin overflow;
230   int32_t rowIndex;
231   int32_t columnIndex;
232   nsTableFrame* table = aFrame->GetTableFrame();
233   aFrame->GetCellIndexes(rowIndex, columnIndex);
234   if (!columnIndex) {
235     overflow.left = table->GetColSpacing(-1);
236     overflow.right = table->GetColSpacing(0) / 2;
237   } else if (columnIndex == table->GetColCount() - 1) {
238     overflow.left = table->GetColSpacing(columnIndex - 1) / 2;
239     overflow.right = table->GetColSpacing(columnIndex + 1);
240   } else {
241     overflow.left = table->GetColSpacing(columnIndex - 1) / 2;
242     overflow.right = table->GetColSpacing(columnIndex) / 2;
243   }
244   if (!rowIndex) {
245     overflow.top = table->GetRowSpacing(-1);
246     overflow.bottom = table->GetRowSpacing(0) / 2;
247   } else if (rowIndex == table->GetRowCount() - 1) {
248     overflow.top = table->GetRowSpacing(rowIndex - 1) / 2;
249     overflow.bottom = table->GetRowSpacing(rowIndex + 1);
250   } else {
251     overflow.top = table->GetRowSpacing(rowIndex - 1) / 2;
252     overflow.bottom = table->GetRowSpacing(rowIndex) / 2;
253   }
254   return overflow;
255 }
256 
257 /*
258  * A variant of the nsDisplayBorder contains special code to render a border
259  * around a nsMathMLmtdFrame based on the rowline and columnline properties
260  * set on the cell frame.
261  */
262 class nsDisplaymtdBorder final : public nsDisplayBorder {
263  public:
nsDisplaymtdBorder(nsDisplayListBuilder * aBuilder,nsMathMLmtdFrame * aFrame)264   nsDisplaymtdBorder(nsDisplayListBuilder* aBuilder, nsMathMLmtdFrame* aFrame)
265       : nsDisplayBorder(aBuilder, aFrame) {}
266 
AllocateGeometry(nsDisplayListBuilder * aBuilder)267   nsDisplayItemGeometry* AllocateGeometry(
268       nsDisplayListBuilder* aBuilder) override {
269     return new nsDisplayItemGenericImageGeometry(this, aBuilder);
270   }
271 
ComputeInvalidationRegion(nsDisplayListBuilder * aBuilder,const nsDisplayItemGeometry * aGeometry,nsRegion * aInvalidRegion) const272   void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
273                                  const nsDisplayItemGeometry* aGeometry,
274                                  nsRegion* aInvalidRegion) const override {
275     auto geometry =
276         static_cast<const nsDisplayItemGenericImageGeometry*>(aGeometry);
277 
278     if (aBuilder->ShouldSyncDecodeImages() &&
279         geometry->ShouldInvalidateToSyncDecodeImages()) {
280       bool snap;
281       aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
282     }
283 
284     nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry,
285                                              aInvalidRegion);
286   }
287 
GetBounds(nsDisplayListBuilder * aBuilder,bool * aSnap) const288   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
289                            bool* aSnap) const override {
290     *aSnap = true;
291     nsStyleBorder styleBorder = *mFrame->StyleBorder();
292     nsMathMLmtdFrame* frame = static_cast<nsMathMLmtdFrame*>(mFrame);
293     ApplyBorderToStyle(frame, styleBorder);
294     nsRect bounds = CalculateBounds<nsRect>(styleBorder);
295     nsMargin overflow = ComputeBorderOverflow(frame, styleBorder);
296     bounds.Inflate(overflow);
297     return bounds;
298   }
299 
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)300   virtual void Paint(nsDisplayListBuilder* aBuilder,
301                      gfxContext* aCtx) override {
302     nsStyleBorder styleBorder = *mFrame->StyleBorder();
303     nsMathMLmtdFrame* frame = static_cast<nsMathMLmtdFrame*>(mFrame);
304     ApplyBorderToStyle(frame, styleBorder);
305 
306     nsRect bounds = nsRect(ToReferenceFrame(), mFrame->GetSize());
307     nsMargin overflow = ComputeBorderOverflow(frame, styleBorder);
308     bounds.Inflate(overflow);
309 
310     PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages()
311                                  ? PaintBorderFlags::SyncDecodeImages
312                                  : PaintBorderFlags();
313 
314     ImgDrawResult result = nsCSSRendering::PaintBorderWithStyleBorder(
315         mFrame->PresContext(), *aCtx, mFrame, GetPaintRect(), bounds,
316         styleBorder, mFrame->Style(), flags, mFrame->GetSkipSides());
317 
318     nsDisplayItemGenericImageGeometry::UpdateDrawResult(this, result);
319   }
320 
CreateWebRenderCommands(mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)321   bool CreateWebRenderCommands(
322       mozilla::wr::DisplayListBuilder& aBuilder,
323       mozilla::wr::IpcResourceUpdateQueue& aResources,
324       const StackingContextHelper& aSc,
325       mozilla::layers::RenderRootStateManager* aManager,
326       nsDisplayListBuilder* aDisplayListBuilder) override {
327     return false;
328   }
329 
IsInvisibleInRect(const nsRect & aRect) const330   virtual bool IsInvisibleInRect(const nsRect& aRect) const override {
331     return false;
332   }
333 };
334 
335 #ifdef DEBUG
336 #  define DEBUG_VERIFY_THAT_FRAME_IS(_frame, _expected)                       \
337     MOZ_ASSERT(                                                               \
338         mozilla::StyleDisplay::_expected == _frame->StyleDisplay()->mDisplay, \
339         "internal error");
340 #else
341 #  define DEBUG_VERIFY_THAT_FRAME_IS(_frame, _expected)
342 #endif
343 
ParseFrameAttribute(nsIFrame * aFrame,nsAtom * aAttribute,bool aAllowMultiValues)344 static void ParseFrameAttribute(nsIFrame* aFrame, nsAtom* aAttribute,
345                                 bool aAllowMultiValues) {
346   nsAutoString attrValue;
347 
348   Element* frameElement = aFrame->GetContent()->AsElement();
349   frameElement->GetAttr(kNameSpaceID_None, aAttribute, attrValue);
350 
351   if (!attrValue.IsEmpty()) {
352     nsTArray<int8_t>* valueList =
353         ExtractStyleValues(attrValue, aAttribute, aAllowMultiValues);
354 
355     // If valueList is null, that indicates a problem with the attribute value.
356     // Only set properties on a valid attribute value.
357     if (valueList) {
358       // The code reading the property assumes that this list is nonempty.
359       NS_ASSERTION(valueList->Length() >= 1, "valueList should not be empty!");
360       aFrame->SetProperty(AttributeToProperty(aAttribute), valueList);
361     } else {
362       ReportParseError(aFrame, aAttribute->GetUTF16String(), attrValue.get());
363     }
364   }
365 }
366 
367 // rowspacing
368 //
369 // Specifies the distance between successive rows in an mtable.  Multiple
370 // lengths can be specified, each corresponding to its respective position
371 // between rows.  For example:
372 //
373 // [ROW_0]
374 // rowspace_0
375 // [ROW_1]
376 // rowspace_1
377 // [ROW_2]
378 //
379 // If the number of row gaps exceeds the number of lengths specified, the final
380 // specified length is repeated.  Additional lengths are ignored.
381 //
382 // values: (length)+
383 // default: 1.0ex
384 //
385 // Unitless values are permitted and provide a multiple of the default value
386 // Negative values are forbidden.
387 //
388 
389 // columnspacing
390 //
391 // Specifies the distance between successive columns in an mtable.  Multiple
392 // lengths can be specified, each corresponding to its respective position
393 // between columns.  For example:
394 //
395 // [COLUMN_0] columnspace_0 [COLUMN_1] columnspace_1 [COLUMN_2]
396 //
397 // If the number of column gaps exceeds the number of lengths specified, the
398 // final specified length is repeated.  Additional lengths are ignored.
399 //
400 // values: (length)+
401 // default: 0.8em
402 //
403 // Unitless values are permitted and provide a multiple of the default value
404 // Negative values are forbidden.
405 //
406 
407 // framespacing
408 //
409 // Specifies the distance between the mtable and its frame (if any).  The
410 // first value specified provides the spacing between the left and right edge
411 // of the table and the frame, the second value determines the spacing between
412 // the top and bottom edges and the frame.
413 //
414 // An error is reported if only one length is passed.  Any additional lengths
415 // are ignored
416 //
417 // values: length length
418 // default: 0em   0ex    If frame attribute is "none" or not specified,
419 //          0.4em 0.5ex  otherwise
420 //
421 // Unitless values are permitted and provide a multiple of the default value
422 // Negative values are forbidden.
423 //
424 
425 static const float kDefaultRowspacingEx = 1.0f;
426 static const float kDefaultColumnspacingEm = 0.8f;
427 static const float kDefaultFramespacingArg0Em = 0.4f;
428 static const float kDefaultFramespacingArg1Ex = 0.5f;
429 
ExtractSpacingValues(const nsAString & aString,nsAtom * aAttribute,nsTArray<nscoord> & aSpacingArray,nsIFrame * aFrame,nscoord aDefaultValue0,nscoord aDefaultValue1,float aFontSizeInflation)430 static void ExtractSpacingValues(const nsAString& aString, nsAtom* aAttribute,
431                                  nsTArray<nscoord>& aSpacingArray,
432                                  nsIFrame* aFrame, nscoord aDefaultValue0,
433                                  nscoord aDefaultValue1,
434                                  float aFontSizeInflation) {
435   nsPresContext* presContext = aFrame->PresContext();
436   ComputedStyle* computedStyle = aFrame->Style();
437 
438   const char16_t* start = aString.BeginReading();
439   const char16_t* end = aString.EndReading();
440 
441   int32_t startIndex = 0;
442   int32_t count = 0;
443   int32_t elementNum = 0;
444 
445   while (start < end) {
446     // Skip leading spaces.
447     while ((start < end) && nsCRT::IsAsciiSpace(*start)) {
448       start++;
449       startIndex++;
450     }
451 
452     // Look for the end of the string, or another space.
453     while ((start < end) && !nsCRT::IsAsciiSpace(*start)) {
454       start++;
455       count++;
456     }
457 
458     // Grab the value found and process it.
459     if (count > 0) {
460       const nsAString& str = Substring(aString, startIndex, count);
461       nsAutoString valueString;
462       valueString.Assign(str);
463       nscoord newValue;
464       if (aAttribute == nsGkAtoms::framespacing_ && elementNum) {
465         newValue = aDefaultValue1;
466       } else {
467         newValue = aDefaultValue0;
468       }
469       nsMathMLFrame::ParseNumericValue(
470           valueString, &newValue, dom::MathMLElement::PARSE_ALLOW_UNITLESS,
471           presContext, computedStyle, aFontSizeInflation);
472       aSpacingArray.AppendElement(newValue);
473 
474       startIndex += count;
475       count = 0;
476       elementNum++;
477     }
478   }
479 }
480 
ParseSpacingAttribute(nsMathMLmtableFrame * aFrame,nsAtom * aAttribute)481 static void ParseSpacingAttribute(nsMathMLmtableFrame* aFrame,
482                                   nsAtom* aAttribute) {
483   NS_ASSERTION(aAttribute == nsGkAtoms::rowspacing_ ||
484                    aAttribute == nsGkAtoms::columnspacing_ ||
485                    aAttribute == nsGkAtoms::framespacing_,
486                "Non spacing attribute passed");
487 
488   nsAutoString attrValue;
489   Element* frameElement = aFrame->GetContent()->AsElement();
490   frameElement->GetAttr(kNameSpaceID_None, aAttribute, attrValue);
491 
492   if (nsGkAtoms::framespacing_ == aAttribute) {
493     nsAutoString frame;
494     frameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::frame, frame);
495     if (frame.IsEmpty() || frame.EqualsLiteral("none")) {
496       aFrame->SetFrameSpacing(0, 0);
497       return;
498     }
499   }
500 
501   nscoord value;
502   nscoord value2;
503   // Set defaults
504   float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(aFrame);
505   RefPtr<nsFontMetrics> fm =
506       nsLayoutUtils::GetFontMetricsForFrame(aFrame, fontSizeInflation);
507   if (nsGkAtoms::rowspacing_ == aAttribute) {
508     value = kDefaultRowspacingEx * fm->XHeight();
509     value2 = 0;
510   } else if (nsGkAtoms::columnspacing_ == aAttribute) {
511     value = kDefaultColumnspacingEm * fm->EmHeight();
512     value2 = 0;
513   } else {
514     value = kDefaultFramespacingArg0Em * fm->EmHeight();
515     value2 = kDefaultFramespacingArg1Ex * fm->XHeight();
516   }
517 
518   nsTArray<nscoord> valueList;
519   ExtractSpacingValues(attrValue, aAttribute, valueList, aFrame, value, value2,
520                        fontSizeInflation);
521   if (valueList.Length() == 0) {
522     if (frameElement->HasAttr(kNameSpaceID_None, aAttribute)) {
523       ReportParseError(aFrame, aAttribute->GetUTF16String(), attrValue.get());
524     }
525     valueList.AppendElement(value);
526   }
527   if (aAttribute == nsGkAtoms::framespacing_) {
528     if (valueList.Length() == 1) {
529       if (frameElement->HasAttr(kNameSpaceID_None, aAttribute)) {
530         ReportParseError(aFrame, aAttribute->GetUTF16String(), attrValue.get());
531       }
532       valueList.AppendElement(value2);
533     } else if (valueList.Length() != 2) {
534       ReportParseError(aFrame, aAttribute->GetUTF16String(), attrValue.get());
535     }
536   }
537 
538   if (aAttribute == nsGkAtoms::rowspacing_) {
539     aFrame->SetRowSpacingArray(valueList);
540   } else if (aAttribute == nsGkAtoms::columnspacing_) {
541     aFrame->SetColSpacingArray(valueList);
542   } else {
543     aFrame->SetFrameSpacing(valueList.ElementAt(0), valueList.ElementAt(1));
544   }
545 }
546 
ParseSpacingAttributes(nsMathMLmtableFrame * aTableFrame)547 static void ParseSpacingAttributes(nsMathMLmtableFrame* aTableFrame) {
548   ParseSpacingAttribute(aTableFrame, nsGkAtoms::rowspacing_);
549   ParseSpacingAttribute(aTableFrame, nsGkAtoms::columnspacing_);
550   ParseSpacingAttribute(aTableFrame, nsGkAtoms::framespacing_);
551   aTableFrame->SetUseCSSSpacing();
552 }
553 
554 // map all attributes within a table -- requires the indices of rows and cells.
555 // so it can only happen after they are made ready by the table base class.
MapAllAttributesIntoCSS(nsMathMLmtableFrame * aTableFrame)556 static void MapAllAttributesIntoCSS(nsMathMLmtableFrame* aTableFrame) {
557   // Map mtable rowalign & rowlines.
558   ParseFrameAttribute(aTableFrame, nsGkAtoms::rowalign_, true);
559   ParseFrameAttribute(aTableFrame, nsGkAtoms::rowlines_, true);
560 
561   // Map mtable columnalign & columnlines.
562   ParseFrameAttribute(aTableFrame, nsGkAtoms::columnalign_, true);
563   ParseFrameAttribute(aTableFrame, nsGkAtoms::columnlines_, true);
564 
565   // Map mtable rowspacing, columnspacing & framespacing
566   ParseSpacingAttributes(aTableFrame);
567 
568   // mtable is simple and only has one (pseudo) row-group
569   nsIFrame* rgFrame = aTableFrame->PrincipalChildList().FirstChild();
570   if (!rgFrame || !rgFrame->IsTableRowGroupFrame()) return;
571 
572   for (nsIFrame* rowFrame : rgFrame->PrincipalChildList()) {
573     DEBUG_VERIFY_THAT_FRAME_IS(rowFrame, TableRow);
574     if (rowFrame->IsTableRowFrame()) {
575       // Map row rowalign.
576       ParseFrameAttribute(rowFrame, nsGkAtoms::rowalign_, false);
577       // Map row columnalign.
578       ParseFrameAttribute(rowFrame, nsGkAtoms::columnalign_, true);
579 
580       for (nsIFrame* cellFrame : rowFrame->PrincipalChildList()) {
581         DEBUG_VERIFY_THAT_FRAME_IS(cellFrame, TableCell);
582         if (cellFrame->IsTableCellFrame()) {
583           // Map cell rowalign.
584           ParseFrameAttribute(cellFrame, nsGkAtoms::rowalign_, false);
585           // Map row columnalign.
586           ParseFrameAttribute(cellFrame, nsGkAtoms::columnalign_, false);
587         }
588       }
589     }
590   }
591 }
592 
593 // the align attribute of mtable can have a row number which indicates
594 // from where to anchor the table, e.g., top 5 means anchor the table at
595 // the top of the 5th row, axis -1 means anchor the table on the axis of
596 // the last row
597 
598 // The REC says that the syntax is
599 // '\s*(top|bottom|center|baseline|axis)(\s+-?[0-9]+)?\s*'
600 // the parsing could have been simpler with that syntax
601 // but for backward compatibility we make optional
602 // the whitespaces between the alignment name and the row number
603 
604 enum eAlign {
605   eAlign_top,
606   eAlign_bottom,
607   eAlign_center,
608   eAlign_baseline,
609   eAlign_axis
610 };
611 
ParseAlignAttribute(nsString & aValue,eAlign & aAlign,int32_t & aRowIndex)612 static void ParseAlignAttribute(nsString& aValue, eAlign& aAlign,
613                                 int32_t& aRowIndex) {
614   // by default, the table is centered about the axis
615   aRowIndex = 0;
616   aAlign = eAlign_axis;
617   int32_t len = 0;
618 
619   // we only have to remove the leading spaces because
620   // ToInteger ignores the whitespaces around the number
621   aValue.CompressWhitespace(true, false);
622 
623   if (0 == aValue.Find("top")) {
624     len = 3;  // 3 is the length of 'top'
625     aAlign = eAlign_top;
626   } else if (0 == aValue.Find("bottom")) {
627     len = 6;  // 6 is the length of 'bottom'
628     aAlign = eAlign_bottom;
629   } else if (0 == aValue.Find("center")) {
630     len = 6;  // 6 is the length of 'center'
631     aAlign = eAlign_center;
632   } else if (0 == aValue.Find("baseline")) {
633     len = 8;  // 8 is the length of 'baseline'
634     aAlign = eAlign_baseline;
635   } else if (0 == aValue.Find("axis")) {
636     len = 4;  // 4 is the length of 'axis'
637     aAlign = eAlign_axis;
638   }
639   if (len) {
640     nsresult error;
641     aValue.Cut(0, len);  // aValue is not a const here
642     aRowIndex = aValue.ToInteger(&error);
643     if (NS_FAILED(error)) aRowIndex = 0;
644   }
645 }
646 
647 #ifdef DEBUG_rbs_off
648 // call ListMathMLTree(mParent) to get the big picture
ListMathMLTree(nsIFrame * atLeast)649 static void ListMathMLTree(nsIFrame* atLeast) {
650   // climb up to <math> or <body> if <math> isn't there
651   nsIFrame* f = atLeast;
652   for (; f; f = f->GetParent()) {
653     nsIContent* c = f->GetContent();
654     if (!c || c->IsMathMLElement(nsGkAtoms::math) ||
655         // XXXbaku which kind of body tag?
656         c->NodeInfo()->NameAtom(nsGkAtoms::body))
657       break;
658   }
659   if (!f) f = atLeast;
660   f->List(stdout, 0);
661 }
662 #endif
663 
664 // --------
665 // implementation of nsMathMLmtableWrapperFrame
666 
667 NS_QUERYFRAME_HEAD(nsMathMLmtableWrapperFrame)
NS_QUERYFRAME_ENTRY(nsIMathMLFrame)668   NS_QUERYFRAME_ENTRY(nsIMathMLFrame)
669 NS_QUERYFRAME_TAIL_INHERITING(nsTableWrapperFrame)
670 
671 nsContainerFrame* NS_NewMathMLmtableOuterFrame(PresShell* aPresShell,
672                                                ComputedStyle* aStyle) {
673   return new (aPresShell)
674       nsMathMLmtableWrapperFrame(aStyle, aPresShell->GetPresContext());
675 }
676 
677 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtableWrapperFrame)
678 
679 nsMathMLmtableWrapperFrame::~nsMathMLmtableWrapperFrame() = default;
680 
AttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)681 nsresult nsMathMLmtableWrapperFrame::AttributeChanged(int32_t aNameSpaceID,
682                                                       nsAtom* aAttribute,
683                                                       int32_t aModType) {
684   // Attributes specific to <mtable>:
685   // frame         : in mathml.css
686   // framespacing  : here
687   // groupalign    : not yet supported
688   // equalrows     : not yet supported
689   // equalcolumns  : not yet supported
690   // displaystyle  : here and in mathml.css
691   // align         : in reflow
692   // rowalign      : here
693   // rowlines      : here
694   // rowspacing    : here
695   // columnalign   : here
696   // columnlines   : here
697   // columnspacing : here
698 
699   // mtable is simple and only has one (pseudo) row-group inside our inner-table
700   nsIFrame* tableFrame = mFrames.FirstChild();
701   NS_ASSERTION(tableFrame && tableFrame->IsTableFrame(),
702                "should always have an inner table frame");
703   nsIFrame* rgFrame = tableFrame->PrincipalChildList().FirstChild();
704   if (!rgFrame || !rgFrame->IsTableRowGroupFrame()) return NS_OK;
705 
706   // align - just need to issue a dirty (resize) reflow command
707   if (aAttribute == nsGkAtoms::align) {
708     PresShell()->FrameNeedsReflow(this, IntrinsicDirty::Resize,
709                                   NS_FRAME_IS_DIRTY);
710     return NS_OK;
711   }
712 
713   // displaystyle - may seem innocuous, but it is actually very harsh --
714   // like changing an unit. Blow away and recompute all our automatic
715   // presentational data, and issue a style-changed reflow request
716   if (aAttribute == nsGkAtoms::displaystyle_) {
717     nsMathMLContainerFrame::RebuildAutomaticDataForChildren(GetParent());
718     // Need to reflow the parent, not us, because this can actually
719     // affect siblings.
720     PresShell()->FrameNeedsReflow(GetParent(), IntrinsicDirty::StyleChange,
721                                   NS_FRAME_IS_DIRTY);
722     return NS_OK;
723   }
724 
725   // ...and the other attributes affect rows or columns in one way or another
726 
727   nsPresContext* presContext = tableFrame->PresContext();
728   if (aAttribute == nsGkAtoms::rowspacing_ ||
729       aAttribute == nsGkAtoms::columnspacing_ ||
730       aAttribute == nsGkAtoms::framespacing_) {
731     nsMathMLmtableFrame* mathMLmtableFrame = do_QueryFrame(tableFrame);
732     if (mathMLmtableFrame) {
733       ParseSpacingAttribute(mathMLmtableFrame, aAttribute);
734       mathMLmtableFrame->SetUseCSSSpacing();
735     }
736   } else if (aAttribute == nsGkAtoms::rowalign_ ||
737              aAttribute == nsGkAtoms::rowlines_ ||
738              aAttribute == nsGkAtoms::columnalign_ ||
739              aAttribute == nsGkAtoms::columnlines_) {
740     // clear any cached property list for this table
741     tableFrame->RemoveProperty(AttributeToProperty(aAttribute));
742     // Reparse the new attribute on the table.
743     ParseFrameAttribute(tableFrame, aAttribute, true);
744   } else {
745     // Ignore attributes that do not affect layout.
746     return NS_OK;
747   }
748 
749   // Explicitly request a reflow in our subtree to pick up any changes
750   presContext->PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
751                                              NS_FRAME_IS_DIRTY);
752 
753   return NS_OK;
754 }
755 
GetRowFrameAt(int32_t aRowIndex)756 nsIFrame* nsMathMLmtableWrapperFrame::GetRowFrameAt(int32_t aRowIndex) {
757   int32_t rowCount = GetRowCount();
758 
759   // Negative indices mean to find upwards from the end.
760   if (aRowIndex < 0) {
761     aRowIndex = rowCount + aRowIndex;
762   } else {
763     // aRowIndex is 1-based, so convert it to a 0-based index
764     --aRowIndex;
765   }
766 
767   // if our inner table says that the index is valid, find the row now
768   if (0 <= aRowIndex && aRowIndex <= rowCount) {
769     nsIFrame* tableFrame = mFrames.FirstChild();
770     NS_ASSERTION(tableFrame && tableFrame->IsTableFrame(),
771                  "should always have an inner table frame");
772     nsIFrame* rgFrame = tableFrame->PrincipalChildList().FirstChild();
773     if (!rgFrame || !rgFrame->IsTableRowGroupFrame()) return nullptr;
774     for (nsIFrame* rowFrame : rgFrame->PrincipalChildList()) {
775       if (aRowIndex == 0) {
776         DEBUG_VERIFY_THAT_FRAME_IS(rowFrame, TableRow);
777         if (!rowFrame->IsTableRowFrame()) return nullptr;
778 
779         return rowFrame;
780       }
781       --aRowIndex;
782     }
783   }
784   return nullptr;
785 }
786 
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)787 void nsMathMLmtableWrapperFrame::Reflow(nsPresContext* aPresContext,
788                                         ReflowOutput& aDesiredSize,
789                                         const ReflowInput& aReflowInput,
790                                         nsReflowStatus& aStatus) {
791   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
792 
793   nsAutoString value;
794   // we want to return a table that is anchored according to the align attribute
795 
796   nsTableWrapperFrame::Reflow(aPresContext, aDesiredSize, aReflowInput,
797                               aStatus);
798   NS_ASSERTION(aDesiredSize.Height() >= 0, "illegal height for mtable");
799   NS_ASSERTION(aDesiredSize.Width() >= 0, "illegal width for mtable");
800 
801   // see if the user has set the align attribute on the <mtable>
802   int32_t rowIndex = 0;
803   eAlign tableAlign = eAlign_axis;
804   mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::align, value);
805   if (!value.IsEmpty()) {
806     ParseAlignAttribute(value, tableAlign, rowIndex);
807   }
808 
809   // adjustments if there is a specified row from where to anchor the table
810   // (conceptually: when there is no row of reference, picture the table as if
811   // it is wrapped in a single big fictional row at dy = 0, this way of
812   // doing so allows us to have a single code path for all cases).
813   nscoord dy = 0;
814   WritingMode wm = aDesiredSize.GetWritingMode();
815   nscoord blockSize = aDesiredSize.BSize(wm);
816   nsIFrame* rowFrame = nullptr;
817   if (rowIndex) {
818     rowFrame = GetRowFrameAt(rowIndex);
819     if (rowFrame) {
820       // translate the coordinates to be relative to us and in our writing mode
821       nsIFrame* frame = rowFrame;
822       LogicalRect rect(wm, frame->GetRect(),
823                        aReflowInput.ComputedSizeAsContainerIfConstrained());
824       blockSize = rect.BSize(wm);
825       do {
826         nsIFrame* parent = frame->GetParent();
827         dy += frame->BStart(wm, parent->GetSize());
828         frame = parent;
829       } while (frame != this);
830     }
831   }
832   switch (tableAlign) {
833     case eAlign_top:
834       aDesiredSize.SetBlockStartAscent(dy);
835       break;
836     case eAlign_bottom:
837       aDesiredSize.SetBlockStartAscent(dy + blockSize);
838       break;
839     case eAlign_center:
840       aDesiredSize.SetBlockStartAscent(dy + blockSize / 2);
841       break;
842     case eAlign_baseline:
843       if (rowFrame) {
844         // anchor the table on the baseline of the row of reference
845         nscoord rowAscent = ((nsTableRowFrame*)rowFrame)->GetMaxCellAscent();
846         if (rowAscent) {  // the row has at least one cell with 'vertical-align:
847                           // baseline'
848           aDesiredSize.SetBlockStartAscent(dy + rowAscent);
849           break;
850         }
851       }
852       // in other situations, fallback to center
853       aDesiredSize.SetBlockStartAscent(dy + blockSize / 2);
854       break;
855     case eAlign_axis:
856     default: {
857       // XXX should instead use style data from the row of reference here ?
858       RefPtr<nsFontMetrics> fm =
859           nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
860       nscoord axisHeight;
861       GetAxisHeight(aReflowInput.mRenderingContext->GetDrawTarget(), fm,
862                     axisHeight);
863       if (rowFrame) {
864         // anchor the table on the axis of the row of reference
865         // XXX fallback to baseline because it is a hard problem
866         // XXX need to fetch the axis of the row; would need rowalign=axis to
867         // work better
868         nscoord rowAscent = ((nsTableRowFrame*)rowFrame)->GetMaxCellAscent();
869         if (rowAscent) {  // the row has at least one cell with 'vertical-align:
870                           // baseline'
871           aDesiredSize.SetBlockStartAscent(dy + rowAscent);
872           break;
873         }
874       }
875       // in other situations, fallback to using half of the height
876       aDesiredSize.SetBlockStartAscent(dy + blockSize / 2 + axisHeight);
877     }
878   }
879 
880   mReference.x = 0;
881   mReference.y = aDesiredSize.BlockStartAscent();
882 
883   // just make-up a bounding metrics
884   mBoundingMetrics = nsBoundingMetrics();
885   mBoundingMetrics.ascent = aDesiredSize.BlockStartAscent();
886   mBoundingMetrics.descent =
887       aDesiredSize.Height() - aDesiredSize.BlockStartAscent();
888   mBoundingMetrics.width = aDesiredSize.Width();
889   mBoundingMetrics.leftBearing = 0;
890   mBoundingMetrics.rightBearing = aDesiredSize.Width();
891 
892   aDesiredSize.mBoundingMetrics = mBoundingMetrics;
893   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
894 }
895 
NS_NewMathMLmtableFrame(PresShell * aPresShell,ComputedStyle * aStyle)896 nsContainerFrame* NS_NewMathMLmtableFrame(PresShell* aPresShell,
897                                           ComputedStyle* aStyle) {
898   return new (aPresShell)
899       nsMathMLmtableFrame(aStyle, aPresShell->GetPresContext());
900 }
901 
902 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtableFrame)
903 
904 nsMathMLmtableFrame::~nsMathMLmtableFrame() = default;
905 
SetInitialChildList(ChildListID aListID,nsFrameList & aChildList)906 void nsMathMLmtableFrame::SetInitialChildList(ChildListID aListID,
907                                               nsFrameList& aChildList) {
908   nsTableFrame::SetInitialChildList(aListID, aChildList);
909   MapAllAttributesIntoCSS(this);
910 }
911 
RestyleTable()912 void nsMathMLmtableFrame::RestyleTable() {
913   // re-sync MathML specific style data that may have changed
914   MapAllAttributesIntoCSS(this);
915 
916   // Explicitly request a re-resolve and reflow in our subtree to pick up any
917   // changes
918   PresContext()->RestyleManager()->PostRestyleEvent(
919       mContent->AsElement(), RestyleHint::RestyleSubtree(),
920       nsChangeHint_AllReflowHints);
921 }
922 
GetColSpacing(int32_t aColIndex)923 nscoord nsMathMLmtableFrame::GetColSpacing(int32_t aColIndex) {
924   if (mUseCSSSpacing) {
925     return nsTableFrame::GetColSpacing(aColIndex);
926   }
927   if (!mColSpacing.Length()) {
928     NS_ERROR("mColSpacing should not be empty");
929     return 0;
930   }
931   if (aColIndex < 0 || aColIndex >= GetColCount()) {
932     NS_ASSERTION(aColIndex == -1 || aColIndex == GetColCount(),
933                  "Desired column beyond bounds of table and border");
934     return mFrameSpacingX;
935   }
936   if ((uint32_t)aColIndex >= mColSpacing.Length()) {
937     return mColSpacing.LastElement();
938   }
939   return mColSpacing.ElementAt(aColIndex);
940 }
941 
GetColSpacing(int32_t aStartColIndex,int32_t aEndColIndex)942 nscoord nsMathMLmtableFrame::GetColSpacing(int32_t aStartColIndex,
943                                            int32_t aEndColIndex) {
944   if (mUseCSSSpacing) {
945     return nsTableFrame::GetColSpacing(aStartColIndex, aEndColIndex);
946   }
947   if (aStartColIndex == aEndColIndex) {
948     return 0;
949   }
950   if (!mColSpacing.Length()) {
951     NS_ERROR("mColSpacing should not be empty");
952     return 0;
953   }
954   nscoord space = 0;
955   if (aStartColIndex < 0) {
956     NS_ASSERTION(aStartColIndex == -1,
957                  "Desired column beyond bounds of table and border");
958     space += mFrameSpacingX;
959     aStartColIndex = 0;
960   }
961   if (aEndColIndex >= GetColCount()) {
962     NS_ASSERTION(aEndColIndex == GetColCount(),
963                  "Desired column beyond bounds of table and border");
964     space += mFrameSpacingX;
965     aEndColIndex = GetColCount();
966   }
967   // Only iterate over column spacing when there is the potential to vary
968   int32_t min = std::min(aEndColIndex, (int32_t)mColSpacing.Length());
969   for (int32_t i = aStartColIndex; i < min; i++) {
970     space += mColSpacing.ElementAt(i);
971   }
972   // The remaining values are constant.  Note that if there are more
973   // column spacings specified than there are columns, LastElement() will be
974   // multiplied by 0, so it is still safe to use.
975   space += (aEndColIndex - min) * mColSpacing.LastElement();
976   return space;
977 }
978 
GetRowSpacing(int32_t aRowIndex)979 nscoord nsMathMLmtableFrame::GetRowSpacing(int32_t aRowIndex) {
980   if (mUseCSSSpacing) {
981     return nsTableFrame::GetRowSpacing(aRowIndex);
982   }
983   if (!mRowSpacing.Length()) {
984     NS_ERROR("mRowSpacing should not be empty");
985     return 0;
986   }
987   if (aRowIndex < 0 || aRowIndex >= GetRowCount()) {
988     NS_ASSERTION(aRowIndex == -1 || aRowIndex == GetRowCount(),
989                  "Desired row beyond bounds of table and border");
990     return mFrameSpacingY;
991   }
992   if ((uint32_t)aRowIndex >= mRowSpacing.Length()) {
993     return mRowSpacing.LastElement();
994   }
995   return mRowSpacing.ElementAt(aRowIndex);
996 }
997 
GetRowSpacing(int32_t aStartRowIndex,int32_t aEndRowIndex)998 nscoord nsMathMLmtableFrame::GetRowSpacing(int32_t aStartRowIndex,
999                                            int32_t aEndRowIndex) {
1000   if (mUseCSSSpacing) {
1001     return nsTableFrame::GetRowSpacing(aStartRowIndex, aEndRowIndex);
1002   }
1003   if (aStartRowIndex == aEndRowIndex) {
1004     return 0;
1005   }
1006   if (!mRowSpacing.Length()) {
1007     NS_ERROR("mRowSpacing should not be empty");
1008     return 0;
1009   }
1010   nscoord space = 0;
1011   if (aStartRowIndex < 0) {
1012     NS_ASSERTION(aStartRowIndex == -1,
1013                  "Desired row beyond bounds of table and border");
1014     space += mFrameSpacingY;
1015     aStartRowIndex = 0;
1016   }
1017   if (aEndRowIndex >= GetRowCount()) {
1018     NS_ASSERTION(aEndRowIndex == GetRowCount(),
1019                  "Desired row beyond bounds of table and border");
1020     space += mFrameSpacingY;
1021     aEndRowIndex = GetRowCount();
1022   }
1023   // Only iterate over row spacing when there is the potential to vary
1024   int32_t min = std::min(aEndRowIndex, (int32_t)mRowSpacing.Length());
1025   for (int32_t i = aStartRowIndex; i < min; i++) {
1026     space += mRowSpacing.ElementAt(i);
1027   }
1028   // The remaining values are constant.  Note that if there are more
1029   // row spacings specified than there are row, LastElement() will be
1030   // multiplied by 0, so it is still safe to use.
1031   space += (aEndRowIndex - min) * mRowSpacing.LastElement();
1032   return space;
1033 }
1034 
SetUseCSSSpacing()1035 void nsMathMLmtableFrame::SetUseCSSSpacing() {
1036   mUseCSSSpacing = !(mContent->AsElement()->HasAttr(kNameSpaceID_None,
1037                                                     nsGkAtoms::rowspacing_) ||
1038                      mContent->AsElement()->HasAttr(
1039                          kNameSpaceID_None, nsGkAtoms::columnspacing_) ||
1040                      mContent->AsElement()->HasAttr(kNameSpaceID_None,
1041                                                     nsGkAtoms::framespacing_));
1042 }
1043 
1044 NS_QUERYFRAME_HEAD(nsMathMLmtableFrame)
NS_QUERYFRAME_ENTRY(nsMathMLmtableFrame)1045   NS_QUERYFRAME_ENTRY(nsMathMLmtableFrame)
1046 NS_QUERYFRAME_TAIL_INHERITING(nsTableFrame)
1047 
1048 // --------
1049 // implementation of nsMathMLmtrFrame
1050 
1051 nsContainerFrame* NS_NewMathMLmtrFrame(PresShell* aPresShell,
1052                                        ComputedStyle* aStyle) {
1053   return new (aPresShell)
1054       nsMathMLmtrFrame(aStyle, aPresShell->GetPresContext());
1055 }
1056 
1057 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtrFrame)
1058 
1059 nsMathMLmtrFrame::~nsMathMLmtrFrame() = default;
1060 
AttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)1061 nsresult nsMathMLmtrFrame::AttributeChanged(int32_t aNameSpaceID,
1062                                             nsAtom* aAttribute,
1063                                             int32_t aModType) {
1064   // Attributes specific to <mtr>:
1065   // groupalign  : Not yet supported.
1066   // rowalign    : Here
1067   // columnalign : Here
1068 
1069   nsPresContext* presContext = PresContext();
1070 
1071   if (aAttribute != nsGkAtoms::rowalign_ &&
1072       aAttribute != nsGkAtoms::columnalign_) {
1073     return NS_OK;
1074   }
1075 
1076   RemoveProperty(AttributeToProperty(aAttribute));
1077 
1078   bool allowMultiValues = (aAttribute == nsGkAtoms::columnalign_);
1079 
1080   // Reparse the new attribute.
1081   ParseFrameAttribute(this, aAttribute, allowMultiValues);
1082 
1083   // Explicitly request a reflow in our subtree to pick up any changes
1084   presContext->PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
1085                                              NS_FRAME_IS_DIRTY);
1086 
1087   return NS_OK;
1088 }
1089 
1090 // --------
1091 // implementation of nsMathMLmtdFrame
1092 
NS_NewMathMLmtdFrame(PresShell * aPresShell,ComputedStyle * aStyle,nsTableFrame * aTableFrame)1093 nsContainerFrame* NS_NewMathMLmtdFrame(PresShell* aPresShell,
1094                                        ComputedStyle* aStyle,
1095                                        nsTableFrame* aTableFrame) {
1096   return new (aPresShell) nsMathMLmtdFrame(aStyle, aTableFrame);
1097 }
1098 
1099 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtdFrame)
1100 
1101 nsMathMLmtdFrame::~nsMathMLmtdFrame() = default;
1102 
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)1103 void nsMathMLmtdFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
1104                             nsIFrame* aPrevInFlow) {
1105   nsTableCellFrame::Init(aContent, aParent, aPrevInFlow);
1106 
1107   // We want to use the ancestor <math> element's font inflation to avoid
1108   // individual cells having their own varying font inflation.
1109   RemoveStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
1110 }
1111 
AttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)1112 nsresult nsMathMLmtdFrame::AttributeChanged(int32_t aNameSpaceID,
1113                                             nsAtom* aAttribute,
1114                                             int32_t aModType) {
1115   // Attributes specific to <mtd>:
1116   // groupalign  : Not yet supported
1117   // rowalign    : here
1118   // columnalign : here
1119   // rowspan     : here
1120   // columnspan  : here
1121 
1122   if (aAttribute == nsGkAtoms::rowalign_ ||
1123       aAttribute == nsGkAtoms::columnalign_) {
1124     RemoveProperty(AttributeToProperty(aAttribute));
1125 
1126     // Reparse the attribute.
1127     ParseFrameAttribute(this, aAttribute, false);
1128     return NS_OK;
1129   }
1130 
1131   if (aAttribute == nsGkAtoms::rowspan ||
1132       aAttribute == nsGkAtoms::columnspan_) {
1133     // use the naming expected by the base class
1134     if (aAttribute == nsGkAtoms::columnspan_) aAttribute = nsGkAtoms::colspan;
1135     return nsTableCellFrame::AttributeChanged(aNameSpaceID, aAttribute,
1136                                               aModType);
1137   }
1138 
1139   return NS_OK;
1140 }
1141 
GetVerticalAlign() const1142 StyleVerticalAlignKeyword nsMathMLmtdFrame::GetVerticalAlign() const {
1143   // Set the default alignment in case no alignment was specified
1144   auto alignment = nsTableCellFrame::GetVerticalAlign();
1145 
1146   nsTArray<int8_t>* alignmentList = FindCellProperty(this, RowAlignProperty());
1147 
1148   if (alignmentList) {
1149     uint32_t rowIndex = RowIndex();
1150 
1151     // If the row number is greater than the number of provided rowalign values,
1152     // we simply repeat the last value.
1153     return static_cast<StyleVerticalAlignKeyword>(
1154         (rowIndex < alignmentList->Length())
1155             ? alignmentList->ElementAt(rowIndex)
1156             : alignmentList->LastElement());
1157   }
1158 
1159   return alignment;
1160 }
1161 
ProcessBorders(nsTableFrame * aFrame,nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)1162 nsresult nsMathMLmtdFrame::ProcessBorders(nsTableFrame* aFrame,
1163                                           nsDisplayListBuilder* aBuilder,
1164                                           const nsDisplayListSet& aLists) {
1165   aLists.BorderBackground()->AppendNewToTop<nsDisplaymtdBorder>(aBuilder, this);
1166   return NS_OK;
1167 }
1168 
GetBorderWidth(WritingMode aWM) const1169 LogicalMargin nsMathMLmtdFrame::GetBorderWidth(WritingMode aWM) const {
1170   nsStyleBorder styleBorder = *StyleBorder();
1171   ApplyBorderToStyle(this, styleBorder);
1172   return LogicalMargin(aWM, styleBorder.GetComputedBorder());
1173 }
1174 
GetBorderOverflow()1175 nsMargin nsMathMLmtdFrame::GetBorderOverflow() {
1176   nsStyleBorder styleBorder = *StyleBorder();
1177   ApplyBorderToStyle(this, styleBorder);
1178   nsMargin overflow = ComputeBorderOverflow(this, styleBorder);
1179   return overflow;
1180 }
1181 
1182 // --------
1183 // implementation of nsMathMLmtdInnerFrame
1184 
1185 NS_QUERYFRAME_HEAD(nsMathMLmtdInnerFrame)
NS_QUERYFRAME_ENTRY(nsIMathMLFrame)1186   NS_QUERYFRAME_ENTRY(nsIMathMLFrame)
1187 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
1188 
1189 nsContainerFrame* NS_NewMathMLmtdInnerFrame(PresShell* aPresShell,
1190                                             ComputedStyle* aStyle) {
1191   return new (aPresShell)
1192       nsMathMLmtdInnerFrame(aStyle, aPresShell->GetPresContext());
1193 }
1194 
NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtdInnerFrame)1195 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtdInnerFrame)
1196 
1197 nsMathMLmtdInnerFrame::nsMathMLmtdInnerFrame(ComputedStyle* aStyle,
1198                                              nsPresContext* aPresContext)
1199     : nsBlockFrame(aStyle, aPresContext, kClassID)
1200       // Make a copy of the parent nsStyleText for later modification.
1201       ,
1202       mUniqueStyleText(MakeUnique<nsStyleText>(*StyleText())) {}
1203 
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)1204 void nsMathMLmtdInnerFrame::Reflow(nsPresContext* aPresContext,
1205                                    ReflowOutput& aDesiredSize,
1206                                    const ReflowInput& aReflowInput,
1207                                    nsReflowStatus& aStatus) {
1208   // Let the base class do the reflow
1209   nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
1210 
1211   // more about <maligngroup/> and <malignmark/> later
1212   // ...
1213 }
1214 
StyleTextForLineLayout()1215 const nsStyleText* nsMathMLmtdInnerFrame::StyleTextForLineLayout() {
1216   // Set the default alignment in case nothing was specified
1217   auto alignment = uint8_t(StyleText()->mTextAlign);
1218 
1219   nsTArray<int8_t>* alignmentList =
1220       FindCellProperty(this, ColumnAlignProperty());
1221 
1222   if (alignmentList) {
1223     nsMathMLmtdFrame* cellFrame = (nsMathMLmtdFrame*)GetParent();
1224     uint32_t columnIndex = cellFrame->ColIndex();
1225 
1226     // If the column number is greater than the number of provided columalign
1227     // values, we simply repeat the last value.
1228     if (columnIndex < alignmentList->Length())
1229       alignment = alignmentList->ElementAt(columnIndex);
1230     else
1231       alignment = alignmentList->ElementAt(alignmentList->Length() - 1);
1232   }
1233 
1234   mUniqueStyleText->mTextAlign = StyleTextAlign(alignment);
1235   return mUniqueStyleText.get();
1236 }
1237 
1238 /* virtual */
DidSetComputedStyle(ComputedStyle * aOldComputedStyle)1239 void nsMathMLmtdInnerFrame::DidSetComputedStyle(
1240     ComputedStyle* aOldComputedStyle) {
1241   nsBlockFrame::DidSetComputedStyle(aOldComputedStyle);
1242   mUniqueStyleText = MakeUnique<nsStyleText>(*StyleText());
1243 }
1244