nsProgressFrame.cpp (9858B)
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 #include "nsProgressFrame.h" 8 9 #include <algorithm> 10 11 #include "mozilla/PresShell.h" 12 #include "mozilla/dom/Document.h" 13 #include "mozilla/dom/Element.h" 14 #include "mozilla/dom/HTMLMeterElement.h" 15 #include "mozilla/dom/HTMLProgressElement.h" 16 #include "nsContentCreatorFunctions.h" 17 #include "nsFontMetrics.h" 18 #include "nsGkAtoms.h" 19 #include "nsIContent.h" 20 #include "nsLayoutUtils.h" 21 #include "nsNodeInfoManager.h" 22 #include "nsPresContext.h" 23 24 using namespace mozilla; 25 using namespace mozilla::dom; 26 27 nsIFrame* NS_NewProgressFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 28 return new (aPresShell) nsProgressFrame(aStyle, aPresShell->GetPresContext(), 29 nsProgressFrame::Type::Progress); 30 } 31 32 nsIFrame* NS_NewMeterFrame(PresShell* aPresShell, ComputedStyle* aStyle) { 33 return new (aPresShell) nsProgressFrame(aStyle, aPresShell->GetPresContext(), 34 nsProgressFrame::Type::Meter); 35 } 36 37 NS_IMPL_FRAMEARENA_HELPERS(nsProgressFrame) 38 39 nsProgressFrame::nsProgressFrame(ComputedStyle* aStyle, 40 nsPresContext* aPresContext, Type aType) 41 : nsContainerFrame(aStyle, aPresContext, kClassID), mType(aType) {} 42 43 nsProgressFrame::~nsProgressFrame() = default; 44 45 void nsProgressFrame::Destroy(DestroyContext& aContext) { 46 NS_ASSERTION(!GetPrevContinuation(), 47 "nsProgressFrame should not have continuations; if it does we " 48 "need to call RegUnregAccessKey only for the first."); 49 aContext.AddAnonymousContent(mBarDiv.forget()); 50 nsContainerFrame::Destroy(aContext); 51 } 52 53 nsresult nsProgressFrame::CreateAnonymousContent( 54 nsTArray<ContentInfo>& aElements) { 55 // Create the progress bar div. 56 nsCOMPtr<Document> doc = mContent->GetComposedDoc(); 57 mBarDiv = doc->CreateHTMLElement(nsGkAtoms::div); 58 59 if (StaticPrefs::layout_css_modern_range_pseudos_enabled()) { 60 // TODO(emilio): Create also a slider-track pseudo-element. 61 mBarDiv->SetPseudoElementType(PseudoStyleType::sliderFill); 62 } else { 63 // Associate ::-moz-{progress,meter}-bar pseudo-element to the anon child. 64 mBarDiv->SetPseudoElementType(mType == Type::Progress 65 ? PseudoStyleType::mozProgressBar 66 : PseudoStyleType::mozMeterBar); 67 } 68 69 // XXX(Bug 1631371) Check if this should use a fallible operation as it 70 // pretended earlier, or change the return type to void. 71 aElements.AppendElement(mBarDiv); 72 73 return NS_OK; 74 } 75 76 void nsProgressFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements, 77 uint32_t aFilter) { 78 if (mBarDiv) { 79 aElements.AppendElement(mBarDiv); 80 } 81 } 82 83 NS_QUERYFRAME_HEAD(nsProgressFrame) 84 NS_QUERYFRAME_ENTRY(nsProgressFrame) 85 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) 86 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) 87 88 void nsProgressFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 89 const nsDisplayListSet& aLists) { 90 if (IsThemed()) { 91 DisplayBorderBackgroundOutline(aBuilder, aLists); 92 } else { 93 BuildDisplayListForInline(aBuilder, aLists); 94 } 95 } 96 97 void nsProgressFrame::Reflow(nsPresContext* aPresContext, 98 ReflowOutput& aDesiredSize, 99 const ReflowInput& aReflowInput, 100 nsReflowStatus& aStatus) { 101 MarkInReflow(); 102 DO_GLOBAL_REFLOW_COUNT("nsProgressFrame"); 103 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 104 MOZ_ASSERT(mBarDiv, "Progress bar div must exist!"); 105 NS_ASSERTION(!GetPrevContinuation(), 106 "nsProgressFrame should not have continuations; if it does we " 107 "need to call RegUnregAccessKey only for the first."); 108 109 const auto wm = aReflowInput.GetWritingMode(); 110 const auto contentBoxSize = aReflowInput.ComputedSizeWithBSizeFallback( 111 [&] { return DefaultSize().BSize(wm); }); 112 aDesiredSize.SetSize( 113 wm, 114 contentBoxSize + aReflowInput.ComputedLogicalBorderPadding(wm).Size(wm)); 115 aDesiredSize.SetOverflowAreasToDesiredBounds(); 116 117 for (nsIFrame* childFrame : PrincipalChildList()) { 118 ReflowChildFrame(childFrame, aPresContext, aReflowInput, contentBoxSize, 119 aStatus); 120 ConsiderChildOverflow(aDesiredSize.mOverflowAreas, childFrame); 121 } 122 123 FinishAndStoreOverflow(&aDesiredSize); 124 125 aStatus.Reset(); // This type of frame can't be split. 126 } 127 128 void nsProgressFrame::ReflowChildFrame(nsIFrame* aChild, 129 nsPresContext* aPresContext, 130 const ReflowInput& aReflowInput, 131 const LogicalSize& aParentContentBoxSize, 132 nsReflowStatus& aStatus) { 133 MOZ_ASSERT(aChild == mBarDiv->GetPrimaryFrame() || 134 aChild->IsPlaceholderFrame()); 135 bool vertical = ResolvedOrientationIsVertical(); 136 const WritingMode wm = aChild->GetWritingMode(); 137 const LogicalSize parentSizeInChildWM = 138 aParentContentBoxSize.ConvertTo(wm, aReflowInput.GetWritingMode()); 139 const nsSize parentPhysicalSize = parentSizeInChildWM.GetPhysicalSize(wm); 140 LogicalSize availSize = parentSizeInChildWM; 141 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; 142 ReflowInput reflowInput(aPresContext, aReflowInput, aChild, availSize, 143 Some(parentSizeInChildWM)); 144 nscoord size = 145 vertical ? parentPhysicalSize.Height() : parentPhysicalSize.Width(); 146 nscoord xoffset = aReflowInput.ComputedPhysicalBorderPadding().left; 147 nscoord yoffset = aReflowInput.ComputedPhysicalBorderPadding().top; 148 149 double position = 150 mType == Type::Progress 151 ? static_cast<HTMLProgressElement*>(GetContent())->Position() 152 : static_cast<HTMLMeterElement*>(GetContent())->Position(); 153 154 // Force the bar's size to match the current progress. 155 // When indeterminate, the progress' size will be 100%. 156 if (position >= 0.0 || mType == Type::Meter) { 157 size = NSToCoordRound(size * position); 158 } 159 160 if (!vertical && wm.IsPhysicalRTL()) { 161 xoffset += parentPhysicalSize.Width() - size; 162 } 163 164 // The bar size is fixed in these cases: 165 // - the progress position is determined: the bar size is fixed according 166 // to it's value. 167 // - the progress position is indeterminate and the bar appearance should be 168 // shown as native: the bar size is forced to 100%. 169 // Otherwise (when the progress is indeterminate and the bar appearance isn't 170 // native), the bar size isn't fixed and can be set by the author. 171 if (position != -1 || ShouldUseNativeStyle()) { 172 if (vertical) { 173 // We want the bar to begin at the bottom. 174 yoffset += parentPhysicalSize.Height() - size; 175 size -= reflowInput.ComputedPhysicalMargin().TopBottom() + 176 reflowInput.ComputedPhysicalBorderPadding().TopBottom(); 177 size = std::max(size, 0); 178 reflowInput.SetComputedHeight(size); 179 } else { 180 size -= reflowInput.ComputedPhysicalMargin().LeftRight() + 181 reflowInput.ComputedPhysicalBorderPadding().LeftRight(); 182 size = std::max(size, 0); 183 reflowInput.SetComputedWidth(size); 184 } 185 } else if (vertical) { 186 // For vertical progress bars, we need to position the bar specifically when 187 // the width isn't constrained (position == -1 and !ShouldUseNativeStyle()) 188 // because parentPhysiscalSize.Height() - size == 0. 189 // FIXME(emilio): This assumes that the bar's height is constrained, which 190 // seems like a wrong assumption? 191 yoffset += parentPhysicalSize.Height() - reflowInput.ComputedHeight(); 192 } 193 194 xoffset += reflowInput.ComputedPhysicalMargin().left; 195 yoffset += reflowInput.ComputedPhysicalMargin().top; 196 197 ReflowOutput barDesiredSize(aReflowInput); 198 ReflowChild(aChild, aPresContext, barDesiredSize, reflowInput, xoffset, 199 yoffset, ReflowChildFlags::Default, aStatus); 200 FinishReflowChild(aChild, aPresContext, barDesiredSize, &reflowInput, xoffset, 201 yoffset, ReflowChildFlags::Default); 202 } 203 204 nsresult nsProgressFrame::AttributeChanged(int32_t aNameSpaceID, 205 nsAtom* aAttribute, 206 AttrModType aModType) { 207 NS_ASSERTION(mBarDiv, "Progress bar div must exist!"); 208 209 if (aNameSpaceID == kNameSpaceID_None && 210 (aAttribute == nsGkAtoms::value || aAttribute == nsGkAtoms::max || 211 aAttribute == nsGkAtoms::min)) { 212 auto* presShell = PresShell(); 213 for (auto* childFrame : PrincipalChildList()) { 214 presShell->FrameNeedsReflow(childFrame, IntrinsicDirty::None, 215 NS_FRAME_IS_DIRTY); 216 } 217 InvalidateFrame(); 218 } 219 220 return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); 221 } 222 223 LogicalSize nsProgressFrame::DefaultSize() const { 224 const auto wm = GetWritingMode(); 225 nscoord em = OneEmInAppUnits(); 226 LogicalSize size(wm, em, em); 227 nscoord& longAxis = ResolvedOrientationIsVertical() == wm.IsVertical() 228 ? size.ISize(wm) 229 : size.BSize(wm); 230 longAxis *= mType == Type::Progress ? 10 : 5; 231 return size; 232 } 233 234 nscoord nsProgressFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 235 IntrinsicISizeType aType) { 236 return DefaultSize().ISize(GetWritingMode()); 237 } 238 239 bool nsProgressFrame::ShouldUseNativeStyle() const { 240 return StyleDisplay()->HasAppearance() && 241 !Style()->HasAuthorSpecifiedBorderOrBackground(); 242 }