/* -*- 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 #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 oldURI; mImageRequest->GetURI(getter_AddRefs(oldURI)); nsCOMPtr newURI; newRequest->GetURI(getter_AddRefs(newURI)); if (oldURI && newURI) { bool same; newURI->Equals(oldURI, &same); if (same) { needNewRequest = false; } } } if (needNewRequest) { RefPtr 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 { public: nsDisplayBulletGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder) : nsDisplayItemGenericGeometry(aItem, aBuilder) , nsImageGeometryMixin(aItem, aBuilder) { nsBulletFrame* f = static_cast(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 *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(aGeometry); nsBulletFrame* f = static_cast(mFrame); if (f->GetOrdinal() != geometry->mOrdinal) { bool snap; aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &snap)); return; } nsCOMPtr 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(mFrame)-> PaintBullet(*aCtx, ToReferenceFrame(), mVisibleRect, flags, mDisableSubpixelAA); nsDisplayBulletGeometry::UpdateDrawResult(this, result); } void nsBulletFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 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 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 builder = drawTarget->CreatePathBuilder(); AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size()); RefPtr 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 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 = builder->Finish(); drawTarget->Fill(path, color); } break; default: { DrawTargetAutoDisableSubpixelAntialiasing disable(aRenderingContext.GetDrawTarget(), aDisableSubpixelAA); aRenderingContext.ThebesContext()->SetColor( Color::FromABGR(nsLayoutUtils::GetColor(this, eCSSProperty_color))); RefPtr 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 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 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 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 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 GetProperty(FontSizeInflationProperty()); } void nsBulletFrame::SetFontSizeInflation(float aInflation) { if (aInflation == 1.0f) { if (HasFontSizeInflation()) { RemoveStateBits(BULLET_FRAME_HAS_FONT_INFLATION); DeleteProperty(FontSizeInflationProperty()); } return; } AddStateBits(BULLET_FRAME_HAS_FONT_INFLATION); SetProperty(FontSizeInflationProperty(), aInflation); } already_AddRefed nsBulletFrame::GetImage() const { if (mImageRequest && StyleList()->GetListStyleImage()) { nsCOMPtr 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 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); }