diff options
Diffstat (limited to 'layout/generic/nsBulletFrame.cpp')
-rw-r--r-- | layout/generic/nsBulletFrame.cpp | 1090 |
1 files changed, 1090 insertions, 0 deletions
diff --git a/layout/generic/nsBulletFrame.cpp b/layout/generic/nsBulletFrame.cpp new file mode 100644 index 000000000..aa8794321 --- /dev/null +++ b/layout/generic/nsBulletFrame.cpp @@ -0,0 +1,1090 @@ +/* -*- 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/. */ + +/* rendering object for list-item bullets */ + +#include "nsBulletFrame.h" + +#include "gfx2DGlue.h" +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/PathHelpers.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Move.h" +#include "nsCOMPtr.h" +#include "nsFontMetrics.h" +#include "nsGkAtoms.h" +#include "nsGenericHTMLElement.h" +#include "nsAttrValueInlines.h" +#include "nsPresContext.h" +#include "nsIPresShell.h" +#include "nsIDocument.h" +#include "nsRenderingContext.h" +#include "nsDisplayList.h" +#include "nsCounterManager.h" +#include "nsBidiUtils.h" +#include "CounterStyleManager.h" + +#include "imgIContainer.h" +#include "imgRequestProxy.h" +#include "nsIURI.h" + +#include <algorithm> + +#ifdef ACCESSIBILITY +#include "nsAccessibilityService.h" +#endif + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::image; + +NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FontSizeInflationProperty, float) + +NS_IMPL_FRAMEARENA_HELPERS(nsBulletFrame) + +#ifdef DEBUG +NS_QUERYFRAME_HEAD(nsBulletFrame) + NS_QUERYFRAME_ENTRY(nsBulletFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsFrame) +#endif + +nsBulletFrame::~nsBulletFrame() +{ + NS_ASSERTION(!mBlockingOnload, "Still blocking onload in destructor?"); +} + +void +nsBulletFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + // Stop image loading first. + DeregisterAndCancelImageRequest(); + + if (mListener) { + mListener->SetFrame(nullptr); + } + + // Let base class do the rest + nsFrame::DestroyFrom(aDestructRoot); +} + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsBulletFrame::GetFrameName(nsAString& aResult) const +{ + return MakeFrameName(NS_LITERAL_STRING("Bullet"), aResult); +} +#endif + +nsIAtom* +nsBulletFrame::GetType() const +{ + return nsGkAtoms::bulletFrame; +} + +bool +nsBulletFrame::IsEmpty() +{ + return IsSelfEmpty(); +} + +bool +nsBulletFrame::IsSelfEmpty() +{ + return StyleList()->GetCounterStyle()->IsNone(); +} + +/* virtual */ void +nsBulletFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + nsFrame::DidSetStyleContext(aOldStyleContext); + + imgRequestProxy *newRequest = StyleList()->GetListStyleImage(); + + if (newRequest) { + + if (!mListener) { + mListener = new nsBulletListener(); + mListener->SetFrame(this); + } + + bool needNewRequest = true; + + if (mImageRequest) { + // Reload the image, maybe... + nsCOMPtr<nsIURI> oldURI; + mImageRequest->GetURI(getter_AddRefs(oldURI)); + nsCOMPtr<nsIURI> newURI; + newRequest->GetURI(getter_AddRefs(newURI)); + if (oldURI && newURI) { + bool same; + newURI->Equals(oldURI, &same); + if (same) { + needNewRequest = false; + } + } + } + + if (needNewRequest) { + RefPtr<imgRequestProxy> newRequestClone; + newRequest->Clone(mListener, getter_AddRefs(newRequestClone)); + + // Deregister the old request. We wait until after Clone is done in case + // the old request and the new request are the same underlying image + // accessed via different URLs. + DeregisterAndCancelImageRequest(); + + // Register the new request. + mImageRequest = Move(newRequestClone); + RegisterImageRequest(/* aKnownToBeAnimated = */ false); + } + } else { + // No image request on the new style context. + DeregisterAndCancelImageRequest(); + } + +#ifdef ACCESSIBILITY + // Update the list bullet accessible. If old style list isn't available then + // no need to update the accessible tree because it's not created yet. + if (aOldStyleContext) { + nsAccessibilityService* accService = nsIPresShell::AccService(); + if (accService) { + const nsStyleList* oldStyleList = aOldStyleContext->PeekStyleList(); + if (oldStyleList) { + bool hadBullet = oldStyleList->GetListStyleImage() || + !oldStyleList->GetCounterStyle()->IsNone(); + + const nsStyleList* newStyleList = StyleList(); + bool hasBullet = newStyleList->GetListStyleImage() || + !newStyleList->GetCounterStyle()->IsNone(); + + if (hadBullet != hasBullet) { + accService->UpdateListBullet(PresContext()->GetPresShell(), mContent, + hasBullet); + } + } + } + } +#endif +} + +class nsDisplayBulletGeometry + : public nsDisplayItemGenericGeometry + , public nsImageGeometryMixin<nsDisplayBulletGeometry> +{ +public: + nsDisplayBulletGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGenericGeometry(aItem, aBuilder) + , nsImageGeometryMixin(aItem, aBuilder) + { + nsBulletFrame* f = static_cast<nsBulletFrame*>(aItem->Frame()); + mOrdinal = f->GetOrdinal(); + } + + int32_t mOrdinal; +}; + +class nsDisplayBullet final : public nsDisplayItem { +public: + nsDisplayBullet(nsDisplayListBuilder* aBuilder, nsBulletFrame* aFrame) + : nsDisplayItem(aBuilder, aFrame) + , mDisableSubpixelAA(false) + { + MOZ_COUNT_CTOR(nsDisplayBullet); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayBullet() { + MOZ_COUNT_DTOR(nsDisplayBullet); + } +#endif + + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) override + { + *aSnap = false; + return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); + } + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*> *aOutFrames) override { + aOutFrames->AppendElement(mFrame); + } + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override; + NS_DISPLAY_DECL_NAME("Bullet", TYPE_BULLET) + + virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override + { + bool snap; + return GetBounds(aBuilder, &snap); + } + + virtual void DisableComponentAlpha() override { + mDisableSubpixelAA = true; + } + + virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override + { + return new nsDisplayBulletGeometry(this, aBuilder); + } + + virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, + const nsDisplayItemGeometry* aGeometry, + nsRegion *aInvalidRegion) override + { + const nsDisplayBulletGeometry* geometry = static_cast<const nsDisplayBulletGeometry*>(aGeometry); + nsBulletFrame* f = static_cast<nsBulletFrame*>(mFrame); + + if (f->GetOrdinal() != geometry->mOrdinal) { + bool snap; + aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &snap)); + return; + } + + nsCOMPtr<imgIContainer> image = f->GetImage(); + if (aBuilder->ShouldSyncDecodeImages() && image && + geometry->ShouldInvalidateToSyncDecodeImages()) { + bool snap; + aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); + } + + return nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion); + } + +protected: + bool mDisableSubpixelAA; +}; + +void nsDisplayBullet::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + uint32_t flags = imgIContainer::FLAG_NONE; + if (aBuilder->ShouldSyncDecodeImages()) { + flags |= imgIContainer::FLAG_SYNC_DECODE; + } + + DrawResult result = static_cast<nsBulletFrame*>(mFrame)-> + PaintBullet(*aCtx, ToReferenceFrame(), mVisibleRect, flags, + mDisableSubpixelAA); + + nsDisplayBulletGeometry::UpdateDrawResult(this, result); +} + +void +nsBulletFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (!IsVisibleForPainting(aBuilder)) + return; + + DO_GLOBAL_REFLOW_COUNT_DSP("nsBulletFrame"); + + aLists.Content()->AppendNewToTop( + new (aBuilder) nsDisplayBullet(aBuilder, this)); +} + +DrawResult +nsBulletFrame::PaintBullet(nsRenderingContext& aRenderingContext, nsPoint aPt, + const nsRect& aDirtyRect, uint32_t aFlags, + bool aDisableSubpixelAA) +{ + const nsStyleList* myList = StyleList(); + CounterStyle* listStyleType = myList->GetCounterStyle(); + nsMargin padding = mPadding.GetPhysicalMargin(GetWritingMode()); + + if (myList->GetListStyleImage() && mImageRequest) { + uint32_t status; + mImageRequest->GetImageStatus(&status); + if (status & imgIRequest::STATUS_LOAD_COMPLETE && + !(status & imgIRequest::STATUS_ERROR)) { + nsCOMPtr<imgIContainer> imageCon; + mImageRequest->GetImage(getter_AddRefs(imageCon)); + if (imageCon) { + nsRect dest(padding.left, padding.top, + mRect.width - (padding.left + padding.right), + mRect.height - (padding.top + padding.bottom)); + return + nsLayoutUtils::DrawSingleImage(*aRenderingContext.ThebesContext(), + PresContext(), + imageCon, nsLayoutUtils::GetSamplingFilterForFrame(this), + dest + aPt, aDirtyRect, nullptr, aFlags); + } + } + } + + ColorPattern color(ToDeviceColor( + nsLayoutUtils::GetColor(this, eCSSProperty_color))); + + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel(); + + switch (listStyleType->GetStyle()) { + case NS_STYLE_LIST_STYLE_NONE: + break; + + case NS_STYLE_LIST_STYLE_DISC: + case NS_STYLE_LIST_STYLE_CIRCLE: + { + nsRect rect(padding.left + aPt.x, + padding.top + aPt.y, + mRect.width - (padding.left + padding.right), + mRect.height - (padding.top + padding.bottom)); + Rect devPxRect = NSRectToRect(rect, appUnitsPerDevPixel); + RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder(); + AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size()); + RefPtr<Path> ellipse = builder->Finish(); + if (listStyleType->GetStyle() == NS_STYLE_LIST_STYLE_DISC) { + drawTarget->Fill(ellipse, color); + } else { + drawTarget->Stroke(ellipse, color); + } + } + break; + + case NS_STYLE_LIST_STYLE_SQUARE: + { + nsRect rect(aPt, mRect.Size()); + rect.Deflate(padding); + + // Snap the height and the width of the rectangle to device pixels, + // and then center the result within the original rectangle, so that + // all square bullets at the same font size have the same visual + // size (bug 376690). + // FIXME: We should really only do this if we're not transformed + // (like gfxContext::UserToDevicePixelSnapped does). + nsPresContext *pc = PresContext(); + nsRect snapRect(rect.x, rect.y, + pc->RoundAppUnitsToNearestDevPixels(rect.width), + pc->RoundAppUnitsToNearestDevPixels(rect.height)); + snapRect.MoveBy((rect.width - snapRect.width) / 2, + (rect.height - snapRect.height) / 2); + Rect devPxRect = + NSRectToSnappedRect(snapRect, appUnitsPerDevPixel, *drawTarget); + drawTarget->FillRect(devPxRect, color); + } + break; + + case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED: + case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN: + { + nsRect rect(aPt, mRect.Size()); + rect.Deflate(padding); + + WritingMode wm = GetWritingMode(); + bool isVertical = wm.IsVertical(); + bool isClosed = + listStyleType->GetStyle() == NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED; + bool isDown = (!isVertical && !isClosed) || (isVertical && isClosed); + nscoord diff = NSToCoordRound(0.1f * rect.height); + if (isDown) { + rect.y += diff * 2; + rect.height -= diff * 2; + } else { + rect.Deflate(diff, 0); + } + nsPresContext *pc = PresContext(); + rect.x = pc->RoundAppUnitsToNearestDevPixels(rect.x); + rect.y = pc->RoundAppUnitsToNearestDevPixels(rect.y); + + RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder(); + if (isDown) { + // to bottom + builder->MoveTo(NSPointToPoint(rect.TopLeft(), appUnitsPerDevPixel)); + builder->LineTo(NSPointToPoint(rect.TopRight(), appUnitsPerDevPixel)); + builder->LineTo(NSPointToPoint((rect.BottomLeft() + rect.BottomRight()) / 2, + appUnitsPerDevPixel)); + } else { + bool isLR = isVertical ? wm.IsVerticalLR() : wm.IsBidiLTR(); + if (isLR) { + // to right + builder->MoveTo(NSPointToPoint(rect.TopLeft(), appUnitsPerDevPixel)); + builder->LineTo(NSPointToPoint((rect.TopRight() + rect.BottomRight()) / 2, + appUnitsPerDevPixel)); + builder->LineTo(NSPointToPoint(rect.BottomLeft(), appUnitsPerDevPixel)); + } else { + // to left + builder->MoveTo(NSPointToPoint(rect.TopRight(), appUnitsPerDevPixel)); + builder->LineTo(NSPointToPoint(rect.BottomRight(), appUnitsPerDevPixel)); + builder->LineTo(NSPointToPoint((rect.TopLeft() + rect.BottomLeft()) / 2, + appUnitsPerDevPixel)); + } + } + RefPtr<Path> path = builder->Finish(); + drawTarget->Fill(path, color); + } + break; + + default: + { + DrawTargetAutoDisableSubpixelAntialiasing + disable(aRenderingContext.GetDrawTarget(), aDisableSubpixelAA); + + aRenderingContext.ThebesContext()->SetColor( + Color::FromABGR(nsLayoutUtils::GetColor(this, eCSSProperty_color))); + + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForFrame(this, GetFontSizeInflation()); + nsAutoString text; + GetListItemText(text); + WritingMode wm = GetWritingMode(); + nscoord ascent = wm.IsLineInverted() + ? fm->MaxDescent() : fm->MaxAscent(); + aPt.MoveBy(padding.left, padding.top); + gfxContext *ctx = aRenderingContext.ThebesContext(); + if (wm.IsVertical()) { + if (wm.IsVerticalLR()) { + aPt.x = NSToCoordRound(nsLayoutUtils::GetSnappedBaselineX( + this, ctx, aPt.x, ascent)); + } else { + aPt.x = NSToCoordRound(nsLayoutUtils::GetSnappedBaselineX( + this, ctx, aPt.x + mRect.width, + -ascent)); + } + } else { + aPt.y = NSToCoordRound(nsLayoutUtils::GetSnappedBaselineY( + this, ctx, aPt.y, ascent)); + } + nsPresContext* presContext = PresContext(); + if (!presContext->BidiEnabled() && HasRTLChars(text)) { + presContext->SetBidiEnabled(); + } + nsLayoutUtils::DrawString(this, *fm, &aRenderingContext, + text.get(), text.Length(), aPt); + } + break; + } + + return DrawResult::SUCCESS; +} + +int32_t +nsBulletFrame::SetListItemOrdinal(int32_t aNextOrdinal, + bool* aChanged, + int32_t aIncrement) +{ + MOZ_ASSERT(aIncrement == 1 || aIncrement == -1, + "We shouldn't have weird increments here"); + + // Assume that the ordinal comes from the caller + int32_t oldOrdinal = mOrdinal; + mOrdinal = aNextOrdinal; + + // Try to get value directly from the list-item, if it specifies a + // value attribute. Note: we do this with our parent's content + // because our parent is the list-item. + nsIContent* parentContent = GetParent()->GetContent(); + if (parentContent) { + nsGenericHTMLElement *hc = + nsGenericHTMLElement::FromContent(parentContent); + if (hc) { + const nsAttrValue* attr = hc->GetParsedAttr(nsGkAtoms::value); + if (attr && attr->Type() == nsAttrValue::eInteger) { + // Use ordinal specified by the value attribute + mOrdinal = attr->GetIntegerValue(); + } + } + } + + *aChanged = oldOrdinal != mOrdinal; + + return nsCounterManager::IncrementCounter(mOrdinal, aIncrement); +} + +void +nsBulletFrame::GetListItemText(nsAString& aResult) +{ + CounterStyle* style = StyleList()->GetCounterStyle(); + NS_ASSERTION(style->GetStyle() != NS_STYLE_LIST_STYLE_NONE && + style->GetStyle() != NS_STYLE_LIST_STYLE_DISC && + style->GetStyle() != NS_STYLE_LIST_STYLE_CIRCLE && + style->GetStyle() != NS_STYLE_LIST_STYLE_SQUARE && + style->GetStyle() != NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED && + style->GetStyle() != NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN, + "we should be using specialized code for these types"); + + bool isRTL; + nsAutoString counter, prefix, suffix; + style->GetPrefix(prefix); + style->GetSuffix(suffix); + style->GetCounterText(mOrdinal, GetWritingMode(), counter, isRTL); + + aResult.Truncate(); + aResult.Append(prefix); + if (GetWritingMode().IsBidiLTR() != isRTL) { + aResult.Append(counter); + } else { + // RLM = 0x200f, LRM = 0x200e + char16_t mark = isRTL ? 0x200f : 0x200e; + aResult.Append(mark); + aResult.Append(counter); + aResult.Append(mark); + } + aResult.Append(suffix); +} + +#define MIN_BULLET_SIZE 1 + +void +nsBulletFrame::AppendSpacingToPadding(nsFontMetrics* aFontMetrics, + LogicalMargin* aPadding) +{ + aPadding->IEnd(GetWritingMode()) += aFontMetrics->EmHeight() / 2; +} + +void +nsBulletFrame::GetDesiredSize(nsPresContext* aCX, + nsRenderingContext *aRenderingContext, + ReflowOutput& aMetrics, + float aFontSizeInflation, + LogicalMargin* aPadding) +{ + // Reset our padding. If we need it, we'll set it below. + WritingMode wm = GetWritingMode(); + aPadding->SizeTo(wm, 0, 0, 0, 0); + LogicalSize finalSize(wm); + + const nsStyleList* myList = StyleList(); + nscoord ascent; + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForFrame(this, aFontSizeInflation); + + RemoveStateBits(BULLET_FRAME_IMAGE_LOADING); + + if (myList->GetListStyleImage() && mImageRequest) { + uint32_t status; + mImageRequest->GetImageStatus(&status); + if (status & imgIRequest::STATUS_SIZE_AVAILABLE && + !(status & imgIRequest::STATUS_ERROR)) { + // auto size the image + finalSize.ISize(wm) = mIntrinsicSize.ISize(wm); + aMetrics.SetBlockStartAscent(finalSize.BSize(wm) = + mIntrinsicSize.BSize(wm)); + aMetrics.SetSize(wm, finalSize); + + AppendSpacingToPadding(fm, aPadding); + + AddStateBits(BULLET_FRAME_IMAGE_LOADING); + + return; + } + } + + // If we're getting our desired size and don't have an image, reset + // mIntrinsicSize to (0,0). Otherwise, if we used to have an image, it + // changed, and the new one is coming in, but we're reflowing before it's + // fully there, we'll end up with mIntrinsicSize not matching our size, but + // won't trigger a reflow in OnStartContainer (because mIntrinsicSize will + // match the image size). + mIntrinsicSize.SizeTo(wm, 0, 0); + + nscoord bulletSize; + + nsAutoString text; + switch (myList->GetCounterStyle()->GetStyle()) { + case NS_STYLE_LIST_STYLE_NONE: + finalSize.ISize(wm) = finalSize.BSize(wm) = 0; + aMetrics.SetBlockStartAscent(0); + break; + + case NS_STYLE_LIST_STYLE_DISC: + case NS_STYLE_LIST_STYLE_CIRCLE: + case NS_STYLE_LIST_STYLE_SQUARE: { + ascent = fm->MaxAscent(); + bulletSize = std::max(nsPresContext::CSSPixelsToAppUnits(MIN_BULLET_SIZE), + NSToCoordRound(0.8f * (float(ascent) / 2.0f))); + aPadding->BEnd(wm) = NSToCoordRound(float(ascent) / 8.0f); + finalSize.ISize(wm) = finalSize.BSize(wm) = bulletSize; + aMetrics.SetBlockStartAscent(bulletSize + aPadding->BEnd(wm)); + AppendSpacingToPadding(fm, aPadding); + break; + } + + case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED: + case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN: + ascent = fm->EmAscent(); + bulletSize = std::max( + nsPresContext::CSSPixelsToAppUnits(MIN_BULLET_SIZE), + NSToCoordRound(0.75f * ascent)); + aPadding->BEnd(wm) = NSToCoordRound(0.125f * ascent); + finalSize.ISize(wm) = finalSize.BSize(wm) = bulletSize; + if (!wm.IsVertical()) { + aMetrics.SetBlockStartAscent(bulletSize + aPadding->BEnd(wm)); + } + AppendSpacingToPadding(fm, aPadding); + break; + + default: + GetListItemText(text); + finalSize.BSize(wm) = fm->MaxHeight(); + finalSize.ISize(wm) = + nsLayoutUtils::AppUnitWidthOfStringBidi(text, this, *fm, *aRenderingContext); + aMetrics.SetBlockStartAscent(wm.IsLineInverted() + ? fm->MaxDescent() : fm->MaxAscent()); + break; + } + aMetrics.SetSize(wm, finalSize); +} + +void +nsBulletFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aMetrics, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) +{ + MarkInReflow(); + DO_GLOBAL_REFLOW_COUNT("nsBulletFrame"); + DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus); + + float inflation = nsLayoutUtils::FontSizeInflationFor(this); + SetFontSizeInflation(inflation); + + // Get the base size + GetDesiredSize(aPresContext, aReflowInput.mRenderingContext, aMetrics, inflation, + &mPadding); + + // Add in the border and padding; split the top/bottom between the + // ascent and descent to make things look nice + WritingMode wm = aReflowInput.GetWritingMode(); + const LogicalMargin& bp = aReflowInput.ComputedLogicalBorderPadding(); + mPadding.BStart(wm) += NSToCoordRound(bp.BStart(wm) * inflation); + mPadding.IEnd(wm) += NSToCoordRound(bp.IEnd(wm) * inflation); + mPadding.BEnd(wm) += NSToCoordRound(bp.BEnd(wm) * inflation); + mPadding.IStart(wm) += NSToCoordRound(bp.IStart(wm) * inflation); + + WritingMode lineWM = aMetrics.GetWritingMode(); + LogicalMargin linePadding = mPadding.ConvertTo(lineWM, wm); + aMetrics.ISize(lineWM) += linePadding.IStartEnd(lineWM); + aMetrics.BSize(lineWM) += linePadding.BStartEnd(lineWM); + aMetrics.SetBlockStartAscent(aMetrics.BlockStartAscent() + + linePadding.BStart(lineWM)); + + // XXX this is a bit of a hack, we're assuming that no glyphs used for bullets + // overflow their font-boxes. It'll do for now; to fix it for real, we really + // should rewrite all the text-handling code here to use gfxTextRun (bug + // 397294). + aMetrics.SetOverflowAreasToDesiredBounds(); + + aStatus = NS_FRAME_COMPLETE; + NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics); +} + +/* virtual */ nscoord +nsBulletFrame::GetMinISize(nsRenderingContext *aRenderingContext) +{ + WritingMode wm = GetWritingMode(); + ReflowOutput reflowOutput(wm); + DISPLAY_MIN_WIDTH(this, reflowOutput.ISize(wm)); + LogicalMargin padding(wm); + GetDesiredSize(PresContext(), aRenderingContext, reflowOutput, 1.0f, &padding); + reflowOutput.ISize(wm) += padding.IStartEnd(wm); + return reflowOutput.ISize(wm); +} + +/* virtual */ nscoord +nsBulletFrame::GetPrefISize(nsRenderingContext *aRenderingContext) +{ + WritingMode wm = GetWritingMode(); + ReflowOutput metrics(wm); + DISPLAY_PREF_WIDTH(this, metrics.ISize(wm)); + LogicalMargin padding(wm); + GetDesiredSize(PresContext(), aRenderingContext, metrics, 1.0f, &padding); + metrics.ISize(wm) += padding.IStartEnd(wm); + return metrics.ISize(wm); +} + +// If a bullet has zero size and is "ignorable" from its styling, we behave +// as if it doesn't exist, from a line-breaking/isize-computation perspective. +// Otherwise, we use the default implementation, same as nsFrame. +static inline bool +IsIgnoreable(const nsIFrame* aFrame, nscoord aISize) +{ + if (aISize != nscoord(0)) { + return false; + } + auto listStyle = aFrame->StyleList(); + return listStyle->GetCounterStyle()->IsNone() && + !listStyle->GetListStyleImage(); +} + +/* virtual */ void +nsBulletFrame::AddInlineMinISize(nsRenderingContext* aRenderingContext, + nsIFrame::InlineMinISizeData* aData) +{ + nscoord isize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, + this, nsLayoutUtils::MIN_ISIZE); + if (MOZ_LIKELY(!::IsIgnoreable(this, isize))) { + aData->DefaultAddInlineMinISize(this, isize); + } +} + +/* virtual */ void +nsBulletFrame::AddInlinePrefISize(nsRenderingContext* aRenderingContext, + nsIFrame::InlinePrefISizeData* aData) +{ + nscoord isize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, + this, nsLayoutUtils::PREF_ISIZE); + if (MOZ_LIKELY(!::IsIgnoreable(this, isize))) { + aData->DefaultAddInlinePrefISize(isize); + } +} + +NS_IMETHODIMP +nsBulletFrame::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) +{ + if (aType == imgINotificationObserver::SIZE_AVAILABLE) { + nsCOMPtr<imgIContainer> image; + aRequest->GetImage(getter_AddRefs(image)); + return OnSizeAvailable(aRequest, image); + } + + if (aType == imgINotificationObserver::FRAME_UPDATE) { + // The image has changed. + // Invalidate the entire content area. Maybe it's not optimal but it's simple and + // always correct, and I'll be a stunned mullet if it ever matters for performance + InvalidateFrame(); + } + + if (aType == imgINotificationObserver::IS_ANIMATED) { + // Register the image request with the refresh driver now that we know it's + // animated. + if (aRequest == mImageRequest) { + RegisterImageRequest(/* aKnownToBeAnimated = */ true); + } + } + + if (aType == imgINotificationObserver::LOAD_COMPLETE) { + // Unconditionally start decoding for now. + // XXX(seth): We eventually want to decide whether to do this based on + // visibility. We should get that for free from bug 1091236. + nsCOMPtr<imgIContainer> container; + aRequest->GetImage(getter_AddRefs(container)); + if (container) { + // Retrieve the intrinsic size of the image. + int32_t width = 0; + int32_t height = 0; + container->GetWidth(&width); + container->GetHeight(&height); + + // Request a decode at that size. + container->RequestDecodeForSize(IntSize(width, height), + imgIContainer::DECODE_FLAGS_DEFAULT); + } + + InvalidateFrame(); + } + + if (aType == imgINotificationObserver::DECODE_COMPLETE) { + if (nsIDocument* parent = GetOurCurrentDoc()) { + nsCOMPtr<imgIContainer> container; + aRequest->GetImage(getter_AddRefs(container)); + if (container) { + container->PropagateUseCounters(parent); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBulletFrame::BlockOnload(imgIRequest* aRequest) +{ + if (aRequest != mImageRequest) { + return NS_OK; + } + + NS_ASSERTION(!mBlockingOnload, "Double BlockOnload for an nsBulletFrame?"); + + nsIDocument* doc = GetOurCurrentDoc(); + if (doc) { + mBlockingOnload = true; + doc->BlockOnload(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBulletFrame::UnblockOnload(imgIRequest* aRequest) +{ + if (aRequest != mImageRequest) { + return NS_OK; + } + + NS_ASSERTION(!mBlockingOnload, "Double UnblockOnload for an nsBulletFrame?"); + + nsIDocument* doc = GetOurCurrentDoc(); + if (doc) { + doc->UnblockOnload(false); + } + mBlockingOnload = false; + + return NS_OK; +} + +nsIDocument* +nsBulletFrame::GetOurCurrentDoc() const +{ + nsIContent* parentContent = GetParent()->GetContent(); + return parentContent ? parentContent->GetComposedDoc() + : nullptr; +} + +nsresult +nsBulletFrame::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage) +{ + if (!aImage) return NS_ERROR_INVALID_ARG; + if (!aRequest) return NS_ERROR_INVALID_ARG; + + uint32_t status; + aRequest->GetImageStatus(&status); + if (status & imgIRequest::STATUS_ERROR) { + return NS_OK; + } + + nscoord w, h; + aImage->GetWidth(&w); + aImage->GetHeight(&h); + + nsPresContext* presContext = PresContext(); + + LogicalSize newsize(GetWritingMode(), + nsSize(nsPresContext::CSSPixelsToAppUnits(w), + nsPresContext::CSSPixelsToAppUnits(h))); + + if (mIntrinsicSize != newsize) { + mIntrinsicSize = newsize; + + // Now that the size is available (or an error occurred), trigger + // a reflow of the bullet frame. + nsIPresShell *shell = presContext->GetPresShell(); + if (shell) { + shell->FrameNeedsReflow(this, nsIPresShell::eStyleChange, + NS_FRAME_IS_DIRTY); + } + } + + // Handle animations + aImage->SetAnimationMode(presContext->ImageAnimationMode()); + // Ensure the animation (if any) is started. Note: There is no + // corresponding call to Decrement for this. This Increment will be + // 'cleaned up' by the Request when it is destroyed, but only then. + aRequest->IncrementAnimationConsumers(); + + return NS_OK; +} + +void +nsBulletFrame::GetLoadGroup(nsPresContext *aPresContext, nsILoadGroup **aLoadGroup) +{ + if (!aPresContext) + return; + + NS_PRECONDITION(nullptr != aLoadGroup, "null OUT parameter pointer"); + + nsIPresShell *shell = aPresContext->GetPresShell(); + + if (!shell) + return; + + nsIDocument *doc = shell->GetDocument(); + if (!doc) + return; + + *aLoadGroup = doc->GetDocumentLoadGroup().take(); +} + +float +nsBulletFrame::GetFontSizeInflation() const +{ + if (!HasFontSizeInflation()) { + return 1.0f; + } + return Properties().Get(FontSizeInflationProperty()); +} + +void +nsBulletFrame::SetFontSizeInflation(float aInflation) +{ + if (aInflation == 1.0f) { + if (HasFontSizeInflation()) { + RemoveStateBits(BULLET_FRAME_HAS_FONT_INFLATION); + Properties().Delete(FontSizeInflationProperty()); + } + return; + } + + AddStateBits(BULLET_FRAME_HAS_FONT_INFLATION); + Properties().Set(FontSizeInflationProperty(), aInflation); +} + +already_AddRefed<imgIContainer> +nsBulletFrame::GetImage() const +{ + if (mImageRequest && StyleList()->GetListStyleImage()) { + nsCOMPtr<imgIContainer> imageCon; + mImageRequest->GetImage(getter_AddRefs(imageCon)); + return imageCon.forget(); + } + + return nullptr; +} + +nscoord +nsBulletFrame::GetLogicalBaseline(WritingMode aWritingMode) const +{ + nscoord ascent = 0, baselinePadding; + if (GetStateBits() & BULLET_FRAME_IMAGE_LOADING) { + ascent = BSize(aWritingMode); + } else { + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForFrame(this, GetFontSizeInflation()); + CounterStyle* listStyleType = StyleList()->GetCounterStyle(); + switch (listStyleType->GetStyle()) { + case NS_STYLE_LIST_STYLE_NONE: + break; + + case NS_STYLE_LIST_STYLE_DISC: + case NS_STYLE_LIST_STYLE_CIRCLE: + case NS_STYLE_LIST_STYLE_SQUARE: + ascent = fm->MaxAscent(); + baselinePadding = NSToCoordRound(float(ascent) / 8.0f); + ascent = std::max(nsPresContext::CSSPixelsToAppUnits(MIN_BULLET_SIZE), + NSToCoordRound(0.8f * (float(ascent) / 2.0f))); + ascent += baselinePadding; + break; + + case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED: + case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN: + ascent = fm->EmAscent(); + baselinePadding = NSToCoordRound(0.125f * ascent); + ascent = std::max( + nsPresContext::CSSPixelsToAppUnits(MIN_BULLET_SIZE), + NSToCoordRound(0.75f * ascent)); + ascent += baselinePadding; + break; + + default: + ascent = fm->MaxAscent(); + break; + } + } + return ascent + + GetLogicalUsedMargin(aWritingMode).BStart(aWritingMode); +} + +void +nsBulletFrame::GetSpokenText(nsAString& aText) +{ + CounterStyle* style = StyleList()->GetCounterStyle(); + bool isBullet; + style->GetSpokenCounterText(mOrdinal, GetWritingMode(), aText, isBullet); + if (isBullet) { + if (!style->IsNone()) { + aText.Append(' '); + } + } else { + nsAutoString prefix, suffix; + style->GetPrefix(prefix); + style->GetSuffix(suffix); + aText = prefix + aText + suffix; + } +} + +void +nsBulletFrame::RegisterImageRequest(bool aKnownToBeAnimated) +{ + if (mImageRequest) { + // mRequestRegistered is a bitfield; unpack it temporarily so we can take + // the address. + bool isRequestRegistered = mRequestRegistered; + + if (aKnownToBeAnimated) { + nsLayoutUtils::RegisterImageRequest(PresContext(), mImageRequest, + &isRequestRegistered); + } else { + nsLayoutUtils::RegisterImageRequestIfAnimated(PresContext(), + mImageRequest, + &isRequestRegistered); + } + + isRequestRegistered = mRequestRegistered; + } +} + + +void +nsBulletFrame::DeregisterAndCancelImageRequest() +{ + if (mImageRequest) { + // mRequestRegistered is a bitfield; unpack it temporarily so we can take + // the address. + bool isRequestRegistered = mRequestRegistered; + + // Deregister our image request from the refresh driver. + nsLayoutUtils::DeregisterImageRequest(PresContext(), + mImageRequest, + &isRequestRegistered); + + isRequestRegistered = mRequestRegistered; + + // Unblock onload if we blocked it. + if (mBlockingOnload) { + nsIDocument* doc = GetOurCurrentDoc(); + if (doc) { + doc->UnblockOnload(false); + } + mBlockingOnload = false; + } + + // Cancel the image request and forget about it. + mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE); + mImageRequest = nullptr; + } +} + + + + + + +NS_IMPL_ISUPPORTS(nsBulletListener, imgINotificationObserver) + +nsBulletListener::nsBulletListener() : + mFrame(nullptr) +{ +} + +nsBulletListener::~nsBulletListener() +{ +} + +NS_IMETHODIMP +nsBulletListener::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aData) +{ + if (!mFrame) { + return NS_ERROR_FAILURE; + } + return mFrame->Notify(aRequest, aType, aData); +} + +NS_IMETHODIMP +nsBulletListener::BlockOnload(imgIRequest* aRequest) +{ + if (!mFrame) { + return NS_ERROR_FAILURE; + } + return mFrame->BlockOnload(aRequest); +} + +NS_IMETHODIMP +nsBulletListener::UnblockOnload(imgIRequest* aRequest) +{ + if (!mFrame) { + return NS_ERROR_FAILURE; + } + return mFrame->UnblockOnload(aRequest); +} |