/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "gfxUtils.h"
#include "mozilla/gfx/2D.h"
#include "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsIPresShell.h"
#include "nsStyleContext.h"
#include "nsNameSpaceManager.h"
#include "nsRenderingContext.h"
#include "nsIDOMMutationEvent.h"
#include "nsGkAtoms.h"
#include "nsDisplayList.h"
#include "nsIReflowCallback.h"
#include "mozilla/Likely.h"
#include "nsIScriptError.h"
#include "nsContentUtils.h"
#include "nsMathMLElement.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)

// =============================================================================

// error handlers
// provide a feedback to the user when a frame with bad markup can not be rendered
nsresult
nsMathMLContainerFrame::ReflowError(DrawTarget* aDrawTarget,
                                    ReflowOutput& aDesiredSize)
{
  // clear all other flags and record that there is an error with this frame
  mEmbellishData.flags = 0;
  mPresentationData.flags = NS_MATHML_ERROR;

  ///////////////
  // Set font
  RefPtr<nsFontMetrics> fm =
    nsLayoutUtils::GetInflatedFontMetricsForFrame(this);

  // bounding metrics
  nsAutoString errorMsg; errorMsg.AssignLiteral("invalid-markup");
  mBoundingMetrics =
    nsLayoutUtils::AppUnitBoundsOfString(errorMsg.get(), errorMsg.Length(),
                                         *fm, aDrawTarget);

  // reflow metrics
  WritingMode wm = aDesiredSize.GetWritingMode();
  aDesiredSize.SetBlockStartAscent(fm->MaxAscent());
  nscoord descent = fm->MaxDescent();
  aDesiredSize.BSize(wm) = aDesiredSize.BlockStartAscent() + descent;
  aDesiredSize.ISize(wm) = mBoundingMetrics.width;

  // Also return our bounding metrics
  aDesiredSize.mBoundingMetrics = mBoundingMetrics;

  return NS_OK;
}

