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