/* -*- 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();
}
}
// 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 ... ... ,
// 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
nsresult rv = Place(aDrawTarget, true, 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);
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_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);
#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