class nsDisplayMathMLError : public nsDisplayItem {
public:
  nsDisplayMathMLError(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
    : nsDisplayItem(aBuilder, aFrame) {
    MOZ_COUNT_CTOR(nsDisplayMathMLError);
  }
#ifdef NS_BUILD_REFCNT_LOGGING
  virtual ~nsDisplayMathMLError() {
    MOZ_COUNT_DTOR(nsDisplayMathMLError);
  }
#endif

  virtual void Paint(nsDisplayListBuilder* aBuilder,
                     nsRenderingContext* aCtx) override;
  NS_DISPLAY_DECL_NAME("MathMLError", TYPE_MATHML_ERROR)
};

void nsDisplayMathMLError::Paint(nsDisplayListBuilder* aBuilder,
                                 nsRenderingContext* aCtx)
{
  // Set color and font ...
  RefPtr<nsFontMetrics> fm =
    nsLayoutUtils::GetFontMetricsForFrame(mFrame, 1.0f);

  nsPoint pt = ToReferenceFrame();
  int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
  DrawTarget* drawTarget = aCtx->GetDrawTarget();
  Rect rect = NSRectToSnappedRect(nsRect(pt, mFrame->GetSize()),
                                  appUnitsPerDevPixel,
                                  *drawTarget);
  ColorPattern red(ToDeviceColor(Color(1.f, 0.f, 0.f, 1.f)));
  drawTarget->FillRect(rect, red);

  aCtx->ThebesContext()->SetColor(Color(1.f, 1.f, 1.f));
  nscoord ascent = fm->MaxAscent();
  NS_NAMED_LITERAL_STRING(errorMsg, "invalid-markup");
  nsLayoutUtils::DrawUniDirString(errorMsg.get(), uint32_t(errorMsg.Length()),
                                  nsPoint(pt.x, pt.y + ascent), *fm, *aCtx);
}

/* /////////////
 * 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->IsFrameOfType(nsIFrame::eMathML)) ||
    aFrame->GetType() == nsGkAtoms::blockFrame;
}

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->Properties().Set(HTMLReflowOutputProperty(), reflowOutput);
}

// helper method to facilitate getting the reflow and bounding metrics
/* static */ void
nsMathMLContainerFrame::GetReflowAndBoundingMetricsFor(nsIFrame*            aFrame,
                                                       ReflowOutput& aReflowOutput,
                                                       nsBoundingMetrics&   aBoundingMetrics,
                                                       eMathMLFrameType*    aMathMLFrameType)
{
  NS_PRECONDITION(aFrame, "null arg");

  ReflowOutput* reflowOutput =
    aFrame->Properties().Get(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();
  FramePropertyTable* props = PresContext()->PropertyTable();
  while (childFrame) {
    props->Delete(childFrame, HTMLReflowOutputProperty());
    childFrame = childFrame->GetNextSibling();
  }
}

// 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()
    ReflowOutput reflowOutput(GetWritingMode());
    Place(aDrawTarget, false, 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 <msup><mo>...</mo> ... </msup>,
          // or <maction>...<mo>...</mo></maction>.
          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;

    if (NS_MATHML_HAS_ERROR(mPresentationData.flags)) {
      NS_WARNING("it is wrong to fire stretch on a erroneous frame");
      return NS_OK;
    }

    // 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 &Sum; might appear big in the following situation
        // <math xmlns='http://www.w3.org/1998/Math/MathML'>
        //   <mstyle>
        //     <msub>
        //        <msub><mo>&Sum;</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub>
        //        <msub><mo>&Sum;</mo><mfrac><mi>a</mi><mi>b</mi></mfrac></msub>
        //      </msub>
        //   </mstyle>
        // </math>
        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
        nsresult rv = Place(aDrawTarget, true, aDesiredStretchSize);
        if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || 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 <mfrac> 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 ?
                        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 <mfrac><mo>...</mo> ... </mfrac>, the test is convoluted
  // because it excludes the particular case of the core <mo>...</mo> itself.
  // (<mo> 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);
  nsresult rv = Place(aDrawTarget, placeOrigin, 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 aPlaceOrigin == true
  // through Stretch() eventually.
  if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || 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 <mo>...</mo> itself */
          (mEmbellishData.direction == NS_STRETCH_DIRECTION_HORIZONTAL &&
           stretchAll) || /* or <mover><mo>...</mo>...</mover>, 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->GetStateBits() & 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 nsRect&           aDirtyRect,
                                         const nsDisplayListSet& aLists)
{
  // report an error if something wrong was found in this frame
  if (NS_MATHML_HAS_ERROR(mPresentationData.flags)) {
    if (!IsVisibleForPainting(aBuilder))
      return;

    aLists.Content()->AppendNewToTop(
      new (aBuilder) nsDisplayMathMLError(aBuilder, this));
    return;
  }

  DisplayBorderBackgroundOutline(aBuilder, aLists);

  BuildDisplayListForNonBlockChildren(aBuilder, aDirtyRect, aLists,
                                      DISPLAY_CHILD_INLINE);

#if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
  // for visual debug
  // ----------------
  // if you want to see your bounding box, make sure to properly fill
  // your mBoundingMetrics and mReference point, and set
  // mPresentationData.flags |= NS_MATHML_SHOW_BOUNDING_METRICS
  // in the Init() of your sub-class
  DisplayBoundingMetrics(aBuilder, this, mReference, mBoundingMetrics, aLists);
#endif
}

// 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 <math>
  nsIFrame* frame = aParentFrame;
  while (1) {
     nsIFrame* parent = frame->GetParent();
     if (!parent || !parent->GetContent())
       break;

    // stop if it is a MathML frame
    nsIMathMLFrame* mathMLFrame = do_QueryFrame(frame);
    if (mathMLFrame)
      break;

    // stop if we reach the root <math> tag
    nsIContent* content = frame->GetContent();
    NS_ASSERTION(content, "dangling frame without a content node");
    if (!content)
      break;
    if (content->IsMathMLElement(nsGkAtoms::math))
      break;

    frame = parent;
  }

  // re-sync the presentation data and embellishment data of our children
  RebuildAutomaticDataForChildren(frame);

  // Ask our parent frame to reflow us
  nsIFrame* parent = frame->GetParent();
  NS_ASSERTION(parent, "No parent to pass the reflow request up to");
  if (!parent)
    return NS_OK;

  frame->PresContext()->PresShell()->
    FrameNeedsReflow(frame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);

  return NS_OK;
}

// There are precise rules governing children of a MathML frame,
// and properties such as the scriptlevel depends on those rules.
// Hence for things to work, callers must use Append/Insert/etc wisely.

nsresult
nsMathMLContainerFrame::ChildListChanged(int32_t aModType)
{
  // If this is an embellished frame we need to rebuild the
  // embellished hierarchy by walking-up to the parent of the
  // outermost embellished container.
  nsIFrame* frame = this;
  if (mEmbellishData.coreFrame) {
    nsIFrame* parent = GetParent();
    nsEmbellishData embellishData;
    for ( ; parent; frame = parent, parent = parent->GetParent()) {
      GetEmbellishDataFrom(parent, embellishData);
      if (embellishData.coreFrame != mEmbellishData.coreFrame)
        break;
    }
  }
  return ReLayoutChildren(frame);
}

void
nsMathMLContainerFrame::AppendFrames(ChildListID     aListID,
                                     nsFrameList&    aFrameList)
{
  MOZ_ASSERT(aListID == kPrincipalList);
  mFrames.AppendFrames(this, aFrameList);
  ChildListChanged(nsIDOMMutationEvent::ADDITION);
}

void
nsMathMLContainerFrame::InsertFrames(ChildListID     aListID,
                                     nsIFrame*       aPrevFrame,
                                     nsFrameList&    aFrameList)
{
  MOZ_ASSERT(aListID == kPrincipalList);
  mFrames.InsertFrames(this, aPrevFrame, aFrameList);
  ChildListChanged(nsIDOMMutationEvent::ADDITION);
}

void
nsMathMLContainerFrame::RemoveFrame(ChildListID     aListID,
                                    nsIFrame*       aOldFrame)
{
  MOZ_ASSERT(aListID == kPrincipalList);
  mFrames.DestroyFrame(aOldFrame);
  ChildListChanged(nsIDOMMutationEvent::REMOVAL);
}

nsresult
nsMathMLContainerFrame::AttributeChanged(int32_t         aNameSpaceID,
                                         nsIAtom*        aAttribute,
                                         int32_t         aModType)
{
  // XXX Since they are numerous MathML attributes that affect layout, and
  // we can't check all of them here, play safe by requesting a reflow.
  // XXXldb This should only do work for attributes that cause changes!
  PresContext()->PresShell()->
    FrameNeedsReflow(this, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);

  return NS_OK;
}

void
nsMathMLContainerFrame::GatherAndStoreOverflow(ReflowOutput* aMetrics)
{
  mBlockStartAscent = aMetrics->BlockStartAscent();

  // nsIFrame::FinishAndStoreOverflow likes the overflow area to include the
  // frame rectangle.
  aMetrics->SetOverflowAreasToDesiredBounds();

  ComputeCustomOverflow(aMetrics->mOverflowAreas);

  // mBoundingMetrics does not necessarily include content of <mpadded>
  // elements whose mBoundingMetrics may not be representative of the true
  // bounds, and doesn't include the CSS2 outline rectangles of children, so
  // make such to include child overflow areas.
  UnionChildOverflow(aMetrics->mOverflowAreas);

  FinishAndStoreOverflow(aMetrics);
}

bool
nsMathMLContainerFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas)
{
  // All non-child-frame content such as nsMathMLChars (and most child-frame
  // content) is included in mBoundingMetrics.
  nsRect boundingBox(mBoundingMetrics.leftBearing,
                     mBlockStartAscent - mBoundingMetrics.ascent,
                     mBoundingMetrics.rightBearing - mBoundingMetrics.leftBearing,
                     mBoundingMetrics.ascent + mBoundingMetrics.descent);

  // REVIEW: Maybe this should contribute only to visual overflow
  // and not scrollable?
  aOverflowAreas.UnionAllWith(boundingBox);
  return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
}

void
nsMathMLContainerFrame::ReflowChild(nsIFrame*                aChildFrame,
                                    nsPresContext*           aPresContext,
                                    ReflowOutput&     aDesiredSize,
                                    const ReflowInput& aReflowInput,
                                    nsReflowStatus&          aStatus)
{
  // Having foreign/hybrid children, e.g., from html markups, is not defined by
  // the MathML spec. But it can happen in practice, e.g., <html:img> allows us
  // to do some cool demos... or we may have a child that is an nsInlineFrame
  // from a generated content such as :before { content: open-quote } or 
  // :after { content: close-quote }. Unfortunately, the other frames out-there
  // may expect their own invariants that are not met when we mix things.
  // Hence we do not claim their support, but we will nevertheless attempt to keep
  // them in the flow, if we can get their desired size. We observed that most
  // frames may be reflowed generically, but nsInlineFrames need extra care.

#ifdef DEBUG
  nsInlineFrame* inlineFrame = do_QueryFrame(aChildFrame);
  NS_ASSERTION(!inlineFrame, "Inline frames should be wrapped in blocks");
#endif
  
  nsContainerFrame::
         ReflowChild(aChildFrame, aPresContext, aDesiredSize, aReflowInput,
                     0, 0, NS_FRAME_NO_MOVE_FRAME, aStatus);

  if (aDesiredSize.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
    // This will be suitable for inline frames, which are wrapped in a block.
    nscoord ascent;
    WritingMode wm = aDesiredSize.GetWritingMode();
    if (!nsLayoutUtils::GetLastLineBaseline(wm, aChildFrame, &ascent)) {
      // We don't expect any other block children so just place the frame on
      // the baseline instead of going through DidReflow() and
      // GetBaseline().  This is what nsFrame::GetBaseline() will do anyway.
      aDesiredSize.SetBlockStartAscent(aDesiredSize.BSize(wm));
    } else {
      aDesiredSize.SetBlockStartAscent(ascent);
    }
  }
  if (IsForeignChild(aChildFrame)) {
    // use ComputeTightBounds API as aDesiredSize.mBoundingMetrics is not set.
    nsRect r = aChildFrame->ComputeTightBounds(aReflowInput.mRenderingContext->GetDrawTarget());
    aDesiredSize.mBoundingMetrics.leftBearing = r.x;
    aDesiredSize.mBoundingMetrics.rightBearing = r.XMost();
    aDesiredSize.mBoundingMetrics.ascent = aDesiredSize.BlockStartAscent() - r.y;
    aDesiredSize.mBoundingMetrics.descent = r.YMost() - aDesiredSize.BlockStartAscent();
    aDesiredSize.mBoundingMetrics.width = aDesiredSize.Width();
  }
}

void
nsMathMLContainerFrame::Reflow(nsPresContext*           aPresContext,
                               ReflowOutput&     aDesiredSize,
                               const ReflowInput& aReflowInput,
                               nsReflowStatus&          aStatus)
{
  MarkInReflow();
  mPresentationData.flags &= ~NS_MATHML_ERROR;
  aDesiredSize.Width() = aDesiredSize.Height() = 0;
  aDesiredSize.SetBlockStartAscent(0);
  aDesiredSize.mBoundingMetrics = nsBoundingMetrics();

  /////////////
  // Reflow children
  // Asking each child to cache its bounding metrics

  nsReflowStatus childStatus;
  nsIFrame* childFrame = mFrames.FirstChild();
  while (childFrame) {
    ReflowOutput childDesiredSize(aReflowInput, // ???
                                         aDesiredSize.mFlags);
    WritingMode wm = childFrame->GetWritingMode();
    LogicalSize availSize = aReflowInput.ComputedSize(wm);
    availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
    ReflowInput childReflowInput(aPresContext, aReflowInput,
                                       childFrame, availSize);
    ReflowChild(childFrame, aPresContext, childDesiredSize,
                childReflowInput, childStatus);
    //NS_ASSERTION(NS_FRAME_IS_COMPLETE(childStatus), "bad status");
    SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize,
                                    childDesiredSize.mBoundingMetrics);
    childFrame = childFrame->GetNextSibling();
  }

  /////////////
  // If we are a container which is entitled to stretch its children, then we
  // ask our stretchy children to stretch themselves

  // The stretching of siblings of an embellished child is _deferred_ until
  // after finishing the stretching of the embellished child - bug 117652

  DrawTarget* drawTarget = aReflowInput.mRenderingContext->GetDrawTarget();

  if (!NS_MATHML_IS_EMBELLISH_OPERATOR(mEmbellishData.flags) &&
      (NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) ||
       NS_MATHML_WILL_STRETCH_ALL_CHILDREN_HORIZONTALLY(mPresentationData.flags))) {

    // get the stretchy direction
    nsStretchDirection stretchDir =
      NS_MATHML_WILL_STRETCH_ALL_CHILDREN_VERTICALLY(mPresentationData.flags) 
      ? NS_STRETCH_DIRECTION_VERTICAL 
      : NS_STRETCH_DIRECTION_HORIZONTAL;

    // what size should we use to stretch our stretchy children
    // We don't use STRETCH_CONSIDER_ACTUAL_SIZE -- because our size is not known yet
    // We don't use STRETCH_CONSIDER_EMBELLISHMENTS -- because we don't want to
    // include them in the caculations of the size of stretchy elements
    nsBoundingMetrics containerSize;
    GetPreferredStretchSize(drawTarget, 0, stretchDir, containerSize);

    // fire the stretch on each child
    childFrame = mFrames.FirstChild();
    while (childFrame) {
      nsIMathMLFrame* mathMLFrame = do_QueryFrame(childFrame);
      if (mathMLFrame) {
        // retrieve the metrics that was stored at the previous pass
        ReflowOutput childDesiredSize(aReflowInput);
        GetReflowAndBoundingMetricsFor(childFrame,
          childDesiredSize, childDesiredSize.mBoundingMetrics);

        mathMLFrame->Stretch(drawTarget, stretchDir,
                             containerSize, childDesiredSize);
        // store the updated metrics
        SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize,
                                        childDesiredSize.mBoundingMetrics);
      }
      childFrame = childFrame->GetNextSibling();
    }
  }

  /////////////
  // Place children now by re-adjusting the origins to align the baselines
  FinalizeReflow(drawTarget, aDesiredSize);

  aStatus = NS_FRAME_COMPLETE;
  NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
}

static nscoord AddInterFrameSpacingToSize(ReflowOutput&    aDesiredSize,
                                          nsMathMLContainerFrame* aFrame);

/* virtual */ void
nsMathMLContainerFrame::MarkIntrinsicISizesDirty()
{
  mIntrinsicWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
  nsContainerFrame::MarkIntrinsicISizesDirty();
}

void
nsMathMLContainerFrame::UpdateIntrinsicWidth(nsRenderingContext* aRenderingContext)
{
  if (mIntrinsicWidth == NS_INTRINSIC_WIDTH_UNKNOWN) {
    ReflowOutput desiredSize(GetWritingMode());
    GetIntrinsicISizeMetrics(aRenderingContext, desiredSize);

    // Include the additional width added by FixInterFrameSpacing to ensure
    // consistent width calculations.
    AddInterFrameSpacingToSize(desiredSize, this);
    mIntrinsicWidth = desiredSize.ISize(GetWritingMode());
  }
}

/* virtual */ nscoord
nsMathMLContainerFrame::GetMinISize(nsRenderingContext* aRenderingContext)
{
  nscoord result;
  DISPLAY_MIN_WIDTH(this, result);
  UpdateIntrinsicWidth(aRenderingContext);
  result = mIntrinsicWidth;
  return result;
}

/* virtual */ nscoord
nsMathMLContainerFrame::GetPrefISize(nsRenderingContext* aRenderingContext)
{
  nscoord result;
  DISPLAY_PREF_WIDTH(this, result);
  UpdateIntrinsicWidth(aRenderingContext);
  result = mIntrinsicWidth;
  return result;
}

/* virtual */ void
nsMathMLContainerFrame::GetIntrinsicISizeMetrics(nsRenderingContext* aRenderingContext,
                                                 ReflowOutput& aDesiredSize)
{
  // Get child widths
  nsIFrame* childFrame = mFrames.FirstChild();
  while (childFrame) {
    ReflowOutput childDesiredSize(GetWritingMode()); // ???

    nsMathMLContainerFrame* containerFrame = do_QueryFrame(childFrame);
    if (containerFrame) {
      containerFrame->GetIntrinsicISizeMetrics(aRenderingContext,
                                               childDesiredSize);
    } else {
      // XXX This includes margin while Reflow currently doesn't consider
      // margin, so we may end up with too much space, but, with stretchy
      // characters, this is an approximation anyway.
      nscoord width =
        nsLayoutUtils::IntrinsicForContainer(aRenderingContext, childFrame,
                                             nsLayoutUtils::PREF_ISIZE);

      childDesiredSize.Width() = width;
      childDesiredSize.mBoundingMetrics.width = width;
      childDesiredSize.mBoundingMetrics.leftBearing = 0;
      childDesiredSize.mBoundingMetrics.rightBearing = width;

      nscoord x, xMost;
      if (NS_SUCCEEDED(childFrame->GetPrefWidthTightBounds(aRenderingContext,
                                                           &x, &xMost))) {
        childDesiredSize.mBoundingMetrics.leftBearing = x;
        childDesiredSize.mBoundingMetrics.rightBearing = xMost;
      }
    }

    SaveReflowAndBoundingMetricsFor(childFrame, childDesiredSize,
                                    childDesiredSize.mBoundingMetrics);

    childFrame = childFrame->GetNextSibling();
  }

  // Measure
  nsresult rv = MeasureForWidth(aRenderingContext->GetDrawTarget(), aDesiredSize);
  if (NS_FAILED(rv)) {
    ReflowError(aRenderingContext->GetDrawTarget(), aDesiredSize);
  }

  ClearSavedChildMetrics();
}

/* virtual */ nsresult
nsMathMLContainerFrame::MeasureForWidth(DrawTarget* aDrawTarget,
                                        ReflowOutput& aDesiredSize)
{
  return Place(aDrawTarget, false, aDesiredSize);
}


// see spacing table in Chapter 18, TeXBook (p.170)
// Our table isn't quite identical to TeX because operators have 
// built-in values for lspace & rspace in the Operator Dictionary.
static int32_t kInterFrameSpacingTable[eMathMLFrameType_COUNT][eMathMLFrameType_COUNT] =
{
  // in units of muspace.
  // upper half of the byte is set if the
  // spacing is not to be used for scriptlevel > 0

  /*           Ord  OpOrd OpInv OpUsr Inner Italic Upright */
  /*Ord  */   {0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00},
  /*OpOrd*/   {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
  /*OpInv*/   {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
  /*OpUsr*/   {0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01},
  /*Inner*/   {0x01, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01},
  /*Italic*/  {0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01},
  /*Upright*/ {0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00}
};

#define GET_INTERSPACE(scriptlevel_, frametype1_, frametype2_, space_)  \
   /* no space if there is a frame that we know nothing about */        \
   if (frametype1_ == eMathMLFrameType_UNKNOWN ||                       \
       frametype2_ == eMathMLFrameType_UNKNOWN)                         \
    space_ = 0;                                                         \
  else {                                                                \
    space_ = kInterFrameSpacingTable[frametype1_][frametype2_];         \
    space_ = (scriptlevel_ > 0 && (space_ & 0xF0))                      \
      ? 0 /* spacing is disabled */                                     \
      : space_ & 0x0F;                                                  \
  }                                                                     \

// This function computes the inter-space between two frames. However, 
// since invisible operators need special treatment, the inter-space may
// be delayed when an invisible operator is encountered. In this case,
// the function will carry the inter-space forward until it is determined
// that it can be applied properly (i.e., until we encounter a visible
// frame where to decide whether to accept or reject the inter-space).
// aFromFrameType: remembers the frame when the carry-forward initiated.
// aCarrySpace: keeps track of the inter-space that is delayed.
// @returns: current inter-space (which is 0 when the true inter-space is
// delayed -- and thus has no effect since the frame is invisible anyway).
static nscoord
GetInterFrameSpacing(int32_t           aScriptLevel,
                     eMathMLFrameType  aFirstFrameType,
                     eMathMLFrameType  aSecondFrameType,
                     eMathMLFrameType* aFromFrameType, // IN/OUT
                     int32_t*          aCarrySpace)    // IN/OUT
{
  eMathMLFrameType firstType = aFirstFrameType;
  eMathMLFrameType secondType = aSecondFrameType;

  int32_t space;
  GET_INTERSPACE(aScriptLevel, firstType, secondType, space);

  // feedback control to avoid the inter-space to be added when not necessary
  if (secondType == eMathMLFrameType_OperatorInvisible) {
    // see if we should start to carry the space forward until we
    // encounter a visible frame
    if (*aFromFrameType == eMathMLFrameType_UNKNOWN) {
      *aFromFrameType = firstType;
      *aCarrySpace = space;
    }
    // keep carrying *aCarrySpace forward, while returning 0 for this stage
    space = 0;
  }
  else if (*aFromFrameType != eMathMLFrameType_UNKNOWN) {
    // no carry-forward anymore, get the real inter-space between
    // the two frames of interest

    firstType = *aFromFrameType;

    // But... the invisible operator that we encountered earlier could
    // be sitting between italic and upright identifiers, e.g.,
    //
    // 1. <mi>sin</mi> <mo>&ApplyFunction;</mo> <mi>x</mi>
    // 2. <mi>x</mi> <mo>&InvisibileTime;</mo> <mi>sin</mi>
    //
    // the trick to get the inter-space in either situation
    // is to promote "<mi>sin</mi><mo>&ApplyFunction;</mo>" and
    // "<mo>&InvisibileTime;</mo><mi>sin</mi>" to user-defined operators...
    if (firstType == eMathMLFrameType_UprightIdentifier) {
      firstType = eMathMLFrameType_OperatorUserDefined;
    }
    else if (secondType == eMathMLFrameType_UprightIdentifier) {
      secondType = eMathMLFrameType_OperatorUserDefined;
    }

    GET_INTERSPACE(aScriptLevel, firstType, secondType, space);

    // Now, we have two values: the computed space and the space that
    // has been carried forward until now. Which value do we pick?
    // If the second type is an operator (e.g., fence), it already has
    // built-in lspace & rspace, so we let them win. Otherwise we pick
    // the max between the two values that we have.
    if (secondType != eMathMLFrameType_OperatorOrdinary &&
        space < *aCarrySpace)
      space = *aCarrySpace;

    // reset everything now that the carry-forward is done
    *aFromFrameType = eMathMLFrameType_UNKNOWN;
    *aCarrySpace = 0;
  }

  return space;
}

static nscoord GetThinSpace(const nsStyleFont* aStyleFont)
{
  return NSToCoordRound(float(aStyleFont->mFont.size)*float(3) / float(18));
}

class nsMathMLContainerFrame::RowChildFrameIterator {
public:
  explicit RowChildFrameIterator(nsMathMLContainerFrame* aParentFrame) :
    mParentFrame(aParentFrame),
    mReflowOutput(aParentFrame->GetWritingMode()),
    mX(0),
    mCarrySpace(0),
    mFromFrameType(eMathMLFrameType_UNKNOWN),
    mRTL(aParentFrame->StyleVisibility()->mDirection)
  {
    if (!mRTL) {
      mChildFrame = aParentFrame->mFrames.FirstChild();
    } else {
      mChildFrame = aParentFrame->mFrames.LastChild();
    }

    if (!mChildFrame)
      return;

    InitMetricsForChild();
  }

  RowChildFrameIterator& operator++()
  {
    // add child size + italic correction
    mX += mReflowOutput.mBoundingMetrics.width + mItalicCorrection;

    if (!mRTL) {
      mChildFrame = mChildFrame->GetNextSibling();
    } else {
      mChildFrame = mChildFrame->GetPrevSibling();
    }

    if (!mChildFrame)
      return *this;

    eMathMLFrameType prevFrameType = mChildFrameType;
    InitMetricsForChild();

    // add inter frame spacing
    const nsStyleFont* font = mParentFrame->StyleFont();
    nscoord space =
      GetInterFrameSpacing(font->mScriptLevel,
                           prevFrameType, mChildFrameType,
                           &mFromFrameType, &mCarrySpace);
    mX += space * GetThinSpace(font);
    return *this;
  }

  nsIFrame* Frame() const { return mChildFrame; }
  nscoord X() const { return mX; }
  const ReflowOutput& GetReflowOutput() const { return mReflowOutput; }
  nscoord Ascent() const { return mReflowOutput.BlockStartAscent(); }
  nscoord Descent() const { return mReflowOutput.Height() - mReflowOutput.BlockStartAscent(); }
  const nsBoundingMetrics& BoundingMetrics() const {
    return mReflowOutput.mBoundingMetrics;
  }

private:
  const nsMathMLContainerFrame* mParentFrame;
  nsIFrame* mChildFrame;
  ReflowOutput mReflowOutput;
  nscoord mX;

  nscoord mItalicCorrection;
  eMathMLFrameType mChildFrameType;
  int32_t mCarrySpace;
  eMathMLFrameType mFromFrameType;

  bool mRTL;

  void InitMetricsForChild()
  {
    GetReflowAndBoundingMetricsFor(mChildFrame, mReflowOutput, mReflowOutput.mBoundingMetrics,
                                   &mChildFrameType);
    nscoord leftCorrection, rightCorrection;
    GetItalicCorrection(mReflowOutput.mBoundingMetrics,
                        leftCorrection, rightCorrection);
    if (!mChildFrame->GetPrevSibling() &&
        mParentFrame->GetContent()->IsMathMLElement(nsGkAtoms::msqrt_)) {
      // Remove leading correction in <msqrt> because the sqrt glyph itself is
      // there first.
      if (!mRTL) {
        leftCorrection = 0;
      } else {
        rightCorrection = 0;
      }
    }
    // add left correction -- this fixes the problem of the italic 'f'
    // e.g., <mo>q</mo> <mi>f</mi> <mo>I</mo> 
    mX += leftCorrection;
    mItalicCorrection = rightCorrection;
  }
};

/* virtual */ nsresult
nsMathMLContainerFrame::Place(DrawTarget*          aDrawTarget,
                              bool                 aPlaceOrigin,
                              ReflowOutput& aDesiredSize)
{
  // This is needed in case this frame is empty (i.e., no child frames)
  mBoundingMetrics = nsBoundingMetrics();

  RowChildFrameIterator child(this);
  nscoord ascent = 0, descent = 0;
  while (child.Frame()) {
    if (descent < child.Descent())
      descent = child.Descent();
    if (ascent < child.Ascent())
      ascent = child.Ascent();
    // add the child size
    mBoundingMetrics.width = child.X();
    mBoundingMetrics += child.BoundingMetrics();
    ++child;
  }
  // Add the italic correction at the end (including the last child).
  // This gives a nice gap between math and non-math frames, and still
  // gives the same math inter-spacing in case this frame connects to
  // another math frame
  mBoundingMetrics.width = child.X();

  aDesiredSize.Width() = std::max(0, mBoundingMetrics.width);
  aDesiredSize.Height() = ascent + descent;
  aDesiredSize.SetBlockStartAscent(ascent);
  aDesiredSize.mBoundingMetrics = mBoundingMetrics;

  mReference.x = 0;
  mReference.y = aDesiredSize.BlockStartAscent();

  //////////////////
  // Place Children

  if (aPlaceOrigin) {
    PositionRowChildFrames(0, aDesiredSize.BlockStartAscent());
  }

  return NS_OK;
}

void
nsMathMLContainerFrame::PositionRowChildFrames(nscoord aOffsetX,
                                               nscoord aBaseline)
{
  RowChildFrameIterator child(this);
  while (child.Frame()) {
    nscoord dx = aOffsetX + child.X();
    nscoord dy = aBaseline - child.Ascent();
    FinishReflowChild(child.Frame(), PresContext(), child.GetReflowOutput(),
                      nullptr, dx, dy, 0);
    ++child;
  }
}

class ForceReflow : public nsIReflowCallback {
public:
  virtual bool ReflowFinished() override {
    return true;
  }
  virtual void ReflowCallbackCanceled() override {}
};

// We only need one of these so we just make it a static global, no need
// to dynamically allocate/destroy it.
static ForceReflow gForceReflow;

void
nsMathMLContainerFrame::SetIncrementScriptLevel(int32_t aChildIndex, bool aIncrement)
{
  nsIFrame* child = PrincipalChildList().FrameAt(aChildIndex);
  if (!child)
    return;
  nsIContent* content = child->GetContent();
  if (!content->IsMathMLElement())
    return;
  nsMathMLElement* element = static_cast<nsMathMLElement*>(content);

  if (element->GetIncrementScriptLevel() == aIncrement)
    return;

  // XXXroc this does a ContentStatesChanged, is it safe to call here? If
  // not we should do it in a post-reflow callback.
  element->SetIncrementScriptLevel(aIncrement, true);
  PresContext()->PresShell()->PostReflowCallback(&gForceReflow);
}

// helpers to fix the inter-spacing when <math> is the only parent
// e.g., it fixes <math> <mi>f</mi> <mo>q</mo> <mi>f</mi> <mo>I</mo> </math>

static nscoord
GetInterFrameSpacingFor(int32_t         aScriptLevel,
                        nsIFrame*       aParentFrame,
                        nsIFrame*       aChildFrame)
{
  nsIFrame* childFrame = aParentFrame->PrincipalChildList().FirstChild();
  if (!childFrame || aChildFrame == childFrame)
    return 0;

  int32_t carrySpace = 0;
  eMathMLFrameType fromFrameType = eMathMLFrameType_UNKNOWN;
  eMathMLFrameType prevFrameType = eMathMLFrameType_UNKNOWN;
  eMathMLFrameType childFrameType = nsMathMLFrame::GetMathMLFrameTypeFor(childFrame);
  childFrame = childFrame->GetNextSibling();
  while (childFrame) {
    prevFrameType = childFrameType;
    childFrameType = nsMathMLFrame::GetMathMLFrameTypeFor(childFrame);
    nscoord space = GetInterFrameSpacing(aScriptLevel,
      prevFrameType, childFrameType, &fromFrameType, &carrySpace);
    if (aChildFrame == childFrame) {
      // get thinspace
      nsStyleContext* parentContext = aParentFrame->StyleContext();
      nscoord thinSpace = GetThinSpace(parentContext->StyleFont());
      // we are done
      return space * thinSpace;
    }
    childFrame = childFrame->GetNextSibling();
  }

  NS_NOTREACHED("child not in the childlist of its parent");
  return 0;
}

static nscoord
AddInterFrameSpacingToSize(ReflowOutput&    aDesiredSize,
                           nsMathMLContainerFrame* aFrame)
{
  nscoord gap = 0;
  nsIFrame* parent = aFrame->GetParent();
  nsIContent* parentContent = parent->GetContent();
  if (MOZ_UNLIKELY(!parentContent)) {
    return 0;
  }
  if (parentContent->IsAnyOfMathMLElements(nsGkAtoms::math,
                                           nsGkAtoms::mtd_)) {
    gap = GetInterFrameSpacingFor(aFrame->StyleFont()->mScriptLevel,
                                  parent, aFrame);
    // add our own italic correction
    nscoord leftCorrection = 0, italicCorrection = 0;
    aFrame->GetItalicCorrection(aDesiredSize.mBoundingMetrics,
                                leftCorrection, italicCorrection);
    gap += leftCorrection;
    if (gap) {
      aDesiredSize.mBoundingMetrics.leftBearing += gap;
      aDesiredSize.mBoundingMetrics.rightBearing += gap;
      aDesiredSize.mBoundingMetrics.width += gap;
      aDesiredSize.Width() += gap;
    }
    aDesiredSize.mBoundingMetrics.width += italicCorrection;
    aDesiredSize.Width() += italicCorrection;
  }
  return gap;
}

nscoord
nsMathMLContainerFrame::FixInterFrameSpacing(ReflowOutput& aDesiredSize)
{
  nscoord gap = 0;
  gap = AddInterFrameSpacingToSize(aDesiredSize, this);
  if (gap) {
    // Shift our children to account for the correction
    nsIFrame* childFrame = mFrames.FirstChild();
    while (childFrame) {
      childFrame->SetPosition(childFrame->GetPosition() + nsPoint(gap, 0));
      childFrame = childFrame->GetNextSibling();
    }
  }
  return gap;
}

/* static */ void
nsMathMLContainerFrame::DidReflowChildren(nsIFrame* aFirst, nsIFrame* aStop)

{
  if (MOZ_UNLIKELY(!aFirst))
    return;

  for (nsIFrame* frame = aFirst;
       frame != aStop;
       frame = frame->GetNextSibling()) {
    NS_ASSERTION(frame, "aStop isn't a sibling");
    if (frame->GetStateBits() & NS_FRAME_IN_REFLOW) {
      // finish off principal descendants, too
      nsIFrame* grandchild = frame->PrincipalChildList().FirstChild();
      if (grandchild)
        DidReflowChildren(grandchild, nullptr);

      frame->DidReflow(frame->PresContext(), nullptr,
                       nsDidReflowStatus::FINISHED);
    }
  }
}

// helper used by mstyle, mphantom, mpadded and mrow in their implementations
// of TransmitAutomaticData().
nsresult
nsMathMLContainerFrame::TransmitAutomaticDataForMrowLikeElement()
{
  //
  // One loop to check both conditions below:
  //
  // 1) whether all the children of the mrow-like element are space-like.
  //
  //   The REC defines the following elements to be "space-like":
  //   * an mstyle, mphantom, or mpadded element, all of whose direct
  //     sub-expressions are space-like;
  //   * an mrow all of whose direct sub-expressions are space-like.
  //
  // 2) whether all but one child of the mrow-like element are space-like and
  //    this non-space-like child is an embellished operator.
  //
  //   The REC defines the following elements to be embellished operators:
  //   * one of the elements mstyle, mphantom, or mpadded, such that an mrow
  //     containing the same arguments would be an embellished operator;
  //   * an mrow whose arguments consist (in any order) of one embellished
  //     operator and zero or more space-like elements.
  //
  nsIFrame *childFrame, *baseFrame;
  bool embellishedOpFound = false;
  nsEmbellishData embellishData;
  
  for (childFrame = PrincipalChildList().FirstChild();
       childFrame;
       childFrame = childFrame->GetNextSibling()) {
    nsIMathMLFrame* mathMLFrame = do_QueryFrame(childFrame);
    if (!mathMLFrame) break;
    if (!mathMLFrame->IsSpaceLike()) {
      if (embellishedOpFound) break;
      baseFrame = childFrame;
      GetEmbellishDataFrom(baseFrame, embellishData);
      if (!NS_MATHML_IS_EMBELLISH_OPERATOR(embellishData.flags)) break;
      embellishedOpFound = true;
    }
  }

  if (!childFrame) {
    // we successfully went to the end of the loop. This means that one of
    // condition 1) or 2) holds.
    if (!embellishedOpFound) {
      // the mrow-like element is space-like.
      mPresentationData.flags |= NS_MATHML_SPACE_LIKE;
    } else {
      // the mrow-like element is an embellished operator.
      // let the state of the embellished operator found bubble to us.
      mPresentationData.baseFrame = baseFrame;
      mEmbellishData = embellishData;
    }
  }

  if (childFrame || !embellishedOpFound) {
    // The element is not embellished operator
    mPresentationData.baseFrame = nullptr;
    mEmbellishData.flags = 0;
    mEmbellishData.coreFrame = nullptr;
    mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
    mEmbellishData.leadingSpace = 0;
    mEmbellishData.trailingSpace = 0;
  }

  if (childFrame || embellishedOpFound) {
    // The element is not space-like
    mPresentationData.flags &= ~NS_MATHML_SPACE_LIKE;
  }

  return NS_OK;
}

/*static*/ void
nsMathMLContainerFrame::PropagateFrameFlagFor(nsIFrame* aFrame,
                                              nsFrameState  aFlags)
{
  if (!aFrame || !aFlags)
    return;

  aFrame->AddStateBits(aFlags);
  for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
    PropagateFrameFlagFor(childFrame, aFlags);
  }
}

nsresult
nsMathMLContainerFrame::ReportErrorToConsole(const char*       errorMsgId,
                                             const char16_t** aParams,
                                             uint32_t          aParamCount)
{
  return nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
                                         NS_LITERAL_CSTRING("Layout: MathML"), mContent->OwnerDoc(),
                                         nsContentUtils::eMATHML_PROPERTIES,
                                         errorMsgId, aParams, aParamCount);
}

nsresult
nsMathMLContainerFrame::ReportParseError(const char16_t* aAttribute,
                                         const char16_t* aValue)
{
  const char16_t* argv[] = 
    { aValue, aAttribute, mContent->NodeInfo()->NameAtom()->GetUTF16String() };
  return ReportErrorToConsole("AttributeParsingError", argv, 3);
}

nsresult
nsMathMLContainerFrame::ReportChildCountError()
{
  const char16_t* arg = mContent->NodeInfo()->NameAtom()->GetUTF16String();
  return ReportErrorToConsole("ChildCountIncorrect", &arg, 1);
}

nsresult
nsMathMLContainerFrame::ReportInvalidChildError(nsIAtom* aChildTag)
{
  const char16_t* argv[] =
    { aChildTag->GetUTF16String(),
      mContent->NodeInfo()->NameAtom()->GetUTF16String() };
  return ReportErrorToConsole("InvalidChild", argv, 2);
}

//==========================

nsContainerFrame*
NS_NewMathMLmathBlockFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
{
  return new (aPresShell) nsMathMLmathBlockFrame(aContext);
}

NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmathBlockFrame)

NS_QUERYFRAME_HEAD(nsMathMLmathBlockFrame)
  NS_QUERYFRAME_ENTRY(nsMathMLmathBlockFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)

nsContainerFrame*
NS_NewMathMLmathInlineFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
{
  return new (aPresShell) nsMathMLmathInlineFrame(aContext);
}

NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmathInlineFrame)

NS_QUERYFRAME_HEAD(nsMathMLmathInlineFrame)
  NS_QUERYFRAME_ENTRY(nsIMathMLFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsInlineFrame)
