summaryrefslogtreecommitdiffstats
path: root/layout/xul/nsBoxFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/xul/nsBoxFrame.cpp')
-rw-r--r--layout/xul/nsBoxFrame.cpp1158
1 files changed, 1158 insertions, 0 deletions
diff --git a/layout/xul/nsBoxFrame.cpp b/layout/xul/nsBoxFrame.cpp
new file mode 100644
index 0000000000..bfca720519
--- /dev/null
+++ b/layout/xul/nsBoxFrame.cpp
@@ -0,0 +1,1158 @@
+/* -*- 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/. */
+
+//
+// Eric Vaughan
+// Netscape Communications
+//
+// See documentation in associated header file
+//
+
+// How boxes layout
+// ----------------
+// Boxes layout a bit differently than html. html does a bottom up layout. Where
+// boxes do a top down.
+//
+// 1) First thing a box does it goes out and askes each child for its min, max,
+// and preferred sizes.
+//
+// 2) It then adds them up to determine its size.
+//
+// 3) If the box was asked to layout it self intrinically it will layout its
+// children at their preferred size otherwise it will layout the child at
+// the size it was told to. It will squeeze or stretch its children if
+// Necessary.
+//
+// However there is a catch. Some html components like block frames can not
+// determine their preferred size. this is their size if they were laid out
+// intrinsically. So the box will flow the child to determine this can cache the
+// value.
+
+// Boxes and Incremental Reflow
+// ----------------------------
+// Boxes layout out top down by adding up their children's min, max, and
+// preferred sizes. Only problem is if a incremental reflow occurs. The
+// preferred size of a child deep in the hierarchy could change. And this could
+// change any number of syblings around the box. Basically any children in the
+// reflow chain must have their caches cleared so when asked for there current
+// size they can relayout themselves.
+
+#include "nsBoxFrame.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "gfxUtils.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/CSSOrderAwareFrameIterator.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "nsBoxLayout.h"
+#include "nsBoxLayoutState.h"
+#include "nsCOMPtr.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsCSSRendering.h"
+#include "nsContainerFrame.h"
+#include "nsDisplayList.h"
+#include "nsGkAtoms.h"
+#include "nsHTMLParts.h"
+#include "nsIContent.h"
+#include "nsIFrameInlines.h"
+#include "nsIScrollableFrame.h"
+#include "nsITheme.h"
+#include "nsLayoutUtils.h"
+#include "nsNameSpaceManager.h"
+#include "nsPlaceholderFrame.h"
+#include "nsPresContext.h"
+#include "nsSliderFrame.h"
+#include "nsSprocketLayout.h"
+#include "nsStyleConsts.h"
+#include "nsTransform2D.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsWidgetsCID.h"
+
+// Needed for Print Preview
+
+#include "mozilla/TouchEvents.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+nsIFrame* NS_NewBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle,
+ bool aIsRoot, nsBoxLayout* aLayoutManager) {
+ return new (aPresShell)
+ nsBoxFrame(aStyle, aPresShell->GetPresContext(), nsBoxFrame::kClassID,
+ aIsRoot, aLayoutManager);
+}
+
+nsIFrame* NS_NewBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsBoxFrame(aStyle, aPresShell->GetPresContext());
+}
+
+NS_IMPL_FRAMEARENA_HELPERS(nsBoxFrame)
+
+#ifdef DEBUG
+NS_QUERYFRAME_HEAD(nsBoxFrame)
+ NS_QUERYFRAME_ENTRY(nsBoxFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
+#endif
+
+nsBoxFrame::nsBoxFrame(ComputedStyle* aStyle, nsPresContext* aPresContext,
+ ClassID aID, bool aIsRoot, nsBoxLayout* aLayoutManager)
+ : nsContainerFrame(aStyle, aPresContext, aID), mFlex(0), mAscent(0) {
+ AddStateBits(NS_STATE_IS_HORIZONTAL | NS_STATE_AUTO_STRETCH);
+
+ if (aIsRoot) AddStateBits(NS_STATE_IS_ROOT);
+
+ mValign = vAlign_Top;
+ mHalign = hAlign_Left;
+
+ // if no layout manager specified us the static sprocket layout
+ nsCOMPtr<nsBoxLayout> layout = aLayoutManager;
+
+ if (layout == nullptr) {
+ NS_NewSprocketLayout(layout);
+ }
+
+ SetXULLayoutManager(layout);
+}
+
+nsBoxFrame::~nsBoxFrame() = default;
+
+void nsBoxFrame::SetInitialChildList(ChildListID aListID,
+ nsFrameList& aChildList) {
+ nsContainerFrame::SetInitialChildList(aListID, aChildList);
+ if (aListID == kPrincipalList) {
+ // initialize our list of infos.
+ nsBoxLayoutState state(PresContext());
+ if (mLayoutManager)
+ mLayoutManager->ChildrenSet(this, state, mFrames.FirstChild());
+ }
+}
+
+/* virtual */
+void nsBoxFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
+ nsContainerFrame::DidSetComputedStyle(aOldComputedStyle);
+
+ // The values that CacheAttributes() computes depend on our style,
+ // so we need to recompute them here...
+ CacheAttributes();
+}
+
+/**
+ * Initialize us. This is a good time to get the alignment of the box
+ */
+void nsBoxFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
+ nsIFrame* aPrevInFlow) {
+ nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
+
+ if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER)) {
+ AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
+ }
+
+ MarkIntrinsicISizesDirty();
+
+ CacheAttributes();
+
+ // register access key
+ RegUnregAccessKey(true);
+}
+
+void nsBoxFrame::CacheAttributes() {
+ /*
+ printf("Caching: ");
+ XULDumpBox(stdout);
+ printf("\n");
+ */
+
+ mValign = vAlign_Top;
+ mHalign = hAlign_Left;
+
+ bool orient = false;
+ GetInitialOrientation(orient);
+ if (orient)
+ AddStateBits(NS_STATE_IS_HORIZONTAL);
+ else
+ RemoveStateBits(NS_STATE_IS_HORIZONTAL);
+
+ bool normal = true;
+ GetInitialDirection(normal);
+ if (normal)
+ AddStateBits(NS_STATE_IS_DIRECTION_NORMAL);
+ else
+ RemoveStateBits(NS_STATE_IS_DIRECTION_NORMAL);
+
+ GetInitialVAlignment(mValign);
+ GetInitialHAlignment(mHalign);
+
+ bool equalSize = false;
+ GetInitialEqualSize(equalSize);
+ if (equalSize)
+ AddStateBits(NS_STATE_EQUAL_SIZE);
+ else
+ RemoveStateBits(NS_STATE_EQUAL_SIZE);
+
+ bool autostretch = !!(mState & NS_STATE_AUTO_STRETCH);
+ GetInitialAutoStretch(autostretch);
+ if (autostretch)
+ AddStateBits(NS_STATE_AUTO_STRETCH);
+ else
+ RemoveStateBits(NS_STATE_AUTO_STRETCH);
+}
+
+bool nsBoxFrame::GetInitialHAlignment(nsBoxFrame::Halignment& aHalign) {
+ if (!GetContent()) return false;
+
+ // For horizontal boxes we're checking PACK. For vertical boxes we are
+ // checking ALIGN.
+ const nsStyleXUL* boxInfo = StyleXUL();
+ if (IsXULHorizontal()) {
+ switch (boxInfo->mBoxPack) {
+ case StyleBoxPack::Start:
+ aHalign = nsBoxFrame::hAlign_Left;
+ return true;
+ case StyleBoxPack::Center:
+ aHalign = nsBoxFrame::hAlign_Center;
+ return true;
+ case StyleBoxPack::End:
+ aHalign = nsBoxFrame::hAlign_Right;
+ return true;
+ default: // Nonsensical value. Just bail.
+ return false;
+ }
+ } else {
+ switch (boxInfo->mBoxAlign) {
+ case StyleBoxAlign::Start:
+ aHalign = nsBoxFrame::hAlign_Left;
+ return true;
+ case StyleBoxAlign::Center:
+ aHalign = nsBoxFrame::hAlign_Center;
+ return true;
+ case StyleBoxAlign::End:
+ aHalign = nsBoxFrame::hAlign_Right;
+ return true;
+ default: // Nonsensical value. Just bail.
+ return false;
+ }
+ }
+
+ return false;
+}
+
+bool nsBoxFrame::GetInitialVAlignment(nsBoxFrame::Valignment& aValign) {
+ if (!GetContent()) return false;
+ // For horizontal boxes we're checking ALIGN. For vertical boxes we are
+ // checking PACK.
+ const nsStyleXUL* boxInfo = StyleXUL();
+ if (IsXULHorizontal()) {
+ switch (boxInfo->mBoxAlign) {
+ case StyleBoxAlign::Start:
+ aValign = nsBoxFrame::vAlign_Top;
+ return true;
+ case StyleBoxAlign::Center:
+ aValign = nsBoxFrame::vAlign_Middle;
+ return true;
+ case StyleBoxAlign::Baseline:
+ aValign = nsBoxFrame::vAlign_BaseLine;
+ return true;
+ case StyleBoxAlign::End:
+ aValign = nsBoxFrame::vAlign_Bottom;
+ return true;
+ default: // Nonsensical value. Just bail.
+ return false;
+ }
+ } else {
+ switch (boxInfo->mBoxPack) {
+ case StyleBoxPack::Start:
+ aValign = nsBoxFrame::vAlign_Top;
+ return true;
+ case StyleBoxPack::Center:
+ aValign = nsBoxFrame::vAlign_Middle;
+ return true;
+ case StyleBoxPack::End:
+ aValign = nsBoxFrame::vAlign_Bottom;
+ return true;
+ default: // Nonsensical value. Just bail.
+ return false;
+ }
+ }
+
+ return false;
+}
+
+void nsBoxFrame::GetInitialOrientation(bool& aIsHorizontal) {
+ // see if we are a vertical or horizontal box.
+ if (!GetContent()) return;
+
+ const nsStyleXUL* boxInfo = StyleXUL();
+ if (boxInfo->mBoxOrient == StyleBoxOrient::Horizontal) {
+ aIsHorizontal = true;
+ } else {
+ aIsHorizontal = false;
+ }
+}
+
+void nsBoxFrame::GetInitialDirection(bool& aIsNormal) {
+ if (!GetContent()) return;
+
+ if (IsXULHorizontal()) {
+ // For horizontal boxes only, we initialize our value based off the CSS
+ // 'direction' property. This means that BiDI users will end up with
+ // horizontally inverted chrome.
+ //
+ // If text runs RTL then so do we.
+ aIsNormal = StyleVisibility()->mDirection == StyleDirection::Ltr;
+ if (GetContent()->IsElement()) {
+ Element* element = GetContent()->AsElement();
+
+ // Now see if we have an attribute. The attribute overrides
+ // the style system 'direction' property.
+ static Element::AttrValuesArray strings[] = {nsGkAtoms::ltr,
+ nsGkAtoms::rtl, nullptr};
+ int32_t index = element->FindAttrValueIn(
+ kNameSpaceID_None, nsGkAtoms::dir, strings, eCaseMatters);
+ if (index >= 0) {
+ bool values[] = {true, false};
+ aIsNormal = values[index];
+ }
+ }
+ } else {
+ aIsNormal = true; // Assume a normal direction in the vertical case.
+ }
+
+ // Now check the style system to see if we should invert aIsNormal.
+ const nsStyleXUL* boxInfo = StyleXUL();
+ if (boxInfo->mBoxDirection == StyleBoxDirection::Reverse) {
+ aIsNormal = !aIsNormal; // Invert our direction.
+ }
+}
+
+/* Returns true if it was set.
+ */
+bool nsBoxFrame::GetInitialEqualSize(bool& aEqualSize) {
+ // see if we are a vertical or horizontal box.
+ if (!GetContent() || !GetContent()->IsElement()) return false;
+
+ if (GetContent()->AsElement()->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::equalsize,
+ nsGkAtoms::always, eCaseMatters)) {
+ aEqualSize = true;
+ return true;
+ }
+
+ return false;
+}
+
+/* Returns true if it was set.
+ */
+bool nsBoxFrame::GetInitialAutoStretch(bool& aStretch) {
+ if (!GetContent()) return false;
+
+ // Check the CSS box-align property.
+ const nsStyleXUL* boxInfo = StyleXUL();
+ aStretch = (boxInfo->mBoxAlign == StyleBoxAlign::Stretch);
+
+ return true;
+}
+
+void nsBoxFrame::DidReflow(nsPresContext* aPresContext,
+ const ReflowInput* aReflowInput) {
+ nsFrameState preserveBits =
+ mState & (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
+ nsIFrame::DidReflow(aPresContext, aReflowInput);
+ AddStateBits(preserveBits);
+ if (preserveBits & NS_FRAME_IS_DIRTY) {
+ this->MarkSubtreeDirty();
+ }
+}
+
+bool nsBoxFrame::HonorPrintBackgroundSettings() const {
+ return !mContent->IsInNativeAnonymousSubtree() &&
+ nsContainerFrame::HonorPrintBackgroundSettings();
+}
+
+#ifdef DO_NOISY_REFLOW
+static int myCounter = 0;
+static void printSize(char* aDesc, nscoord aSize) {
+ printf(" %s: ", aDesc);
+ if (aSize == NS_UNCONSTRAINEDSIZE) {
+ printf("UC");
+ } else {
+ printf("%d", aSize);
+ }
+}
+#endif
+
+/* virtual */
+nscoord nsBoxFrame::GetMinISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_MIN_INLINE_SIZE(this, result);
+
+ nsBoxLayoutState state(PresContext(), aRenderingContext);
+ nsSize minSize = GetXULMinSize(state);
+
+ // GetXULMinSize returns border-box width, and we want to return content
+ // width. Since Reflow uses the reflow input's border and padding, we
+ // actually just want to subtract what GetXULMinSize added, which is the
+ // result of GetXULBorderAndPadding.
+ nsMargin bp;
+ GetXULBorderAndPadding(bp);
+
+ result = minSize.width - bp.LeftRight();
+ result = std::max(result, 0);
+
+ return result;
+}
+
+/* virtual */
+nscoord nsBoxFrame::GetPrefISize(gfxContext* aRenderingContext) {
+ nscoord result;
+ DISPLAY_PREF_INLINE_SIZE(this, result);
+
+ nsBoxLayoutState state(PresContext(), aRenderingContext);
+ nsSize prefSize = GetXULPrefSize(state);
+
+ // GetXULPrefSize returns border-box width, and we want to return content
+ // width. Since Reflow uses the reflow input's border and padding, we
+ // actually just want to subtract what GetXULPrefSize added, which is the
+ // result of GetXULBorderAndPadding.
+ nsMargin bp;
+ GetXULBorderAndPadding(bp);
+
+ result = prefSize.width - bp.LeftRight();
+ result = std::max(result, 0);
+
+ return result;
+}
+
+void nsBoxFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
+ const ReflowInput& aReflowInput,
+ nsReflowStatus& aStatus) {
+ MarkInReflow();
+ // If you make changes to this method, please keep nsLeafBoxFrame::Reflow
+ // in sync, if the changes are applicable there.
+
+ DO_GLOBAL_REFLOW_COUNT("nsBoxFrame");
+ DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
+ MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
+
+ NS_ASSERTION(
+ aReflowInput.ComputedWidth() >= 0 && aReflowInput.ComputedHeight() >= 0,
+ "Computed Size < 0");
+
+#ifdef DO_NOISY_REFLOW
+ printf(
+ "\n-------------Starting BoxFrame Reflow ----------------------------\n");
+ printf("%p ** nsBF::Reflow %d ", this, myCounter++);
+
+ printSize("AW", aReflowInput.AvailableWidth());
+ printSize("AH", aReflowInput.AvailableHeight());
+ printSize("CW", aReflowInput.ComputedWidth());
+ printSize("CH", aReflowInput.ComputedHeight());
+
+ printf(" *\n");
+
+#endif
+
+ // create the layout state
+ nsBoxLayoutState state(aPresContext, aReflowInput.mRenderingContext,
+ &aReflowInput, aReflowInput.mReflowDepth);
+
+ WritingMode wm = aReflowInput.GetWritingMode();
+ LogicalSize computedSize = aReflowInput.ComputedSize();
+
+ LogicalMargin m = aReflowInput.ComputedLogicalBorderPadding(wm);
+ // GetXULBorderAndPadding(m);
+
+ LogicalSize prefSize(wm);
+
+ // if we are told to layout intrinsic then get our preferred size.
+ NS_ASSERTION(computedSize.ISize(wm) != NS_UNCONSTRAINEDSIZE,
+ "computed inline size should always be computed");
+ if (computedSize.BSize(wm) == NS_UNCONSTRAINEDSIZE) {
+ nsSize physicalPrefSize = GetXULPrefSize(state);
+ nsSize minSize = GetXULMinSize(state);
+ nsSize maxSize = GetXULMaxSize(state);
+ // XXXbz isn't GetXULPrefSize supposed to bounds-check for us?
+ physicalPrefSize = XULBoundsCheck(minSize, physicalPrefSize, maxSize);
+ prefSize = LogicalSize(wm, physicalPrefSize);
+ }
+
+ // get our desiredSize
+ computedSize.ISize(wm) += m.IStart(wm) + m.IEnd(wm);
+
+ if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE) {
+ computedSize.BSize(wm) = prefSize.BSize(wm);
+ // prefSize is border-box but min/max constraints are content-box.
+ nscoord blockDirBorderPadding =
+ aReflowInput.ComputedLogicalBorderPadding(wm).BStartEnd(wm);
+ nscoord contentBSize = computedSize.BSize(wm) - blockDirBorderPadding;
+ // Note: contentHeight might be negative, but that's OK because min-height
+ // is never negative.
+ computedSize.BSize(wm) =
+ aReflowInput.ApplyMinMaxHeight(contentBSize) + blockDirBorderPadding;
+ } else {
+ computedSize.BSize(wm) += m.BStart(wm) + m.BEnd(wm);
+ }
+
+ nsSize physicalSize = computedSize.GetPhysicalSize(wm);
+ nsRect r(mRect.x, mRect.y, physicalSize.width, physicalSize.height);
+
+ SetXULBounds(state, r);
+
+ // layout our children
+ XULLayout(state);
+
+ // ok our child could have gotten bigger. So lets get its bounds
+
+ // get the ascent
+ LogicalSize boxSize = GetLogicalSize(wm);
+ nscoord ascent = boxSize.BSize(wm);
+
+ // getting the ascent could be a lot of work. Don't get it if
+ // we are the root. The viewport doesn't care about it.
+ if (!(mState & NS_STATE_IS_ROOT)) {
+ ascent = GetXULBoxAscent(state);
+ }
+
+ aDesiredSize.SetSize(wm, boxSize);
+ aDesiredSize.SetBlockStartAscent(ascent);
+
+ aDesiredSize.mOverflowAreas = GetOverflowAreas();
+
+#ifdef DO_NOISY_REFLOW
+ {
+ printf("%p ** nsBF(done) W:%d H:%d ", this, aDesiredSize.Width(),
+ aDesiredSize.Height());
+
+ if (maxElementSize) {
+ printf("MW:%d\n", *maxElementWidth);
+ } else {
+ printf("MW:?\n");
+ }
+ }
+#endif
+
+ ReflowAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput, aStatus);
+
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
+}
+
+nsSize nsBoxFrame::GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) {
+ NS_ASSERTION(aBoxLayoutState.GetRenderingContext(),
+ "must have rendering context");
+
+ nsSize size(0, 0);
+ DISPLAY_PREF_SIZE(this, size);
+ if (!XULNeedsRecalc(mPrefSize)) {
+ size = mPrefSize;
+ return size;
+ }
+
+ if (IsXULCollapsed()) return size;
+
+ // if the size was not completely redefined in CSS then ask our children
+ bool widthSet, heightSet;
+ if (!nsIFrame::AddXULPrefSize(this, size, widthSet, heightSet)) {
+ if (mLayoutManager) {
+ nsSize layoutSize = mLayoutManager->GetXULPrefSize(this, aBoxLayoutState);
+ if (!widthSet) size.width = layoutSize.width;
+ if (!heightSet) size.height = layoutSize.height;
+ } else {
+ size = nsIFrame::GetUncachedXULPrefSize(aBoxLayoutState);
+ }
+ }
+
+ nsSize minSize = GetXULMinSize(aBoxLayoutState);
+ nsSize maxSize = GetXULMaxSize(aBoxLayoutState);
+ mPrefSize = XULBoundsCheck(minSize, size, maxSize);
+
+ return mPrefSize;
+}
+
+nscoord nsBoxFrame::GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) {
+ if (!XULNeedsRecalc(mAscent)) {
+ return mAscent;
+ }
+
+ if (IsXULCollapsed()) {
+ return 0;
+ }
+
+ if (mLayoutManager) {
+ mAscent = mLayoutManager->GetAscent(this, aBoxLayoutState);
+ } else {
+ mAscent = GetXULPrefSize(aBoxLayoutState).height;
+ }
+
+ return mAscent;
+}
+
+nsSize nsBoxFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) {
+ NS_ASSERTION(aBoxLayoutState.GetRenderingContext(),
+ "must have rendering context");
+
+ nsSize size(0, 0);
+ DISPLAY_MIN_SIZE(this, size);
+ if (!XULNeedsRecalc(mMinSize)) {
+ size = mMinSize;
+ return size;
+ }
+
+ if (IsXULCollapsed()) return size;
+
+ // if the size was not completely redefined in CSS then ask our children
+ bool widthSet, heightSet;
+ if (!nsIFrame::AddXULMinSize(this, size, widthSet, heightSet)) {
+ if (mLayoutManager) {
+ nsSize layoutSize = mLayoutManager->GetXULMinSize(this, aBoxLayoutState);
+ if (!widthSet) size.width = layoutSize.width;
+ if (!heightSet) size.height = layoutSize.height;
+ } else {
+ size = nsIFrame::GetUncachedXULMinSize(aBoxLayoutState);
+ }
+ }
+
+ mMinSize = size;
+
+ return size;
+}
+
+nsSize nsBoxFrame::GetXULMaxSize(nsBoxLayoutState& aBoxLayoutState) {
+ NS_ASSERTION(aBoxLayoutState.GetRenderingContext(),
+ "must have rendering context");
+
+ nsSize size(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ DISPLAY_MAX_SIZE(this, size);
+ if (!XULNeedsRecalc(mMaxSize)) {
+ size = mMaxSize;
+ return size;
+ }
+
+ if (IsXULCollapsed()) return size;
+
+ // if the size was not completely redefined in CSS then ask our children
+ bool widthSet, heightSet;
+ if (!nsIFrame::AddXULMaxSize(this, size, widthSet, heightSet)) {
+ if (mLayoutManager) {
+ nsSize layoutSize = mLayoutManager->GetXULMaxSize(this, aBoxLayoutState);
+ if (!widthSet) size.width = layoutSize.width;
+ if (!heightSet) size.height = layoutSize.height;
+ } else {
+ size = nsIFrame::GetUncachedXULMaxSize(aBoxLayoutState);
+ }
+ }
+
+ mMaxSize = size;
+
+ return size;
+}
+
+nscoord nsBoxFrame::GetXULFlex() {
+ if (XULNeedsRecalc(mFlex)) {
+ nsIFrame::AddXULFlex(this, mFlex);
+ }
+
+ return mFlex;
+}
+
+/**
+ * If subclassing please subclass this method not layout.
+ * layout will call this method.
+ */
+NS_IMETHODIMP
+nsBoxFrame::DoXULLayout(nsBoxLayoutState& aState) {
+ ReflowChildFlags oldFlags = aState.LayoutFlags();
+ aState.SetLayoutFlags(ReflowChildFlags::Default);
+
+ nsresult rv = NS_OK;
+ if (mLayoutManager) {
+ XULCoordNeedsRecalc(mAscent);
+ rv = mLayoutManager->XULLayout(this, aState);
+ }
+
+ aState.SetLayoutFlags(oldFlags);
+
+ if (HasAbsolutelyPositionedChildren()) {
+ // Set up a |reflowInput| to pass into ReflowAbsoluteFrames
+ WritingMode wm = GetWritingMode();
+ ReflowInput reflowInput(
+ aState.PresContext(), this, aState.GetRenderingContext(),
+ LogicalSize(wm, GetLogicalSize().ISize(wm), NS_UNCONSTRAINEDSIZE));
+
+ // Set up a |desiredSize| to pass into ReflowAbsoluteFrames
+ ReflowOutput desiredSize(reflowInput);
+ desiredSize.Width() = mRect.width;
+ desiredSize.Height() = mRect.height;
+
+ // get the ascent (cribbed from ::Reflow)
+ nscoord ascent = mRect.height;
+
+ // getting the ascent could be a lot of work. Don't get it if
+ // we are the root. The viewport doesn't care about it.
+ if (!(mState & NS_STATE_IS_ROOT)) {
+ ascent = GetXULBoxAscent(aState);
+ }
+ desiredSize.SetBlockStartAscent(ascent);
+ desiredSize.mOverflowAreas = GetOverflowAreas();
+
+ AddStateBits(NS_FRAME_IN_REFLOW);
+ // Set up a |reflowStatus| to pass into ReflowAbsoluteFrames
+ // (just a dummy value; hopefully that's OK)
+ nsReflowStatus reflowStatus;
+ ReflowAbsoluteFrames(aState.PresContext(), desiredSize, reflowInput,
+ reflowStatus);
+ RemoveStateBits(NS_FRAME_IN_REFLOW);
+ }
+
+ return rv;
+}
+
+void nsBoxFrame::DestroyFrom(nsIFrame* aDestructRoot,
+ PostDestroyData& aPostDestroyData) {
+ // unregister access key
+ RegUnregAccessKey(false);
+
+ // clean up the container box's layout manager and child boxes
+ SetXULLayoutManager(nullptr);
+
+ nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
+}
+
+/* virtual */
+void nsBoxFrame::MarkIntrinsicISizesDirty() {
+ XULSizeNeedsRecalc(mPrefSize);
+ XULSizeNeedsRecalc(mMinSize);
+ XULSizeNeedsRecalc(mMaxSize);
+ XULCoordNeedsRecalc(mFlex);
+ XULCoordNeedsRecalc(mAscent);
+
+ if (mLayoutManager) {
+ nsBoxLayoutState state(PresContext());
+ mLayoutManager->IntrinsicISizesDirty(this, state);
+ }
+
+ nsContainerFrame::MarkIntrinsicISizesDirty();
+}
+
+void nsBoxFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
+ MOZ_ASSERT(aListID == kPrincipalList, "We don't support out-of-flow kids");
+
+ nsPresContext* presContext = PresContext();
+ nsBoxLayoutState state(presContext);
+
+ // remove the child frame
+ mFrames.RemoveFrame(aOldFrame);
+
+ // notify the layout manager
+ if (mLayoutManager) mLayoutManager->ChildrenRemoved(this, state, aOldFrame);
+
+ // destroy the child frame
+ aOldFrame->Destroy();
+
+ // mark us dirty and generate a reflow command
+ PresShell()->FrameNeedsReflow(this, IntrinsicDirty::TreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void nsBoxFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
+ const nsLineList::iterator* aPrevFrameLine,
+ nsFrameList& aFrameList) {
+ NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
+ "inserting after sibling frame with different parent");
+ NS_ASSERTION(!aPrevFrame || mFrames.ContainsFrame(aPrevFrame),
+ "inserting after sibling frame not in our child list");
+ MOZ_ASSERT(aListID == kPrincipalList, "We don't support out-of-flow kids");
+
+ nsBoxLayoutState state(PresContext());
+
+ // insert the child frames
+ const nsFrameList::Slice& newFrames =
+ mFrames.InsertFrames(this, aPrevFrame, aFrameList);
+
+ // notify the layout manager
+ if (mLayoutManager)
+ mLayoutManager->ChildrenInserted(this, state, aPrevFrame, newFrames);
+
+ PresShell()->FrameNeedsReflow(this, IntrinsicDirty::TreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+void nsBoxFrame::AppendFrames(ChildListID aListID, nsFrameList& aFrameList) {
+ MOZ_ASSERT(aListID == kPrincipalList, "We don't support out-of-flow kids");
+
+ nsBoxLayoutState state(PresContext());
+
+ // append the new frames
+ const nsFrameList::Slice& newFrames = mFrames.AppendFrames(this, aFrameList);
+
+ // notify the layout manager
+ if (mLayoutManager) mLayoutManager->ChildrenAppended(this, state, newFrames);
+
+ // XXXbz why is this NS_FRAME_FIRST_REFLOW check here?
+ if (!HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ PresShell()->FrameNeedsReflow(this, IntrinsicDirty::TreeChange,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+ }
+}
+
+/* virtual */
+nsContainerFrame* nsBoxFrame::GetContentInsertionFrame() {
+ if (HasAnyStateBits(NS_STATE_BOX_WRAPS_KIDS_IN_BLOCK)) {
+ return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
+ }
+ return nsContainerFrame::GetContentInsertionFrame();
+}
+
+nsresult nsBoxFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) {
+ nsresult rv =
+ nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
+
+ // Ignore 'width', 'height', 'screenX', 'screenY' and 'sizemode' on a
+ // <window>.
+ if (mContent->IsXULElement(nsGkAtoms::window) &&
+ (nsGkAtoms::width == aAttribute || nsGkAtoms::height == aAttribute ||
+ nsGkAtoms::screenX == aAttribute || nsGkAtoms::screenY == aAttribute ||
+ nsGkAtoms::sizemode == aAttribute)) {
+ return rv;
+ }
+
+ if (aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height ||
+ aAttribute == nsGkAtoms::align || aAttribute == nsGkAtoms::valign ||
+ aAttribute == nsGkAtoms::minwidth || aAttribute == nsGkAtoms::maxwidth ||
+ aAttribute == nsGkAtoms::minheight ||
+ aAttribute == nsGkAtoms::maxheight || aAttribute == nsGkAtoms::flex ||
+ aAttribute == nsGkAtoms::orient || aAttribute == nsGkAtoms::pack ||
+ aAttribute == nsGkAtoms::dir || aAttribute == nsGkAtoms::equalsize) {
+ if (aAttribute == nsGkAtoms::align || aAttribute == nsGkAtoms::valign ||
+ aAttribute == nsGkAtoms::orient || aAttribute == nsGkAtoms::pack ||
+ aAttribute == nsGkAtoms::dir) {
+ mValign = nsBoxFrame::vAlign_Top;
+ mHalign = nsBoxFrame::hAlign_Left;
+
+ bool orient = true;
+ GetInitialOrientation(orient);
+ if (orient)
+ AddStateBits(NS_STATE_IS_HORIZONTAL);
+ else
+ RemoveStateBits(NS_STATE_IS_HORIZONTAL);
+
+ bool normal = true;
+ GetInitialDirection(normal);
+ if (normal)
+ AddStateBits(NS_STATE_IS_DIRECTION_NORMAL);
+ else
+ RemoveStateBits(NS_STATE_IS_DIRECTION_NORMAL);
+
+ GetInitialVAlignment(mValign);
+ GetInitialHAlignment(mHalign);
+
+ bool equalSize = false;
+ GetInitialEqualSize(equalSize);
+ if (equalSize)
+ AddStateBits(NS_STATE_EQUAL_SIZE);
+ else
+ RemoveStateBits(NS_STATE_EQUAL_SIZE);
+
+ bool autostretch = !!(mState & NS_STATE_AUTO_STRETCH);
+ GetInitialAutoStretch(autostretch);
+ if (autostretch)
+ AddStateBits(NS_STATE_AUTO_STRETCH);
+ else
+ RemoveStateBits(NS_STATE_AUTO_STRETCH);
+ }
+
+ PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
+ NS_FRAME_IS_DIRTY);
+ }
+ // If the accesskey changed, register for the new value
+ // The old value has been unregistered in nsXULElement::SetAttr
+ else if (aAttribute == nsGkAtoms::accesskey) {
+ RegUnregAccessKey(true);
+ } else if (aAttribute == nsGkAtoms::rows &&
+ mContent->IsXULElement(nsGkAtoms::tree)) {
+ // Reflow ourselves and all our children if "rows" changes, since
+ // nsTreeBodyFrame's layout reads this from its parent (this frame).
+ PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
+ NS_FRAME_IS_DIRTY);
+ }
+
+ return rv;
+}
+
+void nsBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ bool forceLayer = false;
+
+ if (GetContent()->IsXULElement()) {
+ // forcelayer is only supported on XUL elements with box layout
+ if (GetContent()->AsElement()->HasAttr(kNameSpaceID_None,
+ nsGkAtoms::layer)) {
+ forceLayer = true;
+ }
+ // Check for frames that are marked as a part of the region used
+ // in calculating glass margins on Windows.
+ const nsStyleDisplay* styles = StyleDisplay();
+ if (styles &&
+ styles->EffectiveAppearance() == StyleAppearance::MozWinExcludeGlass) {
+ aBuilder->AddWindowExcludeGlassRegion(
+ this, nsRect(aBuilder->ToReferenceFrame(this), GetSize()));
+ }
+ }
+
+ nsDisplayListCollection tempLists(aBuilder);
+ const nsDisplayListSet& destination = (forceLayer) ? tempLists : aLists;
+
+ DisplayBorderBackgroundOutline(aBuilder, destination);
+
+ Maybe<nsDisplayListBuilder::AutoContainerASRTracker> contASRTracker;
+ if (forceLayer) {
+ contASRTracker.emplace(aBuilder);
+ }
+
+ BuildDisplayListForChildren(aBuilder, destination);
+
+ // see if we have to draw a selection frame around this container
+ DisplaySelectionOverlay(aBuilder, destination.Content());
+
+ if (forceLayer) {
+ // This is a bit of a hack. Collect up all descendant display items
+ // and merge them into a single Content() list. This can cause us
+ // to violate CSS stacking order, but forceLayer is a magic
+ // XUL-only extension anyway.
+ nsDisplayList masterList;
+ masterList.AppendToTop(tempLists.BorderBackground());
+ masterList.AppendToTop(tempLists.BlockBorderBackgrounds());
+ masterList.AppendToTop(tempLists.Floats());
+ masterList.AppendToTop(tempLists.Content());
+ masterList.AppendToTop(tempLists.PositionedDescendants());
+ masterList.AppendToTop(tempLists.Outlines());
+ const ActiveScrolledRoot* ownLayerASR = contASRTracker->GetContainerASR();
+ DisplayListClipState::AutoSaveRestore ownLayerClipState(aBuilder);
+
+ // Wrap the list to make it its own layer
+ aLists.Content()->AppendNewToTopWithIndex<nsDisplayOwnLayer>(
+ aBuilder, this, /* aIndex = */ nsDisplayOwnLayer::OwnLayerForBoxFrame,
+ &masterList, ownLayerASR, nsDisplayOwnLayerFlags::None,
+ mozilla::layers::ScrollbarData{}, true, true);
+ }
+}
+
+void nsBoxFrame::BuildDisplayListForChildren(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ // Iterate over the children in CSS order.
+ auto iter = CSSOrderAwareFrameIterator(
+ this, mozilla::layout::kPrincipalList,
+ CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
+ CSSOrderAwareFrameIterator::OrderState::Unknown,
+ CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup);
+ // Put each child's background onto the BlockBorderBackgrounds list
+ // to emulate the existing two-layer XUL painting scheme.
+ nsDisplayListSet set(aLists, aLists.BlockBorderBackgrounds());
+ for (; !iter.AtEnd(); iter.Next()) {
+ BuildDisplayListForChild(aBuilder, iter.get(), set);
+ }
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsBoxFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"Box"_ns, aResult);
+}
+#endif
+
+// If you make changes to this function, check its counterparts
+// in nsTextBoxFrame and nsXULLabelFrame
+void nsBoxFrame::RegUnregAccessKey(bool aDoReg) {
+ MOZ_ASSERT(mContent);
+
+ // only support accesskeys for the following elements
+ if (!mContent->IsAnyOfXULElements(nsGkAtoms::button, nsGkAtoms::toolbarbutton,
+ nsGkAtoms::checkbox, nsGkAtoms::tab,
+ nsGkAtoms::radio)) {
+ return;
+ }
+
+ nsAutoString accessKey;
+ mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey,
+ accessKey);
+
+ if (accessKey.IsEmpty()) return;
+
+ // With a valid PresContext we can get the ESM
+ // and register the access key
+ EventStateManager* esm = PresContext()->EventStateManager();
+
+ uint32_t key = accessKey.First();
+ if (aDoReg)
+ esm->RegisterAccessKey(mContent->AsElement(), key);
+ else
+ esm->UnregisterAccessKey(mContent->AsElement(), key);
+}
+
+void nsBoxFrame::AppendDirectlyOwnedAnonBoxes(nsTArray<OwnedAnonBox>& aResult) {
+ if (HasAnyStateBits(NS_STATE_BOX_WRAPS_KIDS_IN_BLOCK)) {
+ aResult.AppendElement(OwnedAnonBox(PrincipalChildList().FirstChild()));
+ }
+}
+
+nsresult nsBoxFrame::LayoutChildAt(nsBoxLayoutState& aState, nsIFrame* aBox,
+ const nsRect& aRect) {
+ // get the current rect
+ nsRect oldRect(aBox->GetRect());
+ aBox->SetXULBounds(aState, aRect);
+
+ bool layout = aBox->IsSubtreeDirty();
+
+ if (layout ||
+ (oldRect.width != aRect.width || oldRect.height != aRect.height)) {
+ return aBox->XULLayout(aState);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * This wrapper class lets us redirect mouse hits from descendant frames
+ * of a menu to the menu itself, if they didn't specify 'allowevents'.
+ *
+ * The wrapper simply turns a hit on a descendant element
+ * into a hit on the menu itself, unless there is an element between the target
+ * and the menu with the "allowevents" attribute.
+ *
+ * This is used by nsMenuFrame and nsTreeColFrame.
+ *
+ * Note that turning a hit on a descendant element into nullptr, so events
+ * could fall through to the menu background, might be an appealing
+ * simplification but it would mean slightly strange behaviour in some cases,
+ * because grabber wrappers can be created for many individual lists and items,
+ * so the exact fallthrough behaviour would be complex. E.g. an element with
+ * "allowevents" on top of the Content() list could receive the event even if it
+ * was covered by a PositionedDescenants() element without "allowevents". It is
+ * best to never convert a non-null hit into null.
+ */
+// REVIEW: This is roughly of what nsMenuFrame::GetFrameForPoint used to do.
+// I've made 'allowevents' affect child elements because that seems the only
+// reasonable thing to do.
+class nsDisplayXULEventRedirector final : public nsDisplayWrapList {
+ public:
+ nsDisplayXULEventRedirector(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayItem* aItem, nsIFrame* aTargetFrame)
+ : nsDisplayWrapList(aBuilder, aFrame, aItem),
+ mTargetFrame(aTargetFrame) {}
+ nsDisplayXULEventRedirector(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
+ nsDisplayList* aList, nsIFrame* aTargetFrame)
+ : nsDisplayWrapList(aBuilder, aFrame, aList),
+ mTargetFrame(aTargetFrame) {}
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) override;
+ virtual bool ShouldFlattenAway(nsDisplayListBuilder* aBuilder) override {
+ return false;
+ }
+ NS_DISPLAY_DECL_NAME("XULEventRedirector", TYPE_XUL_EVENT_REDIRECTOR)
+ private:
+ nsIFrame* mTargetFrame;
+};
+
+void nsDisplayXULEventRedirector::HitTest(nsDisplayListBuilder* aBuilder,
+ const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) {
+ nsTArray<nsIFrame*> outFrames;
+ mList.HitTest(aBuilder, aRect, aState, &outFrames);
+
+ bool topMostAdded = false;
+ uint32_t localLength = outFrames.Length();
+
+ for (uint32_t i = 0; i < localLength; i++) {
+ for (nsIContent* content = outFrames.ElementAt(i)->GetContent();
+ content && content != mTargetFrame->GetContent();
+ content = content->GetParent()) {
+ if (!content->IsElement() ||
+ !content->AsElement()->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::allowevents,
+ nsGkAtoms::_true, eCaseMatters)) {
+ continue;
+ }
+
+ // Events are allowed on 'frame', so let it go.
+ aOutFrames->AppendElement(outFrames.ElementAt(i));
+ topMostAdded = true;
+ }
+
+ // If there was no hit on the topmost frame or its ancestors,
+ // add the target frame itself as the first candidate (see bug 562554).
+ if (!topMostAdded) {
+ topMostAdded = true;
+ aOutFrames->AppendElement(mTargetFrame);
+ }
+ }
+}
+
+class nsXULEventRedirectorWrapper final : public nsDisplayWrapper {
+ public:
+ explicit nsXULEventRedirectorWrapper(nsIFrame* aTargetFrame)
+ : mTargetFrame(aTargetFrame) {}
+ virtual nsDisplayItem* WrapList(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame,
+ nsDisplayList* aList) override {
+ return MakeDisplayItem<nsDisplayXULEventRedirector>(aBuilder, aFrame, aList,
+ mTargetFrame);
+ }
+ virtual nsDisplayItem* WrapItem(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem) override {
+ return MakeDisplayItem<nsDisplayXULEventRedirector>(
+ aBuilder, aItem->Frame(), aItem, mTargetFrame);
+ }
+
+ private:
+ nsIFrame* mTargetFrame;
+};
+
+void nsBoxFrame::WrapListsInRedirector(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aIn,
+ const nsDisplayListSet& aOut) {
+ nsXULEventRedirectorWrapper wrapper(this);
+ wrapper.WrapLists(aBuilder, this, aIn, aOut);
+}
+
+bool nsBoxFrame::GetEventPoint(WidgetGUIEvent* aEvent, nsPoint& aPoint) {
+ LayoutDeviceIntPoint refPoint;
+ bool res = GetEventPoint(aEvent, refPoint);
+ aPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, refPoint,
+ RelativeTo{this});
+ return res;
+}
+
+bool nsBoxFrame::GetEventPoint(WidgetGUIEvent* aEvent,
+ LayoutDeviceIntPoint& aPoint) {
+ NS_ENSURE_TRUE(aEvent, false);
+
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ if (touchEvent) {
+ // return false if there is more than one touch on the page, or if
+ // we can't find a touch point
+ if (touchEvent->mTouches.Length() != 1) {
+ return false;
+ }
+
+ dom::Touch* touch = touchEvent->mTouches.SafeElementAt(0);
+ if (!touch) {
+ return false;
+ }
+ aPoint = touch->mRefPoint;
+ } else {
+ aPoint = aEvent->mRefPoint;
+ }
+ return true;
+}