1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /* rendering object for list-item bullets */
8
9 #include "nsBulletFrame.h"
10
11 #include <algorithm>
12 #include <utility>
13
14 #include "CounterStyleManager.h"
15 #include "ImageLayers.h"
16 #include "SVGImageContext.h"
17 #include "TextDrawTarget.h"
18 #include "UnitTransforms.h"
19 #include "gfx2DGlue.h"
20 #include "gfxContext.h"
21 #include "gfxUtils.h"
22 #include "imgIContainer.h"
23 #include "imgRequestProxy.h"
24 #include "mozilla/MathAlgorithms.h"
25 #include "mozilla/PresShell.h"
26 #include "mozilla/dom/Document.h"
27 #include "mozilla/gfx/2D.h"
28 #include "mozilla/gfx/PathHelpers.h"
29 #include "mozilla/layers/LayersMessages.h"
30 #include "mozilla/layers/RenderRootStateManager.h"
31 #include "mozilla/layers/StackingContextHelper.h"
32 #include "mozilla/layers/WebRenderBridgeChild.h"
33 #include "mozilla/layers/WebRenderMessages.h"
34 #include "nsAttrValueInlines.h"
35 #include "nsBidiUtils.h"
36 #include "nsCOMPtr.h"
37 #include "nsCSSFrameConstructor.h"
38 #include "nsCounterManager.h"
39 #include "nsDisplayList.h"
40 #include "nsFontMetrics.h"
41 #include "nsGenericHTMLElement.h"
42 #include "nsGkAtoms.h"
43 #include "nsIURI.h"
44 #include "nsPresContext.h"
45
46 #ifdef ACCESSIBILITY
47 # include "nsAccessibilityService.h"
48 #endif
49
50 using namespace mozilla;
51 using namespace mozilla::gfx;
52 using namespace mozilla::image;
53 using namespace mozilla::layout;
54 using mozilla::dom::Document;
55
NS_NewBulletFrame(PresShell * aPresShell,ComputedStyle * aStyle)56 nsIFrame* NS_NewBulletFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
57 return new (aPresShell) nsBulletFrame(aStyle, aPresShell->GetPresContext());
58 }
59
60 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FontSizeInflationProperty, float)
61
62 NS_IMPL_FRAMEARENA_HELPERS(nsBulletFrame)
63
64 #ifdef DEBUG
65 NS_QUERYFRAME_HEAD(nsBulletFrame)
66 NS_QUERYFRAME_ENTRY(nsBulletFrame)
67 NS_QUERYFRAME_TAIL_INHERITING(nsFrame)
68 #endif
69
70 nsBulletFrame::~nsBulletFrame() = default;
71
ResolveCounterStyle()72 CounterStyle* nsBulletFrame::ResolveCounterStyle() {
73 return PresContext()->CounterStyleManager()->ResolveCounterStyle(
74 StyleList()->mCounterStyle);
75 }
76
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)77 void nsBulletFrame::DestroyFrom(nsIFrame* aDestructRoot,
78 PostDestroyData& aPostDestroyData) {
79 // Stop image loading first.
80 DeregisterAndCancelImageRequest();
81
82 if (mListener) {
83 mListener->SetFrame(nullptr);
84 }
85
86 // Let base class do the rest
87 nsFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
88 }
89
90 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const91 nsresult nsBulletFrame::GetFrameName(nsAString& aResult) const {
92 return MakeFrameName(NS_LITERAL_STRING("Bullet"), aResult);
93 }
94 #endif
95
IsEmpty()96 bool nsBulletFrame::IsEmpty() { return IsSelfEmpty(); }
97
IsSelfEmpty()98 bool nsBulletFrame::IsSelfEmpty() {
99 return StyleList()->mCounterStyle.IsNone();
100 }
101
102 /* virtual */
DidSetComputedStyle(ComputedStyle * aOldComputedStyle)103 void nsBulletFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
104 nsFrame::DidSetComputedStyle(aOldComputedStyle);
105
106 imgRequestProxy* newRequest = StyleList()->GetListStyleImage();
107
108 if (newRequest) {
109 if (!mListener) {
110 mListener = new nsBulletListener();
111 mListener->SetFrame(this);
112 }
113
114 bool needNewRequest = true;
115
116 if (mImageRequest) {
117 // Reload the image, maybe...
118 nsCOMPtr<nsIURI> oldURI;
119 mImageRequest->GetURI(getter_AddRefs(oldURI));
120 nsCOMPtr<nsIURI> newURI;
121 newRequest->GetURI(getter_AddRefs(newURI));
122 if (oldURI && newURI) {
123 bool same;
124 newURI->Equals(oldURI, &same);
125 if (same) {
126 needNewRequest = false;
127 }
128 }
129 }
130
131 if (needNewRequest) {
132 RefPtr<imgRequestProxy> newRequestClone;
133 newRequest->SyncClone(mListener, PresContext()->Document(),
134 getter_AddRefs(newRequestClone));
135
136 // Deregister the old request. We wait until after Clone is done in case
137 // the old request and the new request are the same underlying image
138 // accessed via different URLs.
139 DeregisterAndCancelImageRequest();
140
141 // Register the new request.
142 mImageRequest = std::move(newRequestClone);
143 RegisterImageRequest(/* aKnownToBeAnimated = */ false);
144
145 // Image bullets can affect the layout of the page, so boost the image
146 // load priority.
147 mImageRequest->BoostPriority(imgIRequest::CATEGORY_SIZE_QUERY);
148 }
149 } else {
150 // No image request on the new ComputedStyle.
151 DeregisterAndCancelImageRequest();
152 }
153
154 #ifdef ACCESSIBILITY
155 // Update the list bullet accessible. If old style list isn't available then
156 // no need to update the accessible tree because it's not created yet.
157 if (aOldComputedStyle) {
158 if (nsAccessibilityService* accService =
159 PresShell::GetAccessibilityService()) {
160 const nsStyleList* oldStyleList = aOldComputedStyle->StyleList();
161 bool hadBullet = oldStyleList->GetListStyleImage() ||
162 !oldStyleList->mCounterStyle.IsNone();
163
164 const nsStyleList* newStyleList = StyleList();
165 bool hasBullet = newStyleList->GetListStyleImage() ||
166 !newStyleList->mCounterStyle.IsNone();
167
168 if (hadBullet != hasBullet) {
169 nsIContent* listItem = mContent->GetParent();
170 accService->UpdateListBullet(PresContext()->GetPresShell(), listItem,
171 hasBullet);
172 }
173 }
174 }
175 #endif // #ifdef ACCESSIBILITY
176 }
177
178 class nsDisplayBulletGeometry
179 : public nsDisplayItemGenericGeometry,
180 public nsImageGeometryMixin<nsDisplayBulletGeometry> {
181 public:
nsDisplayBulletGeometry(nsDisplayItem * aItem,nsDisplayListBuilder * aBuilder)182 nsDisplayBulletGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
183 : nsDisplayItemGenericGeometry(aItem, aBuilder),
184 nsImageGeometryMixin(aItem, aBuilder) {
185 nsBulletFrame* f = static_cast<nsBulletFrame*>(aItem->Frame());
186 mOrdinal = f->Ordinal();
187 }
188
InvalidateForSyncDecodeImages() const189 virtual bool InvalidateForSyncDecodeImages() const override {
190 return ShouldInvalidateToSyncDecodeImages();
191 }
192
193 int32_t mOrdinal;
194 };
195
196 class BulletRenderer final {
197 public:
BulletRenderer(imgIContainer * image,const nsRect & dest)198 BulletRenderer(imgIContainer* image, const nsRect& dest)
199 : mImage(image),
200 mDest(dest),
201 mColor(NS_RGBA(0, 0, 0, 0)),
202 mListStyleType(NS_STYLE_LIST_STYLE_NONE) {
203 MOZ_ASSERT(IsImageType());
204 }
205
BulletRenderer(Path * path,nscolor color,int32_t listStyleType)206 BulletRenderer(Path* path, nscolor color, int32_t listStyleType)
207 : mColor(color), mPath(path), mListStyleType(listStyleType) {
208 MOZ_ASSERT(IsPathType());
209 }
210
BulletRenderer(const LayoutDeviceRect & aPathRect,nscolor color,int32_t listStyleType)211 BulletRenderer(const LayoutDeviceRect& aPathRect, nscolor color,
212 int32_t listStyleType)
213 : mPathRect(aPathRect), mColor(color), mListStyleType(listStyleType) {
214 MOZ_ASSERT(IsPathType());
215 }
216
BulletRenderer(const nsString & text,nsFontMetrics * fm,nscolor color,const nsPoint & point,int32_t listStyleType)217 BulletRenderer(const nsString& text, nsFontMetrics* fm, nscolor color,
218 const nsPoint& point, int32_t listStyleType)
219 : mColor(color),
220 mText(text),
221 mFontMetrics(fm),
222 mPoint(point),
223 mListStyleType(listStyleType) {
224 MOZ_ASSERT(IsTextType());
225 }
226
227 ImgDrawResult CreateWebRenderCommands(
228 nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
229 wr::IpcResourceUpdateQueue& aResources,
230 const layers::StackingContextHelper& aSc,
231 mozilla::layers::RenderRootStateManager* aManager,
232 nsDisplayListBuilder* aDisplayListBuilder);
233
234 ImgDrawResult Paint(gfxContext& aRenderingContext, nsPoint aPt,
235 const nsRect& aDirtyRect, uint32_t aFlags,
236 bool aDisableSubpixelAA, nsIFrame* aFrame);
237
IsImageType() const238 bool IsImageType() const {
239 return mListStyleType == NS_STYLE_LIST_STYLE_NONE && mImage;
240 }
241
IsPathType() const242 bool IsPathType() const {
243 return mListStyleType == NS_STYLE_LIST_STYLE_DISC ||
244 mListStyleType == NS_STYLE_LIST_STYLE_CIRCLE ||
245 mListStyleType == NS_STYLE_LIST_STYLE_SQUARE ||
246 mListStyleType == NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN ||
247 mListStyleType == NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED;
248 }
249
IsTextType() const250 bool IsTextType() const {
251 return mListStyleType != NS_STYLE_LIST_STYLE_NONE &&
252 mListStyleType != NS_STYLE_LIST_STYLE_DISC &&
253 mListStyleType != NS_STYLE_LIST_STYLE_CIRCLE &&
254 mListStyleType != NS_STYLE_LIST_STYLE_SQUARE &&
255 mListStyleType != NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN &&
256 mListStyleType != NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED &&
257 !mText.IsEmpty();
258 }
259
260 void PaintTextToContext(nsIFrame* aFrame, gfxContext* aCtx,
261 bool aDisableSubpixelAA);
262
263 bool IsImageContainerAvailable(layers::LayerManager* aManager,
264 uint32_t aFlags);
265
266 private:
267 ImgDrawResult CreateWebRenderCommandsForImage(
268 nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
269 wr::IpcResourceUpdateQueue& aResources,
270 const layers::StackingContextHelper& aSc,
271 mozilla::layers::RenderRootStateManager* aManager,
272 nsDisplayListBuilder* aDisplayListBuilder);
273
274 bool CreateWebRenderCommandsForPath(
275 nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
276 wr::IpcResourceUpdateQueue& aResources,
277 const layers::StackingContextHelper& aSc,
278 mozilla::layers::RenderRootStateManager* aManager,
279 nsDisplayListBuilder* aDisplayListBuilder);
280
281 bool CreateWebRenderCommandsForText(
282 nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
283 wr::IpcResourceUpdateQueue& aResources,
284 const layers::StackingContextHelper& aSc,
285 mozilla::layers::RenderRootStateManager* aManager,
286 nsDisplayListBuilder* aDisplayListBuilder);
287
288 private:
289 // mImage and mDest are the properties for list-style-image.
290 // mImage is the image content and mDest is the image position.
291 RefPtr<imgIContainer> mImage;
292 nsRect mDest;
293
294 // Some bullet types are stored as a rect (in device pixels) instead of a Path
295 // to allow generating proper WebRender commands. When webrender is disabled
296 // the Path is lazily created for these items before painting.
297 // TODO: The size of this structure doesn't seem to be an issue since it has
298 // so many fields that are specific to a bullet style or another, but if it
299 // becomes one we can easily store mDest and mPathRect into the same memory
300 // location since they are never used by the same bullet types.
301 LayoutDeviceRect mPathRect;
302
303 // mColor indicate the color of list-style. Both text and path type would use
304 // this member.
305 nscolor mColor;
306
307 // mPath record the path of the list-style for later drawing.
308 // Included following types: square, circle, disc, disclosure open and
309 // disclosure closed.
310 RefPtr<Path> mPath;
311
312 // mText, mFontMetrics, mPoint are for other list-style-type which can be
313 // drawed by text.
314 nsString mText;
315 RefPtr<nsFontMetrics> mFontMetrics;
316 nsPoint mPoint;
317
318 // Store the type of list-style-type.
319 int32_t mListStyleType;
320 };
321
CreateWebRenderCommands(nsDisplayItem * aItem,wr::DisplayListBuilder & aBuilder,wr::IpcResourceUpdateQueue & aResources,const layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)322 ImgDrawResult BulletRenderer::CreateWebRenderCommands(
323 nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
324 wr::IpcResourceUpdateQueue& aResources,
325 const layers::StackingContextHelper& aSc,
326 mozilla::layers::RenderRootStateManager* aManager,
327 nsDisplayListBuilder* aDisplayListBuilder) {
328 if (IsImageType()) {
329 return CreateWebRenderCommandsForImage(aItem, aBuilder, aResources, aSc,
330 aManager, aDisplayListBuilder);
331 }
332
333 bool success;
334 if (IsPathType()) {
335 success = CreateWebRenderCommandsForPath(aItem, aBuilder, aResources, aSc,
336 aManager, aDisplayListBuilder);
337 } else {
338 MOZ_ASSERT(IsTextType());
339 success = CreateWebRenderCommandsForText(aItem, aBuilder, aResources, aSc,
340 aManager, aDisplayListBuilder);
341 }
342
343 return success ? ImgDrawResult::SUCCESS : ImgDrawResult::NOT_SUPPORTED;
344 }
345
Paint(gfxContext & aRenderingContext,nsPoint aPt,const nsRect & aDirtyRect,uint32_t aFlags,bool aDisableSubpixelAA,nsIFrame * aFrame)346 ImgDrawResult BulletRenderer::Paint(gfxContext& aRenderingContext, nsPoint aPt,
347 const nsRect& aDirtyRect, uint32_t aFlags,
348 bool aDisableSubpixelAA, nsIFrame* aFrame) {
349 if (IsImageType()) {
350 SamplingFilter filter = nsLayoutUtils::GetSamplingFilterForFrame(aFrame);
351 return nsLayoutUtils::DrawSingleImage(
352 aRenderingContext, aFrame->PresContext(), mImage, filter, mDest,
353 aDirtyRect,
354 /* no SVGImageContext */ Nothing(), aFlags);
355 }
356
357 if (IsPathType()) {
358 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
359
360 if (!mPath) {
361 RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder();
362 switch (mListStyleType) {
363 case NS_STYLE_LIST_STYLE_CIRCLE:
364 case NS_STYLE_LIST_STYLE_DISC:
365 AppendEllipseToPath(builder, mPathRect.Center().ToUnknownPoint(),
366 mPathRect.Size().ToUnknownSize());
367 break;
368 case NS_STYLE_LIST_STYLE_SQUARE:
369 AppendRectToPath(builder, mPathRect.ToUnknownRect());
370 break;
371 default:
372 MOZ_ASSERT(false, "Should have a parth.");
373 }
374 mPath = builder->Finish();
375 }
376
377 switch (mListStyleType) {
378 case NS_STYLE_LIST_STYLE_CIRCLE:
379 drawTarget->Stroke(mPath, ColorPattern(ToDeviceColor(mColor)));
380 break;
381 case NS_STYLE_LIST_STYLE_DISC:
382 case NS_STYLE_LIST_STYLE_SQUARE:
383 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
384 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
385 drawTarget->Fill(mPath, ColorPattern(ToDeviceColor(mColor)));
386 break;
387 default:
388 MOZ_CRASH("unreachable");
389 }
390 }
391
392 if (IsTextType()) {
393 PaintTextToContext(aFrame, &aRenderingContext, aDisableSubpixelAA);
394 }
395
396 return ImgDrawResult::SUCCESS;
397 }
398
PaintTextToContext(nsIFrame * aFrame,gfxContext * aCtx,bool aDisableSubpixelAA)399 void BulletRenderer::PaintTextToContext(nsIFrame* aFrame, gfxContext* aCtx,
400 bool aDisableSubpixelAA) {
401 MOZ_ASSERT(IsTextType());
402
403 DrawTarget* drawTarget = aCtx->GetDrawTarget();
404 DrawTargetAutoDisableSubpixelAntialiasing disable(drawTarget,
405 aDisableSubpixelAA);
406
407 aCtx->SetColor(sRGBColor::FromABGR(mColor));
408
409 nsPresContext* presContext = aFrame->PresContext();
410 if (!presContext->BidiEnabled() && HasRTLChars(mText)) {
411 presContext->SetBidiEnabled();
412 }
413 nsLayoutUtils::DrawString(aFrame, *mFontMetrics, aCtx, mText.get(),
414 mText.Length(), mPoint);
415 }
416
IsImageContainerAvailable(layers::LayerManager * aManager,uint32_t aFlags)417 bool BulletRenderer::IsImageContainerAvailable(layers::LayerManager* aManager,
418 uint32_t aFlags) {
419 MOZ_ASSERT(IsImageType());
420
421 return mImage->IsImageContainerAvailable(aManager, aFlags);
422 }
423
CreateWebRenderCommandsForImage(nsDisplayItem * aItem,wr::DisplayListBuilder & aBuilder,wr::IpcResourceUpdateQueue & aResources,const layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)424 ImgDrawResult BulletRenderer::CreateWebRenderCommandsForImage(
425 nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
426 wr::IpcResourceUpdateQueue& aResources,
427 const layers::StackingContextHelper& aSc,
428 mozilla::layers::RenderRootStateManager* aManager,
429 nsDisplayListBuilder* aDisplayListBuilder) {
430 MOZ_RELEASE_ASSERT(IsImageType());
431 MOZ_RELEASE_ASSERT(mImage);
432
433 uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY;
434 if (aDisplayListBuilder->IsPaintingToWindow()) {
435 flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING;
436 }
437 if (aDisplayListBuilder->ShouldSyncDecodeImages()) {
438 flags |= imgIContainer::FLAG_SYNC_DECODE;
439 }
440
441 const int32_t appUnitsPerDevPixel =
442 aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
443 LayoutDeviceRect destRect =
444 LayoutDeviceRect::FromAppUnits(mDest, appUnitsPerDevPixel);
445
446 Maybe<SVGImageContext> svgContext;
447 gfx::IntSize decodeSize =
448 nsLayoutUtils::ComputeImageContainerDrawingParameters(
449 mImage, aItem->Frame(), destRect, aSc, flags, svgContext);
450
451 RefPtr<layers::ImageContainer> container;
452 ImgDrawResult drawResult = mImage->GetImageContainerAtSize(
453 aManager->LayerManager(), decodeSize, svgContext, flags,
454 getter_AddRefs(container));
455 if (!container) {
456 return drawResult;
457 }
458
459 mozilla::wr::ImageRendering rendering = wr::ToImageRendering(
460 nsLayoutUtils::GetSamplingFilterForFrame(aItem->Frame()));
461 gfx::IntSize size;
462 Maybe<wr::ImageKey> key = aManager->CommandBuilder().CreateImageKey(
463 aItem, container, aBuilder, aResources, rendering, aSc, size, Nothing());
464 if (key.isNothing()) {
465 return drawResult;
466 }
467
468 wr::LayoutRect dest = wr::ToLayoutRect(destRect);
469
470 aBuilder.PushImage(dest, dest, !aItem->BackfaceIsHidden(), rendering,
471 key.value());
472
473 return drawResult;
474 }
475
CreateWebRenderCommandsForPath(nsDisplayItem * aItem,wr::DisplayListBuilder & aBuilder,wr::IpcResourceUpdateQueue & aResources,const layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)476 bool BulletRenderer::CreateWebRenderCommandsForPath(
477 nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
478 wr::IpcResourceUpdateQueue& aResources,
479 const layers::StackingContextHelper& aSc,
480 mozilla::layers::RenderRootStateManager* aManager,
481 nsDisplayListBuilder* aDisplayListBuilder) {
482 MOZ_ASSERT(IsPathType());
483 wr::LayoutRect dest = wr::ToLayoutRect(mPathRect);
484 auto color = wr::ToColorF(ToDeviceColor(mColor));
485 bool isBackfaceVisible = !aItem->BackfaceIsHidden();
486 switch (mListStyleType) {
487 case NS_STYLE_LIST_STYLE_CIRCLE: {
488 LayoutDeviceSize radii = mPathRect.Size() / 2.0;
489 auto borderWidths = wr::ToBorderWidths(1.0, 1.0, 1.0, 1.0);
490 wr::BorderSide side = {color, wr::BorderStyle::Solid};
491 wr::BorderSide sides[4] = {side, side, side, side};
492 Range<const wr::BorderSide> sidesRange(sides, 4);
493 aBuilder.PushBorder(dest, dest, isBackfaceVisible, borderWidths,
494 sidesRange,
495 wr::ToBorderRadius(radii, radii, radii, radii));
496 return true;
497 }
498 case NS_STYLE_LIST_STYLE_DISC: {
499 aBuilder.PushRoundedRect(dest, dest, isBackfaceVisible, color);
500 return true;
501 }
502 case NS_STYLE_LIST_STYLE_SQUARE: {
503 aBuilder.PushRect(dest, dest, isBackfaceVisible, color);
504 return true;
505 }
506 default:
507 if (!aManager->CommandBuilder().PushItemAsImage(
508 aItem, aBuilder, aResources, aSc, aDisplayListBuilder)) {
509 NS_WARNING("Fail to create WebRender commands for Bullet path.");
510 return false;
511 }
512 }
513
514 return true;
515 }
516
CreateWebRenderCommandsForText(nsDisplayItem * aItem,wr::DisplayListBuilder & aBuilder,wr::IpcResourceUpdateQueue & aResources,const layers::StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)517 bool BulletRenderer::CreateWebRenderCommandsForText(
518 nsDisplayItem* aItem, wr::DisplayListBuilder& aBuilder,
519 wr::IpcResourceUpdateQueue& aResources,
520 const layers::StackingContextHelper& aSc,
521 mozilla::layers::RenderRootStateManager* aManager,
522 nsDisplayListBuilder* aDisplayListBuilder) {
523 MOZ_ASSERT(IsTextType());
524
525 bool dummy;
526 nsRect bounds = aItem->GetBounds(aDisplayListBuilder, &dummy);
527
528 if (bounds.IsEmpty()) {
529 return true;
530 }
531
532 RefPtr<TextDrawTarget> textDrawer =
533 new TextDrawTarget(aBuilder, aResources, aSc, aManager, aItem, bounds);
534 RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(textDrawer);
535 PaintTextToContext(aItem->Frame(), captureCtx, aItem->IsSubpixelAADisabled());
536 textDrawer->TerminateShadows();
537
538 return textDrawer->Finish();
539 }
540
541 class nsDisplayBullet final : public nsPaintedDisplayItem {
542 public:
nsDisplayBullet(nsDisplayListBuilder * aBuilder,nsBulletFrame * aFrame)543 nsDisplayBullet(nsDisplayListBuilder* aBuilder, nsBulletFrame* aFrame)
544 : nsPaintedDisplayItem(aBuilder, aFrame) {
545 MOZ_COUNT_CTOR(nsDisplayBullet);
546 }
MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBullet)547 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayBullet)
548
549 virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
550 bool* aSnap) const override {
551 *aSnap = false;
552 return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
553 }
554
555 virtual bool CreateWebRenderCommands(
556 mozilla::wr::DisplayListBuilder& aBuilder,
557 mozilla::wr::IpcResourceUpdateQueue&, const StackingContextHelper& aSc,
558 mozilla::layers::RenderRootStateManager* aManager,
559 nsDisplayListBuilder* aDisplayListBuilder) override;
560
HitTest(nsDisplayListBuilder * aBuilder,const nsRect & aRect,HitTestState * aState,nsTArray<nsIFrame * > * aOutFrames)561 virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
562 HitTestState* aState,
563 nsTArray<nsIFrame*>* aOutFrames) override {
564 aOutFrames->AppendElement(mFrame);
565 }
566 virtual void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) override;
567 NS_DISPLAY_DECL_NAME("Bullet", TYPE_BULLET)
568
GetComponentAlphaBounds(nsDisplayListBuilder * aBuilder) const569 virtual nsRect GetComponentAlphaBounds(
570 nsDisplayListBuilder* aBuilder) const override {
571 bool snap;
572 return GetBounds(aBuilder, &snap);
573 }
574
AllocateGeometry(nsDisplayListBuilder * aBuilder)575 virtual nsDisplayItemGeometry* AllocateGeometry(
576 nsDisplayListBuilder* aBuilder) override {
577 return new nsDisplayBulletGeometry(this, aBuilder);
578 }
579
ComputeInvalidationRegion(nsDisplayListBuilder * aBuilder,const nsDisplayItemGeometry * aGeometry,nsRegion * aInvalidRegion) const580 virtual void ComputeInvalidationRegion(
581 nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
582 nsRegion* aInvalidRegion) const override {
583 const nsDisplayBulletGeometry* geometry =
584 static_cast<const nsDisplayBulletGeometry*>(aGeometry);
585 nsBulletFrame* f = static_cast<nsBulletFrame*>(mFrame);
586
587 if (f->Ordinal() != geometry->mOrdinal) {
588 bool snap;
589 aInvalidRegion->Or(geometry->mBounds, GetBounds(aBuilder, &snap));
590 return;
591 }
592
593 nsCOMPtr<imgIContainer> image = f->GetImage();
594 if (aBuilder->ShouldSyncDecodeImages() && image &&
595 geometry->ShouldInvalidateToSyncDecodeImages()) {
596 bool snap;
597 aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap));
598 }
599
600 return nsPaintedDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry,
601 aInvalidRegion);
602 }
603
604 protected:
605 Maybe<BulletRenderer> mBulletRenderer;
606 };
607
CreateWebRenderCommands(wr::DisplayListBuilder & aBuilder,wr::IpcResourceUpdateQueue & aResources,const StackingContextHelper & aSc,mozilla::layers::RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)608 bool nsDisplayBullet::CreateWebRenderCommands(
609 wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources,
610 const StackingContextHelper& aSc,
611 mozilla::layers::RenderRootStateManager* aManager,
612 nsDisplayListBuilder* aDisplayListBuilder) {
613 // FIXME: avoid needing to make this target if we're drawing text
614 // (non-trivial refactor of all this code)
615 RefPtr<gfxContext> screenRefCtx = gfxContext::CreateOrNull(
616 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get());
617 Maybe<BulletRenderer> br =
618 static_cast<nsBulletFrame*>(mFrame)->CreateBulletRenderer(
619 *screenRefCtx, ToReferenceFrame());
620
621 if (!br) {
622 return false;
623 }
624
625 ImgDrawResult drawResult = br->CreateWebRenderCommands(
626 this, aBuilder, aResources, aSc, aManager, aDisplayListBuilder);
627 if (drawResult == ImgDrawResult::NOT_SUPPORTED) {
628 return false;
629 }
630
631 nsDisplayBulletGeometry::UpdateDrawResult(this, drawResult);
632 return true;
633 }
634
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)635 void nsDisplayBullet::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
636 uint32_t flags = imgIContainer::FLAG_NONE;
637 if (aBuilder->ShouldSyncDecodeImages()) {
638 flags |= imgIContainer::FLAG_SYNC_DECODE;
639 }
640
641 ImgDrawResult result = static_cast<nsBulletFrame*>(mFrame)->PaintBullet(
642 *aCtx, ToReferenceFrame(), GetPaintRect(), flags, IsSubpixelAADisabled());
643
644 nsDisplayBulletGeometry::UpdateDrawResult(this, result);
645 }
646
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)647 void nsBulletFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
648 const nsDisplayListSet& aLists) {
649 if (!IsVisibleForPainting()) return;
650
651 DO_GLOBAL_REFLOW_COUNT_DSP("nsBulletFrame");
652
653 aLists.Content()->AppendNewToTop<nsDisplayBullet>(aBuilder, this);
654 }
655
CreateBulletRenderer(gfxContext & aRenderingContext,nsPoint aPt)656 Maybe<BulletRenderer> nsBulletFrame::CreateBulletRenderer(
657 gfxContext& aRenderingContext, nsPoint aPt) {
658 const nsStyleList* myList = StyleList();
659 CounterStyle* listStyleType = ResolveCounterStyle();
660 nsMargin padding = mPadding.GetPhysicalMargin(GetWritingMode());
661
662 if (myList->GetListStyleImage() && mImageRequest) {
663 uint32_t status;
664 mImageRequest->GetImageStatus(&status);
665 if (!(status & imgIRequest::STATUS_ERROR)) {
666 if (status & imgIRequest::STATUS_LOAD_COMPLETE) {
667 nsCOMPtr<imgIContainer> imageCon;
668 mImageRequest->GetImage(getter_AddRefs(imageCon));
669 if (imageCon) {
670 nsRect dest(padding.left, padding.top,
671 mRect.width - (padding.left + padding.right),
672 mRect.height - (padding.top + padding.bottom));
673 BulletRenderer br(imageCon, dest + aPt);
674 return Some(br);
675 }
676 } else {
677 // Boost the load priority further now that we know we want to display
678 // the bullet image.
679 mImageRequest->BoostPriority(imgIRequest::CATEGORY_DISPLAY);
680 }
681 }
682 }
683
684 nscolor color = nsLayoutUtils::GetColor(this, &nsStyleText::mColor);
685
686 DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
687 int32_t appUnitsPerDevPixel = PresContext()->AppUnitsPerDevPixel();
688
689 switch (listStyleType->GetStyle()) {
690 case NS_STYLE_LIST_STYLE_NONE:
691 return Nothing();
692
693 case NS_STYLE_LIST_STYLE_DISC:
694 case NS_STYLE_LIST_STYLE_CIRCLE: {
695 nsRect rect(padding.left + aPt.x, padding.top + aPt.y,
696 mRect.width - (padding.left + padding.right),
697 mRect.height - (padding.top + padding.bottom));
698 auto devPxRect =
699 LayoutDeviceRect::FromAppUnits(rect, appUnitsPerDevPixel);
700 return Some(BulletRenderer(devPxRect, color, listStyleType->GetStyle()));
701 }
702
703 case NS_STYLE_LIST_STYLE_SQUARE: {
704 nsRect rect(aPt, mRect.Size());
705 rect.Deflate(padding);
706
707 // Snap the height and the width of the rectangle to device pixels,
708 // and then center the result within the original rectangle, so that
709 // all square bullets at the same font size have the same visual
710 // size (bug 376690).
711 // FIXME: We should really only do this if we're not transformed
712 // (like gfxContext::UserToDevicePixelSnapped does).
713 nsPresContext* pc = PresContext();
714 nsRect snapRect(rect.x, rect.y,
715 pc->RoundAppUnitsToNearestDevPixels(rect.width),
716 pc->RoundAppUnitsToNearestDevPixels(rect.height));
717 snapRect.MoveBy((rect.width - snapRect.width) / 2,
718 (rect.height - snapRect.height) / 2);
719 auto devPxRect =
720 LayoutDeviceRect::FromAppUnits(snapRect, appUnitsPerDevPixel);
721 return Some(BulletRenderer(devPxRect, color, listStyleType->GetStyle()));
722 }
723
724 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
725 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN: {
726 nsRect rect(aPt, mRect.Size());
727 rect.Deflate(padding);
728
729 WritingMode wm = GetWritingMode();
730 bool isVertical = wm.IsVertical();
731 bool isClosed =
732 listStyleType->GetStyle() == NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED;
733 bool isDown = (!isVertical && !isClosed) || (isVertical && isClosed);
734 nscoord diff = NSToCoordRound(0.1f * rect.height);
735 if (isDown) {
736 rect.y += diff * 2;
737 rect.height -= diff * 2;
738 } else {
739 rect.Deflate(diff, 0);
740 }
741 nsPresContext* pc = PresContext();
742 rect.x = pc->RoundAppUnitsToNearestDevPixels(rect.x);
743 rect.y = pc->RoundAppUnitsToNearestDevPixels(rect.y);
744
745 RefPtr<PathBuilder> builder = drawTarget->CreatePathBuilder();
746 if (isDown) {
747 // to bottom
748 builder->MoveTo(NSPointToPoint(rect.TopLeft(), appUnitsPerDevPixel));
749 builder->LineTo(NSPointToPoint(rect.TopRight(), appUnitsPerDevPixel));
750 builder->LineTo(NSPointToPoint(
751 (rect.BottomLeft() + rect.BottomRight()) / 2, appUnitsPerDevPixel));
752 } else {
753 if (wm.IsPhysicalLTR()) {
754 // to right
755 builder->MoveTo(NSPointToPoint(rect.TopLeft(), appUnitsPerDevPixel));
756 builder->LineTo(NSPointToPoint(
757 (rect.TopRight() + rect.BottomRight()) / 2, appUnitsPerDevPixel));
758 builder->LineTo(
759 NSPointToPoint(rect.BottomLeft(), appUnitsPerDevPixel));
760 } else {
761 // to left
762 builder->MoveTo(NSPointToPoint(rect.TopRight(), appUnitsPerDevPixel));
763 builder->LineTo(
764 NSPointToPoint(rect.BottomRight(), appUnitsPerDevPixel));
765 builder->LineTo(NSPointToPoint(
766 (rect.TopLeft() + rect.BottomLeft()) / 2, appUnitsPerDevPixel));
767 }
768 }
769
770 RefPtr<Path> path = builder->Finish();
771 BulletRenderer br(path, color, listStyleType->GetStyle());
772 return Some(br);
773 }
774
775 default: {
776 RefPtr<nsFontMetrics> fm =
777 nsLayoutUtils::GetFontMetricsForFrame(this, GetFontSizeInflation());
778 nsAutoString text;
779 WritingMode wm = GetWritingMode();
780 GetListItemText(listStyleType, wm, Ordinal(), text);
781 nscoord ascent = wm.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent();
782 aPt.MoveBy(padding.left, padding.top);
783 if (wm.IsVertical()) {
784 if (wm.IsVerticalLR()) {
785 aPt.x = NSToCoordRound(nsLayoutUtils::GetSnappedBaselineX(
786 this, &aRenderingContext, aPt.x, ascent));
787 } else {
788 aPt.x = NSToCoordRound(nsLayoutUtils::GetSnappedBaselineX(
789 this, &aRenderingContext, aPt.x + mRect.width, -ascent));
790 }
791 } else {
792 aPt.y = NSToCoordRound(nsLayoutUtils::GetSnappedBaselineY(
793 this, &aRenderingContext, aPt.y, ascent));
794 }
795
796 BulletRenderer br(text, fm, color, aPt, listStyleType->GetStyle());
797 return Some(br);
798 }
799 }
800
801 MOZ_CRASH("unreachable");
802 return Nothing();
803 }
804
PaintBullet(gfxContext & aRenderingContext,nsPoint aPt,const nsRect & aDirtyRect,uint32_t aFlags,bool aDisableSubpixelAA)805 ImgDrawResult nsBulletFrame::PaintBullet(gfxContext& aRenderingContext,
806 nsPoint aPt, const nsRect& aDirtyRect,
807 uint32_t aFlags,
808 bool aDisableSubpixelAA) {
809 Maybe<BulletRenderer> br = CreateBulletRenderer(aRenderingContext, aPt);
810
811 if (!br) {
812 return ImgDrawResult::SUCCESS;
813 }
814
815 return br->Paint(aRenderingContext, aPt, aDirtyRect, aFlags,
816 aDisableSubpixelAA, this);
817 }
818
GetListItemText(CounterStyle * aStyle,mozilla::WritingMode aWritingMode,int32_t aOrdinal,nsAString & aResult)819 void nsBulletFrame::GetListItemText(CounterStyle* aStyle,
820 mozilla::WritingMode aWritingMode,
821 int32_t aOrdinal, nsAString& aResult) {
822 NS_ASSERTION(
823 aStyle->GetStyle() != NS_STYLE_LIST_STYLE_NONE &&
824 aStyle->GetStyle() != NS_STYLE_LIST_STYLE_DISC &&
825 aStyle->GetStyle() != NS_STYLE_LIST_STYLE_CIRCLE &&
826 aStyle->GetStyle() != NS_STYLE_LIST_STYLE_SQUARE &&
827 aStyle->GetStyle() != NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED &&
828 aStyle->GetStyle() != NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN,
829 "we should be using specialized code for these types");
830
831 bool isRTL;
832 nsAutoString counter, prefix, suffix;
833 aStyle->GetPrefix(prefix);
834 aStyle->GetSuffix(suffix);
835 aStyle->GetCounterText(aOrdinal, aWritingMode, counter, isRTL);
836
837 aResult.Truncate();
838 aResult.Append(prefix);
839 if (aWritingMode.IsBidiRTL() == isRTL) {
840 aResult.Append(counter);
841 } else {
842 // RLM = 0x200f, LRM = 0x200e
843 char16_t mark = isRTL ? 0x200f : 0x200e;
844 aResult.Append(mark);
845 aResult.Append(counter);
846 aResult.Append(mark);
847 }
848 aResult.Append(suffix);
849 }
850
851 #define MIN_BULLET_SIZE 1
852
AppendSpacingToPadding(nsFontMetrics * aFontMetrics,LogicalMargin * aPadding)853 void nsBulletFrame::AppendSpacingToPadding(nsFontMetrics* aFontMetrics,
854 LogicalMargin* aPadding) {
855 aPadding->IEnd(GetWritingMode()) += aFontMetrics->EmHeight() / 2;
856 }
857
GetDesiredSize(nsPresContext * aCX,gfxContext * aRenderingContext,ReflowOutput & aMetrics,float aFontSizeInflation,LogicalMargin * aPadding)858 void nsBulletFrame::GetDesiredSize(nsPresContext* aCX,
859 gfxContext* aRenderingContext,
860 ReflowOutput& aMetrics,
861 float aFontSizeInflation,
862 LogicalMargin* aPadding) {
863 // Reset our padding. If we need it, we'll set it below.
864 WritingMode wm = GetWritingMode();
865 aPadding->SizeTo(wm, 0, 0, 0, 0);
866 LogicalSize finalSize(wm);
867
868 const nsStyleList* myList = StyleList();
869 nscoord ascent;
870 RefPtr<nsFontMetrics> fm =
871 nsLayoutUtils::GetFontMetricsForFrame(this, aFontSizeInflation);
872
873 RemoveStateBits(BULLET_FRAME_IMAGE_LOADING);
874
875 if (myList->GetListStyleImage() && mImageRequest) {
876 uint32_t status;
877 mImageRequest->GetImageStatus(&status);
878 if (status & imgIRequest::STATUS_SIZE_AVAILABLE &&
879 !(status & imgIRequest::STATUS_ERROR)) {
880 // auto size the image
881 finalSize.ISize(wm) = mIntrinsicSize.ISize(wm);
882 aMetrics.SetBlockStartAscent(finalSize.BSize(wm) =
883 mIntrinsicSize.BSize(wm));
884 aMetrics.SetSize(wm, finalSize);
885
886 AppendSpacingToPadding(fm, aPadding);
887
888 AddStateBits(BULLET_FRAME_IMAGE_LOADING);
889
890 return;
891 }
892 }
893
894 // If we're getting our desired size and don't have an image, reset
895 // mIntrinsicSize to (0,0). Otherwise, if we used to have an image, it
896 // changed, and the new one is coming in, but we're reflowing before it's
897 // fully there, we'll end up with mIntrinsicSize not matching our size, but
898 // won't trigger a reflow in OnStartContainer (because mIntrinsicSize will
899 // match the image size).
900 mIntrinsicSize.SizeTo(wm, 0, 0);
901
902 nscoord bulletSize;
903
904 nsAutoString text;
905 CounterStyle* style = ResolveCounterStyle();
906 switch (style->GetStyle()) {
907 case NS_STYLE_LIST_STYLE_NONE:
908 finalSize.ISize(wm) = finalSize.BSize(wm) = 0;
909 aMetrics.SetBlockStartAscent(0);
910 break;
911
912 case NS_STYLE_LIST_STYLE_DISC:
913 case NS_STYLE_LIST_STYLE_CIRCLE:
914 case NS_STYLE_LIST_STYLE_SQUARE: {
915 ascent = fm->MaxAscent();
916 bulletSize = std::max(nsPresContext::CSSPixelsToAppUnits(MIN_BULLET_SIZE),
917 NSToCoordRound(0.8f * (float(ascent) / 2.0f)));
918 aPadding->BEnd(wm) = NSToCoordRound(float(ascent) / 8.0f);
919 finalSize.ISize(wm) = finalSize.BSize(wm) = bulletSize;
920 aMetrics.SetBlockStartAscent(bulletSize + aPadding->BEnd(wm));
921 AppendSpacingToPadding(fm, aPadding);
922 break;
923 }
924
925 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED:
926 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN:
927 ascent = fm->EmAscent();
928 bulletSize = std::max(nsPresContext::CSSPixelsToAppUnits(MIN_BULLET_SIZE),
929 NSToCoordRound(0.75f * ascent));
930 aPadding->BEnd(wm) = NSToCoordRound(0.125f * ascent);
931 finalSize.ISize(wm) = finalSize.BSize(wm) = bulletSize;
932 if (!wm.IsVertical()) {
933 aMetrics.SetBlockStartAscent(bulletSize + aPadding->BEnd(wm));
934 }
935 AppendSpacingToPadding(fm, aPadding);
936 break;
937
938 default:
939 GetListItemText(style, wm, Ordinal(), text);
940 finalSize.BSize(wm) = fm->MaxHeight();
941 finalSize.ISize(wm) = nsLayoutUtils::AppUnitWidthOfStringBidi(
942 text, this, *fm, *aRenderingContext);
943 aMetrics.SetBlockStartAscent(wm.IsLineInverted() ? fm->MaxDescent()
944 : fm->MaxAscent());
945 break;
946 }
947 aMetrics.SetSize(wm, finalSize);
948 }
949
Reflow(nsPresContext * aPresContext,ReflowOutput & aMetrics,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)950 void nsBulletFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
951 const ReflowInput& aReflowInput,
952 nsReflowStatus& aStatus) {
953 MarkInReflow();
954 DO_GLOBAL_REFLOW_COUNT("nsBulletFrame");
955 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
956 MOZ_ASSERT(aStatus.IsEmpty(), "The reflow status should be empty!");
957
958 float inflation = nsLayoutUtils::FontSizeInflationFor(this);
959 SetFontSizeInflation(inflation);
960
961 // Get the base size
962 GetDesiredSize(aPresContext, aReflowInput.mRenderingContext, aMetrics,
963 inflation, &mPadding);
964
965 // Add in the border and padding; split the top/bottom between the
966 // ascent and descent to make things look nice
967 WritingMode wm = aReflowInput.GetWritingMode();
968 const LogicalMargin& bp = aReflowInput.ComputedLogicalBorderPadding();
969 mPadding.BStart(wm) += NSToCoordRound(bp.BStart(wm) * inflation);
970 mPadding.IEnd(wm) += NSToCoordRound(bp.IEnd(wm) * inflation);
971 mPadding.BEnd(wm) += NSToCoordRound(bp.BEnd(wm) * inflation);
972 mPadding.IStart(wm) += NSToCoordRound(bp.IStart(wm) * inflation);
973
974 WritingMode lineWM = aMetrics.GetWritingMode();
975 LogicalMargin linePadding = mPadding.ConvertTo(lineWM, wm);
976 aMetrics.ISize(lineWM) += linePadding.IStartEnd(lineWM);
977 aMetrics.BSize(lineWM) += linePadding.BStartEnd(lineWM);
978 aMetrics.SetBlockStartAscent(aMetrics.BlockStartAscent() +
979 linePadding.BStart(lineWM));
980
981 // XXX this is a bit of a hack, we're assuming that no glyphs used for bullets
982 // overflow their font-boxes. It'll do for now; to fix it for real, we really
983 // should rewrite all the text-handling code here to use gfxTextRun (bug
984 // 397294).
985 aMetrics.SetOverflowAreasToDesiredBounds();
986
987 NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics);
988 }
989
990 /* virtual */
GetMinISize(gfxContext * aRenderingContext)991 nscoord nsBulletFrame::GetMinISize(gfxContext* aRenderingContext) {
992 WritingMode wm = GetWritingMode();
993 ReflowOutput reflowOutput(wm);
994 DISPLAY_MIN_INLINE_SIZE(this, reflowOutput.ISize(wm));
995 LogicalMargin padding(wm);
996 GetDesiredSize(PresContext(), aRenderingContext, reflowOutput, 1.0f,
997 &padding);
998 reflowOutput.ISize(wm) += padding.IStartEnd(wm);
999 return reflowOutput.ISize(wm);
1000 }
1001
1002 /* virtual */
GetPrefISize(gfxContext * aRenderingContext)1003 nscoord nsBulletFrame::GetPrefISize(gfxContext* aRenderingContext) {
1004 WritingMode wm = GetWritingMode();
1005 ReflowOutput metrics(wm);
1006 DISPLAY_PREF_INLINE_SIZE(this, metrics.ISize(wm));
1007 LogicalMargin padding(wm);
1008 GetDesiredSize(PresContext(), aRenderingContext, metrics, 1.0f, &padding);
1009 metrics.ISize(wm) += padding.IStartEnd(wm);
1010 return metrics.ISize(wm);
1011 }
1012
1013 // If a bullet has zero size and is "ignorable" from its styling, we behave
1014 // as if it doesn't exist, from a line-breaking/isize-computation perspective.
1015 // Otherwise, we use the default implementation, same as nsFrame.
IsIgnoreable(const nsIFrame * aFrame,nscoord aISize)1016 static inline bool IsIgnoreable(const nsIFrame* aFrame, nscoord aISize) {
1017 if (aISize != nscoord(0)) {
1018 return false;
1019 }
1020 auto listStyle = aFrame->StyleList();
1021 return listStyle->mCounterStyle.IsNone() && !listStyle->GetListStyleImage();
1022 }
1023
1024 /* virtual */
AddInlineMinISize(gfxContext * aRenderingContext,nsIFrame::InlineMinISizeData * aData)1025 void nsBulletFrame::AddInlineMinISize(gfxContext* aRenderingContext,
1026 nsIFrame::InlineMinISizeData* aData) {
1027 nscoord isize = nsLayoutUtils::IntrinsicForContainer(
1028 aRenderingContext, this, nsLayoutUtils::MIN_ISIZE);
1029 if (MOZ_LIKELY(!::IsIgnoreable(this, isize))) {
1030 aData->DefaultAddInlineMinISize(this, isize);
1031 }
1032 }
1033
1034 /* virtual */
AddInlinePrefISize(gfxContext * aRenderingContext,nsIFrame::InlinePrefISizeData * aData)1035 void nsBulletFrame::AddInlinePrefISize(gfxContext* aRenderingContext,
1036 nsIFrame::InlinePrefISizeData* aData) {
1037 nscoord isize = nsLayoutUtils::IntrinsicForContainer(
1038 aRenderingContext, this, nsLayoutUtils::PREF_ISIZE);
1039 if (MOZ_LIKELY(!::IsIgnoreable(this, isize))) {
1040 aData->DefaultAddInlinePrefISize(isize);
1041 }
1042 }
1043
Notify(imgIRequest * aRequest,int32_t aType,const nsIntRect * aData)1044 void nsBulletFrame::Notify(imgIRequest* aRequest, int32_t aType,
1045 const nsIntRect* aData) {
1046 if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
1047 nsCOMPtr<imgIContainer> image;
1048 aRequest->GetImage(getter_AddRefs(image));
1049 return OnSizeAvailable(aRequest, image);
1050 }
1051
1052 if (aType == imgINotificationObserver::FRAME_UPDATE) {
1053 // The image has changed.
1054 // Invalidate the entire content area. Maybe it's not optimal but it's
1055 // simple and always correct, and I'll be a stunned mullet if it ever
1056 // matters for performance
1057 InvalidateFrame();
1058 }
1059
1060 if (aType == imgINotificationObserver::IS_ANIMATED) {
1061 // Register the image request with the refresh driver now that we know it's
1062 // animated.
1063 if (aRequest == mImageRequest) {
1064 RegisterImageRequest(/* aKnownToBeAnimated = */ true);
1065 }
1066 }
1067
1068 if (aType == imgINotificationObserver::LOAD_COMPLETE) {
1069 // Unconditionally start decoding for now.
1070 // XXX(seth): We eventually want to decide whether to do this based on
1071 // visibility. We should get that for free from bug 1091236.
1072 nsCOMPtr<imgIContainer> container;
1073 aRequest->GetImage(getter_AddRefs(container));
1074 if (container) {
1075 // Retrieve the intrinsic size of the image.
1076 int32_t width = 0;
1077 int32_t height = 0;
1078 container->GetWidth(&width);
1079 container->GetHeight(&height);
1080
1081 // Request a decode at that size.
1082 container->RequestDecodeForSize(
1083 IntSize(width, height), imgIContainer::DECODE_FLAGS_DEFAULT |
1084 imgIContainer::FLAG_HIGH_QUALITY_SCALING);
1085 }
1086
1087 InvalidateFrame();
1088 }
1089
1090 if (aType == imgINotificationObserver::DECODE_COMPLETE) {
1091 if (Document* parent = GetOurCurrentDoc()) {
1092 nsCOMPtr<imgIContainer> container;
1093 aRequest->GetImage(getter_AddRefs(container));
1094 if (container) {
1095 container->PropagateUseCounters(parent);
1096 }
1097 }
1098 }
1099 }
1100
GetOurCurrentDoc() const1101 Document* nsBulletFrame::GetOurCurrentDoc() const {
1102 nsIContent* parentContent = GetParent()->GetContent();
1103 return parentContent ? parentContent->GetComposedDoc() : nullptr;
1104 }
1105
OnSizeAvailable(imgIRequest * aRequest,imgIContainer * aImage)1106 void nsBulletFrame::OnSizeAvailable(imgIRequest* aRequest,
1107 imgIContainer* aImage) {
1108 if (!aImage) return;
1109 if (!aRequest) return;
1110
1111 uint32_t status;
1112 aRequest->GetImageStatus(&status);
1113 if (status & imgIRequest::STATUS_ERROR) {
1114 return;
1115 }
1116
1117 nscoord w, h;
1118 aImage->GetWidth(&w);
1119 aImage->GetHeight(&h);
1120
1121 nsPresContext* presContext = PresContext();
1122
1123 LogicalSize newsize(GetWritingMode(),
1124 nsSize(nsPresContext::CSSPixelsToAppUnits(w),
1125 nsPresContext::CSSPixelsToAppUnits(h)));
1126
1127 if (mIntrinsicSize != newsize) {
1128 mIntrinsicSize = newsize;
1129
1130 // Now that the size is available (or an error occurred), trigger
1131 // a reflow of the bullet frame.
1132 mozilla::PresShell* presShell = presContext->GetPresShell();
1133 if (presShell) {
1134 presShell->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
1135 NS_FRAME_IS_DIRTY);
1136 }
1137 }
1138
1139 // Handle animations
1140 aImage->SetAnimationMode(presContext->ImageAnimationMode());
1141 // Ensure the animation (if any) is started. Note: There is no
1142 // corresponding call to Decrement for this. This Increment will be
1143 // 'cleaned up' by the Request when it is destroyed, but only then.
1144 aRequest->IncrementAnimationConsumers();
1145 }
1146
GetLoadGroup(nsPresContext * aPresContext,nsILoadGroup ** aLoadGroup)1147 void nsBulletFrame::GetLoadGroup(nsPresContext* aPresContext,
1148 nsILoadGroup** aLoadGroup) {
1149 if (!aPresContext) return;
1150
1151 MOZ_ASSERT(nullptr != aLoadGroup, "null OUT parameter pointer");
1152
1153 mozilla::PresShell* presShell = aPresContext->GetPresShell();
1154 if (!presShell) {
1155 return;
1156 }
1157
1158 Document* doc = presShell->GetDocument();
1159 if (!doc) return;
1160
1161 *aLoadGroup = doc->GetDocumentLoadGroup().take();
1162 }
1163
GetFontSizeInflation() const1164 float nsBulletFrame::GetFontSizeInflation() const {
1165 if (!HasFontSizeInflation()) {
1166 return 1.0f;
1167 }
1168 return GetProperty(FontSizeInflationProperty());
1169 }
1170
SetFontSizeInflation(float aInflation)1171 void nsBulletFrame::SetFontSizeInflation(float aInflation) {
1172 if (aInflation == 1.0f) {
1173 if (HasFontSizeInflation()) {
1174 RemoveStateBits(BULLET_FRAME_HAS_FONT_INFLATION);
1175 RemoveProperty(FontSizeInflationProperty());
1176 }
1177 return;
1178 }
1179
1180 AddStateBits(BULLET_FRAME_HAS_FONT_INFLATION);
1181 SetProperty(FontSizeInflationProperty(), aInflation);
1182 }
1183
GetImage() const1184 already_AddRefed<imgIContainer> nsBulletFrame::GetImage() const {
1185 if (mImageRequest && StyleList()->GetListStyleImage()) {
1186 nsCOMPtr<imgIContainer> imageCon;
1187 mImageRequest->GetImage(getter_AddRefs(imageCon));
1188 return imageCon.forget();
1189 }
1190
1191 return nullptr;
1192 }
1193
GetListStyleAscent() const1194 nscoord nsBulletFrame::GetListStyleAscent() const {
1195 RefPtr<nsFontMetrics> fm =
1196 nsLayoutUtils::GetFontMetricsForFrame(this, GetFontSizeInflation());
1197 auto* list = StyleList();
1198 if (list->mCounterStyle.IsNone()) {
1199 return 0;
1200 }
1201 if (list->mCounterStyle.IsAnonymous()) {
1202 return fm->MaxAscent();
1203 }
1204 // NOTE(emilio): @counter-style can override most of the styles from this
1205 // list, and we still return the changed ascent. Do we care about that?
1206 //
1207 // https://github.com/w3c/csswg-drafts/issues/3584
1208 nsAtom* style = list->mCounterStyle.AsAtom();
1209 if (style == nsGkAtoms::disc || style == nsGkAtoms::circle ||
1210 style == nsGkAtoms::square) {
1211 nscoord ascent = fm->MaxAscent();
1212 nscoord baselinePadding = NSToCoordRound(float(ascent) / 8.0f);
1213 ascent = std::max(nsPresContext::CSSPixelsToAppUnits(MIN_BULLET_SIZE),
1214 NSToCoordRound(0.8f * (float(ascent) / 2.0f)));
1215 return ascent + baselinePadding;
1216 }
1217 if (style == nsGkAtoms::disclosure_open ||
1218 style == nsGkAtoms::disclosure_closed) {
1219 nscoord ascent = fm->EmAscent();
1220 nscoord baselinePadding = NSToCoordRound(0.125f * ascent);
1221 ascent = std::max(nsPresContext::CSSPixelsToAppUnits(MIN_BULLET_SIZE),
1222 NSToCoordRound(0.75f * ascent));
1223 return ascent + baselinePadding;
1224 }
1225 return fm->MaxAscent();
1226 }
1227
GetLogicalBaseline(WritingMode aWritingMode) const1228 nscoord nsBulletFrame::GetLogicalBaseline(WritingMode aWritingMode) const {
1229 nscoord ascent = 0;
1230 if (GetStateBits() & BULLET_FRAME_IMAGE_LOADING) {
1231 ascent = BSize(aWritingMode);
1232 } else {
1233 ascent = GetListStyleAscent();
1234 }
1235 return ascent + GetLogicalUsedMargin(aWritingMode).BStart(aWritingMode);
1236 }
1237
GetNaturalBaselineBOffset(WritingMode aWM,BaselineSharingGroup,nscoord * aBaseline) const1238 bool nsBulletFrame::GetNaturalBaselineBOffset(WritingMode aWM,
1239 BaselineSharingGroup,
1240 nscoord* aBaseline) const {
1241 nscoord ascent = 0;
1242 if (GetStateBits() & BULLET_FRAME_IMAGE_LOADING) {
1243 ascent = BSize(aWM);
1244 } else {
1245 ascent = GetListStyleAscent();
1246 }
1247 *aBaseline = ascent;
1248 return true;
1249 }
1250
1251 #ifdef ACCESSIBILITY
GetSpokenText(nsAString & aText)1252 void nsBulletFrame::GetSpokenText(nsAString& aText) {
1253 CounterStyle* style =
1254 PresContext()->CounterStyleManager()->ResolveCounterStyle(
1255 StyleList()->mCounterStyle);
1256 bool isBullet;
1257 style->GetSpokenCounterText(Ordinal(), GetWritingMode(), aText, isBullet);
1258 if (isBullet) {
1259 if (!style->IsNone()) {
1260 aText.Append(' ');
1261 }
1262 } else {
1263 nsAutoString prefix, suffix;
1264 style->GetPrefix(prefix);
1265 style->GetSuffix(suffix);
1266 aText = prefix + aText + suffix;
1267 }
1268 }
1269 #endif
1270
RegisterImageRequest(bool aKnownToBeAnimated)1271 void nsBulletFrame::RegisterImageRequest(bool aKnownToBeAnimated) {
1272 if (mImageRequest) {
1273 // mRequestRegistered is a bitfield; unpack it temporarily so we can take
1274 // the address.
1275 bool isRequestRegistered = mRequestRegistered;
1276
1277 if (aKnownToBeAnimated) {
1278 nsLayoutUtils::RegisterImageRequest(PresContext(), mImageRequest,
1279 &isRequestRegistered);
1280 } else {
1281 nsLayoutUtils::RegisterImageRequestIfAnimated(
1282 PresContext(), mImageRequest, &isRequestRegistered);
1283 }
1284
1285 mRequestRegistered = isRequestRegistered;
1286 }
1287 }
1288
DeregisterAndCancelImageRequest()1289 void nsBulletFrame::DeregisterAndCancelImageRequest() {
1290 if (mImageRequest) {
1291 // mRequestRegistered is a bitfield; unpack it temporarily so we can take
1292 // the address.
1293 bool isRequestRegistered = mRequestRegistered;
1294
1295 // Deregister our image request from the refresh driver.
1296 nsLayoutUtils::DeregisterImageRequest(PresContext(), mImageRequest,
1297 &isRequestRegistered);
1298
1299 mRequestRegistered = isRequestRegistered;
1300
1301 // Cancel the image request and forget about it.
1302 mImageRequest->CancelAndForgetObserver(NS_ERROR_FAILURE);
1303 mImageRequest = nullptr;
1304 }
1305 }
1306
SetOrdinal(int32_t aOrdinal,bool aNotify)1307 void nsBulletFrame::SetOrdinal(int32_t aOrdinal, bool aNotify) {
1308 if (mOrdinal == aOrdinal) {
1309 return;
1310 }
1311 mOrdinal = aOrdinal;
1312 if (aNotify) {
1313 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::StyleChange,
1314 NS_FRAME_IS_DIRTY);
1315 }
1316 }
1317
NS_IMPL_ISUPPORTS(nsBulletListener,imgINotificationObserver)1318 NS_IMPL_ISUPPORTS(nsBulletListener, imgINotificationObserver)
1319
1320 nsBulletListener::nsBulletListener() : mFrame(nullptr) {}
1321
1322 nsBulletListener::~nsBulletListener() = default;
1323
Notify(imgIRequest * aRequest,int32_t aType,const nsIntRect * aData)1324 void nsBulletListener::Notify(imgIRequest* aRequest, int32_t aType,
1325 const nsIntRect* aData) {
1326 if (!mFrame) {
1327 return;
1328 }
1329 return mFrame->Notify(aRequest, aType, aData);
1330 }
1331