summaryrefslogtreecommitdiffstats
path: root/layout/generic/nsBulletFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/generic/nsBulletFrame.cpp')
-rw-r--r--layout/generic/nsBulletFrame.cpp1090
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);
+}