/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsMathMLContainerFrame.h"
#include "gfxContext.h"
#include "gfxUtils.h"
#include "mozilla/Likely.h"
#include "mozilla/PresShell.h"
#include "mozilla/dom/MutationEventBinding.h"
#include "mozilla/gfx/2D.h"
#include "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsNameSpaceManager.h"
#include "nsGkAtoms.h"
#include "nsDisplayList.h"
#include "nsIScriptError.h"
#include "nsContentUtils.h"
#include "mozilla/dom/MathMLElement.h"
using namespace mozilla;
using namespace mozilla::gfx;
//
// nsMathMLContainerFrame implementation
//
NS_QUERYFRAME_HEAD(nsMathMLContainerFrame)
  NS_QUERYFRAME_ENTRY(nsIMathMLFrame)
  NS_QUERYFRAME_ENTRY(nsMathMLContainerFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
/* /////////////
 * nsIMathMLFrame - support methods for stretchy elements
 * =============================================================================
 */
static bool IsForeignChild(const nsIFrame* aFrame) {
  // This counts nsMathMLmathBlockFrame as a foreign child, because it
  // uses block reflow
  return !aFrame->IsMathMLFrame() || aFrame->IsBlockFrame();
}
NS_DECLARE_FRAME_PROPERTY_DELETABLE(HTMLReflowOutputProperty, ReflowOutput)
/* static */
void nsMathMLContainerFrame::SaveReflowAndBoundingMetricsFor(
    nsIFrame* aFrame, const ReflowOutput& aReflowOutput,
    const nsBoundingMetrics& aBoundingMetrics) {
  ReflowOutput* reflowOutput = new ReflowOutput(aReflowOutput);
  reflowOutput->mBoundingMetrics = aBoundingMetrics;
  aFrame->SetProperty(HTMLReflowOutputProperty(), reflowOutput);
}
// helper method to facilitate getting the reflow and bounding metrics
/* static */
void nsMathMLContainerFrame::GetReflowAndBoundingMetricsFor(
    nsIFrame* aFrame, ReflowOutput& aReflowOutput,
    nsBoundingMetrics& aBoundingMetrics, eMathMLFrameType* aMathMLFrameType) {
  MOZ_ASSERT(aFrame, "null arg");
  ReflowOutput* reflowOutput = aFrame->GetProperty(HTMLReflowOutputProperty());
  // IMPORTANT: This function is only meant to be called in Place() methods
  // where it is assumed that SaveReflowAndBoundingMetricsFor has recorded the
  // information.
  NS_ASSERTION(reflowOutput, "Didn't SaveReflowAndBoundingMetricsFor frame!");
  if (reflowOutput) {
    aReflowOutput = *reflowOutput;
    aBoundingMetrics = reflowOutput->mBoundingMetrics;
  }
  if (aMathMLFrameType) {
    if (!IsForeignChild(aFrame)) {
      nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame);
      if (mathMLFrame) {
        *aMathMLFrameType = mathMLFrame->GetMathMLFrameType();
        return;
      }
    }
    *aMathMLFrameType = eMathMLFrameType_UNKNOWN;
  }
}
void nsMathMLContainerFrame::ClearSavedChildMetrics() {
  nsIFrame* childFrame = mFrames.FirstChild();
  while (childFrame) {
    childFrame->RemoveProperty(HTMLReflowOutputProperty());
    childFrame = childFrame->GetNextSibling();
  }
}
nsMargin nsMathMLContainerFrame::GetBorderPaddingForPlace(
    const PlaceFlags& aFlags) {
  if (aFlags.contains(PlaceFlag::IgnoreBorderPadding)) {
    return nsMargin();
  }
  if (aFlags.contains(PlaceFlag::IntrinsicSize)) {
    // Bug 1910859: Should we provide separate left and right border/padding?
    return nsMargin(0, IntrinsicISizeOffsets().BorderPadding(), 0, 0);
  }
  return GetUsedBorderAndPadding();
}
/* static */
nsMargin nsMathMLContainerFrame::GetMarginForPlace(const PlaceFlags& aFlags,
                                                   nsIFrame* aChild) {
  if (aFlags.contains(PlaceFlag::IntrinsicSize)) {
    // Bug 1910859: Should we provide separate left and right margin?
    return nsMargin(0, aChild->IntrinsicISizeOffsets().margin, 0, 0);
  }
  return aChild->GetUsedMargin();
}
void nsMathMLContainerFrame::InflateReflowAndBoundingMetrics(
    const nsMargin& aBorderPadding, ReflowOutput& aReflowOutput,
    nsBoundingMetrics& aBoundingMetrics) {
  // Bug 1910858: It is not really clear what is the right way to update the
  // ink bounding box when adding border or padding. Below, we assume that
  // border/padding inflate it.
  aBoundingMetrics.rightBearing += aBorderPadding.LeftRight();
  aBoundingMetrics.width += aBorderPadding.LeftRight();
  aReflowOutput.mBoundingMetrics = aBoundingMetrics;
  aReflowOutput.Width() += aBorderPadding.LeftRight();
  aReflowOutput.SetBlockStartAscent(aReflowOutput.BlockStartAscent() +
                                    aBorderPadding.top);
  aReflowOutput.Height() += aBorderPadding.TopBottom();
}
nsMathMLContainerFrame::WidthAndHeightForPlaceAdjustment
nsMathMLContainerFrame::GetWidthAndHeightForPlaceAdjustment(
    const PlaceFlags& aFlags) {
  WidthAndHeightForPlaceAdjustment sizes;
  if (aFlags.contains(PlaceFlag::DoNotAdjustForWidthAndHeight)) {
    return sizes;
  }
  const nsStylePosition* stylePos = StylePosition();
  const auto& width = stylePos->mWidth;
  // TODO: Resolve percentages.
  // https://github.com/w3c/mathml-core/issues/76
  if (width.ConvertsToLength()) {
    sizes.width = Some(width.ToLength());
  }
  if (!aFlags.contains(PlaceFlag::IntrinsicSize)) {
    // TODO: Resolve percentages.
    // https://github.com/w3c/mathml-core/issues/77
    const auto& height = stylePos->mHeight;
    if (height.ConvertsToLength()) {
      sizes.height = Some(height.ToLength());
    }
  }
  return sizes;
}
nscoord nsMathMLContainerFrame::ApplyAdjustmentForWidthAndHeight(
    const PlaceFlags& aFlags, const WidthAndHeightForPlaceAdjustment& aSizes,
    ReflowOutput& aReflowOutput, nsBoundingMetrics& aBoundingMetrics) {
  nscoord shiftX = 0;
  if (aSizes.width) {
    MOZ_ASSERT(!aFlags.contains(PlaceFlag::DoNotAdjustForWidthAndHeight));
    auto width = *aSizes.width;
    auto oldWidth = aReflowOutput.Width();
    if (IsMathContentBoxHorizontallyCentered()) {
      shiftX = (width - oldWidth) / 2;
    } else if (StyleVisibility()->mDirection == StyleDirection::Rtl) {
      shiftX = width - oldWidth;
    }
    aBoundingMetrics.leftBearing = 0;
    aBoundingMetrics.rightBearing = width;
    aBoundingMetrics.width = width;
    aReflowOutput.mBoundingMetrics = aBoundingMetrics;
    aReflowOutput.Width() = width;
  }
  if (aSizes.height) {
    MOZ_ASSERT(!aFlags.contains(PlaceFlag::DoNotAdjustForWidthAndHeight));
    MOZ_ASSERT(!aFlags.contains(PlaceFlag::IntrinsicSize));
    auto height = *aSizes.height;
    aReflowOutput.Height() = height;
  }
  return shiftX;
}
// helper to get the preferred size that a container frame should use to fire
// the stretch on its stretchy child frames.
void nsMathMLContainerFrame::GetPreferredStretchSize(
    DrawTarget* aDrawTarget, uint32_t aOptions,
    nsStretchDirection aStretchDirection,
    nsBoundingMetrics& aPreferredStretchSize) {
  if (aOptions & STRETCH_CONSIDER_ACTUAL_SIZE) {
    // when our actual size is ok, just use it
    aPreferredStretchSize = mBoundingMetrics;
  } else if (aOptions & STRETCH_CONSIDER_EMBELLISHMENTS) {
    // compute our up-to-date size using Place(), without border/padding.
    ReflowOutput reflowOutput(GetWritingMode());
    PlaceFlags flags(PlaceFlag::MeasureOnly, PlaceFlag::IgnoreBorderPadding);
    Place(aDrawTarget, flags, reflowOutput);
    aPreferredStretchSize = reflowOutput.mBoundingMetrics;
  } else {
    // compute a size that includes embellishments iff the container stretches
    // in the same direction as the embellished operator.
    bool stretchAll = aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL
                          ? NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(
                                mPresentationData.flags)
                          : NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(
                                mPresentationData.flags);
    NS_ASSERTION(aStretchDirection == NS_STRETCH_DIRECTION_HORIZONTAL ||
                     aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL,
                 "You must specify a direction in which to stretch");
    NS_ASSERTION(
        NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags) || stretchAll,
        "invalid call to GetPreferredStretchSize");
    bool firstTime = true;
    nsBoundingMetrics bm, bmChild;
    nsIFrame* childFrame = stretchAll ? PrincipalChildList().FirstChild()
                                      : mPresentationData.baseFrame;
    while (childFrame) {
      // initializations in case this child happens not to be a MathML frame
      nsIMathMLFrame* mathMLFrame = do_QueryFrame(childFrame);
      if (mathMLFrame) {
        nsEmbellishData embellishData;
        nsPresentationData presentationData;
        mathMLFrame->GetEmbellishData(embellishData);
        mathMLFrame->GetPresentationData(presentationData);
        if (NS_MATHML_IS_EMBELLISH_OPERATOR(embellishData.flags) &&
            embellishData.direction == aStretchDirection &&
            presentationData.baseFrame) {
          // embellishements are not included, only consider the inner first
          // child itself
          // XXXkt Does that mean the core descendent frame should be used
          // instead of the base child?
          nsIMathMLFrame* mathMLchildFrame =
              do_QueryFrame(presentationData.baseFrame);
          if (mathMLchildFrame) {
            mathMLFrame = mathMLchildFrame;
          }
        }
        mathMLFrame->GetBoundingMetrics(bmChild);
      } else {
        ReflowOutput unused(GetWritingMode());
        GetReflowAndBoundingMetricsFor(childFrame, unused, bmChild);
      }
      if (firstTime) {
        firstTime = false;
        bm = bmChild;
        if (!stretchAll) {
          // we may get here for cases such as ... ... ,
          // or .......
          break;
        }
      } else {
        if (aStretchDirection == NS_STRETCH_DIRECTION_HORIZONTAL) {
          // if we get here, it means this is container that will stack its
          // children vertically and fire an horizontal stretch on each them.
          // This is the case for \munder, \mover, \munderover. We just sum-up
          // the size vertically.
          bm.descent += bmChild.ascent + bmChild.descent;
          // Sometimes non-spacing marks (when width is zero) are positioned
          // to the left of the origin, but it is the distance between left
          // and right bearing that is important rather than the offsets from
          // the origin.
          if (bmChild.width == 0) {
            bmChild.rightBearing -= bmChild.leftBearing;
            bmChild.leftBearing = 0;
          }
          if (bm.leftBearing > bmChild.leftBearing) {
            bm.leftBearing = bmChild.leftBearing;
          }
          if (bm.rightBearing < bmChild.rightBearing) {
            bm.rightBearing = bmChild.rightBearing;
          }
        } else if (aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL) {
          // just sum-up the sizes horizontally.
          bm += bmChild;
        } else {
          NS_ERROR("unexpected case in GetPreferredStretchSize");
          break;
        }
      }
      childFrame = childFrame->GetNextSibling();
    }
    aPreferredStretchSize = bm;
  }
}
NS_IMETHODIMP
nsMathMLContainerFrame::Stretch(DrawTarget* aDrawTarget,
                                nsStretchDirection aStretchDirection,
                                nsBoundingMetrics& aContainerSize,
                                ReflowOutput& aDesiredStretchSize) {
  if (NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags)) {
    if (NS_MATHML_STRETCH_WAS_DONE(mPresentationData.flags)) {
      NS_WARNING("it is wrong to fire stretch more than once on a frame");
      return NS_OK;
    }
    mPresentationData.flags |= NS_MATHML_STRETCH_DONE;
    // Pass the stretch to the base child ...
    nsIFrame* baseFrame = mPresentationData.baseFrame;
    if (baseFrame) {
      nsIMathMLFrame* mathMLFrame = do_QueryFrame(baseFrame);
      NS_ASSERTION(mathMLFrame, "Something is wrong somewhere");
      if (mathMLFrame) {
        // And the trick is that the child's rect.x is still holding the
        // descent, and rect.y is still holding the ascent ...
        ReflowOutput childSize(aDesiredStretchSize);
        GetReflowAndBoundingMetricsFor(baseFrame, childSize,
                                       childSize.mBoundingMetrics);
        // See if we should downsize and confine the stretch to us...
        // XXX there may be other cases where we can downsize the stretch,
        // e.g., the first ∑ might appear big in the following situation
        // 
        nsBoundingMetrics containerSize = aContainerSize;
        if (aStretchDirection != mEmbellishData.direction &&
            mEmbellishData.direction != NS_STRETCH_DIRECTION_UNSUPPORTED) {
          NS_ASSERTION(
              mEmbellishData.direction != NS_STRETCH_DIRECTION_DEFAULT,
              "Stretches may have a default direction, operators can not.");
          if (mEmbellishData.direction == NS_STRETCH_DIRECTION_VERTICAL
                  ? NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(
                        mPresentationData.flags)
                  : NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(
                        mPresentationData.flags)) {
            GetPreferredStretchSize(aDrawTarget, 0, mEmbellishData.direction,
                                    containerSize);
            // Stop further recalculations
            aStretchDirection = mEmbellishData.direction;
          } else {
            // We aren't going to stretch the child, so just use the child
            // metrics.
            containerSize = childSize.mBoundingMetrics;
          }
        }
        // do the stretching...
        mathMLFrame->Stretch(aDrawTarget, aStretchDirection, containerSize,
                             childSize);
        // store the updated metrics
        SaveReflowAndBoundingMetricsFor(baseFrame, childSize,
                                        childSize.mBoundingMetrics);
        // Remember the siblings which were _deferred_.
        // Now that this embellished child may have changed, we need to
        // fire the stretch on its siblings using our updated size
        if (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(
                mPresentationData.flags) ||
            NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(
                mPresentationData.flags)) {
          nsStretchDirection stretchDir =
              NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(
                  mPresentationData.flags)
                  ? NS_STRETCH_DIRECTION_VERTICAL
                  : NS_STRETCH_DIRECTION_HORIZONTAL;
          GetPreferredStretchSize(aDrawTarget, STRETCH_CONSIDER_EMBELLISHMENTS,
                                  stretchDir, containerSize);
          nsIFrame* childFrame = mFrames.FirstChild();
          while (childFrame) {
            if (childFrame != mPresentationData.baseFrame) {
              mathMLFrame = do_QueryFrame(childFrame);
              if (mathMLFrame) {
                // retrieve the metrics that was stored at the previous pass
                GetReflowAndBoundingMetricsFor(childFrame, childSize,
                                               childSize.mBoundingMetrics);
                // do the stretching...
                mathMLFrame->Stretch(aDrawTarget, stretchDir, containerSize,
                                     childSize);
                // store the updated metrics
                SaveReflowAndBoundingMetricsFor(childFrame, childSize,
                                                childSize.mBoundingMetrics);
              }
            }
            childFrame = childFrame->GetNextSibling();
          }
        }
        // re-position all our children
        PlaceFlags flags;
        nsresult rv = Place(aDrawTarget, flags, aDesiredStretchSize);
        if (NS_FAILED(rv)) {
          // Make sure the child frames get their DidReflow() calls.
          DidReflowChildren(mFrames.FirstChild());
        }
        // If our parent is not embellished, it means we are the outermost
        // embellished container and so we put the spacing, otherwise we don't
        // include the spacing, the outermost embellished container will take
        // care of it.
        nsEmbellishData parentData;
        GetEmbellishDataFrom(GetParent(), parentData);
        // ensure that we are the embellished child, not just a sibling
        // (need to test coreFrame since  resets other things)
        if (parentData.coreFrame != mEmbellishData.coreFrame) {
          // (we fetch values from the core since they may use units that depend
          // on style data, and style changes could have occurred in the core
          // since our last visit there)
          nsEmbellishData coreData;
          GetEmbellishDataFrom(mEmbellishData.coreFrame, coreData);
          mBoundingMetrics.width +=
              coreData.leadingSpace + coreData.trailingSpace;
          aDesiredStretchSize.Width() = mBoundingMetrics.width;
          aDesiredStretchSize.mBoundingMetrics.width = mBoundingMetrics.width;
          nscoord dx = StyleVisibility()->mDirection == StyleDirection::Rtl
                           ? coreData.trailingSpace
                           : coreData.leadingSpace;
          if (dx != 0) {
            mBoundingMetrics.leftBearing += dx;
            mBoundingMetrics.rightBearing += dx;
            aDesiredStretchSize.mBoundingMetrics.leftBearing += dx;
            aDesiredStretchSize.mBoundingMetrics.rightBearing += dx;
            nsIFrame* childFrame = mFrames.FirstChild();
            while (childFrame) {
              childFrame->SetPosition(childFrame->GetPosition() +
                                      nsPoint(dx, 0));
              childFrame = childFrame->GetNextSibling();
            }
          }
        }
        // Finished with these:
        ClearSavedChildMetrics();
        // Set our overflow area
        GatherAndStoreOverflow(&aDesiredStretchSize);
      }
    }
  }
  return NS_OK;
}
nsresult nsMathMLContainerFrame::FinalizeReflow(DrawTarget* aDrawTarget,
                                                ReflowOutput& aDesiredSize) {
  // During reflow, we use rect.x and rect.y as placeholders for the child's
  // ascent and descent in expectation of a stretch command. Hence we need to
  // ensure that a stretch command will actually be fired later on, after
  // exiting from our reflow. If the stretch is not fired, the rect.x, and
  // rect.y will remain with inappropriate data causing children to be
  // improperly positioned. This helper method checks to see if our parent will
  // fire a stretch command targeted at us. If not, we go ahead and fire an
  // involutive stretch on ourselves. This will clear all the rect.x and rect.y,
  // and return our desired size.
  // First, complete the post-reflow hook.
  // We use the information in our children rectangles to position them.
  // If placeOrigin==false, then Place() will not touch rect.x, and rect.y.
  // They will still be holding the ascent and descent for each child.
  // The first clause caters for any non-embellished container.
  // The second clause is for a container which won't fire stretch even though
  // it is embellished, e.g., as in ... ... , the test
  // is convoluted because it excludes the particular case of the core
  // ... itself.
  // ( needs to fire stretch on its MathMLChar in any case to initialize it)
  bool placeOrigin =
      !NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags) ||
      (mEmbellishData.coreFrame != this && !mPresentationData.baseFrame &&
       mEmbellishData.direction == NS_STRETCH_DIRECTION_UNSUPPORTED);
  PlaceFlags flags;
  if (!placeOrigin) {
    flags += PlaceFlag::MeasureOnly;
  }
  nsresult rv = Place(aDrawTarget, flags, aDesiredSize);
  // Place() will call FinishReflowChild() when placeOrigin is true but if
  // it returns before reaching FinishReflowChild() due to errors we need
  // to fulfill the reflow protocol by calling DidReflow for the child frames
  // that still needs it here (or we may crash - bug 366012).
  // If placeOrigin is false we should reach Place() with
  // PlaceFlag::MeasureOnly unset through Stretch() eventually.
  if (NS_FAILED(rv)) {
    GatherAndStoreOverflow(&aDesiredSize);
    DidReflowChildren(PrincipalChildList().FirstChild());
    return rv;
  }
  bool parentWillFireStretch = false;
  if (!placeOrigin) {
    // This means the rect.x and rect.y of our children were not set!!
    // Don't go without checking to see if our parent will later fire a
    // Stretch() command targeted at us. The Stretch() will cause the rect.x and
    // rect.y to clear...
    nsIMathMLFrame* mathMLFrame = do_QueryFrame(GetParent());
    if (mathMLFrame) {
      nsEmbellishData embellishData;
      nsPresentationData presentationData;
      mathMLFrame->GetEmbellishData(embellishData);
      mathMLFrame->GetPresentationData(presentationData);
      if (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(
              presentationData.flags) ||
          NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(
              presentationData.flags) ||
          (NS_MATHML_IS_EMBELLISH_OPERATOR(embellishData.flags) &&
           presentationData.baseFrame == this)) {
        parentWillFireStretch = true;
      }
    }
    if (!parentWillFireStretch) {
      // There is nobody who will fire the stretch for us, we do it ourselves!
      bool stretchAll =
          /* NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags)
             || */
          NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(
              mPresentationData.flags);
      nsStretchDirection stretchDir;
      if (mEmbellishData.coreFrame ==
              this || /* case of a bare ... itself */
          (mEmbellishData.direction == NS_STRETCH_DIRECTION_HORIZONTAL &&
           stretchAll) || /* or ......, or friends */
          mEmbellishData.direction ==
              NS_STRETCH_DIRECTION_UNSUPPORTED) { /* Doesn't stretch */
        stretchDir = mEmbellishData.direction;
      } else {
        // Let the Stretch() call decide the direction.
        stretchDir = NS_STRETCH_DIRECTION_DEFAULT;
      }
      // Use our current size as computed earlier by Place()
      // The stretch call will detect if this is incorrect and recalculate the
      // size.
      nsBoundingMetrics defaultSize = aDesiredSize.mBoundingMetrics;
      Stretch(aDrawTarget, stretchDir, defaultSize, aDesiredSize);
#ifdef DEBUG
      {
        // The Place() call above didn't request FinishReflowChild(),
        // so let's check that we eventually did through Stretch().
        for (nsIFrame* childFrame : PrincipalChildList()) {
          NS_ASSERTION(!childFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW),
                       "DidReflow() was never called");
        }
      }
#endif
    }
  }
  // Also return our bounding metrics
  aDesiredSize.mBoundingMetrics = mBoundingMetrics;
  // see if we should fix the spacing
  FixInterFrameSpacing(aDesiredSize);
  if (!parentWillFireStretch) {
    // Not expecting a stretch.
    // Finished with these:
    ClearSavedChildMetrics();
    // Set our overflow area.
    GatherAndStoreOverflow(&aDesiredSize);
  }
  return NS_OK;
}
/* /////////////
 * nsIMathMLFrame - support methods for scripting elements (nested frames
 * within msub, msup, msubsup, munder, mover, munderover, mmultiscripts,
 * mfrac, mroot, mtable).
 * =============================================================================
 */
