gecko/layout/tables/nsTableCellFrame.cpp

1196 lines
39 KiB
C++
Raw Normal View History

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Pierre Phaneuf <pp@ludusdesign.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsTableFrame.h"
#include "nsTableColFrame.h"
#include "nsTableCellFrame.h"
#include "nsTableFrame.h"
#include "nsTableRowGroupFrame.h"
#include "nsTablePainter.h"
#include "nsStyleContext.h"
#include "nsStyleConsts.h"
#include "nsPresContext.h"
#include "nsIRenderingContext.h"
#include "nsCSSRendering.h"
#include "nsIContent.h"
#include "nsGenericHTMLElement.h"
#include "nsHTMLParts.h"
#include "nsGkAtoms.h"
#include "nsIPresShell.h"
#include "nsCOMPtr.h"
#include "nsIDOMHTMLTableCellElement.h"
#ifdef ACCESSIBILITY
#include "nsIAccessibilityService.h"
#endif
#include "nsIServiceManager.h"
#include "nsIDOMNode.h"
#include "nsINameSpaceManager.h"
#include "nsDisplayList.h"
#include "nsLayoutUtils.h"
#include "nsTextFrame.h"
//TABLECELL SELECTION
#include "nsFrameSelection.h"
#include "nsILookAndFeel.h"
nsTableCellFrame::nsTableCellFrame(nsStyleContext* aContext) :
nsHTMLContainerFrame(aContext)
{
mColIndex = 0;
mPriorAvailWidth = 0;
SetContentEmpty(PR_FALSE);
SetHasPctOverHeight(PR_FALSE);
}
nsTableCellFrame::~nsTableCellFrame()
{
}
NS_IMPL_FRAMEARENA_HELPERS(nsTableCellFrame)
nsTableCellFrame*
nsTableCellFrame::GetNextCell() const
{
nsIFrame* childFrame = GetNextSibling();
while (childFrame) {
nsTableCellFrame *cellFrame = do_QueryFrame(childFrame);
if (cellFrame) {
return cellFrame;
}
childFrame = childFrame->GetNextSibling();
}
return nsnull;
}
NS_IMETHODIMP
nsTableCellFrame::Init(nsIContent* aContent,
nsIFrame* aParent,
nsIFrame* aPrevInFlow)
{
// Let the base class do its initialization
nsresult rv = nsHTMLContainerFrame::Init(aContent, aParent, aPrevInFlow);
if (aPrevInFlow) {
// Set the column index
nsTableCellFrame* cellFrame = (nsTableCellFrame*)aPrevInFlow;
PRInt32 colIndex;
cellFrame->GetColIndex(colIndex);
SetColIndex(colIndex);
}
return rv;
}
// nsIPercentHeightObserver methods
void
nsTableCellFrame::NotifyPercentHeight(const nsHTMLReflowState& aReflowState)
{
// nsHTMLReflowState ensures the mCBReflowState of blocks inside a
// cell is the cell frame, not the inner-cell block, and that the
// containing block of an inner table is the containing block of its
// outer table.
// XXXldb Given the now-stricter |NeedsToObserve|, many if not all of
// these tests are probably unnecessary.
// Maybe the cell reflow state; we sure if we're inside the |if|.
const nsHTMLReflowState *cellRS = aReflowState.mCBReflowState;
if (cellRS && cellRS->frame == this &&
(cellRS->ComputedHeight() == NS_UNCONSTRAINEDSIZE ||
cellRS->ComputedHeight() == 0)) { // XXXldb Why 0?
// This is a percentage height on a frame whose percentage heights
// are based on the height of the cell, since its containing block
// is the inner cell frame.
// We'll only honor the percent height if sibling-cells/ancestors
// have specified/pct height. (Also, siblings only count for this if
// both this cell and the sibling cell span exactly 1 row.)
if (nsTableFrame::AncestorsHaveStyleHeight(*cellRS) ||
(nsTableFrame::GetTableFrame(this)->GetEffectiveRowSpan(*this) == 1 &&
(cellRS->parentReflowState->frame->GetStateBits() &
NS_ROW_HAS_CELL_WITH_STYLE_HEIGHT))) {
for (const nsHTMLReflowState *rs = aReflowState.parentReflowState;
rs != cellRS;
rs = rs->parentReflowState) {
rs->frame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_HEIGHT);
}
nsTableFrame::RequestSpecialHeightReflow(*cellRS);
}
}
}
// The cell needs to observe its block and things inside its block but nothing below that
PRBool
nsTableCellFrame::NeedsToObserve(const nsHTMLReflowState& aReflowState)
{
const nsHTMLReflowState *rs = aReflowState.parentReflowState;
if (!rs)
return PR_FALSE;
if (rs->frame == this) {
// We always observe the child block. It will never send any
// notifications, but we need this so that the observer gets
// propagated to its kids.
return PR_TRUE;
}
rs = rs->parentReflowState;
if (!rs) {
return PR_FALSE;
}
// We always need to let the percent height observer be propagated
// from an outer table frame to an inner table frame.
nsIAtom *fType = aReflowState.frame->GetType();
if (fType == nsGkAtoms::tableFrame) {
return PR_TRUE;
}
// We need the observer to be propagated to all children of the cell
// (i.e., children of the child block) in quirks mode, but only to
// tables in standards mode.
return rs->frame == this &&
(PresContext()->CompatibilityMode() == eCompatibility_NavQuirks ||
fType == nsGkAtoms::tableOuterFrame);
}
nsresult
nsTableCellFrame::GetRowIndex(PRInt32 &aRowIndex) const
{
nsresult result;
nsTableRowFrame* row = static_cast<nsTableRowFrame*>(GetParent());
if (row) {
aRowIndex = row->GetRowIndex();
result = NS_OK;
}
else {
aRowIndex = 0;
result = NS_ERROR_NOT_INITIALIZED;
}
return result;
}
nsresult
nsTableCellFrame::GetColIndex(PRInt32 &aColIndex) const
{
if (GetPrevInFlow()) {
return ((nsTableCellFrame*)GetFirstInFlow())->GetColIndex(aColIndex);
}
else {
aColIndex = mColIndex;
return NS_OK;
}
}
NS_IMETHODIMP
nsTableCellFrame::AttributeChanged(PRInt32 aNameSpaceID,
nsIAtom* aAttribute,
PRInt32 aModType)
{
// We need to recalculate in this case because of the nowrap quirk in
// BasicTableLayoutStrategy
if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::nowrap &&
PresContext()->CompatibilityMode() == eCompatibility_NavQuirks) {
PresContext()->PresShell()->
FrameNeedsReflow(this, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY);
}
// let the table frame decide what to do
nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this);
if (tableFrame) {
tableFrame->AttributeChangedFor(this, mContent, aAttribute);
}
return NS_OK;
}
/* virtual */ void
nsTableCellFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
{
if (!aOldStyleContext) //avoid this on init
return;
nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this);
if (tableFrame->IsBorderCollapse() &&
tableFrame->BCRecalcNeeded(aOldStyleContext, GetStyleContext())) {
PRInt32 colIndex, rowIndex;
GetColIndex(colIndex);
GetRowIndex(rowIndex);
nsRect damageArea(colIndex, rowIndex, GetColSpan(), GetRowSpan());
tableFrame->SetBCDamageArea(damageArea);
}
}
NS_IMETHODIMP
nsTableCellFrame::AppendFrames(nsIAtom* aListName,
nsFrameList& aFrameList)
{
NS_PRECONDITION(PR_FALSE, "unsupported operation");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsTableCellFrame::InsertFrames(nsIAtom* aListName,
nsIFrame* aPrevFrame,
nsFrameList& aFrameList)
{
NS_PRECONDITION(PR_FALSE, "unsupported operation");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsTableCellFrame::RemoveFrame(nsIAtom* aListName,
nsIFrame* aOldFrame)
{
NS_PRECONDITION(PR_FALSE, "unsupported operation");
return NS_ERROR_NOT_IMPLEMENTED;
}
void nsTableCellFrame::SetColIndex(PRInt32 aColIndex)
{
mColIndex = aColIndex;
}
/* virtual */ nsMargin
nsTableCellFrame::GetUsedMargin() const
{
return nsMargin(0,0,0,0);
}
//ASSURE DIFFERENT COLORS for selection
inline nscolor EnsureDifferentColors(nscolor colorA, nscolor colorB)
{
if (colorA == colorB)
{
nscolor res;
res = NS_RGB(NS_GET_R(colorA) ^ 0xff,
NS_GET_G(colorA) ^ 0xff,
NS_GET_B(colorA) ^ 0xff);
return res;
}
return colorA;
}
void
nsTableCellFrame::DecorateForSelection(nsIRenderingContext& aRenderingContext,
nsPoint aPt)
{
NS_ASSERTION(GetStateBits() & NS_FRAME_SELECTED_CONTENT,
"Should only be called for selected cells");
PRInt16 displaySelection;
nsPresContext* presContext = PresContext();
displaySelection = DisplaySelection(presContext);
if (displaySelection) {
nsCOMPtr<nsFrameSelection> frameSelection =
presContext->PresShell()->FrameSelection();
if (frameSelection->GetTableCellSelection()) {
nscolor bordercolor;
if (displaySelection == nsISelectionController::SELECTION_DISABLED) {
bordercolor = NS_RGB(176,176,176);// disabled color
}
else {
presContext->LookAndFeel()->
GetColor(nsILookAndFeel::eColor_TextSelectBackground,
bordercolor);
}
nscoord threePx = nsPresContext::CSSPixelsToAppUnits(3);
if ((mRect.width > threePx) && (mRect.height > threePx))
{
//compare bordercolor to ((nsStyleColor *)myColor)->mBackgroundColor)
bordercolor = EnsureDifferentColors(bordercolor,
GetStyleBackground()->mBackgroundColor);
nsIRenderingContext::AutoPushTranslation
translate(&aRenderingContext, aPt.x, aPt.y);
nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
aRenderingContext.SetColor(bordercolor);
aRenderingContext.DrawLine(onePixel, 0, mRect.width, 0);
aRenderingContext.DrawLine(0, onePixel, 0, mRect.height);
aRenderingContext.DrawLine(onePixel, mRect.height, mRect.width, mRect.height);
aRenderingContext.DrawLine(mRect.width, onePixel, mRect.width, mRect.height);
//middle
aRenderingContext.DrawRect(onePixel, onePixel, mRect.width-onePixel,
mRect.height-onePixel);
//shading
aRenderingContext.DrawLine(2*onePixel, mRect.height-2*onePixel,
mRect.width-onePixel, mRect.height- (2*onePixel));
aRenderingContext.DrawLine(mRect.width - (2*onePixel), 2*onePixel,
mRect.width - (2*onePixel), mRect.height-onePixel);
}
}
}
}
void
nsTableCellFrame::PaintBackground(nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect,
nsPoint aPt,
PRUint32 aFlags)
{
nsRect rect(aPt, GetSize());
nsCSSRendering::PaintBackground(PresContext(), aRenderingContext, this,
aDirtyRect, rect, aFlags);
}
// Called by nsTablePainter
void
nsTableCellFrame::PaintCellBackground(nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect, nsPoint aPt,
PRUint32 aFlags)
{
if (!GetStyleVisibility()->IsVisible())
return;
PaintBackground(aRenderingContext, aDirtyRect, aPt, aFlags);
}
class nsDisplayTableCellBackground : public nsDisplayTableItem {
public:
nsDisplayTableCellBackground(nsDisplayListBuilder* aBuilder,
nsTableCellFrame* aFrame) :
nsDisplayTableItem(aBuilder, aFrame) {
MOZ_COUNT_CTOR(nsDisplayTableCellBackground);
}
#ifdef NS_BUILD_REFCNT_LOGGING
virtual ~nsDisplayTableCellBackground() {
MOZ_COUNT_DTOR(nsDisplayTableCellBackground);
}
#endif
virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) {
aOutFrames->AppendElement(mFrame);
}
virtual void Paint(nsDisplayListBuilder* aBuilder,
nsIRenderingContext* aCtx);
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder);
NS_DISPLAY_DECL_NAME("TableCellBackground", TYPE_TABLE_CELL_BACKGROUND)
};
void nsDisplayTableCellBackground::Paint(nsDisplayListBuilder* aBuilder,
nsIRenderingContext* aCtx)
{
static_cast<nsTableCellFrame*>(mFrame)->
PaintBackground(*aCtx, mVisibleRect, ToReferenceFrame(),
aBuilder->GetBackgroundPaintFlags());
}
nsRect
nsDisplayTableCellBackground::GetBounds(nsDisplayListBuilder* aBuilder)
{
// revert from nsDisplayTableItem's implementation ... cell backgrounds
// don't overflow the cell
return nsDisplayItem::GetBounds(aBuilder);
}
static void
PaintTableCellSelection(nsIFrame* aFrame, nsIRenderingContext* aCtx,
const nsRect& aRect, nsPoint aPt)
{
static_cast<nsTableCellFrame*>(aFrame)->DecorateForSelection(*aCtx, aPt);
}
NS_IMETHODIMP
nsTableCellFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsRect& aDirtyRect,
const nsDisplayListSet& aLists)
{
if (!IsVisibleInSelection(aBuilder))
return NS_OK;
DO_GLOBAL_REFLOW_COUNT_DSP("nsTableCellFrame");
nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this);
PRInt32 emptyCellStyle = GetContentEmpty() && !tableFrame->IsBorderCollapse() ?
GetStyleTableBorder()->mEmptyCells
: NS_STYLE_TABLE_EMPTY_CELLS_SHOW;
// take account of 'empty-cells'
if (GetStyleVisibility()->IsVisible() &&
(NS_STYLE_TABLE_EMPTY_CELLS_HIDE != emptyCellStyle)) {
PRBool isRoot = aBuilder->IsAtRootOfPseudoStackingContext();
if (!isRoot) {
nsDisplayTableItem* currentItem = aBuilder->GetCurrentTableItem();
if (currentItem) {
currentItem->UpdateForFrameBackground(this);
}
}
// display outset box-shadows if we need to.
PRBool hasBoxShadow = !!(GetStyleBorder()->mBoxShadow);
if (hasBoxShadow) {
nsresult rv = aLists.BorderBackground()->AppendNewToTop(
new (aBuilder) nsDisplayBoxShadowOuter(aBuilder, this));
NS_ENSURE_SUCCESS(rv, rv);
}
// display background if we need to.
if (aBuilder->IsForEventDelivery() ||
(((!tableFrame->IsBorderCollapse() || isRoot) &&
(!GetStyleBackground()->IsTransparent() || GetStyleDisplay()->mAppearance)))) {
// The cell background was not painted by the nsTablePainter,
// so we need to do it. We have special background processing here
// so we need to duplicate some code from nsFrame::DisplayBorderBackgroundOutline
nsDisplayTableItem* item =
new (aBuilder) nsDisplayTableCellBackground(aBuilder, this);
nsresult rv = aLists.BorderBackground()->AppendNewToTop(item);
NS_ENSURE_SUCCESS(rv, rv);
item->UpdateForFrameBackground(this);
}
// display inset box-shadows if we need to.
if (hasBoxShadow) {
nsresult rv = aLists.BorderBackground()->AppendNewToTop(
new (aBuilder) nsDisplayBoxShadowInner(aBuilder, this));
NS_ENSURE_SUCCESS(rv, rv);
}
// display borders if we need to
if (!tableFrame->IsBorderCollapse() && HasBorder() &&
emptyCellStyle == NS_STYLE_TABLE_EMPTY_CELLS_SHOW) {
nsresult rv = aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
nsDisplayBorder(aBuilder, this));
NS_ENSURE_SUCCESS(rv, rv);
}
// and display the selection border if we need to
PRBool isSelected =
(GetStateBits() & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT;
if (isSelected) {
nsresult rv = aLists.BorderBackground()->AppendNewToTop(new (aBuilder)
nsDisplayGeneric(aBuilder, this, ::PaintTableCellSelection,
"TableCellSelection",
nsDisplayItem::TYPE_TABLE_CELL_SELECTION));
NS_ENSURE_SUCCESS(rv, rv);
}
}
// the 'empty-cells' property has no effect on 'outline'
nsresult rv = DisplayOutline(aBuilder, aLists);
NS_ENSURE_SUCCESS(rv, rv);
// Push a null 'current table item' so that descendant tables can't
// accidentally mess with our table
nsAutoPushCurrentTableItem pushTableItem;
pushTableItem.Push(aBuilder, nsnull);
nsIFrame* kid = mFrames.FirstChild();
NS_ASSERTION(kid && !kid->GetNextSibling(), "Table cells should have just one child");
// The child's background will go in our BorderBackground() list.
// This isn't a problem since it won't have a real background except for
// event handling. We do not call BuildDisplayListForNonBlockChildren
// because that/ would put the child's background in the Content() list
// which isn't right (e.g., would end up on top of our child floats for
// event handling).
return BuildDisplayListForChild(aBuilder, kid, aDirtyRect, aLists);
}
PRIntn
nsTableCellFrame::GetSkipSides() const
{
PRIntn skip = 0;
if (nsnull != GetPrevInFlow()) {
skip |= 1 << NS_SIDE_TOP;
}
if (nsnull != GetNextInFlow()) {
skip |= 1 << NS_SIDE_BOTTOM;
}
return skip;
}
/* virtual */ void
nsTableCellFrame::GetSelfOverflow(nsRect& aOverflowArea)
{
aOverflowArea = nsRect(nsPoint(0,0), GetSize());
}
// Align the cell's child frame within the cell
void nsTableCellFrame::VerticallyAlignChild(nscoord aMaxAscent)
{
const nsStyleTextReset* textStyle = GetStyleTextReset();
/* It's the 'border-collapse' on the table that matters */
nsMargin borderPadding = GetUsedBorderAndPadding();
nscoord topInset = borderPadding.top;
nscoord bottomInset = borderPadding.bottom;
PRUint8 verticalAlignFlags = GetVerticalAlign();
nscoord height = mRect.height;
nsIFrame* firstKid = mFrames.FirstChild();
NS_ASSERTION(firstKid, "Frame construction error, a table cell always has an inner cell frame");
nsRect kidRect = firstKid->GetRect();
nscoord childHeight = kidRect.height;
// Vertically align the child
nscoord kidYTop = 0;
switch (verticalAlignFlags)
{
case NS_STYLE_VERTICAL_ALIGN_BASELINE:
// Align the baselines of the child frame with the baselines of
// other children in the same row which have 'vertical-align: baseline'
kidYTop = topInset + aMaxAscent - GetCellBaseline();
break;
case NS_STYLE_VERTICAL_ALIGN_TOP:
// Align the top of the child frame with the top of the content area,
kidYTop = topInset;
break;
case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
// Align the bottom of the child frame with the bottom of the content area,
kidYTop = height - childHeight - bottomInset;
break;
default:
case NS_STYLE_VERTICAL_ALIGN_MIDDLE:
// Align the middle of the child frame with the middle of the content area,
kidYTop = (height - childHeight - bottomInset + topInset) / 2;
}
// if the content is larger than the cell height align from top
kidYTop = NS_MAX(0, kidYTop);
if (kidYTop != kidRect.y) {
// Invalidate at the old position first
firstKid->InvalidateFrameSubtree();
}
firstKid->SetPosition(nsPoint(kidRect.x, kidYTop));
nsHTMLReflowMetrics desiredSize;
desiredSize.width = mRect.width;
desiredSize.height = mRect.height;
GetSelfOverflow(desiredSize.mOverflowArea);
ConsiderChildOverflow(desiredSize.mOverflowArea, firstKid);
FinishAndStoreOverflow(&desiredSize);
if (kidYTop != kidRect.y) {
// Make sure any child views are correctly positioned. We know the inner table
// cell won't have a view
nsContainerFrame::PositionChildViews(firstKid);
// Invalidate new overflow rect
firstKid->InvalidateFrameSubtree();
}
if (HasView()) {
nsContainerFrame::SyncFrameViewAfterReflow(PresContext(), this,
GetView(),
&desiredSize.mOverflowArea, 0);
}
}
// Per CSS 2.1, we map 'sub', 'super', 'text-top', 'text-bottom',
// length, percentage, and calc() values to 'baseline'.
PRUint8
nsTableCellFrame::GetVerticalAlign() const
{
const nsStyleCoord& verticalAlign = GetStyleTextReset()->mVerticalAlign;
if (verticalAlign.GetUnit() == eStyleUnit_Enumerated) {
PRUint8 value = verticalAlign.GetIntValue();
if (value == NS_STYLE_VERTICAL_ALIGN_TOP ||
value == NS_STYLE_VERTICAL_ALIGN_MIDDLE ||
value == NS_STYLE_VERTICAL_ALIGN_BOTTOM) {
return value;
}
}
return NS_STYLE_VERTICAL_ALIGN_BASELINE;
}
PRBool
nsTableCellFrame::CellHasVisibleContent(nscoord height,
nsTableFrame* tableFrame,
nsIFrame* kidFrame)
{
// see http://www.w3.org/TR/CSS21/tables.html#empty-cells
if (height > 0)
return PR_TRUE;
if (tableFrame->IsBorderCollapse())
return PR_TRUE;
nsIFrame* innerFrame = kidFrame->GetFirstChild(nsnull);
while(innerFrame) {
nsIAtom* frameType = innerFrame->GetType();
if (nsGkAtoms::textFrame == frameType) {
nsTextFrame* textFrame = static_cast<nsTextFrame*>(innerFrame);
if (textFrame->HasNoncollapsedCharacters())
return PR_TRUE;
}
else if (nsGkAtoms::placeholderFrame != frameType) {
return PR_TRUE;
}
else {
nsIFrame *floatFrame = nsLayoutUtils::GetFloatFromPlaceholder(innerFrame);
if (floatFrame)
return PR_TRUE;
}
innerFrame = innerFrame->GetNextSibling();
}
return PR_FALSE;
}
nscoord
nsTableCellFrame::GetCellBaseline() const
{
// Ignore the position of the inner frame relative to the cell frame
// since we want the position as though the inner were top-aligned.
nsIFrame *inner = mFrames.FirstChild();
nscoord borderPadding = GetUsedBorderAndPadding().top;
nscoord result;
if (nsLayoutUtils::GetFirstLineBaseline(inner, &result))
return result + borderPadding;
return inner->GetContentRect().YMost() - inner->GetPosition().y +
borderPadding;
}
PRInt32 nsTableCellFrame::GetRowSpan()
{
PRInt32 rowSpan=1;
nsGenericHTMLElement *hc = nsGenericHTMLElement::FromContent(mContent);
// Don't look at the content's rowspan if we're a pseudo cell
if (hc && !GetStyleContext()->GetPseudo()) {
const nsAttrValue* attr = hc->GetParsedAttr(nsGkAtoms::rowspan);
// Note that we don't need to check the tag name, because only table cells
// and table headers parse the "rowspan" attribute into an integer.
if (attr && attr->Type() == nsAttrValue::eInteger) {
rowSpan = attr->GetIntegerValue();
}
}
return rowSpan;
}
PRInt32 nsTableCellFrame::GetColSpan()
{
PRInt32 colSpan=1;
nsGenericHTMLElement *hc = nsGenericHTMLElement::FromContent(mContent);
// Don't look at the content's colspan if we're a pseudo cell
if (hc && !GetStyleContext()->GetPseudo()) {
const nsAttrValue* attr = hc->GetParsedAttr(nsGkAtoms::colspan);
// Note that we don't need to check the tag name, because only table cells
// and table headers parse the "colspan" attribute into an integer.
if (attr && attr->Type() == nsAttrValue::eInteger) {
colSpan = attr->GetIntegerValue();
}
}
return colSpan;
}
/* virtual */ nscoord
nsTableCellFrame::GetMinWidth(nsIRenderingContext *aRenderingContext)
{
nscoord result = 0;
DISPLAY_MIN_WIDTH(this, result);
nsIFrame *inner = mFrames.FirstChild();
result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, inner,
nsLayoutUtils::MIN_WIDTH);
return result;
}
/* virtual */ nscoord
nsTableCellFrame::GetPrefWidth(nsIRenderingContext *aRenderingContext)
{
nscoord result = 0;
DISPLAY_PREF_WIDTH(this, result);
nsIFrame *inner = mFrames.FirstChild();
result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, inner,
nsLayoutUtils::PREF_WIDTH);
return result;
}
/* virtual */ nsIFrame::IntrinsicWidthOffsetData
nsTableCellFrame::IntrinsicWidthOffsets(nsIRenderingContext* aRenderingContext)
{
IntrinsicWidthOffsetData result =
nsHTMLContainerFrame::IntrinsicWidthOffsets(aRenderingContext);
result.hMargin = 0;
result.hPctMargin = 0;
nsMargin border;
GetBorderWidth(border);
result.hBorder = border.LeftRight();
return result;
}
#ifdef DEBUG
#define PROBABLY_TOO_LARGE 1000000
static
void DebugCheckChildSize(nsIFrame* aChild,
nsHTMLReflowMetrics& aMet,
nsSize& aAvailSize)
{
if ((aMet.width < 0) || (aMet.width > PROBABLY_TOO_LARGE)) {
printf("WARNING: cell content %p has large width %d \n",
static_cast<void*>(aChild), aMet.width);
}
}
#endif
// the computed height for the cell, which descendants use for percent height calculations
// it is the height (minus border, padding) of the cell's first in flow during its final
// reflow without an unconstrained height.
static nscoord
CalcUnpaginagedHeight(nsPresContext* aPresContext,
nsTableCellFrame& aCellFrame,
nsTableFrame& aTableFrame,
nscoord aVerticalBorderPadding)
{
const nsTableCellFrame* firstCellInFlow = (nsTableCellFrame*)aCellFrame.GetFirstInFlow();
nsTableFrame* firstTableInFlow = (nsTableFrame*)aTableFrame.GetFirstInFlow();
nsTableRowFrame* row
= static_cast<nsTableRowFrame*>(firstCellInFlow->GetParent());
nsTableRowGroupFrame* firstRGInFlow
= static_cast<nsTableRowGroupFrame*>(row->GetParent());
PRInt32 rowIndex;
firstCellInFlow->GetRowIndex(rowIndex);
PRInt32 rowSpan = aTableFrame.GetEffectiveRowSpan(*firstCellInFlow);
nscoord cellSpacing = firstTableInFlow->GetCellSpacingX();
nscoord computedHeight = ((rowSpan - 1) * cellSpacing) - aVerticalBorderPadding;
PRInt32 rowX;
for (row = firstRGInFlow->GetFirstRow(), rowX = 0; row; row = row->GetNextRow(), rowX++) {
if (rowX > rowIndex + rowSpan - 1) {
break;
}
else if (rowX >= rowIndex) {
computedHeight += row->GetUnpaginatedHeight(aPresContext);
}
}
return computedHeight;
}
NS_METHOD nsTableCellFrame::Reflow(nsPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
DO_GLOBAL_REFLOW_COUNT("nsTableCellFrame");
DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
if (aReflowState.mFlags.mSpecialHeightReflow) {
GetFirstInFlow()->AddStateBits(NS_TABLE_CELL_HAD_SPECIAL_REFLOW);
}
// see if a special height reflow needs to occur due to having a pct height
nsTableFrame::CheckRequestSpecialHeightReflow(aReflowState);
aStatus = NS_FRAME_COMPLETE;
nsSize availSize(aReflowState.availableWidth, aReflowState.availableHeight);
/* It's the 'border-collapse' on the table that matters */
nsTableFrame* tableFrame = nsTableFrame::GetTableFrame(this);
if (!tableFrame)
ABORT1(NS_ERROR_NULL_POINTER);
nsMargin borderPadding = aReflowState.mComputedPadding;
nsMargin border;
GetBorderWidth(border);
borderPadding += border;
nscoord topInset = borderPadding.top;
nscoord rightInset = borderPadding.right;
nscoord bottomInset = borderPadding.bottom;
nscoord leftInset = borderPadding.left;
// reduce available space by insets, if we're in a constrained situation
availSize.width -= leftInset + rightInset;
if (NS_UNCONSTRAINEDSIZE != availSize.height)
availSize.height -= topInset + bottomInset;
// Try to reflow the child into the available space. It might not
// fit or might need continuing.
if (availSize.height < 0)
availSize.height = 1;
nsHTMLReflowMetrics kidSize(aDesiredSize.mFlags);
kidSize.width = kidSize.height = 0;
SetPriorAvailWidth(aReflowState.availableWidth);
nsIFrame* firstKid = mFrames.FirstChild();
NS_ASSERTION(firstKid, "Frame construction error, a table cell always has an inner cell frame");
if (aReflowState.mFlags.mSpecialHeightReflow) {
const_cast<nsHTMLReflowState&>(aReflowState).SetComputedHeight(mRect.height - topInset - bottomInset);
DISPLAY_REFLOW_CHANGE();
}
else if (aPresContext->IsPaginated()) {
nscoord computedUnpaginatedHeight =
CalcUnpaginagedHeight(aPresContext, (nsTableCellFrame&)*this,
*tableFrame, topInset + bottomInset);
if (computedUnpaginatedHeight > 0) {
const_cast<nsHTMLReflowState&>(aReflowState).SetComputedHeight(computedUnpaginatedHeight);
DISPLAY_REFLOW_CHANGE();
}
}
else {
SetHasPctOverHeight(PR_FALSE);
}
nsHTMLReflowState kidReflowState(aPresContext, aReflowState, firstKid,
availSize);
// Don't be a percent height observer if we're in the middle of
// special-height reflow, in case we get an accidental NotifyPercentHeight()
// call (which we shouldn't honor during special-height reflow)
if (!aReflowState.mFlags.mSpecialHeightReflow) {
// mPercentHeightObserver is for children of cells in quirks mode,
// but only those than are tables in standards mode. NeedsToObserve
// will determine how far this is propagated to descendants.
kidReflowState.mPercentHeightObserver = this;
}
// Don't propagate special height reflow state to our kids
kidReflowState.mFlags.mSpecialHeightReflow = PR_FALSE;
if (aReflowState.mFlags.mSpecialHeightReflow ||
(GetFirstInFlow()->GetStateBits() & NS_TABLE_CELL_HAD_SPECIAL_REFLOW)) {
// We need to force the kid to have mVResize set if we've had a
// special reflow in the past, since the non-special reflow needs to
// resize back to what it was without the special height reflow.
kidReflowState.mFlags.mVResize = PR_TRUE;
}
nsPoint kidOrigin(leftInset, topInset);
nsRect origRect = firstKid->GetRect();
nsRect origOverflowRect = firstKid->GetOverflowRect();
PRBool firstReflow = (firstKid->GetStateBits() & NS_FRAME_FIRST_REFLOW) != 0;
ReflowChild(firstKid, aPresContext, kidSize, kidReflowState,
kidOrigin.x, kidOrigin.y, NS_FRAME_INVALIDATE_ON_MOVE, aStatus);
if (NS_FRAME_OVERFLOW_IS_INCOMPLETE(aStatus)) {
// Don't pass OVERFLOW_INCOMPLETE through tables until they can actually handle it
//XXX should paginate overflow as overflow, but not in this patch (bug 379349)
NS_FRAME_SET_INCOMPLETE(aStatus);
printf("Set table cell incomplete %p\n", static_cast<void*>(this));
}
// XXXbz is this invalidate actually needed, really?
if (GetStateBits() & NS_FRAME_IS_DIRTY) {
InvalidateFrameSubtree();
}
#ifdef NS_DEBUG
DebugCheckChildSize(firstKid, kidSize, availSize);
#endif
// 0 dimensioned cells need to be treated specially in Standard/NavQuirks mode
// see testcase "emptyCells.html"
nsIFrame* prevInFlow = GetPrevInFlow();
PRBool isEmpty;
if (prevInFlow) {
isEmpty = static_cast<nsTableCellFrame*>(prevInFlow)->GetContentEmpty();
} else {
isEmpty = !CellHasVisibleContent(kidSize.height, tableFrame, firstKid);
}
SetContentEmpty(isEmpty);
// Place the child
FinishReflowChild(firstKid, aPresContext, &kidReflowState, kidSize,
kidOrigin.x, kidOrigin.y, 0);
nsTableFrame::InvalidateFrame(firstKid, origRect, origOverflowRect,
firstReflow);
// first, compute the height which can be set w/o being restricted by aMaxSize.height
nscoord cellHeight = kidSize.height;
if (NS_UNCONSTRAINEDSIZE != cellHeight) {
cellHeight += topInset + bottomInset;
}
// next determine the cell's width
nscoord cellWidth = kidSize.width; // at this point, we've factored in the cell's style attributes
// factor in border and padding
if (NS_UNCONSTRAINEDSIZE != cellWidth) {
cellWidth += leftInset + rightInset;
}
// set the cell's desired size and max element size
aDesiredSize.width = cellWidth;
aDesiredSize.height = cellHeight;
// the overflow area will be computed when the child will be vertically aligned
if (aReflowState.mFlags.mSpecialHeightReflow) {
if (aDesiredSize.height > mRect.height) {
// set a bit indicating that the pct height contents exceeded
// the height that they could honor in the pass 2 reflow
SetHasPctOverHeight(PR_TRUE);
}
if (NS_UNCONSTRAINEDSIZE == aReflowState.availableHeight) {
aDesiredSize.height = mRect.height;
}
}
// If our parent is in initial reflow, it'll handle invalidating our
// entire overflow rect.
if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
CheckInvalidateSizeChange(aDesiredSize);
}
// remember the desired size for this reflow
SetDesiredSize(aDesiredSize);
NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
return NS_OK;
}
/* ----- global methods ----- */
NS_QUERYFRAME_HEAD(nsTableCellFrame)
NS_QUERYFRAME_ENTRY(nsTableCellFrame)
NS_QUERYFRAME_ENTRY(nsITableCellLayout)
NS_QUERYFRAME_ENTRY(nsIPercentHeightObserver)
NS_QUERYFRAME_TAIL_INHERITING(nsHTMLContainerFrame)
#ifdef ACCESSIBILITY
already_AddRefed<nsAccessible>
nsTableCellFrame::CreateAccessible()
{
nsCOMPtr<nsIAccessibilityService> accService = do_GetService("@mozilla.org/accessibilityService;1");
if (accService) {
return accService->CreateHTMLTableCellAccessible(mContent,
PresContext()->PresShell());
}
return nsnull;
}
#endif
/* This is primarily for editor access via nsITableLayout */
NS_IMETHODIMP
nsTableCellFrame::GetCellIndexes(PRInt32 &aRowIndex, PRInt32 &aColIndex)
{
nsresult res = GetRowIndex(aRowIndex);
if (NS_FAILED(res))
{
aColIndex = 0;
return res;
}
aColIndex = mColIndex;
return NS_OK;
}
nsIFrame*
NS_NewTableCellFrame(nsIPresShell* aPresShell,
nsStyleContext* aContext,
PRBool aIsBorderCollapse)
{
if (aIsBorderCollapse)
return new (aPresShell) nsBCTableCellFrame(aContext);
else
return new (aPresShell) nsTableCellFrame(aContext);
}
NS_IMPL_FRAMEARENA_HELPERS(nsBCTableCellFrame)
nsMargin*
nsTableCellFrame::GetBorderWidth(nsMargin& aBorder) const
{
aBorder = GetStyleBorder()->GetActualBorder();
return &aBorder;
}
nsIAtom*
nsTableCellFrame::GetType() const
{
return nsGkAtoms::tableCellFrame;
}
/* virtual */ PRBool
nsTableCellFrame::IsContainingBlock() const
{
return PR_TRUE;
}
#ifdef DEBUG
NS_IMETHODIMP
nsTableCellFrame::GetFrameName(nsAString& aResult) const
{
return MakeFrameName(NS_LITERAL_STRING("TableCell"), aResult);
}
#endif
// nsBCTableCellFrame
nsBCTableCellFrame::nsBCTableCellFrame(nsStyleContext* aContext)
:nsTableCellFrame(aContext)
{
mTopBorder = mRightBorder = mBottomBorder = mLeftBorder = 0;
}
nsBCTableCellFrame::~nsBCTableCellFrame()
{
}
nsIAtom*
nsBCTableCellFrame::GetType() const
{
return nsGkAtoms::bcTableCellFrame;
}
/* virtual */ nsMargin
nsBCTableCellFrame::GetUsedBorder() const
{
nsMargin result;
GetBorderWidth(result);
return result;
}
/* virtual */ PRBool
nsBCTableCellFrame::GetBorderRadii(nscoord aRadii[8]) const
{
NS_FOR_CSS_HALF_CORNERS(corner) {
aRadii[corner] = 0;
}
return PR_FALSE;
}
#ifdef DEBUG
NS_IMETHODIMP
nsBCTableCellFrame::GetFrameName(nsAString& aResult) const
{
return MakeFrameName(NS_LITERAL_STRING("BCTableCell"), aResult);
}
#endif
nsMargin*
nsBCTableCellFrame::GetBorderWidth(nsMargin& aBorder) const
{
PRInt32 aPixelsToTwips = nsPresContext::AppUnitsPerCSSPixel();
aBorder.top = BC_BORDER_BOTTOM_HALF_COORD(aPixelsToTwips, mTopBorder);
aBorder.right = BC_BORDER_LEFT_HALF_COORD(aPixelsToTwips, mRightBorder);
aBorder.bottom = BC_BORDER_TOP_HALF_COORD(aPixelsToTwips, mBottomBorder);
aBorder.left = BC_BORDER_RIGHT_HALF_COORD(aPixelsToTwips, mLeftBorder);
return &aBorder;
}
BCPixelSize
nsBCTableCellFrame::GetBorderWidth(mozilla::css::Side aSide) const
{
switch(aSide) {
case NS_SIDE_TOP:
return BC_BORDER_BOTTOM_HALF(mTopBorder);
case NS_SIDE_RIGHT:
return BC_BORDER_LEFT_HALF(mRightBorder);
case NS_SIDE_BOTTOM:
return BC_BORDER_TOP_HALF(mBottomBorder);
default:
return BC_BORDER_RIGHT_HALF(mLeftBorder);
}
}
void
nsBCTableCellFrame::SetBorderWidth(mozilla::css::Side aSide,
BCPixelSize aValue)
{
switch(aSide) {
case NS_SIDE_TOP:
mTopBorder = aValue;
break;
case NS_SIDE_RIGHT:
mRightBorder = aValue;
break;
case NS_SIDE_BOTTOM:
mBottomBorder = aValue;
break;
default:
mLeftBorder = aValue;
}
}
/* virtual */ void
nsBCTableCellFrame::GetSelfOverflow(nsRect& aOverflowArea)
{
nsMargin halfBorder;
PRInt32 p2t = nsPresContext::AppUnitsPerCSSPixel();
halfBorder.top = BC_BORDER_TOP_HALF_COORD(p2t, mTopBorder);
halfBorder.right = BC_BORDER_RIGHT_HALF_COORD(p2t, mRightBorder);
halfBorder.bottom = BC_BORDER_BOTTOM_HALF_COORD(p2t, mBottomBorder);
halfBorder.left = BC_BORDER_LEFT_HALF_COORD(p2t, mLeftBorder);
nsRect overflow(nsPoint(0,0), GetSize());
overflow.Inflate(halfBorder);
aOverflowArea = overflow;
}
void
nsBCTableCellFrame::PaintBackground(nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect,
nsPoint aPt,
PRUint32 aFlags)
{
// make border-width reflect the half of the border-collapse
// assigned border that's inside the cell
nsMargin borderWidth;
GetBorderWidth(borderWidth);
nsStyleBorder myBorder(*GetStyleBorder());
// We're making an ephemeral stack copy here, so just copy this debug-only
// member to prevent assertions.
#ifdef DEBUG
myBorder.mImageTracked = GetStyleBorder()->mImageTracked;
#endif
NS_FOR_CSS_SIDES(side) {
myBorder.SetBorderWidth(side, borderWidth.side(side));
}
nsRect rect(aPt, GetSize());
// bypassing nsCSSRendering::PaintBackground is safe because this kind
// of frame cannot be used for the root element
nsCSSRendering::PaintBackgroundWithSC(PresContext(), aRenderingContext, this,
aDirtyRect, rect,
GetStyleContext(), myBorder,
aFlags, nsnull);
#ifdef DEBUG
myBorder.mImageTracked = false;
#endif
}