michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsBoxLayoutState.h" michael@0: #include "nsBox.h" michael@0: #include "nsBoxFrame.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIContent.h" michael@0: #include "nsContainerFrame.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsFrameManager.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMMozNamedAttrMap.h" michael@0: #include "nsIDOMAttr.h" michael@0: #include "nsITheme.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsBoxLayout.h" michael@0: #include "FrameLayerBuilder.h" michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #ifdef DEBUG_LAYOUT michael@0: int32_t gIndent = 0; michael@0: #endif michael@0: michael@0: #ifdef DEBUG_LAYOUT michael@0: void michael@0: nsBoxAddIndents() michael@0: { michael@0: for(int32_t i=0; i < gIndent; i++) michael@0: { michael@0: printf(" "); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: #ifdef DEBUG_LAYOUT michael@0: void michael@0: nsBox::AppendAttribute(const nsAutoString& aAttribute, const nsAutoString& aValue, nsAutoString& aResult) michael@0: { michael@0: aResult.Append(aAttribute); michael@0: aResult.AppendLiteral("='"); michael@0: aResult.Append(aValue); michael@0: aResult.AppendLiteral("' "); michael@0: } michael@0: michael@0: void michael@0: nsBox::ListBox(nsAutoString& aResult) michael@0: { michael@0: nsAutoString name; michael@0: GetBoxName(name); michael@0: michael@0: char addr[100]; michael@0: sprintf(addr, "[@%p] ", static_cast(this)); michael@0: michael@0: aResult.AppendASCII(addr); michael@0: aResult.Append(name); michael@0: aResult.AppendLiteral(" "); michael@0: michael@0: nsIContent* content = GetContent(); michael@0: michael@0: // add on all the set attributes michael@0: if (content) { michael@0: nsCOMPtr node(do_QueryInterface(content)); michael@0: nsCOMPtr namedMap; michael@0: michael@0: node->GetAttributes(getter_AddRefs(namedMap)); michael@0: uint32_t length; michael@0: namedMap->GetLength(&length); michael@0: michael@0: nsCOMPtr attribute; michael@0: for (uint32_t i = 0; i < length; ++i) michael@0: { michael@0: namedMap->Item(i, getter_AddRefs(attribute)); michael@0: attribute->GetName(name); michael@0: nsAutoString value; michael@0: attribute->GetValue(value); michael@0: AppendAttribute(name, value, aResult); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsBox::DumpBox(FILE* aFile) michael@0: { michael@0: nsAutoString s; michael@0: ListBox(s); michael@0: fprintf(aFile, "%s", NS_LossyConvertUTF16toASCII(s).get()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsBox::PropagateDebug(nsBoxLayoutState& aState) michael@0: { michael@0: // propagate debug information michael@0: if (mState & NS_STATE_DEBUG_WAS_SET) { michael@0: if (mState & NS_STATE_SET_TO_DEBUG) michael@0: SetDebug(aState, true); michael@0: else michael@0: SetDebug(aState, false); michael@0: } else if (mState & NS_STATE_IS_ROOT) { michael@0: SetDebug(aState, gDebug); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: #ifdef DEBUG_LAYOUT michael@0: void michael@0: nsBox::GetBoxName(nsAutoString& aName) michael@0: { michael@0: aName.AssignLiteral("Box"); michael@0: } michael@0: #endif michael@0: michael@0: nsresult michael@0: nsBox::BeginLayout(nsBoxLayoutState& aState) michael@0: { michael@0: #ifdef DEBUG_LAYOUT michael@0: michael@0: nsBoxAddIndents(); michael@0: printf("Layout: "); michael@0: DumpBox(stdout); michael@0: printf("\n"); michael@0: gIndent++; michael@0: #endif michael@0: michael@0: // mark ourselves as dirty so no child under us michael@0: // can post an incremental layout. michael@0: // XXXldb Is this still needed? michael@0: mState |= NS_FRAME_HAS_DIRTY_CHILDREN; michael@0: michael@0: if (GetStateBits() & NS_FRAME_IS_DIRTY) michael@0: { michael@0: // If the parent is dirty, all the children are dirty (nsHTMLReflowState michael@0: // does this too). michael@0: nsIFrame* box; michael@0: for (box = GetChildBox(); box; box = box->GetNextBox()) michael@0: box->AddStateBits(NS_FRAME_IS_DIRTY); michael@0: } michael@0: michael@0: // Another copy-over from nsHTMLReflowState. michael@0: // Since we are in reflow, we don't need to store these properties anymore. michael@0: FrameProperties props = Properties(); michael@0: props.Delete(UsedBorderProperty()); michael@0: props.Delete(UsedPaddingProperty()); michael@0: props.Delete(UsedMarginProperty()); michael@0: michael@0: #ifdef DEBUG_LAYOUT michael@0: PropagateDebug(aState); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsBox::DoLayout(nsBoxLayoutState& aState) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsBox::EndLayout(nsBoxLayoutState& aState) michael@0: { michael@0: michael@0: #ifdef DEBUG_LAYOUT michael@0: --gIndent; michael@0: #endif michael@0: michael@0: return SyncLayout(aState); michael@0: } michael@0: michael@0: bool nsBox::gGotTheme = false; michael@0: nsITheme* nsBox::gTheme = nullptr; michael@0: michael@0: nsBox::nsBox() michael@0: { michael@0: MOZ_COUNT_CTOR(nsBox); michael@0: //mX = 0; michael@0: //mY = 0; michael@0: if (!gGotTheme) { michael@0: gGotTheme = true; michael@0: CallGetService("@mozilla.org/chrome/chrome-native-theme;1", &gTheme); michael@0: } michael@0: } michael@0: michael@0: nsBox::~nsBox() michael@0: { michael@0: // NOTE: This currently doesn't get called for |nsBoxToBlockAdaptor| michael@0: // objects, so don't rely on putting anything here. michael@0: MOZ_COUNT_DTOR(nsBox); michael@0: } michael@0: michael@0: /* static */ void michael@0: nsBox::Shutdown() michael@0: { michael@0: gGotTheme = false; michael@0: NS_IF_RELEASE(gTheme); michael@0: } michael@0: michael@0: nsresult michael@0: nsBox::RelayoutChildAtOrdinal(nsBoxLayoutState& aState, nsIFrame* aChild) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsIFrame::GetClientRect(nsRect& aClientRect) michael@0: { michael@0: aClientRect = mRect; michael@0: aClientRect.MoveTo(0,0); michael@0: michael@0: nsMargin borderPadding; michael@0: GetBorderAndPadding(borderPadding); michael@0: michael@0: aClientRect.Deflate(borderPadding); michael@0: michael@0: if (aClientRect.width < 0) michael@0: aClientRect.width = 0; michael@0: michael@0: if (aClientRect.height < 0) michael@0: aClientRect.height = 0; michael@0: michael@0: // NS_ASSERTION(aClientRect.width >=0 && aClientRect.height >= 0, "Content Size < 0"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsBox::SetBounds(nsBoxLayoutState& aState, const nsRect& aRect, bool aRemoveOverflowAreas) michael@0: { michael@0: NS_BOX_ASSERTION(this, aRect.width >=0 && aRect.height >= 0, "SetBounds Size < 0"); michael@0: michael@0: nsRect rect(mRect); michael@0: michael@0: uint32_t flags = 0; michael@0: GetLayoutFlags(flags); michael@0: michael@0: uint32_t stateFlags = aState.LayoutFlags(); michael@0: michael@0: flags |= stateFlags; michael@0: michael@0: if ((flags & NS_FRAME_NO_MOVE_FRAME) == NS_FRAME_NO_MOVE_FRAME) michael@0: SetSize(aRect.Size()); michael@0: else michael@0: SetRect(aRect); michael@0: michael@0: // Nuke the overflow area. The caller is responsible for restoring michael@0: // it if necessary. michael@0: if (aRemoveOverflowAreas) { michael@0: // remove the previously stored overflow area michael@0: ClearOverflowRects(); michael@0: } michael@0: michael@0: if (!(flags & NS_FRAME_NO_MOVE_VIEW)) michael@0: { michael@0: nsContainerFrame::PositionFrameView(this); michael@0: if ((rect.x != aRect.x) || (rect.y != aRect.y)) michael@0: nsContainerFrame::PositionChildViews(this); michael@0: } michael@0: michael@0: michael@0: /* michael@0: // only if the origin changed michael@0: if ((rect.x != aRect.x) || (rect.y != aRect.y)) { michael@0: if (frame->HasView()) { michael@0: nsContainerFrame::PositionFrameView(presContext, frame, michael@0: frame->GetView()); michael@0: } else { michael@0: nsContainerFrame::PositionChildViews(presContext, frame); michael@0: } michael@0: } michael@0: */ michael@0: } michael@0: michael@0: void michael@0: nsBox::GetLayoutFlags(uint32_t& aFlags) michael@0: { michael@0: aFlags = 0; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsIFrame::GetBorderAndPadding(nsMargin& aBorderAndPadding) michael@0: { michael@0: aBorderAndPadding.SizeTo(0, 0, 0, 0); michael@0: nsresult rv = GetBorder(aBorderAndPadding); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsMargin padding; michael@0: rv = GetPadding(padding); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: aBorderAndPadding += padding; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsBox::GetBorder(nsMargin& aMargin) michael@0: { michael@0: aMargin.SizeTo(0,0,0,0); michael@0: michael@0: const nsStyleDisplay* disp = StyleDisplay(); michael@0: if (disp->mAppearance && gTheme) { michael@0: // Go to the theme for the border. michael@0: nsPresContext *context = PresContext(); michael@0: if (gTheme->ThemeSupportsWidget(context, this, disp->mAppearance)) { michael@0: nsIntMargin margin(0, 0, 0, 0); michael@0: gTheme->GetWidgetBorder(context->DeviceContext(), this, michael@0: disp->mAppearance, &margin); michael@0: aMargin.top = context->DevPixelsToAppUnits(margin.top); michael@0: aMargin.right = context->DevPixelsToAppUnits(margin.right); michael@0: aMargin.bottom = context->DevPixelsToAppUnits(margin.bottom); michael@0: aMargin.left = context->DevPixelsToAppUnits(margin.left); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: aMargin = StyleBorder()->GetComputedBorder(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsBox::GetPadding(nsMargin& aMargin) michael@0: { michael@0: const nsStyleDisplay *disp = StyleDisplay(); michael@0: if (disp->mAppearance && gTheme) { michael@0: // Go to the theme for the padding. michael@0: nsPresContext *context = PresContext(); michael@0: if (gTheme->ThemeSupportsWidget(context, this, disp->mAppearance)) { michael@0: nsIntMargin margin(0, 0, 0, 0); michael@0: bool useThemePadding; michael@0: michael@0: useThemePadding = gTheme->GetWidgetPadding(context->DeviceContext(), michael@0: this, disp->mAppearance, michael@0: &margin); michael@0: if (useThemePadding) { michael@0: aMargin.top = context->DevPixelsToAppUnits(margin.top); michael@0: aMargin.right = context->DevPixelsToAppUnits(margin.right); michael@0: aMargin.bottom = context->DevPixelsToAppUnits(margin.bottom); michael@0: aMargin.left = context->DevPixelsToAppUnits(margin.left); michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: michael@0: aMargin.SizeTo(0,0,0,0); michael@0: StylePadding()->GetPadding(aMargin); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsBox::GetMargin(nsMargin& aMargin) michael@0: { michael@0: aMargin.SizeTo(0,0,0,0); michael@0: StyleMargin()->GetMargin(aMargin); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsBox::SizeNeedsRecalc(nsSize& aSize) michael@0: { michael@0: aSize.width = -1; michael@0: aSize.height = -1; michael@0: } michael@0: michael@0: void michael@0: nsBox::CoordNeedsRecalc(nscoord& aFlex) michael@0: { michael@0: aFlex = -1; michael@0: } michael@0: michael@0: bool michael@0: nsBox::DoesNeedRecalc(const nsSize& aSize) michael@0: { michael@0: return (aSize.width == -1 || aSize.height == -1); michael@0: } michael@0: michael@0: bool michael@0: nsBox::DoesNeedRecalc(nscoord aCoord) michael@0: { michael@0: return (aCoord == -1); michael@0: } michael@0: michael@0: nsSize michael@0: nsBox::GetPrefSize(nsBoxLayoutState& aState) michael@0: { michael@0: NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context"); michael@0: michael@0: nsSize pref(0,0); michael@0: DISPLAY_PREF_SIZE(this, pref); michael@0: michael@0: if (IsCollapsed()) michael@0: return pref; michael@0: michael@0: AddBorderAndPadding(pref); michael@0: bool widthSet, heightSet; michael@0: nsIFrame::AddCSSPrefSize(this, pref, widthSet, heightSet); michael@0: michael@0: nsSize minSize = GetMinSize(aState); michael@0: nsSize maxSize = GetMaxSize(aState); michael@0: return BoundsCheck(minSize, pref, maxSize); michael@0: } michael@0: michael@0: nsSize michael@0: nsBox::GetMinSize(nsBoxLayoutState& aState) michael@0: { michael@0: NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context"); michael@0: michael@0: nsSize min(0,0); michael@0: DISPLAY_MIN_SIZE(this, min); michael@0: michael@0: if (IsCollapsed()) michael@0: return min; michael@0: michael@0: AddBorderAndPadding(min); michael@0: bool widthSet, heightSet; michael@0: nsIFrame::AddCSSMinSize(aState, this, min, widthSet, heightSet); michael@0: return min; michael@0: } michael@0: michael@0: nsSize michael@0: nsBox::GetMinSizeForScrollArea(nsBoxLayoutState& aBoxLayoutState) michael@0: { michael@0: return nsSize(0, 0); michael@0: } michael@0: michael@0: nsSize michael@0: nsBox::GetMaxSize(nsBoxLayoutState& aState) michael@0: { michael@0: NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context"); michael@0: michael@0: nsSize maxSize(NS_INTRINSICSIZE, NS_INTRINSICSIZE); michael@0: DISPLAY_MAX_SIZE(this, maxSize); michael@0: michael@0: if (IsCollapsed()) michael@0: return maxSize; michael@0: michael@0: AddBorderAndPadding(maxSize); michael@0: bool widthSet, heightSet; michael@0: nsIFrame::AddCSSMaxSize(this, maxSize, widthSet, heightSet); michael@0: return maxSize; michael@0: } michael@0: michael@0: nscoord michael@0: nsBox::GetFlex(nsBoxLayoutState& aState) michael@0: { michael@0: nscoord flex = 0; michael@0: michael@0: nsIFrame::AddCSSFlex(aState, this, flex); michael@0: michael@0: return flex; michael@0: } michael@0: michael@0: uint32_t michael@0: nsIFrame::GetOrdinal() michael@0: { michael@0: uint32_t ordinal = StyleXUL()->mBoxOrdinal; michael@0: michael@0: // When present, attribute value overrides CSS. michael@0: nsIContent* content = GetContent(); michael@0: if (content && content->IsXUL()) { michael@0: nsresult error; michael@0: nsAutoString value; michael@0: michael@0: content->GetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, value); michael@0: if (!value.IsEmpty()) { michael@0: ordinal = value.ToInteger(&error); michael@0: } michael@0: } michael@0: michael@0: return ordinal; michael@0: } michael@0: michael@0: nscoord michael@0: nsBox::GetBoxAscent(nsBoxLayoutState& aState) michael@0: { michael@0: if (IsCollapsed()) michael@0: return 0; michael@0: michael@0: return GetPrefSize(aState).height; michael@0: } michael@0: michael@0: bool michael@0: nsBox::IsCollapsed() michael@0: { michael@0: return StyleVisibility()->mVisible == NS_STYLE_VISIBILITY_COLLAPSE; michael@0: } michael@0: michael@0: nsresult michael@0: nsIFrame::Layout(nsBoxLayoutState& aState) michael@0: { michael@0: NS_ASSERTION(aState.GetRenderingContext(), "must have rendering context"); michael@0: michael@0: nsBox *box = static_cast(this); michael@0: DISPLAY_LAYOUT(box); michael@0: michael@0: box->BeginLayout(aState); michael@0: michael@0: box->DoLayout(aState); michael@0: michael@0: box->EndLayout(aState); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsBox::DoesClipChildren() michael@0: { michael@0: const nsStyleDisplay* display = StyleDisplay(); michael@0: NS_ASSERTION((display->mOverflowY == NS_STYLE_OVERFLOW_CLIP) == michael@0: (display->mOverflowX == NS_STYLE_OVERFLOW_CLIP), michael@0: "If one overflow is clip, the other should be too"); michael@0: return display->mOverflowX == NS_STYLE_OVERFLOW_CLIP; michael@0: } michael@0: michael@0: nsresult michael@0: nsBox::SyncLayout(nsBoxLayoutState& aState) michael@0: { michael@0: /* michael@0: if (IsCollapsed()) { michael@0: CollapseChild(aState, this, true); michael@0: return NS_OK; michael@0: } michael@0: */ michael@0: michael@0: michael@0: if (GetStateBits() & NS_FRAME_IS_DIRTY) michael@0: Redraw(aState); michael@0: michael@0: RemoveStateBits(NS_FRAME_HAS_DIRTY_CHILDREN | NS_FRAME_IS_DIRTY michael@0: | NS_FRAME_FIRST_REFLOW | NS_FRAME_IN_REFLOW); michael@0: michael@0: nsPresContext* presContext = aState.PresContext(); michael@0: michael@0: uint32_t flags = 0; michael@0: GetLayoutFlags(flags); michael@0: michael@0: uint32_t stateFlags = aState.LayoutFlags(); michael@0: michael@0: flags |= stateFlags; michael@0: michael@0: nsRect visualOverflow; michael@0: michael@0: if (ComputesOwnOverflowArea()) { michael@0: visualOverflow = GetVisualOverflowRect(); michael@0: } michael@0: else { michael@0: nsRect rect(nsPoint(0, 0), GetSize()); michael@0: nsOverflowAreas overflowAreas(rect, rect); michael@0: if (!DoesClipChildren() && !IsCollapsed()) { michael@0: // See if our child frames caused us to overflow after being laid michael@0: // out. If so, store the overflow area. This normally can't happen michael@0: // in XUL, but it can happen with the CSS 'outline' property and michael@0: // possibly with other exotic stuff (e.g. relatively positioned michael@0: // frames in HTML inside XUL). michael@0: nsLayoutUtils::UnionChildOverflow(this, overflowAreas); michael@0: } michael@0: michael@0: FinishAndStoreOverflow(overflowAreas, GetSize()); michael@0: visualOverflow = overflowAreas.VisualOverflow(); michael@0: } michael@0: michael@0: nsView* view = GetView(); michael@0: if (view) { michael@0: // Make sure the frame's view is properly sized and positioned and has michael@0: // things like opacity correct michael@0: nsContainerFrame::SyncFrameViewAfterReflow(presContext, this, view, michael@0: visualOverflow, flags); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsIFrame::Redraw(nsBoxLayoutState& aState) michael@0: { michael@0: if (aState.PaintingDisabled()) michael@0: return NS_OK; michael@0: michael@0: // nsStackLayout, at least, expects us to repaint descendants even michael@0: // if a damage rect is provided michael@0: InvalidateFrameSubtree(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsIFrame::AddCSSPrefSize(nsIFrame* aBox, nsSize& aSize, bool &aWidthSet, bool &aHeightSet) michael@0: { michael@0: aWidthSet = false; michael@0: aHeightSet = false; michael@0: michael@0: // add in the css min, max, pref michael@0: const nsStylePosition* position = aBox->StylePosition(); michael@0: michael@0: // see if the width or height was specifically set michael@0: // XXX Handle eStyleUnit_Enumerated? michael@0: // (Handling the eStyleUnit_Enumerated types requires michael@0: // GetPrefSize/GetMinSize methods that don't consider michael@0: // (min-/max-/)(width/height) properties.) michael@0: const nsStyleCoord &width = position->mWidth; michael@0: if (width.GetUnit() == eStyleUnit_Coord) { michael@0: aSize.width = width.GetCoordValue(); michael@0: aWidthSet = true; michael@0: } else if (width.IsCalcUnit()) { michael@0: if (!width.CalcHasPercent()) { michael@0: // pass 0 for percentage basis since we know there are no %s michael@0: aSize.width = nsRuleNode::ComputeComputedCalc(width, 0); michael@0: if (aSize.width < 0) michael@0: aSize.width = 0; michael@0: aWidthSet = true; michael@0: } michael@0: } michael@0: michael@0: const nsStyleCoord &height = position->mHeight; michael@0: if (height.GetUnit() == eStyleUnit_Coord) { michael@0: aSize.height = height.GetCoordValue(); michael@0: aHeightSet = true; michael@0: } else if (height.IsCalcUnit()) { michael@0: if (!height.CalcHasPercent()) { michael@0: // pass 0 for percentage basis since we know there are no %s michael@0: aSize.height = nsRuleNode::ComputeComputedCalc(height, 0); michael@0: if (aSize.height < 0) michael@0: aSize.height = 0; michael@0: aHeightSet = true; michael@0: } michael@0: } michael@0: michael@0: nsIContent* content = aBox->GetContent(); michael@0: // ignore 'height' and 'width' attributes if the actual element is not XUL michael@0: // For example, we might be magic XUL frames whose primary content is an HTML michael@0: //