// helper to let the update of presentation data pass through
// a subtree that may contain non-mathml container frames
/* static */
void nsMathMLContainerFrame::PropagatePresentationDataFor(
    nsIFrame* aFrame, uint32_t aFlagsValues, uint32_t aFlagsToUpdate) {
  if (!aFrame || !aFlagsToUpdate) {
    return;
  }
  nsIMathMLFrame* mathMLFrame = do_QueryFrame(aFrame);
  if (mathMLFrame) {
    // update
    mathMLFrame->UpdatePresentationData(aFlagsValues, aFlagsToUpdate);
    // propagate using the base method to make sure that the control
    // is passed on to MathML frames that may be overloading the method
    mathMLFrame->UpdatePresentationDataFromChildAt(0, -1, aFlagsValues,
                                                   aFlagsToUpdate);
  } else {
    // propagate down the subtrees
    for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
      PropagatePresentationDataFor(childFrame, aFlagsValues, aFlagsToUpdate);
    }
  }
}
/* static */
void nsMathMLContainerFrame::PropagatePresentationDataFromChildAt(
    nsIFrame* aParentFrame, int32_t aFirstChildIndex, int32_t aLastChildIndex,
    uint32_t aFlagsValues, uint32_t aFlagsToUpdate) {
  if (!aParentFrame || !aFlagsToUpdate) {
    return;
  }
  int32_t index = 0;
  for (nsIFrame* childFrame : aParentFrame->PrincipalChildList()) {
    if ((index >= aFirstChildIndex) &&
        ((aLastChildIndex <= 0) ||
         ((aLastChildIndex > 0) && (index <= aLastChildIndex)))) {
      PropagatePresentationDataFor(childFrame, aFlagsValues, aFlagsToUpdate);
    }
    index++;
  }
}
/* //////////////////
 * Frame construction
 * =============================================================================
 */
void nsMathMLContainerFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                              const nsDisplayListSet& aLists) {
  BuildDisplayListForInline(aBuilder, aLists);
}
// Note that this method re-builds the automatic data in the children -- not
// in aParentFrame itself (except for those particular operations that the
// parent frame may do in its TransmitAutomaticData()).
/* static */
void nsMathMLContainerFrame::RebuildAutomaticDataForChildren(
    nsIFrame* aParentFrame) {
  // 1. As we descend the tree, make each child frame inherit data from
  // the parent
  // 2. As we ascend the tree, transmit any specific change that we want
  // down the subtrees
  for (nsIFrame* childFrame : aParentFrame->PrincipalChildList()) {
    nsIMathMLFrame* childMathMLFrame = do_QueryFrame(childFrame);
    if (childMathMLFrame) {
      childMathMLFrame->InheritAutomaticData(aParentFrame);
    }
    RebuildAutomaticDataForChildren(childFrame);
  }
  nsIMathMLFrame* mathMLFrame = do_QueryFrame(aParentFrame);
  if (mathMLFrame) {
    mathMLFrame->TransmitAutomaticData();
  }
}
/* static */
nsresult nsMathMLContainerFrame::ReLayoutChildren(nsIFrame* aParentFrame) {
  if (!aParentFrame) {
    return NS_OK;
  }
  // walk-up to the first frame that is a MathML frame, stop if we reach