summaryrefslogtreecommitdiffstats
path: root/layout/generic/nsBulletFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--layout/generic/nsBulletFrame.cpp1326
1 files changed, 1326 insertions, 0 deletions
diff --git a/layout/generic/nsBulletFrame.cpp b/layout/generic/nsBulletFrame.cpp
new file mode 100644
index 0000000000..525dc5a186
--- /dev/null
+++ b/layout/generic/nsBulletFrame.cpp
@@ -0,0 +1,1326 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 <algorithm>
+#include <utility>
+
+#include "CounterStyleManager.h"
+#include "ImageLayers.h"
+#include "TextDrawTarget.h"
+#include "UnitTransforms.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxUtils.h"
+#include "imgIContainer.h"
+#include "imgRequestProxy.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGImageContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/layers/LayersMessages.h"
+#include "mozilla/layers/RenderRootStateManager.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/layers/WebRenderMessages.h"
+#include "nsAttrValueInlines.h"
+#include "nsBidiUtils.h"
+#include "nsCOMPtr.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsCounterManager.h"
+#include "nsDisplayList.h"
+#include "nsFontMetrics.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsIURI.h"
+#include "nsPresContext.h"
+
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+using namespace mozilla::layout;
+using mozilla::dom::Document;
+
+nsIFrame* NS_NewBulletFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
+ return new (aPresShell) nsBulletFrame(aStyle, aPresShell->GetPresContext());
+}
+
+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(nsIFrame)
+#endif
+
+nsBulletFrame::~nsBulletFrame() = default;
+
+CounterStyle* nsBulletFrame::ResolveCounterStyle() {
+ return PresContext()->CounterStyleManager()->ResolveCounterStyle(
+ StyleList()->mCounterStyle);
+}
+
+void nsBulletFrame::DestroyFrom(nsIFrame* aDestructRoot,
+ PostDestroyData& aPostDestroyData) {
+ // Stop image loading first.
+ DeregisterAndCancelImageRequest();
+
+ if (mListener) {
+ mListener->SetFrame(nullptr);
+ }
+
+ // Let base class do the rest
+ nsIFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
+}
+
+#ifdef DEBUG_FRAME_DUMP
+nsresult nsBulletFrame::GetFrameName(nsAString& aResult) const {
+ return MakeFrameName(u"Bullet"_ns, aResult);
+}
+#endif
+
+bool nsBulletFrame::IsEmpty() { return IsSelfEmpty(); }
+
+bool nsBulletFrame::IsSelfEmpty() {
+ return StyleList()->mCounterStyle.IsNone();
+}
+
+/* virtual */
+void nsBulletFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
+ nsIFrame::DidSetComputedStyle(aOldComputedStyle);
+
+ 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->SyncClone(mListener, PresContext()->Document(),
+ 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 = std::move(newRequestClone);
+ RegisterImageRequest(/* aKnownToBeAnimated = */ false);
+
+ // Image bullets can affect the layout of the page, so boost the image
+ // load priority.
+ mImageRequest->BoostPriority(imgIRequest::CATEGORY_SIZE_QUERY);
+ }
+ } else {
+ // No image request on the new ComputedStyle.
+ 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 (aOldComputedStyle) {
+ if (nsAccessibilityService* accService =
+ PresShell::GetAccessibilityService()) {
+ const nsStyleList* oldStyleList = aOldComputedStyle->StyleList();
+ bool hadBullet = oldStyleList->GetListStyleImage() ||
+ !oldStyleList->mCounterStyle.IsNone();
+
+ const nsStyleList* newStyleList = StyleList();
+ bool hasBullet = newStyleList->GetListStyleImage() ||
+ !newStyleList->mCounterStyle.IsNone();
+
+ if (hadBullet != hasBullet) {
+ nsIContent* listItem = mContent->GetParent();
+ accService->UpdateListBullet(PresContext()->GetPresShell(), listItem,
+ hasBullet);
+ }
+ }
+ }
+#endif // #ifdef ACCESSIBILITY
+}
+
+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->Ordinal();
+ }
+
+ virtual bool InvalidateForSyncDecodeImages() const override {
+ return ShouldInvalidateToSyncDecodeImages();
+ }
+
+ int32_t mOrdinal;
+};
+
+class BulletRenderer final {
+ public:
+ BulletRenderer(imgIContainer* image, const nsRect& dest)
+ : mImage(image),
+ mDest(dest),
+ mColor(NS_RGBA(0, 0, 0, 0)),
+ mListStyleType(NS_STYLE_LIST_STYLE_NONE) {
+ MOZ_ASSERT(IsImageType());
+ }
+
+ BulletRenderer(Path* path, nscolor color, int32_t listStyleType)
+ : mColor(color), mPath(path), mListStyleType(listStyleType) {
+ MOZ_ASSERT(IsPathType());
+ }
+
+ BulletRenderer(const LayoutDeviceRect& aPathRect, nscolor color,
+ int32_t listStyleType)
+ : mPathRect(aPathRect), mColor(color), mListStyleType(listStyleType) {
+ MOZ_ASSERT(IsPathType());
+ }
+
+ BulletRenderer(const nsString& text, nsFontMetrics* fm, nscolor color,
+ const nsPoint& point, int32_t listStyleType)
+ : mColor(color),
+ mText(text),
+ mFontMetrics(fm),
+ mPoint(point),
+ mListStyleType(listStyleType) {
+ MOZ_ASSERT(IsTextType());
+ }
+
+ ImgDrawResult CreateWebRenderCommands(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder);
+
+ ImgDrawResult Paint(gfxContext& aRenderingContext, nsPoint aPt,
+ const nsRect& aDirtyRect, uint32_t aFlags,
+ bool aDisableSubpixelAA, nsIFrame* aFrame);
+
+ bool IsImageType() const {
+ return mListStyleType == NS_STYLE_LIST_STYLE_NONE && mImage;
+ }
+
+ bool IsPathType() const {
+ return mListStyleType == NS_STYLE_LIST_STYLE_DISC ||
+ mListStyleType == NS_STYLE_LIST_STYLE_CIRCLE ||
+ mListStyleType == NS_STYLE_LIST_STYLE_SQUARE ||
+ mListStyleType == NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN ||
+ mListStyleType == NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED;
+ }
+
+ bool IsTextType() const {
+ return mListStyleType != NS_STYLE_LIST_STYLE_NONE &&
+ mListStyleType != NS_STYLE_LIST_STYLE_DISC &&
+ mListStyleType != NS_STYLE_LIST_STYLE_CIRCLE &&
+ mListStyleType != NS_STYLE_LIST_STYLE_SQUARE &&
+ mListStyleType != NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN &&
+ mListStyleType != NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED &&
+ !mText.IsEmpty();
+ }
+
+ void PaintTextToContext(nsIFrame* aFrame, gfxContext* aCtx,
+ bool aDisableSubpixelAA);
+
+ bool IsImageContainerAvailable(layers::LayerManager* aManager,
+ uint32_t aFlags);
+
+ private:
+ ImgDrawResult CreateWebRenderCommandsForImage(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder);
+
+ bool CreateWebRenderCommandsForPath(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder);
+
+ bool CreateWebRenderCommandsForText(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder);
+
+ private:
+ // mImage and mDest are the properties for list-style-image.
+ // mImage is the image content and mDest is the image position.
+ RefPtr<imgIContainer> mImage;
+ nsRect mDest;
+
+ // Some bullet types are stored as a rect (in device pixels) instead of a Path
+ // to allow generating proper WebRender commands. When webrender is disabled
+ // the Path is lazily created for these items before painting.
+ // TODO: The size of this structure doesn't seem to be an issue since it has
+ // so many fields that are specific to a bullet style or another, but if it
+ // becomes one we can easily store mDest and mPathRect into the same memory
+ // location since they are never used by the same bullet types.
+ LayoutDeviceRect mPathRect;
+
+ // mColor indicate the color of list-style. Both text and path type would use
+ // this member.
+ nscolor mColor;
+
+ // mPath record the path of the list-style for later drawing.
+ // Included following types: square, circle, disc, disclosure open and
+ // disclosure closed.
+ RefPtr<Path> mPath;
+
+ // mText, mFontMetrics, mPoint are for other list-style-type which can be
+ // drawed by text.
+ nsString mText;
+ RefPtr<nsFontMetrics> mFontMetrics;
+ nsPoint mPoint;
+
+ // Store the type of list-style-type.
+ int32_t mListStyleType;
+};
+
+ImgDrawResult BulletRenderer::CreateWebRenderCommands(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ if (IsImageType()) {
+ return CreateWebRenderCommandsForImage(aItem, aBuilder, aResources, aSc,
+ aManager, aDisplayListBuilder);
+ }
+
+ bool success;
+ if (IsPathType()) {
+ success = CreateWebRenderCommandsForPath(aItem, aBuilder, aResources, aSc,
+ aManager, aDisplayListBuilder);
+ } else {
+ MOZ_ASSERT(IsTextType());
+ success = CreateWebRenderCommandsForText(aItem, aBuilder, aResources, aSc,
+ aManager, aDisplayListBuilder);
+ }
+
+ return success ? ImgDrawResult::SUCCESS : ImgDrawResult::NOT_SUPPORTED;
+}
+
+ImgDrawResult BulletRenderer::Paint(gfxContext& aRenderingContext, nsPoint aPt,
+ const nsRect& aDirtyRect, uint32_t aFlags,
+ bool aDisableSubpixelAA, nsIFrame* aFrame) {
+ if (IsImageType()) {
+ SamplingFilter filter = nsLayoutUtils::GetSamplingFilterForFrame(aFrame);
+ return nsLayoutUtils::DrawSingleImage(
+ aRenderingContext, aFrame->PresContext(), mImage, filter, mDest,
+ aDirtyRect,
+ /* no SVGImageContext */ Nothing(), aFlags);
+ }
+
+ if (IsPathType()) {
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+
+ if (!mPath) {
+ RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder();
+ switch (mListStyleType) {
+ case NS_STYLE_LIST_STYLE_CIRCLE:
+ case NS_STYLE_LIST_STYLE_DISC:
+ AppendEllipseToPath(builder, mPathRect.Center().ToUnknownPoint(),
+ mPathRect.Size().ToUnknownSize());
+ break;
+ case NS_STYLE_LIST_STYLE_SQUARE:
+ AppendRectToPath(builder, mPathRect.ToUnknownRect());
+ break;
+ default:
+ MOZ_ASSERT(false, "Should have a parth.");
+ }
+ mPath = builder->Finish();
+ }
+
+ switch (mListStyleType) {
+ case NS_STYLE_LIST_STYLE_CIRCLE:
+ drawTarget->Stroke(mPath, ColorPattern(ToDeviceColor(mColor)));
+ break;
+ case NS_STYLE_LIST_STYLE_DISC:
+ case NS_STYLE_LIST_STYLE_SQUARE:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
+ case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
+ drawTarget->Fill(mPath, ColorPattern(ToDeviceColor(mColor)));
+ break;
+ default:
+ MOZ_CRASH("unreachable");
+ }
+ }
+
+ if (IsTextType()) {
+ PaintTextToContext(aFrame, &aRenderingContext, aDisableSubpixelAA);
+ }
+
+ return ImgDrawResult::SUCCESS;
+}
+
+void BulletRenderer::PaintTextToContext(nsIFrame* aFrame, gfxContext* aCtx,
+ bool aDisableSubpixelAA) {
+ MOZ_ASSERT(IsTextType());
+
+ DrawTarget* drawTarget = aCtx->GetDrawTarget();
+ DrawTargetAutoDisableSubpixelAntialiasing disable(drawTarget,
+ aDisableSubpixelAA);
+
+ aCtx->SetColor(sRGBColor::FromABGR(mColor));
+
+ nsPresContext* presContext = aFrame->PresContext();
+ if (!presContext->BidiEnabled() && HasRTLChars(mText)) {
+ presContext->SetBidiEnabled();
+ }
+ nsLayoutUtils::DrawString(aFrame, *mFontMetrics, aCtx, mText.get(),
+ mText.Length(), mPoint);
+}
+
+bool BulletRenderer::IsImageContainerAvailable(layers::LayerManager* aManager,
+ uint32_t aFlags) {
+ MOZ_ASSERT(IsImageType());
+
+ return mImage->IsImageContainerAvailable(aManager, aFlags);
+}
+
+ImgDrawResult BulletRenderer::CreateWebRenderCommandsForImage(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ MOZ_RELEASE_ASSERT(IsImageType());
+ MOZ_RELEASE_ASSERT(mImage);
+
+ uint32_t flags = aDisplayListBuilder->GetImageDecodeFlags();
+
+ const int32_t appUnitsPerDevPixel =
+ aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
+ LayoutDeviceRect destRect =
+ LayoutDeviceRect::FromAppUnits(mDest, appUnitsPerDevPixel);
+
+ Maybe<SVGImageContext> svgContext;
+ gfx::IntSize decodeSize =
+ nsLayoutUtils::ComputeImageContainerDrawingParameters(
+ mImage, aItem->Frame(), destRect, aSc, flags, svgContext);
+
+ RefPtr<layers::ImageContainer> container;
+ ImgDrawResult drawResult = mImage->GetImageContainerAtSize(
+ aManager->LayerManager(), decodeSize, svgContext, flags,
+ getter_AddRefs(container));
+ if (!container) {
+ return drawResult;
+ }
+
+ mozilla::wr::ImageRendering rendering = wr::ToImageRendering(
+ nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame()));
+ gfx::IntSize size;
+ Maybe<wr::ImageKey> key = aManager->CommandBuilder().CreateImageKey(
+ aItem, container, aBuilder, aResources, rendering, aSc, size, Nothing());
+ if (key.isNothing()) {
+ return drawResult;
+ }
+
+ wr::LayoutRect dest = wr::ToLayoutRect(destRect);
+
+ aBuilder.PushImage(dest, dest, !aItem->BackfaceIsHidden(), rendering,
+ key.value());
+
+ return drawResult;
+}
+
+bool BulletRenderer::CreateWebRenderCommandsForPath(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ MOZ_ASSERT(IsPathType());
+ wr::LayoutRect dest = wr::ToLayoutRect(mPathRect);
+ auto color = wr::ToColorF(ToDeviceColor(mColor));
+ bool isBackfaceVisible = !aItem->BackfaceIsHidden();
+ switch (mListStyleType) {
+ case NS_STYLE_LIST_STYLE_CIRCLE: {
+ LayoutDeviceSize radii = mPathRect.Size() / 2.0;
+ auto borderWidths = wr::ToBorderWidths(1.0, 1.0, 1.0, 1.0);
+ wr::BorderSide side = {color, wr::BorderStyle::Solid};
+ wr::BorderSide sides[4] = {side, side, side, side};
+ Range<const wr::BorderSide> sidesRange(sides, 4);
+ aBuilder.PushBorder(dest, dest, isBackfaceVisible, borderWidths,
+ sidesRange,
+ wr::ToBorderRadius(radii, radii, radii, radii));
+ return true;
+ }
+ case NS_STYLE_LIST_STYLE_DISC: {
+ aBuilder.PushRoundedRect(dest, dest, isBackfaceVisible, color);
+ return true;
+ }
+ case NS_STYLE_LIST_STYLE_SQUARE: {
+ aBuilder.PushRect(dest, dest, isBackfaceVisible, color);
+ return true;
+ }
+ default:
+ if (!aManager->CommandBuilder().PushItemAsImage(
+ aItem, aBuilder, aResources, aSc, aDisplayListBuilder)) {
+ NS_WARNING("Fail to create WebRender commands for Bullet path.");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool BulletRenderer::CreateWebRenderCommandsForText(
+ nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
+ wr::IpcResourceUpdateQueue& aResources,
+ const layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ MOZ_ASSERT(IsTextType());
+
+ bool dummy;
+ nsRect bounds = aItem->GetBounds(aDisplayListBuilder, &dummy);
+
+ if (bounds.IsEmpty()) {
+ return true;
+ }
+
+ RefPtr<TextDrawTarget> textDrawer =
+ new TextDrawTarget(aBuilder, aResources, aSc, aManager, aItem, bounds);
+ RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(textDrawer);
+ PaintTextToContext(aItem->Frame(), captureCtx, aItem->IsSubpixelAADisabled());
+ textDrawer->TerminateShadows();
+
+ return textDrawer->Finish();
+}
+
+class nsDisplayBullet final : public nsPaintedDisplayItem {
+ public:
+ nsDisplayBullet(nsDisplayListBuilder* aBuilder, nsBulletFrame* aFrame)
+ : nsPaintedDisplayItem(aBuilder, aFrame) {
+ MOZ_COUNT_CTOR(nsDisplayBullet);
+ }
+ MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBullet)
+
+ virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
+ bool* aSnap) const override {
+ *aSnap = false;
+ return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame();
+ }
+
+ virtual bool CreateWebRenderCommands(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue&, const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) override;
+
+ virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+ HitTestState* aState,
+ nsTArray<nsIFrame*>* aOutFrames) override {
+ aOutFrames->AppendElement(mFrame);
+ }
+ virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
+ NS_DISPLAY_DECL_NAME("Bullet", TYPE_BULLET)
+
+ virtual nsRect GetComponentAlphaBounds(
+ nsDisplayListBuilder* aBuilder) const override {
+ bool snap;
+ return GetBounds(aBuilder, &snap);
+ }
+
+ virtual nsDisplayItemGeometry* AllocateGeometry(
+ nsDisplayListBuilder* aBuilder) override {
+ return new nsDisplayBulletGeometry(this, aBuilder);
+ }
+
+ virtual void ComputeInvalidationRegion(
+ nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+ nsRegion* aInvalidRegion) const override {
+ const nsDisplayBulletGeometry* geometry =
+ static_cast<const nsDisplayBulletGeometry*>(aGeometry);
+ nsBulletFrame* f = static_cast<nsBulletFrame*>(mFrame);
+
+ if (f->Ordinal() != 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 nsPaintedDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry,
+ aInvalidRegion);
+ }
+
+ protected:
+ Maybe<BulletRenderer> mBulletRenderer;
+};
+
+bool nsDisplayBullet::CreateWebRenderCommands(
+ wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
+ const StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager,
+ nsDisplayListBuilder* aDisplayListBuilder) {
+ // FIXME: avoid needing to make this target if we're drawing text
+ // (non-trivial refactor of all this code)
+ RefPtr<gfxContext> screenRefCtx = gfxContext::CreateOrNull(
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get());
+ Maybe<BulletRenderer> br =
+ static_cast<nsBulletFrame*>(mFrame)->CreateBulletRenderer(
+ *screenRefCtx, ToReferenceFrame());
+
+ if (!br) {
+ return false;
+ }
+
+ ImgDrawResult drawResult = br->CreateWebRenderCommands(
+ this, aBuilder, aResources, aSc, aManager, aDisplayListBuilder);
+ if (drawResult == ImgDrawResult::NOT_SUPPORTED) {
+ return false;
+ }
+
+ nsDisplayBulletGeometry::UpdateDrawResult(this, drawResult);
+ return true;
+}
+
+void nsDisplayBullet::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+ uint32_t flags = imgIContainer::FLAG_NONE;
+ if (aBuilder->ShouldSyncDecodeImages()) {
+ flags |= imgIContainer::FLAG_SYNC_DECODE;
+ }
+
+ ImgDrawResult result = static_cast<nsBulletFrame*>(mFrame)->PaintBullet(
+ *aCtx, ToReferenceFrame(), GetPaintRect(), flags, IsSubpixelAADisabled());
+
+ nsDisplayBulletGeometry::UpdateDrawResult(this, result);
+}
+
+void nsBulletFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aLists) {
+ if (!IsVisibleForPainting()) return;
+
+ DO_GLOBAL_REFLOW_COUNT_DSP("nsBulletFrame");
+
+ aLists.Content()->AppendNewToTop<nsDisplayBullet>(aBuilder, this);
+}
+
+Maybe<BulletRenderer> nsBulletFrame::CreateBulletRenderer(
+ gfxContext& aRenderingContext, nsPoint aPt) {
+ const nsStyleList* myList = StyleList();
+ CounterStyle* listStyleType = ResolveCounterStyle();
+ nsMargin padding = mPadding.GetPhysicalMargin(GetWritingMode());
+
+ if (myList->GetListStyleImage() && mImageRequest) {
+ uint32_t status;
+ mImageRequest->GetImageStatus(&status);
+ if (!(status & imgIRequest::STATUS_ERROR)) {
+ if (status & imgIRequest::STATUS_LOAD_COMPLETE) {
+ nsCOMPtr<imgIContainer> imageCon;
+ mImageRequest->GetImage(getter_AddRefs(imageCon));
+ if (imageCon) {
+ imageCon = nsLayoutUtils::OrientImage(
+ imageCon, StyleVisibility()->mImageOrientation);
+ nsRect dest(padding.left, padding.top,
+ mRect.width - (padding.left + padding.right),
+ mRect.height - (padding.top + padding.bottom));
+ BulletRenderer br(imageCon, dest + aPt);
+ return Some(br);
+ }
+ } else {
+ // Boost the load priority further now that we know we want to display
+ // the bullet image.
+ mImageRequest->BoostPriority(imgIRequest::CATEGORY_DISPLAY);
+ }
+ }
+ }
+
+ nscolor color = nsLayoutUtils::GetColor(this, &nsStyleText::mColor);
+
+ DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
+ int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
+
+ switch (listStyleType->GetStyle()) {
+ case NS_STYLE_LIST_STYLE_NONE:
+ return Nothing();
+
+ 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));
+ auto devPxRect =
+ LayoutDeviceRect::FromAppUnits(rect, appUnitsPerDevPixel);
+ return Some(BulletRenderer(devPxRect, color, listStyleType->GetStyle()));
+ }
+
+ 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);
+ auto devPxRect =
+ LayoutDeviceRect::FromAppUnits(snapRect, appUnitsPerDevPixel);
+ return Some(BulletRenderer(devPxRect, color, listStyleType->GetStyle()));
+ }
+
+ 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 {
+ if (wm.IsPhysicalLTR()) {
+ // 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();
+ BulletRenderer br(path, color, listStyleType->GetStyle());
+ return Some(br);
+ }
+
+ default: {
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, GetFontSizeInflation());
+ nsAutoString text;
+ WritingMode wm = GetWritingMode();
+ GetListItemText(listStyleType, wm, Ordinal(), text);
+ nscoord ascent = wm.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent();
+ aPt.MoveBy(padding.left, padding.top);
+ if (wm.IsVertical()) {
+ if (wm.IsVerticalLR()) {
+ aPt.x = NSToCoordRound(nsLayoutUtils::GetSnappedBaselineX(
+ this, &aRenderingContext, aPt.x, ascent));
+ } else {
+ aPt.x = NSToCoordRound(nsLayoutUtils::GetSnappedBaselineX(
+ this, &aRenderingContext, aPt.x + mRect.width, -ascent));
+ }
+ } else {
+ aPt.y = NSToCoordRound(nsLayoutUtils::GetSnappedBaselineY(
+ this, &aRenderingContext, aPt.y, ascent));
+ }
+
+ BulletRenderer br(text, fm, color, aPt, listStyleType->GetStyle());
+ return Some(br);
+ }
+ }
+
+ MOZ_CRASH("unreachable");
+ return Nothing();
+}
+
+ImgDrawResult nsBulletFrame::PaintBullet(gfxContext& aRenderingContext,
+ nsPoint aPt, const nsRect& aDirtyRect,
+ uint32_t aFlags,
+ bool aDisableSubpixelAA) {
+ Maybe<BulletRenderer> br = CreateBulletRenderer(aRenderingContext, aPt);
+
+ if (!br) {
+ return ImgDrawResult::SUCCESS;
+ }
+
+ return br->Paint(aRenderingContext, aPt, aDirtyRect, aFlags,
+ aDisableSubpixelAA, this);
+}
+
+void nsBulletFrame::GetListItemText(CounterStyle* aStyle,
+ mozilla::WritingMode aWritingMode,
+ int32_t aOrdinal, nsAString& aResult) {
+ NS_ASSERTION(
+ aStyle->GetStyle() != NS_STYLE_LIST_STYLE_NONE &&
+ aStyle->GetStyle() != NS_STYLE_LIST_STYLE_DISC &&
+ aStyle->GetStyle() != NS_STYLE_LIST_STYLE_CIRCLE &&
+ aStyle->GetStyle() != NS_STYLE_LIST_STYLE_SQUARE &&
+ aStyle->GetStyle() != NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED &&
+ aStyle->GetStyle() != NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN,
+ "we should be using specialized code for these types");
+
+ bool isRTL;
+ nsAutoString counter, prefix, suffix;
+ aStyle->GetPrefix(prefix);
+ aStyle->GetSuffix(suffix);
+ aStyle->GetCounterText(aOrdinal, aWritingMode, counter, isRTL);
+
+ aResult.Truncate();
+ aResult.Append(prefix);
+ if (aWritingMode.IsBidiRTL() == 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,
+ gfxContext* 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;
+ CounterStyle* style = ResolveCounterStyle();
+ switch (style->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(style, wm, Ordinal(), 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);
+ MOZ_ASSERT(aStatus.IsEmpty(), "The reflow status should be empty!");
+
+ 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 auto bp = aReflowInput.ComputedLogicalBorderPadding(wm);
+ 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();
+
+ NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics);
+}
+
+/* virtual */
+nscoord nsBulletFrame::GetMinISize(gfxContext* aRenderingContext) {
+ WritingMode wm = GetWritingMode();
+ ReflowOutput reflowOutput(wm);
+ DISPLAY_MIN_INLINE_SIZE(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(gfxContext* aRenderingContext) {
+ WritingMode wm = GetWritingMode();
+ ReflowOutput metrics(wm);
+ DISPLAY_PREF_INLINE_SIZE(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->mCounterStyle.IsNone() && !listStyle->GetListStyleImage();
+}
+
+/* virtual */
+void nsBulletFrame::AddInlineMinISize(gfxContext* aRenderingContext,
+ nsIFrame::InlineMinISizeData* aData) {
+ nscoord isize = nsLayoutUtils::IntrinsicForContainer(
+ aRenderingContext, this, IntrinsicISizeType::MinISize);
+ if (MOZ_LIKELY(!::IsIgnoreable(this, isize))) {
+ aData->DefaultAddInlineMinISize(this, isize);
+ }
+}
+
+/* virtual */
+void nsBulletFrame::AddInlinePrefISize(gfxContext* aRenderingContext,
+ nsIFrame::InlinePrefISizeData* aData) {
+ nscoord isize = nsLayoutUtils::IntrinsicForContainer(
+ aRenderingContext, this, IntrinsicISizeType::PrefISize);
+ if (MOZ_LIKELY(!::IsIgnoreable(this, isize))) {
+ aData->DefaultAddInlinePrefISize(isize);
+ }
+}
+
+void 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 |
+ imgIContainer::FLAG_HIGH_QUALITY_SCALING);
+ }
+
+ InvalidateFrame();
+ }
+
+ if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+ if (Document* parent = GetOurCurrentDoc()) {
+ nsCOMPtr<imgIContainer> container;
+ aRequest->GetImage(getter_AddRefs(container));
+ if (container) {
+ container->PropagateUseCounters(parent);
+ }
+ }
+ }
+}
+
+Document* nsBulletFrame::GetOurCurrentDoc() const {
+ nsIContent* parentContent = GetParent()->GetContent();
+ return parentContent ? parentContent->GetComposedDoc() : nullptr;
+}
+
+void nsBulletFrame::OnSizeAvailable(imgIRequest* aRequest,
+ imgIContainer* aImage) {
+ if (!aImage) return;
+ if (!aRequest) return;
+
+ uint32_t status;
+ aRequest->GetImageStatus(&status);
+ if (status & imgIRequest::STATUS_ERROR) {
+ return;
+ }
+
+ 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.
+ mozilla::PresShell* presShell = presContext->GetPresShell();
+ if (presShell) {
+ presShell->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
+ 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();
+}
+
+void nsBulletFrame::GetLoadGroup(nsPresContext* aPresContext,
+ nsILoadGroup** aLoadGroup) {
+ if (!aPresContext) return;
+
+ MOZ_ASSERT(nullptr != aLoadGroup, "null OUT parameter pointer");
+
+ mozilla::PresShell* presShell = aPresContext->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ Document* doc = presShell->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);
+ RemoveProperty(FontSizeInflationProperty());
+ }
+ return;
+ }
+
+ AddStateBits(BULLET_FRAME_HAS_FONT_INFLATION);
+ SetProperty(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::GetListStyleAscent() const {
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetFontMetricsForFrame(this, GetFontSizeInflation());
+ auto* list = StyleList();
+ if (list->mCounterStyle.IsNone()) {
+ return 0;
+ }
+ if (list->mCounterStyle.IsAnonymous()) {
+ return fm->MaxAscent();
+ }
+ // NOTE(emilio): @counter-style can override most of the styles from this
+ // list, and we still return the changed ascent. Do we care about that?
+ //
+ // https://github.com/w3c/csswg-drafts/issues/3584
+ nsAtom* style = list->mCounterStyle.AsAtom();
+ if (style == nsGkAtoms::disc || style == nsGkAtoms::circle ||
+ style == nsGkAtoms::square) {
+ nscoord ascent = fm->MaxAscent();
+ nscoord baselinePadding = NSToCoordRound(float(ascent) / 8.0f);
+ ascent = std::max(nsPresContext::CSSPixelsToAppUnits(MIN_BULLET_SIZE),
+ NSToCoordRound(0.8f * (float(ascent) / 2.0f)));
+ return ascent + baselinePadding;
+ }
+ if (style == nsGkAtoms::disclosure_open ||
+ style == nsGkAtoms::disclosure_closed) {
+ nscoord ascent = fm->EmAscent();
+ nscoord baselinePadding = NSToCoordRound(0.125f * ascent);
+ ascent = std::max(nsPresContext::CSSPixelsToAppUnits(MIN_BULLET_SIZE),
+ NSToCoordRound(0.75f * ascent));
+ return ascent + baselinePadding;
+ }
+ return fm->MaxAscent();
+}
+
+nscoord nsBulletFrame::GetLogicalBaseline(WritingMode aWritingMode) const {
+ nscoord ascent = 0;
+ if (HasAnyStateBits(BULLET_FRAME_IMAGE_LOADING)) {
+ ascent = BSize(aWritingMode);
+ } else {
+ ascent = GetListStyleAscent();
+ }
+ return ascent + GetLogicalUsedMargin(aWritingMode).BStart(aWritingMode);
+}
+
+bool nsBulletFrame::GetNaturalBaselineBOffset(WritingMode aWM,
+ BaselineSharingGroup,
+ nscoord* aBaseline) const {
+ nscoord ascent = 0;
+ if (HasAnyStateBits(BULLET_FRAME_IMAGE_LOADING)) {
+ ascent = BSize(aWM);
+ } else {
+ ascent = GetListStyleAscent();
+ }
+ *aBaseline = ascent;
+ return true;
+}
+
+#ifdef ACCESSIBILITY
+void nsBulletFrame::GetSpokenText(nsAString& aText) {
+ CounterStyle* style =
+ PresContext()->CounterStyleManager()->ResolveCounterStyle(
+ StyleList()->mCounterStyle);
+ bool isBullet;
+ style->GetSpokenCounterText(Ordinal(), GetWritingMode(), aText, isBullet);
+ if (isBullet) {
+ if (!style->IsNone()) {
+ aText.Append(' ');
+ }
+ } else {
+ nsAutoString prefix, suffix;
+ style->GetPrefix(prefix);
+ style->GetSuffix(suffix);
+ aText = prefix + aText + suffix;
+ }
+}
+#endif
+
+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);
+ }
+
+ mRequestRegistered = isRequestRegistered;
+ }
+}
+
+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);
+
+ mRequestRegistered = isRequestRegistered;
+
+ // Cancel the image request and forget about it.
+ mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
+ mImageRequest = nullptr;
+ }
+}
+
+void nsBulletFrame::SetOrdinal(int32_t aOrdinal, bool aNotify) {
+ if (mOrdinal == aOrdinal) {
+ return;
+ }
+ mOrdinal = aOrdinal;
+ if (aNotify) {
+ PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
+ NS_FRAME_IS_DIRTY);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsBulletListener, imgINotificationObserver)
+
+nsBulletListener::nsBulletListener() : mFrame(nullptr) {}
+
+nsBulletListener::~nsBulletListener() = default;
+
+void nsBulletListener::Notify(imgIRequest* aRequest, int32_t aType,
+ const nsIntRect* aData) {
+ if (!mFrame) {
+ return;
+ }
+ return mFrame->Notify(aRequest, aType, aData);
+}