no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / layout / base / nsLayoutUtils.cpp
blob41b7096b5c99d19e900df17cb8ce34944520c396
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/. */
7 #include "nsLayoutUtils.h"
9 #include <algorithm>
10 #include <limits>
12 #include "ActiveLayerTracker.h"
13 #include "DisplayItemClip.h"
14 #include "gfx2DGlue.h"
15 #include "gfxContext.h"
16 #include "gfxDrawable.h"
17 #include "gfxEnv.h"
18 #include "gfxMatrix.h"
19 #include "gfxPlatform.h"
20 #include "gfxRect.h"
21 #include "gfxTypes.h"
22 #include "gfxUtils.h"
23 #include "ImageContainer.h"
24 #include "ImageOps.h"
25 #include "ImageRegion.h"
26 #include "imgIContainer.h"
27 #include "imgIRequest.h"
28 #include "LayoutLogging.h"
29 #include "MobileViewportManager.h"
30 #include "mozilla/AccessibleCaretEventHub.h"
31 #include "mozilla/ArrayUtils.h"
32 #include "mozilla/Baseline.h"
33 #include "mozilla/BasicEvents.h"
34 #include "mozilla/ClearOnShutdown.h"
35 #include "mozilla/DisplayPortUtils.h"
36 #include "mozilla/glean/GleanMetrics.h"
37 #include "mozilla/dom/AnonymousContent.h"
38 #include "mozilla/dom/BrowserChild.h"
39 #include "mozilla/dom/CanvasUtils.h"
40 #include "mozilla/dom/Document.h"
41 #include "mozilla/dom/DocumentInlines.h"
42 #include "mozilla/dom/DOMRect.h"
43 #include "mozilla/dom/DOMStringList.h"
44 #include "mozilla/dom/Element.h"
45 #include "mozilla/dom/HTMLBodyElement.h"
46 #include "mozilla/dom/HTMLCanvasElement.h"
47 #include "mozilla/dom/HTMLImageElement.h"
48 #include "mozilla/dom/HTMLMediaElementBinding.h"
49 #include "mozilla/dom/HTMLVideoElement.h"
50 #include "mozilla/dom/InspectorFontFace.h"
51 #include "mozilla/dom/ImageBitmap.h"
52 #include "mozilla/dom/KeyframeEffect.h"
53 #include "mozilla/dom/SVGViewportElement.h"
54 #include "mozilla/dom/UIEvent.h"
55 #include "mozilla/dom/VideoFrame.h"
56 #include "mozilla/dom/VideoFrameBinding.h"
57 #include "mozilla/intl/BidiEmbeddingLevel.h"
58 #include "mozilla/EffectCompositor.h"
59 #include "mozilla/EffectSet.h"
60 #include "mozilla/EventDispatcher.h"
61 #include "mozilla/EventStateManager.h"
62 #include "mozilla/FloatingPoint.h"
63 #include "mozilla/gfx/2D.h"
64 #include "mozilla/gfx/gfxVars.h"
65 #include "mozilla/gfx/PathHelpers.h"
66 #include "mozilla/gfx/DataSurfaceHelpers.h"
67 #include "mozilla/IntegerRange.h"
68 #include "mozilla/layers/APZCCallbackHelper.h"
69 #include "mozilla/layers/APZPublicUtils.h" // for apz::CalculatePendingDisplayPort
70 #include "mozilla/layers/CompositorBridgeChild.h"
71 #include "mozilla/layers/PAPZ.h"
72 #include "mozilla/layers/StackingContextHelper.h"
73 #include "mozilla/layers/WebRenderLayerManager.h"
74 #include "mozilla/Likely.h"
75 #include "mozilla/LookAndFeel.h"
76 #include "mozilla/Maybe.h"
77 #include "mozilla/MemoryReporting.h"
78 #include "mozilla/PerfStats.h"
79 #include "mozilla/Preferences.h"
80 #include "mozilla/PresShell.h"
81 #include "mozilla/ProfilerLabels.h"
82 #include "mozilla/ProfilerMarkers.h"
83 #include "mozilla/RestyleManager.h"
84 #include "mozilla/ScopeExit.h"
85 #include "mozilla/ScrollOrigin.h"
86 #include "mozilla/ServoStyleSet.h"
87 #include "mozilla/ServoStyleSetInlines.h"
88 #include "mozilla/StaticPrefs_apz.h"
89 #include "mozilla/StaticPrefs_browser.h"
90 #include "mozilla/StaticPrefs_dom.h"
91 #include "mozilla/StaticPrefs_font.h"
92 #include "mozilla/StaticPrefs_general.h"
93 #include "mozilla/StaticPrefs_gfx.h"
94 #include "mozilla/StaticPrefs_image.h"
95 #include "mozilla/StaticPrefs_layers.h"
96 #include "mozilla/StaticPrefs_layout.h"
97 #include "mozilla/StaticPtr.h"
98 #include "mozilla/StyleAnimationValue.h"
99 #include "mozilla/SVGImageContext.h"
100 #include "mozilla/SVGIntegrationUtils.h"
101 #include "mozilla/SVGTextFrame.h"
102 #include "mozilla/SVGUtils.h"
103 #include "mozilla/Telemetry.h"
104 #include "mozilla/ToString.h"
105 #include "mozilla/Unused.h"
106 #include "mozilla/ViewportFrame.h"
107 #include "mozilla/ViewportUtils.h"
108 #include "mozilla/WheelHandlingHelper.h" // for WheelHandlingUtils
109 #include "nsAnimationManager.h"
110 #include "nsAtom.h"
111 #include "nsBidiPresUtils.h"
112 #include "nsBlockFrame.h"
113 #include "nsCanvasFrame.h"
114 #include "nsCaret.h"
115 #include "nsCharTraits.h"
116 #include "nsCOMPtr.h"
117 #include "nsComputedDOMStyle.h"
118 #include "nsContentUtils.h"
119 #include "nsCSSAnonBoxes.h"
120 #include "nsCSSColorUtils.h"
121 #include "nsCSSFrameConstructor.h"
122 #include "nsCSSProps.h"
123 #include "nsCSSPseudoElements.h"
124 #include "nsCSSRendering.h"
125 #include "nsDisplayList.h"
126 #include "nsFieldSetFrame.h"
127 #include "nsFlexContainerFrame.h"
128 #include "nsFontInflationData.h"
129 #include "nsFontMetrics.h"
130 #include "nsFrameList.h"
131 #include "nsFrameSelection.h"
132 #include "nsGenericHTMLElement.h"
133 #include "nsGkAtoms.h"
134 #include "nsICanvasRenderingContextInternal.h"
135 #include "nsIContent.h"
136 #include "nsIContentInlines.h"
137 #include "nsIDocShell.h"
138 #include "nsIDocumentViewer.h"
139 #include "nsIFrameInlines.h"
140 #include "nsIImageLoadingContent.h"
141 #include "nsIInterfaceRequestorUtils.h"
142 #include "nsIScrollableFrame.h"
143 #include "nsIWidget.h"
144 #include "nsListControlFrame.h"
145 #include "nsPIDOMWindow.h"
146 #include "nsPlaceholderFrame.h"
147 #include "nsPresContext.h"
148 #include "nsPresContextInlines.h"
149 #include "nsRefreshDriver.h"
150 #include "nsRegion.h"
151 #include "nsStyleConsts.h"
152 #include "nsStyleStructInlines.h"
153 #include "nsStyleTransformMatrix.h"
154 #include "nsSubDocumentFrame.h"
155 #include "nsTableWrapperFrame.h"
156 #include "nsTArray.h"
157 #include "nsTextFragment.h"
158 #include "nsTextFrame.h"
159 #include "nsTHashMap.h"
160 #include "nsTransitionManager.h"
161 #include "nsView.h"
162 #include "nsViewManager.h"
163 #include "prenv.h"
164 #include "RegionBuilder.h"
165 #include "RetainedDisplayListBuilder.h"
166 #include "TextDrawTarget.h"
167 #include "UnitTransforms.h"
168 #include "ViewportFrame.h"
170 #include "nsXULPopupManager.h"
172 // Make sure getpid() works.
173 #ifdef XP_WIN
174 # include <process.h>
175 # define getpid _getpid
176 #else
177 # include <unistd.h>
178 #endif
180 using namespace mozilla;
181 using namespace mozilla::dom;
182 using namespace mozilla::image;
183 using namespace mozilla::layers;
184 using namespace mozilla::layout;
185 using namespace mozilla::gfx;
186 using mozilla::dom::HTMLMediaElement_Binding::HAVE_METADATA;
187 using mozilla::dom::HTMLMediaElement_Binding::HAVE_NOTHING;
189 typedef ScrollableLayerGuid::ViewID ViewID;
190 typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox;
192 static ViewID sScrollIdCounter = ScrollableLayerGuid::START_SCROLL_ID;
194 typedef nsTHashMap<nsUint64HashKey, nsIContent*> ContentMap;
195 static StaticAutoPtr<ContentMap> sContentMap;
197 static ContentMap& GetContentMap() {
198 if (!sContentMap) {
199 sContentMap = new ContentMap();
201 return *sContentMap;
204 template <typename TestType>
205 static bool HasMatchingAnimations(EffectSet& aEffects, TestType&& aTest) {
206 for (KeyframeEffect* effect : aEffects) {
207 if (!effect->GetAnimation() || !effect->GetAnimation()->IsRelevant()) {
208 continue;
211 if (aTest(*effect, aEffects)) {
212 return true;
216 return false;
219 template <typename TestType>
220 static bool HasMatchingAnimations(const nsIFrame* aFrame,
221 const nsCSSPropertyIDSet& aPropertySet,
222 TestType&& aTest) {
223 MOZ_ASSERT(aFrame);
225 if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()) &&
226 !aFrame->MayHaveOpacityAnimation()) {
227 return false;
230 if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) &&
231 !aFrame->MayHaveTransformAnimation()) {
232 return false;
235 EffectSet* effectSet = EffectSet::GetForFrame(aFrame, aPropertySet);
236 if (!effectSet) {
237 return false;
240 return HasMatchingAnimations(*effectSet, aTest);
243 /* static */
244 bool nsLayoutUtils::HasAnimationOfPropertySet(
245 const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet) {
246 return HasMatchingAnimations(
247 aFrame, aPropertySet,
248 [&aPropertySet](KeyframeEffect& aEffect, const EffectSet&) {
249 return aEffect.HasAnimationOfPropertySet(aPropertySet);
253 /* static */
254 bool nsLayoutUtils::HasAnimationOfPropertySet(
255 const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet,
256 EffectSet* aEffectSet) {
257 MOZ_ASSERT(
258 !aEffectSet || EffectSet::GetForFrame(aFrame, aPropertySet) == aEffectSet,
259 "The EffectSet, if supplied, should match what we would otherwise fetch");
261 if (!aEffectSet) {
262 return nsLayoutUtils::HasAnimationOfPropertySet(aFrame, aPropertySet);
265 if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) &&
266 !aEffectSet->MayHaveTransformAnimation()) {
267 return false;
270 if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()) &&
271 !aEffectSet->MayHaveOpacityAnimation()) {
272 return false;
275 return HasMatchingAnimations(
276 *aEffectSet,
277 [&aPropertySet](KeyframeEffect& aEffect, const EffectSet& aEffectSet) {
278 return aEffect.HasAnimationOfPropertySet(aPropertySet);
282 /* static */
283 bool nsLayoutUtils::HasAnimationOfTransformAndMotionPath(
284 const nsIFrame* aFrame) {
285 return nsLayoutUtils::HasAnimationOfPropertySet(
286 aFrame,
287 nsCSSPropertyIDSet{eCSSProperty_transform, eCSSProperty_translate,
288 eCSSProperty_rotate, eCSSProperty_scale,
289 eCSSProperty_offset_path}) ||
290 (!aFrame->StyleDisplay()->mOffsetPath.IsNone() &&
291 nsLayoutUtils::HasAnimationOfPropertySet(
292 aFrame, nsCSSPropertyIDSet::MotionPathProperties()));
295 /* static */
296 bool nsLayoutUtils::HasEffectiveAnimation(
297 const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet) {
298 return HasMatchingAnimations(
299 aFrame, aPropertySet,
300 [&aPropertySet](KeyframeEffect& aEffect, const EffectSet& aEffectSet) {
301 return aEffect.HasEffectiveAnimationOfPropertySet(aPropertySet,
302 aEffectSet);
306 /* static */
307 nsCSSPropertyIDSet nsLayoutUtils::GetAnimationPropertiesForCompositor(
308 const nsIFrame* aStyleFrame) {
309 nsCSSPropertyIDSet properties;
311 // We fetch the effects for the style frame here since this method is called
312 // by RestyleManager::AddLayerChangesForAnimation which takes care to apply
313 // the relevant hints to the primary frame as needed.
314 EffectSet* effects = EffectSet::GetForStyleFrame(aStyleFrame);
315 if (!effects) {
316 return properties;
319 AnimationPerformanceWarning::Type warning;
320 if (!EffectCompositor::AllowCompositorAnimationsOnFrame(aStyleFrame,
321 warning)) {
322 return properties;
325 for (const KeyframeEffect* effect : *effects) {
326 properties |= effect->GetPropertiesForCompositor(*effects, aStyleFrame);
329 // If properties only have motion-path properties, we have to make sure they
330 // have effects. i.e. offset-path is not none or we have offset-path
331 // animations.
332 if (properties.IsSubsetOf(nsCSSPropertyIDSet::MotionPathProperties()) &&
333 !properties.HasProperty(eCSSProperty_offset_path) &&
334 aStyleFrame->StyleDisplay()->mOffsetPath.IsNone()) {
335 properties.Empty();
338 return properties;
341 static float GetSuitableScale(float aMaxScale, float aMinScale,
342 nscoord aVisibleDimension,
343 nscoord aDisplayDimension) {
344 float displayVisibleRatio =
345 float(aDisplayDimension) / float(aVisibleDimension);
346 // We want to rasterize based on the largest scale used during the
347 // transform animation, unless that would make us rasterize something
348 // larger than the screen. But we never want to go smaller than the
349 // minimum scale over the animation.
350 if (FuzzyEqualsMultiplicative(displayVisibleRatio, aMaxScale, .01f)) {
351 // Using aMaxScale may make us rasterize something a fraction larger than
352 // the screen. However, if aMaxScale happens to be the final scale of a
353 // transform animation it is better to use aMaxScale so that for the
354 // fraction of a second before we delayerize the composited texture it has
355 // a better chance of being pixel aligned and composited without resampling
356 // (avoiding visually clunky delayerization).
357 return aMaxScale;
359 return std::max(std::min(aMaxScale, displayVisibleRatio), aMinScale);
362 // The first value in this pair is the min scale, and the second one is the max
363 // scale.
364 using MinAndMaxScale = std::pair<MatrixScales, MatrixScales>;
366 static inline void UpdateMinMaxScale(const nsIFrame* aFrame,
367 const AnimationValue& aValue,
368 MinAndMaxScale& aMinAndMaxScale) {
369 MatrixScales size = aValue.GetScaleValue(aFrame);
370 MatrixScales& minScale = aMinAndMaxScale.first;
371 MatrixScales& maxScale = aMinAndMaxScale.second;
373 minScale = Min(minScale, size);
374 maxScale = Max(maxScale, size);
377 // The final transform matrix is calculated by merging the final results of each
378 // transform-like properties, so do the scale factors. In other words, the
379 // potential min/max scales could be gotten by multiplying the max/min scales of
380 // each properties.
382 // For example, there is an animation:
383 // from { "transform: scale(1, 1)", "scale: 3, 3" };
384 // to { "transform: scale(2, 2)", "scale: 1, 1" };
386 // the min scale is (1, 1) * (1, 1) = (1, 1), and
387 // The max scale is (2, 2) * (3, 3) = (6, 6).
388 // This means we multiply the min/max scale factor of transform property and the
389 // min/max scale factor of scale property to get the final max/min scale factor.
390 static Array<MinAndMaxScale, 2> GetMinAndMaxScaleForAnimationProperty(
391 const nsIFrame* aFrame,
392 const nsTArray<RefPtr<dom::Animation>>& aAnimations) {
393 // We use a fixed array to store the min/max scales for each property.
394 // The first element in the array is for eCSSProperty_transform, and the
395 // second one is for eCSSProperty_scale.
396 const MinAndMaxScale defaultValue =
397 std::make_pair(MatrixScales(std::numeric_limits<float>::max(),
398 std::numeric_limits<float>::max()),
399 MatrixScales(std::numeric_limits<float>::min(),
400 std::numeric_limits<float>::min()));
401 Array<MinAndMaxScale, 2> minAndMaxScales(defaultValue, defaultValue);
403 for (dom::Animation* anim : aAnimations) {
404 // This method is only expected to be passed animations that are running on
405 // the compositor and we only pass playing animations to the compositor,
406 // which are, by definition, "relevant" animations (animations that are
407 // not yet finished or which are filling forwards).
408 MOZ_ASSERT(anim->IsRelevant());
410 const dom::KeyframeEffect* effect =
411 anim->GetEffect() ? anim->GetEffect()->AsKeyframeEffect() : nullptr;
412 MOZ_ASSERT(effect, "A playing animation should have a keyframe effect");
413 for (const AnimationProperty& prop : effect->Properties()) {
414 if (prop.mProperty.mID != eCSSProperty_transform &&
415 prop.mProperty.mID != eCSSProperty_scale) {
416 continue;
419 // 0: eCSSProperty_transform.
420 // 1: eCSSProperty_scale.
421 MinAndMaxScale& scales =
422 minAndMaxScales[prop.mProperty.mID == eCSSProperty_transform ? 0 : 1];
424 // We need to factor in the scale of the base style if the base style
425 // will be used on the compositor.
426 const AnimationValue& baseStyle = effect->BaseStyle(prop.mProperty);
427 if (!baseStyle.IsNull()) {
428 UpdateMinMaxScale(aFrame, baseStyle, scales);
431 for (const AnimationPropertySegment& segment : prop.mSegments) {
432 // In case of add or accumulate composite, StyleAnimationValue does
433 // not have a valid value.
434 if (segment.HasReplaceableFromValue()) {
435 UpdateMinMaxScale(aFrame, segment.mFromValue, scales);
438 if (segment.HasReplaceableToValue()) {
439 UpdateMinMaxScale(aFrame, segment.mToValue, scales);
445 return minAndMaxScales;
448 MatrixScales nsLayoutUtils::ComputeSuitableScaleForAnimation(
449 const nsIFrame* aFrame, const nsSize& aVisibleSize,
450 const nsSize& aDisplaySize) {
451 const nsTArray<RefPtr<dom::Animation>> compositorAnimations =
452 EffectCompositor::GetAnimationsForCompositor(
453 aFrame,
454 nsCSSPropertyIDSet{eCSSProperty_transform, eCSSProperty_scale});
456 if (compositorAnimations.IsEmpty()) {
457 return MatrixScales();
460 const Array<MinAndMaxScale, 2> minAndMaxScales =
461 GetMinAndMaxScaleForAnimationProperty(aFrame, compositorAnimations);
463 // This might cause an issue if users use std::numeric_limits<float>::min()
464 // (or max()) as the scale value. However, in this case, we may render an
465 // extreme small (or large) element, so this may not be a problem. If so,
466 // please fix this.
467 MatrixScales maxScale(std::numeric_limits<float>::min(),
468 std::numeric_limits<float>::min());
469 MatrixScales minScale(std::numeric_limits<float>::max(),
470 std::numeric_limits<float>::max());
472 auto isUnset = [](const MatrixScales& aMax, const MatrixScales& aMin) {
473 return aMax.xScale == std::numeric_limits<float>::min() &&
474 aMax.yScale == std::numeric_limits<float>::min() &&
475 aMin.xScale == std::numeric_limits<float>::max() &&
476 aMin.yScale == std::numeric_limits<float>::max();
479 // Iterate the slots to get the final scale value.
480 for (const auto& pair : minAndMaxScales) {
481 const MatrixScales& currMinScale = pair.first;
482 const MatrixScales& currMaxScale = pair.second;
484 if (isUnset(currMaxScale, currMinScale)) {
485 // We don't have this animation property, so skip.
486 continue;
489 if (isUnset(maxScale, minScale)) {
490 // Initialize maxScale and minScale.
491 maxScale = currMaxScale;
492 minScale = currMinScale;
493 } else {
494 // The scale factors of each transform-like property should be multiplied
495 // by others because we merge their sampled values as a final matrix by
496 // matrix multiplication, so here we multiply the scale factors by the
497 // previous one to get the possible max and min scale factors.
498 maxScale = maxScale * currMaxScale;
499 minScale = minScale * currMinScale;
503 if (isUnset(maxScale, minScale)) {
504 // We didn't encounter any transform-like property.
505 return MatrixScales();
508 return MatrixScales(
509 GetSuitableScale(maxScale.xScale, minScale.xScale, aVisibleSize.width,
510 aDisplaySize.width),
511 GetSuitableScale(maxScale.yScale, minScale.yScale, aVisibleSize.height,
512 aDisplaySize.height));
515 bool nsLayoutUtils::AreAsyncAnimationsEnabled() {
516 return StaticPrefs::layers_offmainthreadcomposition_async_animations() &&
517 gfxPlatform::OffMainThreadCompositingEnabled();
520 bool nsLayoutUtils::AreRetainedDisplayListsEnabled() {
521 #ifdef MOZ_WIDGET_ANDROID
522 return StaticPrefs::layout_display_list_retain();
523 #else
524 if (XRE_IsContentProcess()) {
525 return StaticPrefs::layout_display_list_retain();
528 if (XRE_IsE10sParentProcess()) {
529 return StaticPrefs::layout_display_list_retain_chrome();
532 // Retained display lists require e10s.
533 return false;
534 #endif
537 bool nsLayoutUtils::DisplayRootHasRetainedDisplayListBuilder(nsIFrame* aFrame) {
538 return GetRetainedDisplayListBuilder(aFrame) != nullptr;
541 RetainedDisplayListBuilder* nsLayoutUtils::GetRetainedDisplayListBuilder(
542 nsIFrame* aFrame) {
543 MOZ_ASSERT(aFrame);
544 MOZ_ASSERT(aFrame->PresShell());
546 // Use the pres shell root frame to get the display root frame. This skips
547 // the early exit in |nsLayoutUtils::GetDisplayRootFrame()| for popup frames.
548 const nsIFrame* rootFrame = aFrame->PresShell()->GetRootFrame();
549 if (!rootFrame) {
550 return nullptr;
553 const nsIFrame* displayRootFrame = GetDisplayRootFrame(rootFrame);
554 MOZ_ASSERT(displayRootFrame);
556 return displayRootFrame->GetProperty(RetainedDisplayListBuilder::Cached());
559 bool nsLayoutUtils::GPUImageScalingEnabled() {
560 static bool sGPUImageScalingEnabled;
561 static bool sGPUImageScalingPrefInitialised = false;
563 if (!sGPUImageScalingPrefInitialised) {
564 sGPUImageScalingPrefInitialised = true;
565 sGPUImageScalingEnabled =
566 Preferences::GetBool("layout.gpu-image-scaling.enabled", false);
569 return sGPUImageScalingEnabled;
572 void nsLayoutUtils::UnionChildOverflow(nsIFrame* aFrame,
573 OverflowAreas& aOverflowAreas,
574 FrameChildListIDs aSkipChildLists) {
575 // Iterate over all children except pop-ups.
576 FrameChildListIDs skip(aSkipChildLists);
577 skip += FrameChildListID::Popup;
579 for (const auto& [list, listID] : aFrame->ChildLists()) {
580 if (skip.contains(listID)) {
581 continue;
583 for (nsIFrame* child : list) {
584 aOverflowAreas.UnionWith(
585 child->GetActualAndNormalOverflowAreasRelativeToParent());
590 static void DestroyViewID(void* aObject, nsAtom* aPropertyName,
591 void* aPropertyValue, void* aData) {
592 ViewID* id = static_cast<ViewID*>(aPropertyValue);
593 GetContentMap().Remove(*id);
594 delete id;
598 * A namespace class for static layout utilities.
601 bool nsLayoutUtils::FindIDFor(const nsIContent* aContent, ViewID* aOutViewId) {
602 void* scrollIdProperty = aContent->GetProperty(nsGkAtoms::RemoteId);
603 if (scrollIdProperty) {
604 *aOutViewId = *static_cast<ViewID*>(scrollIdProperty);
605 return true;
607 return false;
610 ViewID nsLayoutUtils::FindOrCreateIDFor(nsIContent* aContent) {
611 ViewID scrollId;
613 if (!FindIDFor(aContent, &scrollId)) {
614 scrollId = sScrollIdCounter++;
615 aContent->SetProperty(nsGkAtoms::RemoteId, new ViewID(scrollId),
616 DestroyViewID);
617 GetContentMap().InsertOrUpdate(scrollId, aContent);
620 return scrollId;
623 nsIContent* nsLayoutUtils::FindContentFor(ViewID aId) {
624 MOZ_ASSERT(aId != ScrollableLayerGuid::NULL_SCROLL_ID,
625 "Cannot find a content element in map for null IDs.");
626 nsIContent* content;
627 bool exists = GetContentMap().Get(aId, &content);
629 if (exists) {
630 return content;
631 } else {
632 return nullptr;
636 nsIFrame* nsLayoutUtils::GetScrollFrameFromContent(nsIContent* aContent) {
637 nsIFrame* frame = aContent->GetPrimaryFrame();
638 if (aContent->OwnerDoc()->GetRootElement() == aContent) {
639 PresShell* presShell = frame ? frame->PresShell() : nullptr;
640 if (!presShell) {
641 presShell = aContent->OwnerDoc()->GetPresShell();
643 // We want the scroll frame, the root scroll frame differs from all
644 // others in that the primary frame is not the scroll frame.
645 nsIFrame* rootScrollFrame =
646 presShell ? presShell->GetRootScrollFrame() : nullptr;
647 if (rootScrollFrame) {
648 frame = rootScrollFrame;
651 return frame;
654 nsIScrollableFrame* nsLayoutUtils::FindScrollableFrameFor(
655 nsIContent* aContent) {
656 nsIFrame* scrollFrame = GetScrollFrameFromContent(aContent);
657 return scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr;
660 nsIScrollableFrame* nsLayoutUtils::FindScrollableFrameFor(ViewID aId) {
661 nsIContent* content = FindContentFor(aId);
662 if (!content) {
663 return nullptr;
666 return FindScrollableFrameFor(content);
669 ViewID nsLayoutUtils::FindIDForScrollableFrame(
670 nsIScrollableFrame* aScrollable) {
671 if (!aScrollable) {
672 return ScrollableLayerGuid::NULL_SCROLL_ID;
675 nsIFrame* scrollFrame = do_QueryFrame(aScrollable);
676 nsIContent* scrollContent = scrollFrame->GetContent();
678 ScrollableLayerGuid::ViewID scrollId;
679 if (scrollContent && nsLayoutUtils::FindIDFor(scrollContent, &scrollId)) {
680 return scrollId;
683 return ScrollableLayerGuid::NULL_SCROLL_ID;
686 bool nsLayoutUtils::UsesAsyncScrolling(nsIFrame* aFrame) {
687 #ifdef MOZ_WIDGET_ANDROID
688 // We always have async scrolling for android
689 return true;
690 #endif
692 return AsyncPanZoomEnabled(aFrame);
695 bool nsLayoutUtils::AsyncPanZoomEnabled(const nsIFrame* aFrame) {
696 // We use this as a shortcut, since if the compositor will never use APZ,
697 // no widget will either.
698 if (!gfxPlatform::AsyncPanZoomEnabled()) {
699 return false;
702 const nsIFrame* frame = nsLayoutUtils::GetDisplayRootFrame(aFrame);
703 nsIWidget* widget = frame->GetNearestWidget();
704 if (!widget) {
705 return false;
707 return widget->AsyncPanZoomEnabled();
710 bool nsLayoutUtils::AllowZoomingForDocument(
711 const mozilla::dom::Document* aDocument) {
712 if (aDocument->GetPresShell() &&
713 !aDocument->GetPresShell()->AsyncPanZoomEnabled()) {
714 return false;
716 // True if we allow zooming for all documents on this platform, or if we are
717 // in RDM.
718 BrowsingContext* bc = aDocument->GetBrowsingContext();
719 return StaticPrefs::apz_allow_zooming() || (bc && bc->InRDMPane());
722 static bool HasVisibleAnonymousContents(Document* aDoc) {
723 for (RefPtr<AnonymousContent>& ac : aDoc->GetAnonymousContents()) {
724 // We check to see if the anonymous content node has a frame. If it doesn't,
725 // that means that's not visible to the user because e.g. it's display:none.
726 // For now we assume that if it has a frame, it is visible. We might be able
727 // to refine this further by adding complexity if it turns out this
728 // condition results in a lot of false positives.
729 if (ac->Host()->GetPrimaryFrame()) {
730 return true;
733 return false;
736 bool nsLayoutUtils::ShouldDisableApzForElement(nsIContent* aContent) {
737 if (!aContent) {
738 return false;
741 if (aContent->GetProperty(nsGkAtoms::apzDisabled)) {
742 return true;
745 Document* doc = aContent->GetComposedDoc();
746 if (PresShell* rootPresShell =
747 APZCCallbackHelper::GetRootContentDocumentPresShellForContent(
748 aContent)) {
749 if (Document* rootDoc = rootPresShell->GetDocument()) {
750 nsIContent* rootContent =
751 rootPresShell->GetRootScrollFrame()
752 ? rootPresShell->GetRootScrollFrame()->GetContent()
753 : rootDoc->GetDocumentElement();
754 // For the AccessibleCaret and other anonymous contents: disable APZ on
755 // any scrollable subframes that are not the root scrollframe of a
756 // document, if the document has any visible anonymous contents.
758 // If we find this is triggering in too many scenarios then we might
759 // want to tighten this check further. The main use cases for which we
760 // want to disable APZ as of this writing are listed in bug 1316318.
761 if (aContent != rootContent && HasVisibleAnonymousContents(rootDoc)) {
762 return true;
767 if (!doc) {
768 return false;
771 if (PresShell* presShell = doc->GetPresShell()) {
772 if (RefPtr<AccessibleCaretEventHub> eventHub =
773 presShell->GetAccessibleCaretEventHub()) {
774 // Disable APZ for all elements if AccessibleCaret tells us to do so.
775 if (eventHub->ShouldDisableApz()) {
776 return true;
781 return StaticPrefs::apz_disable_for_scroll_linked_effects() &&
782 doc->HasScrollLinkedEffect();
785 void nsLayoutUtils::NotifyPaintSkipTransaction(ViewID aScrollId) {
786 if (nsIScrollableFrame* scrollFrame =
787 nsLayoutUtils::FindScrollableFrameFor(aScrollId)) {
788 #ifdef DEBUG
789 nsIFrame* f = do_QueryFrame(scrollFrame);
790 MOZ_ASSERT(f && f->PresShell() && !f->PresShell()->IsResolutionUpdated());
791 #endif
792 scrollFrame->NotifyApzTransaction();
796 nsContainerFrame* nsLayoutUtils::LastContinuationWithChild(
797 nsContainerFrame* aFrame) {
798 MOZ_ASSERT(aFrame, "NULL frame pointer");
799 for (auto f = aFrame->LastContinuation(); f; f = f->GetPrevContinuation()) {
800 for (const auto& childList : f->ChildLists()) {
801 if (MOZ_LIKELY(!childList.mList.IsEmpty())) {
802 return static_cast<nsContainerFrame*>(f);
806 return aFrame;
809 // static
810 FrameChildListID nsLayoutUtils::GetChildListNameFor(nsIFrame* aChildFrame) {
811 FrameChildListID id = FrameChildListID::Principal;
813 MOZ_DIAGNOSTIC_ASSERT(!aChildFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
815 if (aChildFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
816 nsIFrame* pif = aChildFrame->GetPrevInFlow();
817 if (pif->GetParent() == aChildFrame->GetParent()) {
818 id = FrameChildListID::ExcessOverflowContainers;
819 } else {
820 id = FrameChildListID::OverflowContainers;
822 } else {
823 LayoutFrameType childType = aChildFrame->Type();
824 if (LayoutFrameType::TableColGroup == childType) {
825 id = FrameChildListID::ColGroup;
826 } else if (aChildFrame->IsTableCaption()) {
827 id = FrameChildListID::Caption;
828 } else {
829 id = FrameChildListID::Principal;
833 #ifdef DEBUG
834 // Verify that the frame is actually in that child list or in the
835 // corresponding overflow list.
836 nsContainerFrame* parent = aChildFrame->GetParent();
837 bool found = parent->GetChildList(id).ContainsFrame(aChildFrame);
838 if (!found) {
839 found = parent->GetChildList(FrameChildListID::Overflow)
840 .ContainsFrame(aChildFrame);
841 MOZ_ASSERT(found, "not in child list");
843 #endif
845 return id;
848 static Element* GetPseudo(const nsIContent* aContent, nsAtom* aPseudoProperty) {
849 MOZ_ASSERT(aPseudoProperty == nsGkAtoms::beforePseudoProperty ||
850 aPseudoProperty == nsGkAtoms::afterPseudoProperty ||
851 aPseudoProperty == nsGkAtoms::markerPseudoProperty);
852 if (!aContent->MayHaveAnonymousChildren()) {
853 return nullptr;
855 return static_cast<Element*>(aContent->GetProperty(aPseudoProperty));
858 /*static*/
859 Element* nsLayoutUtils::GetBeforePseudo(const nsIContent* aContent) {
860 return GetPseudo(aContent, nsGkAtoms::beforePseudoProperty);
863 /*static*/
864 nsIFrame* nsLayoutUtils::GetBeforeFrame(const nsIContent* aContent) {
865 Element* pseudo = GetBeforePseudo(aContent);
866 return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
869 /*static*/
870 Element* nsLayoutUtils::GetAfterPseudo(const nsIContent* aContent) {
871 return GetPseudo(aContent, nsGkAtoms::afterPseudoProperty);
874 /*static*/
875 nsIFrame* nsLayoutUtils::GetAfterFrame(const nsIContent* aContent) {
876 Element* pseudo = GetAfterPseudo(aContent);
877 return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
880 /*static*/
881 Element* nsLayoutUtils::GetMarkerPseudo(const nsIContent* aContent) {
882 return GetPseudo(aContent, nsGkAtoms::markerPseudoProperty);
885 /*static*/
886 nsIFrame* nsLayoutUtils::GetMarkerFrame(const nsIContent* aContent) {
887 Element* pseudo = GetMarkerPseudo(aContent);
888 return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
891 #ifdef ACCESSIBILITY
892 void nsLayoutUtils::GetMarkerSpokenText(const nsIContent* aContent,
893 nsAString& aText) {
894 MOZ_ASSERT(aContent && aContent->IsGeneratedContentContainerForMarker());
896 aText.Truncate();
898 nsIFrame* frame = aContent->GetPrimaryFrame();
899 if (!frame) {
900 return;
903 if (frame->StyleContent()->ContentCount() > 0) {
904 for (nsIFrame* child : frame->PrincipalChildList()) {
905 nsIFrame::RenderedText text = child->GetRenderedText();
906 aText += text.mString;
908 return;
911 if (!frame->StyleList()->mListStyleImage.IsNone()) {
912 // ::marker is an image, so use default bullet character.
913 static const char16_t kDiscMarkerString[] = {0x2022, ' ', 0};
914 aText.AssignLiteral(kDiscMarkerString);
915 return;
918 frame->PresContext()
919 ->FrameConstructor()
920 ->GetContainStyleScopeManager()
921 .GetSpokenCounterText(frame, aText);
923 #endif
925 // static
926 nsIFrame* nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame,
927 LayoutFrameType aFrameType,
928 nsIFrame* aStopAt) {
929 for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
930 if (frame->Type() == aFrameType) {
931 return frame;
933 if (frame == aStopAt) {
934 break;
937 return nullptr;
940 /* static */
941 nsIFrame* nsLayoutUtils::GetPageFrame(nsIFrame* aFrame) {
942 return GetClosestFrameOfType(aFrame, LayoutFrameType::Page);
945 /* static */
946 nsIFrame* nsLayoutUtils::GetStyleFrame(nsIFrame* aPrimaryFrame) {
947 MOZ_ASSERT(aPrimaryFrame);
948 if (aPrimaryFrame->IsTableWrapperFrame()) {
949 nsIFrame* inner = aPrimaryFrame->PrincipalChildList().FirstChild();
950 // inner may be null, if aPrimaryFrame is mid-destruction
951 return inner;
954 return aPrimaryFrame;
957 const nsIFrame* nsLayoutUtils::GetStyleFrame(const nsIFrame* aPrimaryFrame) {
958 return nsLayoutUtils::GetStyleFrame(const_cast<nsIFrame*>(aPrimaryFrame));
961 nsIFrame* nsLayoutUtils::GetStyleFrame(const nsIContent* aContent) {
962 nsIFrame* frame = aContent->GetPrimaryFrame();
963 if (!frame) {
964 return nullptr;
967 return nsLayoutUtils::GetStyleFrame(frame);
970 CSSIntCoord nsLayoutUtils::UnthemedScrollbarSize(StyleScrollbarWidth aWidth) {
971 switch (aWidth) {
972 case StyleScrollbarWidth::Auto:
973 return 12;
974 case StyleScrollbarWidth::Thin:
975 return 6;
976 case StyleScrollbarWidth::None:
977 return 0;
979 return 0;
982 /* static */
983 nsIFrame* nsLayoutUtils::GetPrimaryFrameFromStyleFrame(nsIFrame* aStyleFrame) {
984 nsIFrame* parent = aStyleFrame->GetParent();
985 return parent && parent->IsTableWrapperFrame() ? parent : aStyleFrame;
988 /* static */
989 const nsIFrame* nsLayoutUtils::GetPrimaryFrameFromStyleFrame(
990 const nsIFrame* aStyleFrame) {
991 return nsLayoutUtils::GetPrimaryFrameFromStyleFrame(
992 const_cast<nsIFrame*>(aStyleFrame));
995 /*static*/
996 bool nsLayoutUtils::IsPrimaryStyleFrame(const nsIFrame* aFrame) {
997 if (aFrame->IsTableWrapperFrame()) {
998 return false;
1001 const nsIFrame* parent = aFrame->GetParent();
1002 if (parent && parent->IsTableWrapperFrame()) {
1003 return parent->PrincipalChildList().FirstChild() == aFrame;
1006 return aFrame->IsPrimaryFrame();
1009 nsIFrame* nsLayoutUtils::GetFloatFromPlaceholder(nsIFrame* aFrame) {
1010 NS_ASSERTION(aFrame->IsPlaceholderFrame(), "Must have a placeholder here");
1011 if (aFrame->HasAnyStateBits(PLACEHOLDER_FOR_FLOAT)) {
1012 nsIFrame* outOfFlowFrame =
1013 nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
1014 NS_ASSERTION(outOfFlowFrame && outOfFlowFrame->IsFloating(),
1015 "How did that happen?");
1016 return outOfFlowFrame;
1019 return nullptr;
1022 // static
1023 nsIFrame* nsLayoutUtils::GetCrossDocParentFrameInProcess(
1024 const nsIFrame* aFrame, nsPoint* aCrossDocOffset) {
1025 nsIFrame* p = aFrame->GetParent();
1026 if (p) {
1027 return p;
1030 nsView* v = aFrame->GetView();
1031 if (!v) {
1032 return nullptr;
1034 v = v->GetParent(); // anonymous inner view
1035 if (!v) {
1036 return nullptr;
1038 v = v->GetParent(); // subdocumentframe's view
1039 if (!v) {
1040 return nullptr;
1043 p = v->GetFrame();
1044 if (p && aCrossDocOffset) {
1045 nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(p);
1046 MOZ_ASSERT(subdocumentFrame);
1047 *aCrossDocOffset += subdocumentFrame->GetExtraOffset();
1050 return p;
1053 // static
1054 nsIFrame* nsLayoutUtils::GetCrossDocParentFrame(const nsIFrame* aFrame,
1055 nsPoint* aCrossDocOffset) {
1056 return GetCrossDocParentFrameInProcess(aFrame, aCrossDocOffset);
1059 // static
1060 bool nsLayoutUtils::IsProperAncestorFrameCrossDoc(
1061 const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
1062 const nsIFrame* aCommonAncestor) {
1063 if (aFrame == aAncestorFrame) return false;
1064 return IsAncestorFrameCrossDoc(aAncestorFrame, aFrame, aCommonAncestor);
1067 // static
1068 bool nsLayoutUtils::IsProperAncestorFrameCrossDocInProcess(
1069 const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
1070 const nsIFrame* aCommonAncestor) {
1071 if (aFrame == aAncestorFrame) return false;
1072 return IsAncestorFrameCrossDocInProcess(aAncestorFrame, aFrame,
1073 aCommonAncestor);
1076 // static
1077 bool nsLayoutUtils::IsAncestorFrameCrossDoc(const nsIFrame* aAncestorFrame,
1078 const nsIFrame* aFrame,
1079 const nsIFrame* aCommonAncestor) {
1080 for (const nsIFrame* f = aFrame; f != aCommonAncestor;
1081 f = GetCrossDocParentFrameInProcess(f)) {
1082 if (f == aAncestorFrame) return true;
1084 return aCommonAncestor == aAncestorFrame;
1087 // static
1088 bool nsLayoutUtils::IsAncestorFrameCrossDocInProcess(
1089 const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
1090 const nsIFrame* aCommonAncestor) {
1091 for (const nsIFrame* f = aFrame; f != aCommonAncestor;
1092 f = GetCrossDocParentFrameInProcess(f)) {
1093 if (f == aAncestorFrame) return true;
1095 return aCommonAncestor == aAncestorFrame;
1098 // static
1099 bool nsLayoutUtils::IsProperAncestorFrame(const nsIFrame* aAncestorFrame,
1100 const nsIFrame* aFrame,
1101 const nsIFrame* aCommonAncestor) {
1102 if (aFrame == aAncestorFrame) return false;
1103 for (const nsIFrame* f = aFrame; f != aCommonAncestor; f = f->GetParent()) {
1104 if (f == aAncestorFrame) return true;
1106 return aCommonAncestor == aAncestorFrame;
1109 // static
1110 nsIFrame* nsLayoutUtils::FillAncestors(nsIFrame* aFrame,
1111 nsIFrame* aStopAtAncestor,
1112 nsTArray<nsIFrame*>* aAncestors) {
1113 while (aFrame && aFrame != aStopAtAncestor) {
1114 aAncestors->AppendElement(aFrame);
1115 aFrame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
1117 return aFrame;
1120 // Return true if aFrame1 is after aFrame2
1121 static bool IsFrameAfter(nsIFrame* aFrame1, nsIFrame* aFrame2) {
1122 nsIFrame* f = aFrame2;
1123 do {
1124 f = f->GetNextSibling();
1125 if (f == aFrame1) return true;
1126 } while (f);
1127 return false;
1130 // static
1131 int32_t nsLayoutUtils::DoCompareTreePosition(nsIFrame* aFrame1,
1132 nsIFrame* aFrame2,
1133 nsIFrame* aCommonAncestor) {
1134 MOZ_ASSERT(aFrame1, "aFrame1 must not be null");
1135 MOZ_ASSERT(aFrame2, "aFrame2 must not be null");
1137 AutoTArray<nsIFrame*, 20> frame2Ancestors;
1138 nsIFrame* nonCommonAncestor =
1139 FillAncestors(aFrame2, aCommonAncestor, &frame2Ancestors);
1140 return DoCompareTreePosition(aFrame1, aFrame2, frame2Ancestors,
1141 nonCommonAncestor ? aCommonAncestor : nullptr);
1144 // static
1145 int32_t nsLayoutUtils::DoCompareTreePosition(
1146 nsIFrame* aFrame1, nsIFrame* aFrame2, nsTArray<nsIFrame*>& aFrame2Ancestors,
1147 nsIFrame* aCommonAncestor) {
1148 MOZ_ASSERT(aFrame1, "aFrame1 must not be null");
1149 MOZ_ASSERT(aFrame2, "aFrame2 must not be null");
1151 nsPresContext* presContext = aFrame1->PresContext();
1152 if (presContext != aFrame2->PresContext()) {
1153 NS_ERROR("no common ancestor at all, different documents");
1154 return 0;
1157 AutoTArray<nsIFrame*, 20> frame1Ancestors;
1158 if (aCommonAncestor &&
1159 !FillAncestors(aFrame1, aCommonAncestor, &frame1Ancestors)) {
1160 // We reached the root of the frame tree ... if aCommonAncestor was set,
1161 // it is wrong
1162 return DoCompareTreePosition(aFrame1, aFrame2, nullptr);
1165 int32_t last1 = int32_t(frame1Ancestors.Length()) - 1;
1166 int32_t last2 = int32_t(aFrame2Ancestors.Length()) - 1;
1167 while (last1 >= 0 && last2 >= 0 &&
1168 frame1Ancestors[last1] == aFrame2Ancestors[last2]) {
1169 last1--;
1170 last2--;
1173 if (last1 < 0) {
1174 if (last2 < 0) {
1175 NS_ASSERTION(aFrame1 == aFrame2, "internal error?");
1176 return 0;
1178 // aFrame1 is an ancestor of aFrame2
1179 return -1;
1182 if (last2 < 0) {
1183 // aFrame2 is an ancestor of aFrame1
1184 return 1;
1187 nsIFrame* ancestor1 = frame1Ancestors[last1];
1188 nsIFrame* ancestor2 = aFrame2Ancestors[last2];
1189 // Now we should be able to walk sibling chains to find which one is first
1190 if (IsFrameAfter(ancestor2, ancestor1)) {
1191 return -1;
1193 if (IsFrameAfter(ancestor1, ancestor2)) {
1194 return 1;
1196 NS_WARNING("Frames were in different child lists???");
1197 return 0;
1200 // static
1201 nsIFrame* nsLayoutUtils::GetLastSibling(nsIFrame* aFrame) {
1202 if (!aFrame) {
1203 return nullptr;
1206 nsIFrame* next;
1207 while ((next = aFrame->GetNextSibling()) != nullptr) {
1208 aFrame = next;
1210 return aFrame;
1213 // static
1214 nsView* nsLayoutUtils::FindSiblingViewFor(nsView* aParentView,
1215 nsIFrame* aFrame) {
1216 nsIFrame* parentViewFrame = aParentView->GetFrame();
1217 nsIContent* parentViewContent =
1218 parentViewFrame ? parentViewFrame->GetContent() : nullptr;
1219 for (nsView* insertBefore = aParentView->GetFirstChild(); insertBefore;
1220 insertBefore = insertBefore->GetNextSibling()) {
1221 nsIFrame* f = insertBefore->GetFrame();
1222 if (!f) {
1223 // this view could be some anonymous view attached to a meaningful parent
1224 for (nsView* searchView = insertBefore->GetParent(); searchView;
1225 searchView = searchView->GetParent()) {
1226 f = searchView->GetFrame();
1227 if (f) {
1228 break;
1231 NS_ASSERTION(f, "Can't find a frame anywhere!");
1233 if (!f || !aFrame->GetContent() || !f->GetContent() ||
1234 nsContentUtils::CompareTreePosition<TreeKind::Flat>(
1235 aFrame->GetContent(), f->GetContent(), parentViewContent) > 0) {
1236 // aFrame's content is after f's content (or we just don't know),
1237 // so put our view before f's view
1238 return insertBefore;
1241 return nullptr;
1244 // static
1245 nsIScrollableFrame* nsLayoutUtils::GetScrollableFrameFor(
1246 const nsIFrame* aScrolledFrame) {
1247 nsIFrame* frame = aScrolledFrame->GetParent();
1248 nsIScrollableFrame* sf = do_QueryFrame(frame);
1249 return (sf && sf->GetScrolledFrame() == aScrolledFrame) ? sf : nullptr;
1252 /* static */
1253 SideBits nsLayoutUtils::GetSideBitsForFixedPositionContent(
1254 const nsIFrame* aFixedPosFrame) {
1255 SideBits sides = SideBits::eNone;
1256 if (aFixedPosFrame) {
1257 const nsStylePosition* position = aFixedPosFrame->StylePosition();
1258 if (!position->mOffset.Get(eSideRight).IsAuto()) {
1259 sides |= SideBits::eRight;
1261 if (!position->mOffset.Get(eSideLeft).IsAuto()) {
1262 sides |= SideBits::eLeft;
1264 if (!position->mOffset.Get(eSideBottom).IsAuto()) {
1265 sides |= SideBits::eBottom;
1267 if (!position->mOffset.Get(eSideTop).IsAuto()) {
1268 sides |= SideBits::eTop;
1271 return sides;
1274 ScrollableLayerGuid::ViewID nsLayoutUtils::ScrollIdForRootScrollFrame(
1275 nsPresContext* aPresContext) {
1276 ViewID id = ScrollableLayerGuid::NULL_SCROLL_ID;
1277 if (nsIFrame* rootScrollFrame =
1278 aPresContext->PresShell()->GetRootScrollFrame()) {
1279 if (nsIContent* content = rootScrollFrame->GetContent()) {
1280 id = FindOrCreateIDFor(content);
1283 return id;
1286 // static
1287 nsIScrollableFrame* nsLayoutUtils::GetNearestScrollableFrameForDirection(
1288 nsIFrame* aFrame, ScrollDirections aDirections) {
1289 NS_ASSERTION(
1290 aFrame, "GetNearestScrollableFrameForDirection expects a non-null frame");
1291 // FIXME Bug 1714720 : This nearest scroll target is not going to work over
1292 // process boundaries, in such cases we need to hand over in APZ side.
1293 for (nsIFrame* f = aFrame; f;
1294 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
1295 nsIScrollableFrame* scrollableFrame = do_QueryFrame(f);
1296 if (scrollableFrame) {
1297 ScrollDirections directions =
1298 scrollableFrame->GetAvailableScrollingDirectionsForUserInputEvents();
1299 if (aDirections.contains(ScrollDirection::eVertical)) {
1300 if (directions.contains(ScrollDirection::eVertical)) {
1301 return scrollableFrame;
1304 if (aDirections.contains(ScrollDirection::eHorizontal)) {
1305 if (directions.contains(ScrollDirection::eHorizontal)) {
1306 return scrollableFrame;
1311 return nullptr;
1314 static nsIFrame* GetNearestScrollableOrOverflowClipFrame(
1315 nsIFrame* aFrame, uint32_t aFlags,
1316 const std::function<bool(const nsIFrame* aCurrentFrame)>& aClipFrameCheck =
1317 nullptr) {
1318 MOZ_ASSERT(
1319 aFrame,
1320 "GetNearestScrollableOrOverflowClipFrame expects a non-null frame");
1322 auto GetNextFrame = [aFlags](const nsIFrame* aFrame) -> nsIFrame* {
1323 return (aFlags & nsLayoutUtils::SCROLLABLE_SAME_DOC)
1324 ? aFrame->GetParent()
1325 : nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame);
1328 for (nsIFrame* f = aFrame; f; f = GetNextFrame(f)) {
1329 if (aClipFrameCheck && aClipFrameCheck(f)) {
1330 return f;
1333 if ((aFlags & nsLayoutUtils::SCROLLABLE_STOP_AT_PAGE) && f->IsPageFrame()) {
1334 break;
1336 if (nsIScrollableFrame* scrollableFrame = do_QueryFrame(f)) {
1337 if (aFlags & nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE) {
1338 if (scrollableFrame->WantAsyncScroll()) {
1339 return f;
1341 } else {
1342 ScrollStyles ss = scrollableFrame->GetScrollStyles();
1343 if ((aFlags & nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN) ||
1344 ss.mVertical != StyleOverflow::Hidden ||
1345 ss.mHorizontal != StyleOverflow::Hidden) {
1346 return f;
1349 if (aFlags & nsLayoutUtils::SCROLLABLE_ALWAYS_MATCH_ROOT) {
1350 PresShell* presShell = f->PresShell();
1351 if (presShell->GetRootScrollFrame() == f && presShell->GetDocument() &&
1352 presShell->GetDocument()->IsRootDisplayDocument()) {
1353 return f;
1357 if ((aFlags & nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT) &&
1358 f->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
1359 nsLayoutUtils::IsReallyFixedPos(f)) {
1360 return f->PresShell()->GetRootScrollFrame();
1363 return nullptr;
1366 // static
1367 nsIScrollableFrame* nsLayoutUtils::GetNearestScrollableFrame(nsIFrame* aFrame,
1368 uint32_t aFlags) {
1369 nsIFrame* found = GetNearestScrollableOrOverflowClipFrame(aFrame, aFlags);
1370 if (!found) {
1371 return nullptr;
1374 return do_QueryFrame(found);
1377 // static
1378 nsIFrame* nsLayoutUtils::GetNearestOverflowClipFrame(nsIFrame* aFrame) {
1379 return GetNearestScrollableOrOverflowClipFrame(
1380 aFrame, SCROLLABLE_SAME_DOC | SCROLLABLE_INCLUDE_HIDDEN,
1381 [](const nsIFrame* currentFrame) -> bool {
1382 // In cases of SVG Inner/Outer frames it basically clips descendants
1383 // unless overflow: visible is explicitly specified.
1384 LayoutFrameType type = currentFrame->Type();
1385 return ((type == LayoutFrameType::SVGOuterSVG ||
1386 type == LayoutFrameType::SVGInnerSVG) &&
1387 (currentFrame->StyleDisplay()->mOverflowX !=
1388 StyleOverflow::Visible &&
1389 currentFrame->StyleDisplay()->mOverflowY !=
1390 StyleOverflow::Visible));
1394 // static
1395 nsRect nsLayoutUtils::GetScrolledRect(nsIFrame* aScrolledFrame,
1396 const nsRect& aScrolledFrameOverflowArea,
1397 const nsSize& aScrollPortSize,
1398 StyleDirection aDirection) {
1399 WritingMode wm = aScrolledFrame->GetWritingMode();
1400 // Potentially override the frame's direction to use the direction found
1401 // by nsHTMLScrollFrame::GetScrolledFrameDir()
1402 wm.SetDirectionFromBidiLevel(aDirection == StyleDirection::Rtl
1403 ? mozilla::intl::BidiEmbeddingLevel::RTL()
1404 : mozilla::intl::BidiEmbeddingLevel::LTR());
1406 nscoord x1 = aScrolledFrameOverflowArea.x,
1407 x2 = aScrolledFrameOverflowArea.XMost(),
1408 y1 = aScrolledFrameOverflowArea.y,
1409 y2 = aScrolledFrameOverflowArea.YMost();
1411 const bool isHorizontalWM = !wm.IsVertical();
1412 const bool isVerticalWM = wm.IsVertical();
1413 bool isInlineFlowFromTopOrLeft = !wm.IsInlineReversed();
1414 bool isBlockFlowFromTopOrLeft = isHorizontalWM || wm.IsVerticalLR();
1416 if (aScrolledFrame->IsFlexContainerFrame()) {
1417 // In a flex container, the children flow (and overflow) along the flex
1418 // container's main axis and cross axis. These are analogous to the
1419 // inline/block axes, and by default they correspond exactly to those axes;
1420 // but the flex container's CSS (e.g. flex-direction: column-reverse) may
1421 // have swapped and/or reversed them, and we need to account for that here.
1422 FlexboxAxisInfo info(aScrolledFrame);
1423 if (info.mIsRowOriented) {
1424 // The flex container's inline axis is the main axis.
1425 isInlineFlowFromTopOrLeft =
1426 isInlineFlowFromTopOrLeft == !info.mIsMainAxisReversed;
1427 isBlockFlowFromTopOrLeft =
1428 isBlockFlowFromTopOrLeft == !info.mIsCrossAxisReversed;
1429 } else {
1430 // The flex container's block axis is the main axis.
1431 isBlockFlowFromTopOrLeft =
1432 isBlockFlowFromTopOrLeft == !info.mIsMainAxisReversed;
1433 isInlineFlowFromTopOrLeft =
1434 isInlineFlowFromTopOrLeft == !info.mIsCrossAxisReversed;
1438 // Clamp the horizontal start-edge (x1 or x2, depending whether the logical
1439 // axis that corresponds to horizontal progresses from left-to-right or
1440 // right-to-left).
1441 if ((isHorizontalWM && isInlineFlowFromTopOrLeft) ||
1442 (isVerticalWM && isBlockFlowFromTopOrLeft)) {
1443 if (x1 < 0) {
1444 x1 = 0;
1446 } else {
1447 if (x2 > aScrollPortSize.width) {
1448 x2 = aScrollPortSize.width;
1450 // When the scrolled frame chooses a size larger than its available width
1451 // (because its padding alone is larger than the available width), we need
1452 // to keep the start-edge of the scroll frame anchored to the start-edge of
1453 // the scrollport.
1454 // When the scrolled frame is RTL, this means moving it in our left-based
1455 // coordinate system, so we need to compensate for its extra width here by
1456 // effectively repositioning the frame.
1457 nscoord extraWidth =
1458 std::max(0, aScrolledFrame->GetSize().width - aScrollPortSize.width);
1459 x2 += extraWidth;
1462 // Similarly, clamp the vertical start-edge (y1 or y2, depending whether the
1463 // logical axis that corresponds to vertical progresses from top-to-bottom or
1464 // buttom-to-top).
1465 if ((isHorizontalWM && isBlockFlowFromTopOrLeft) ||
1466 (isVerticalWM && isInlineFlowFromTopOrLeft)) {
1467 if (y1 < 0) {
1468 y1 = 0;
1470 } else {
1471 if (y2 > aScrollPortSize.height) {
1472 y2 = aScrollPortSize.height;
1474 nscoord extraHeight =
1475 std::max(0, aScrolledFrame->GetSize().height - aScrollPortSize.height);
1476 y2 += extraHeight;
1479 return nsRect(x1, y1, x2 - x1, y2 - y1);
1482 // static
1483 bool nsLayoutUtils::HasPseudoStyle(nsIContent* aContent,
1484 ComputedStyle* aComputedStyle,
1485 PseudoStyleType aPseudoElement,
1486 nsPresContext* aPresContext) {
1487 MOZ_ASSERT(aPresContext, "Must have a prescontext");
1489 RefPtr<ComputedStyle> pseudoContext;
1490 if (aContent) {
1491 pseudoContext = aPresContext->StyleSet()->ProbePseudoElementStyle(
1492 *aContent->AsElement(), aPseudoElement, nullptr, aComputedStyle);
1494 return pseudoContext != nullptr;
1497 nsPoint nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(Event* aDOMEvent,
1498 nsIFrame* aFrame) {
1499 if (!aDOMEvent) return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1500 WidgetEvent* event = aDOMEvent->WidgetEventPtr();
1501 if (!event) return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1502 return GetEventCoordinatesRelativeTo(event, RelativeTo{aFrame});
1505 static bool IsValidCoordinateTypeEvent(const WidgetEvent* aEvent) {
1506 if (!aEvent) {
1507 return false;
1509 return aEvent->mClass == eMouseEventClass ||
1510 aEvent->mClass == eMouseScrollEventClass ||
1511 aEvent->mClass == eWheelEventClass ||
1512 aEvent->mClass == eDragEventClass ||
1513 aEvent->mClass == eSimpleGestureEventClass ||
1514 aEvent->mClass == ePointerEventClass ||
1515 aEvent->mClass == eGestureNotifyEventClass ||
1516 aEvent->mClass == eTouchEventClass ||
1517 aEvent->mClass == eQueryContentEventClass;
1520 nsPoint nsLayoutUtils::GetEventCoordinatesRelativeTo(const WidgetEvent* aEvent,
1521 RelativeTo aFrame) {
1522 if (!IsValidCoordinateTypeEvent(aEvent)) {
1523 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1526 return GetEventCoordinatesRelativeTo(aEvent, aEvent->AsGUIEvent()->mRefPoint,
1527 aFrame);
1530 nsPoint nsLayoutUtils::GetEventCoordinatesRelativeTo(
1531 const WidgetEvent* aEvent, const LayoutDeviceIntPoint& aPoint,
1532 RelativeTo aFrame) {
1533 if (!aFrame.mFrame) {
1534 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1537 nsIWidget* widget = aEvent->AsGUIEvent()->mWidget;
1538 if (!widget) {
1539 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1542 return GetEventCoordinatesRelativeTo(widget, aPoint, aFrame);
1545 nsPoint GetEventCoordinatesRelativeTo(nsIWidget* aWidget,
1546 const LayoutDeviceIntPoint& aPoint,
1547 RelativeTo aFrame) {
1548 const nsIFrame* frame = aFrame.mFrame;
1549 if (!frame || !aWidget) {
1550 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1553 nsView* view = frame->GetView();
1554 if (view) {
1555 nsIWidget* frameWidget = view->GetWidget();
1556 if (frameWidget && frameWidget == aWidget) {
1557 // Special case this cause it happens a lot.
1558 // This also fixes bug 664707, events in the extra-special case of select
1559 // dropdown popups that are transformed.
1560 nsPresContext* presContext = frame->PresContext();
1561 nsPoint pt(presContext->DevPixelsToAppUnits(aPoint.x),
1562 presContext->DevPixelsToAppUnits(aPoint.y));
1563 return pt - view->ViewToWidgetOffset();
1567 /* If we walk up the frame tree and discover that any of the frames are
1568 * transformed, we need to do extra work to convert from the global
1569 * space to the local space.
1571 const nsIFrame* rootFrame = frame;
1572 bool transformFound = false;
1573 for (const nsIFrame* f = frame; f;
1574 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
1575 if (f->IsTransformed() || ViewportUtils::IsZoomedContentRoot(f)) {
1576 transformFound = true;
1579 rootFrame = f;
1582 nsView* rootView = rootFrame->GetView();
1583 if (!rootView) {
1584 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1587 nsPoint widgetToView = nsLayoutUtils::TranslateWidgetToView(
1588 rootFrame->PresContext(), aWidget, aPoint, rootView);
1590 if (widgetToView == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
1591 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1594 // Convert from root document app units to app units of the document aFrame
1595 // is in.
1596 int32_t rootAPD = rootFrame->PresContext()->AppUnitsPerDevPixel();
1597 int32_t localAPD = frame->PresContext()->AppUnitsPerDevPixel();
1598 widgetToView = widgetToView.ScaleToOtherAppUnits(rootAPD, localAPD);
1600 /* If we encountered a transform, we can't do simple arithmetic to figure
1601 * out how to convert back to aFrame's coordinates and must use the CTM.
1603 if (transformFound || frame->IsInSVGTextSubtree()) {
1604 return nsLayoutUtils::TransformRootPointToFrame(ViewportType::Visual,
1605 aFrame, widgetToView);
1608 /* Otherwise, all coordinate systems are translations of one another,
1609 * so we can just subtract out the difference.
1611 return widgetToView - frame->GetOffsetToCrossDoc(rootFrame);
1614 nsPoint nsLayoutUtils::GetEventCoordinatesRelativeTo(
1615 nsIWidget* aWidget, const LayoutDeviceIntPoint& aPoint, RelativeTo aFrame) {
1616 nsPoint result = ::GetEventCoordinatesRelativeTo(aWidget, aPoint, aFrame);
1617 if (aFrame.mViewportType == ViewportType::Layout && aFrame.mFrame &&
1618 aFrame.mFrame->Type() == LayoutFrameType::Viewport &&
1619 aFrame.mFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
1620 result = ViewportUtils::VisualToLayout(result, aFrame.mFrame->PresShell());
1622 return result;
1625 nsIFrame* nsLayoutUtils::GetPopupFrameForEventCoordinates(
1626 nsPresContext* aRootPresContext, const WidgetEvent* aEvent) {
1627 if (!IsValidCoordinateTypeEvent(aEvent)) {
1628 return nullptr;
1631 const auto* guiEvent = aEvent->AsGUIEvent();
1632 return GetPopupFrameForPoint(aRootPresContext, guiEvent->mWidget,
1633 guiEvent->mRefPoint);
1636 nsIFrame* nsLayoutUtils::GetPopupFrameForPoint(
1637 nsPresContext* aRootPresContext, nsIWidget* aWidget,
1638 const mozilla::LayoutDeviceIntPoint& aPoint,
1639 GetPopupFrameForPointFlags aFlags /* = GetPopupFrameForPointFlags(0) */) {
1640 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1641 if (!pm) {
1642 return nullptr;
1644 nsTArray<nsIFrame*> popups;
1645 pm->GetVisiblePopups(popups);
1646 // Search from top to bottom
1647 for (nsIFrame* popup : popups) {
1648 if (popup->PresContext()->GetRootPresContext() != aRootPresContext) {
1649 continue;
1651 if (!popup->ScrollableOverflowRect().Contains(GetEventCoordinatesRelativeTo(
1652 aWidget, aPoint, RelativeTo{popup}))) {
1653 continue;
1655 if (aFlags & GetPopupFrameForPointFlags::OnlyReturnFramesWithWidgets) {
1656 if (!popup->HasView() || !popup->GetView()->HasWidget()) {
1657 continue;
1660 return popup;
1662 return nullptr;
1665 void nsLayoutUtils::GetContainerAndOffsetAtEvent(PresShell* aPresShell,
1666 const WidgetEvent* aEvent,
1667 nsIContent** aContainer,
1668 int32_t* aOffset) {
1669 MOZ_ASSERT(aContainer || aOffset);
1671 if (aContainer) {
1672 *aContainer = nullptr;
1674 if (aOffset) {
1675 *aOffset = 0;
1678 if (!aPresShell) {
1679 return;
1682 aPresShell->FlushPendingNotifications(FlushType::Layout);
1684 RefPtr<nsPresContext> presContext = aPresShell->GetPresContext();
1685 if (!presContext) {
1686 return;
1689 nsIFrame* targetFrame = presContext->EventStateManager()->GetEventTarget();
1690 if (!targetFrame) {
1691 return;
1694 WidgetEvent* openingEvent = nullptr;
1695 // For popupshowing events, redirect via the original mouse event
1696 // that triggered the popup to open.
1697 if (aEvent->mMessage == eXULPopupShowing) {
1698 if (auto* pm = nsXULPopupManager::GetInstance()) {
1699 if (Event* openingPopupEvent = pm->GetOpeningPopupEvent()) {
1700 openingEvent = openingPopupEvent->WidgetEventPtr();
1705 nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo(
1706 openingEvent ? openingEvent : aEvent, RelativeTo{targetFrame});
1708 if (aContainer) {
1709 // TODO: This result may be useful to change to Selection. However, this
1710 // may return improper node (e.g., native anonymous node) for the
1711 // Selection. Perhaps, this should take Selection optionally and
1712 // if it's specified, needs to check if it's proper for the
1713 // Selection.
1714 nsCOMPtr<nsIContent> container =
1715 targetFrame->GetContentOffsetsFromPoint(point).content;
1716 if (container && (!container->ChromeOnlyAccess() ||
1717 nsContentUtils::CanAccessNativeAnon())) {
1718 container.forget(aContainer);
1721 if (aOffset) {
1722 *aOffset = targetFrame->GetContentOffsetsFromPoint(point).offset;
1726 void nsLayoutUtils::ConstrainToCoordValues(float& aStart, float& aSize) {
1727 MOZ_ASSERT(aSize >= 0);
1729 // Here we try to make sure that the resulting nsRect will continue to cover
1730 // as much of the area that was covered by the original gfx Rect as possible.
1732 // We clamp the bounds of the rect to {nscoord_MIN,nscoord_MAX} since
1733 // nsRect::X/Y() and nsRect::XMost/YMost() can't return values outwith this
1734 // range:
1735 float end = aStart + aSize;
1736 aStart = clamped(aStart, float(nscoord_MIN), float(nscoord_MAX));
1737 end = clamped(end, float(nscoord_MIN), float(nscoord_MAX));
1739 aSize = end - aStart;
1741 // We must also clamp aSize to {0,nscoord_MAX} since nsRect::Width/Height()
1742 // can't return a value greater than nscoord_MAX. If aSize is greater than
1743 // nscoord_MAX then we reduce it to nscoord_MAX while keeping the rect
1744 // centered:
1745 if (MOZ_UNLIKELY(std::isnan(aSize))) {
1746 // Can happen if aStart is -inf and aSize is +inf for example.
1747 aStart = 0.0f;
1748 aSize = float(nscoord_MAX);
1749 } else if (aSize > float(nscoord_MAX)) {
1750 float excess = aSize - float(nscoord_MAX);
1751 excess /= 2;
1752 aStart += excess;
1753 aSize = float(nscoord_MAX);
1758 * Given a gfxFloat, constrains its value to be between nscoord_MIN and
1759 * nscoord_MAX.
1761 * @param aVal The value to constrain (in/out)
1763 static void ConstrainToCoordValues(gfxFloat& aVal) {
1764 if (aVal <= nscoord_MIN)
1765 aVal = nscoord_MIN;
1766 else if (aVal >= nscoord_MAX)
1767 aVal = nscoord_MAX;
1770 void nsLayoutUtils::ConstrainToCoordValues(gfxFloat& aStart, gfxFloat& aSize) {
1771 gfxFloat max = aStart + aSize;
1773 // Clamp the end points to within nscoord range
1774 ::ConstrainToCoordValues(aStart);
1775 ::ConstrainToCoordValues(max);
1777 aSize = max - aStart;
1778 // If the width if still greater than the max nscoord, then bring both
1779 // endpoints in by the same amount until it fits.
1780 if (MOZ_UNLIKELY(std::isnan(aSize))) {
1781 // Can happen if aStart is -inf and aSize is +inf for example.
1782 aStart = 0.0f;
1783 aSize = nscoord_MAX;
1784 } else if (aSize > nscoord_MAX) {
1785 gfxFloat excess = aSize - nscoord_MAX;
1786 excess /= 2;
1788 aStart += excess;
1789 aSize = nscoord_MAX;
1790 } else if (aSize < nscoord_MIN) {
1791 gfxFloat excess = aSize - nscoord_MIN;
1792 excess /= 2;
1794 aStart -= excess;
1795 aSize = nscoord_MIN;
1799 nsRegion nsLayoutUtils::RoundedRectIntersectRect(const nsRect& aRoundedRect,
1800 const nscoord aRadii[8],
1801 const nsRect& aContainedRect) {
1802 // rectFullHeight and rectFullWidth together will approximately contain
1803 // the total area of the frame minus the rounded corners.
1804 nsRect rectFullHeight = aRoundedRect;
1805 nscoord xDiff = std::max(aRadii[eCornerTopLeftX], aRadii[eCornerBottomLeftX]);
1806 rectFullHeight.x += xDiff;
1807 rectFullHeight.width -=
1808 std::max(aRadii[eCornerTopRightX], aRadii[eCornerBottomRightX]) + xDiff;
1809 nsRect r1;
1810 r1.IntersectRect(rectFullHeight, aContainedRect);
1812 nsRect rectFullWidth = aRoundedRect;
1813 nscoord yDiff = std::max(aRadii[eCornerTopLeftY], aRadii[eCornerTopRightY]);
1814 rectFullWidth.y += yDiff;
1815 rectFullWidth.height -=
1816 std::max(aRadii[eCornerBottomLeftY], aRadii[eCornerBottomRightY]) + yDiff;
1817 nsRect r2;
1818 r2.IntersectRect(rectFullWidth, aContainedRect);
1820 nsRegion result;
1821 result.Or(r1, r2);
1822 return result;
1825 nsIntRegion nsLayoutUtils::RoundedRectIntersectIntRect(
1826 const nsIntRect& aRoundedRect, const RectCornerRadii& aCornerRadii,
1827 const nsIntRect& aContainedRect) {
1828 // rectFullHeight and rectFullWidth together will approximately contain
1829 // the total area of the frame minus the rounded corners.
1830 nsIntRect rectFullHeight = aRoundedRect;
1831 uint32_t xDiff =
1832 std::max(aCornerRadii.TopLeft().width, aCornerRadii.BottomLeft().width);
1833 rectFullHeight.x += xDiff;
1834 rectFullHeight.width -= std::max(aCornerRadii.TopRight().width,
1835 aCornerRadii.BottomRight().width) +
1836 xDiff;
1837 nsIntRect r1;
1838 r1.IntersectRect(rectFullHeight, aContainedRect);
1840 nsIntRect rectFullWidth = aRoundedRect;
1841 uint32_t yDiff =
1842 std::max(aCornerRadii.TopLeft().height, aCornerRadii.TopRight().height);
1843 rectFullWidth.y += yDiff;
1844 rectFullWidth.height -= std::max(aCornerRadii.BottomLeft().height,
1845 aCornerRadii.BottomRight().height) +
1846 yDiff;
1847 nsIntRect r2;
1848 r2.IntersectRect(rectFullWidth, aContainedRect);
1850 nsIntRegion result;
1851 result.Or(r1, r2);
1852 return result;
1855 // Helper for RoundedRectIntersectsRect.
1856 static bool CheckCorner(nscoord aXOffset, nscoord aYOffset, nscoord aXRadius,
1857 nscoord aYRadius) {
1858 MOZ_ASSERT(aXOffset > 0 && aYOffset > 0,
1859 "must not pass nonpositives to CheckCorner");
1860 MOZ_ASSERT(aXRadius >= 0 && aYRadius >= 0,
1861 "must not pass negatives to CheckCorner");
1863 // Avoid floating point math unless we're either (1) within the
1864 // quarter-ellipse area at the rounded corner or (2) outside the
1865 // rounding.
1866 if (aXOffset >= aXRadius || aYOffset >= aYRadius) return true;
1868 // Convert coordinates to a unit circle with (0,0) as the center of
1869 // curvature, and see if we're inside the circle or outside.
1870 float scaledX = float(aXRadius - aXOffset) / float(aXRadius);
1871 float scaledY = float(aYRadius - aYOffset) / float(aYRadius);
1872 return scaledX * scaledX + scaledY * scaledY < 1.0f;
1875 bool nsLayoutUtils::RoundedRectIntersectsRect(const nsRect& aRoundedRect,
1876 const nscoord aRadii[8],
1877 const nsRect& aTestRect) {
1878 if (!aTestRect.Intersects(aRoundedRect)) return false;
1880 // distances from this edge of aRoundedRect to opposite edge of aTestRect,
1881 // which we know are positive due to the Intersects check above.
1882 nsMargin insets;
1883 insets.top = aTestRect.YMost() - aRoundedRect.y;
1884 insets.right = aRoundedRect.XMost() - aTestRect.x;
1885 insets.bottom = aRoundedRect.YMost() - aTestRect.y;
1886 insets.left = aTestRect.XMost() - aRoundedRect.x;
1888 // Check whether the bottom-right corner of aTestRect is inside the
1889 // top left corner of aBounds when rounded by aRadii, etc. If any
1890 // corner is not, then fail; otherwise succeed.
1891 return CheckCorner(insets.left, insets.top, aRadii[eCornerTopLeftX],
1892 aRadii[eCornerTopLeftY]) &&
1893 CheckCorner(insets.right, insets.top, aRadii[eCornerTopRightX],
1894 aRadii[eCornerTopRightY]) &&
1895 CheckCorner(insets.right, insets.bottom, aRadii[eCornerBottomRightX],
1896 aRadii[eCornerBottomRightY]) &&
1897 CheckCorner(insets.left, insets.bottom, aRadii[eCornerBottomLeftX],
1898 aRadii[eCornerBottomLeftY]);
1901 nsRect nsLayoutUtils::MatrixTransformRect(const nsRect& aBounds,
1902 const Matrix4x4& aMatrix,
1903 float aFactor) {
1904 RectDouble image =
1905 RectDouble(NSAppUnitsToDoublePixels(aBounds.x, aFactor),
1906 NSAppUnitsToDoublePixels(aBounds.y, aFactor),
1907 NSAppUnitsToDoublePixels(aBounds.width, aFactor),
1908 NSAppUnitsToDoublePixels(aBounds.height, aFactor));
1910 RectDouble maxBounds = RectDouble(
1911 double(nscoord_MIN) / aFactor * 0.5, double(nscoord_MIN) / aFactor * 0.5,
1912 double(nscoord_MAX) / aFactor, double(nscoord_MAX) / aFactor);
1914 image = aMatrix.TransformAndClipBounds(image, maxBounds);
1916 return RoundGfxRectToAppRect(ThebesRect(image), aFactor);
1919 nsRect nsLayoutUtils::MatrixTransformRect(const nsRect& aBounds,
1920 const Matrix4x4Flagged& aMatrix,
1921 float aFactor) {
1922 RectDouble image =
1923 RectDouble(NSAppUnitsToDoublePixels(aBounds.x, aFactor),
1924 NSAppUnitsToDoublePixels(aBounds.y, aFactor),
1925 NSAppUnitsToDoublePixels(aBounds.width, aFactor),
1926 NSAppUnitsToDoublePixels(aBounds.height, aFactor));
1928 RectDouble maxBounds = RectDouble(
1929 double(nscoord_MIN) / aFactor * 0.5, double(nscoord_MIN) / aFactor * 0.5,
1930 double(nscoord_MAX) / aFactor, double(nscoord_MAX) / aFactor);
1932 image = aMatrix.TransformAndClipBounds(image, maxBounds);
1934 return RoundGfxRectToAppRect(ThebesRect(image), aFactor);
1937 nsPoint nsLayoutUtils::MatrixTransformPoint(const nsPoint& aPoint,
1938 const Matrix4x4& aMatrix,
1939 float aFactor) {
1940 gfxPoint image = gfxPoint(NSAppUnitsToFloatPixels(aPoint.x, aFactor),
1941 NSAppUnitsToFloatPixels(aPoint.y, aFactor));
1942 image = aMatrix.TransformPoint(image);
1943 return nsPoint(NSFloatPixelsToAppUnits(float(image.x), aFactor),
1944 NSFloatPixelsToAppUnits(float(image.y), aFactor));
1947 void nsLayoutUtils::PostTranslate(Matrix4x4& aTransform, const nsPoint& aOrigin,
1948 float aAppUnitsPerPixel, bool aRounded) {
1949 Point3D gfxOrigin =
1950 Point3D(NSAppUnitsToFloatPixels(aOrigin.x, aAppUnitsPerPixel),
1951 NSAppUnitsToFloatPixels(aOrigin.y, aAppUnitsPerPixel), 0.0f);
1952 if (aRounded) {
1953 gfxOrigin.x = NS_round(gfxOrigin.x);
1954 gfxOrigin.y = NS_round(gfxOrigin.y);
1956 aTransform.PostTranslate(gfxOrigin);
1959 bool nsLayoutUtils::ShouldSnapToGrid(const nsIFrame* aFrame) {
1960 return !aFrame || !aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
1961 aFrame->IsSVGOuterSVGAnonChildFrame();
1964 Matrix4x4Flagged nsLayoutUtils::GetTransformToAncestor(
1965 RelativeTo aFrame, RelativeTo aAncestor, uint32_t aFlags,
1966 nsIFrame** aOutAncestor) {
1967 nsIFrame* parent;
1968 Matrix4x4Flagged ctm;
1969 // Make sure we don't get an invalid combination of source and destination
1970 // RelativeTo values.
1971 MOZ_ASSERT(!(aFrame.mViewportType == ViewportType::Visual &&
1972 aAncestor.mViewportType == ViewportType::Layout));
1973 if (aFrame == aAncestor) {
1974 return ctm;
1976 ctm = aFrame.mFrame->GetTransformMatrix(aFrame.mViewportType, aAncestor,
1977 &parent, aFlags);
1978 if (!aFrame.mFrame->Combines3DTransformWithAncestors()) {
1979 ctm.ProjectTo2D();
1981 while (parent && parent != aAncestor.mFrame &&
1982 (!(aFlags & nsIFrame::STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT) ||
1983 (!parent->IsStackingContext() &&
1984 !DisplayPortUtils::FrameHasDisplayPort(parent)))) {
1985 nsIFrame* cur = parent;
1986 ctm = ctm * cur->GetTransformMatrix(aFrame.mViewportType, aAncestor,
1987 &parent, aFlags);
1988 if (!cur->Combines3DTransformWithAncestors()) {
1989 ctm.ProjectTo2D();
1992 if (aOutAncestor) {
1993 *aOutAncestor = parent;
1995 return ctm;
1998 MatrixScales nsLayoutUtils::GetTransformToAncestorScale(
1999 const nsIFrame* aFrame) {
2000 Matrix4x4Flagged transform = GetTransformToAncestor(
2001 RelativeTo{aFrame},
2002 RelativeTo{nsLayoutUtils::GetDisplayRootFrame(aFrame)});
2003 Matrix transform2D;
2004 if (transform.CanDraw2D(&transform2D)) {
2005 return ThebesMatrix(transform2D).ScaleFactors().ConvertTo<float>();
2007 return MatrixScales();
2010 static Matrix4x4Flagged GetTransformToAncestorExcludingAnimated(
2011 nsIFrame* aFrame, const nsIFrame* aAncestor) {
2012 nsIFrame* parent;
2013 Matrix4x4Flagged ctm;
2014 if (aFrame == aAncestor) {
2015 return ctm;
2017 if (ActiveLayerTracker::IsScaleSubjectToAnimation(aFrame)) {
2018 return ctm;
2020 ctm = aFrame->GetTransformMatrix(ViewportType::Layout, RelativeTo{aAncestor},
2021 &parent);
2022 while (parent && parent != aAncestor) {
2023 if (ActiveLayerTracker::IsScaleSubjectToAnimation(parent)) {
2024 return Matrix4x4Flagged();
2026 if (!parent->Extend3DContext()) {
2027 ctm.ProjectTo2D();
2029 ctm = ctm * parent->GetTransformMatrix(ViewportType::Layout,
2030 RelativeTo{aAncestor}, &parent);
2032 return ctm;
2035 MatrixScales nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(
2036 nsIFrame* aFrame) {
2037 Matrix4x4Flagged transform = GetTransformToAncestorExcludingAnimated(
2038 aFrame, nsLayoutUtils::GetDisplayRootFrame(aFrame));
2039 Matrix transform2D;
2040 if (transform.Is2D(&transform2D)) {
2041 return ThebesMatrix(transform2D).ScaleFactors().ConvertTo<float>();
2043 return MatrixScales();
2046 const nsIFrame* nsLayoutUtils::FindNearestCommonAncestorFrame(
2047 const nsIFrame* aFrame1, const nsIFrame* aFrame2) {
2048 AutoTArray<const nsIFrame*, 100> ancestors1;
2049 AutoTArray<const nsIFrame*, 100> ancestors2;
2050 const nsIFrame* commonAncestor = nullptr;
2051 if (aFrame1->PresContext() == aFrame2->PresContext()) {
2052 commonAncestor = aFrame1->PresShell()->GetRootFrame();
2054 for (const nsIFrame* f = aFrame1; f != commonAncestor;
2055 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
2056 ancestors1.AppendElement(f);
2058 for (const nsIFrame* f = aFrame2; f != commonAncestor;
2059 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
2060 ancestors2.AppendElement(f);
2062 uint32_t minLengths = std::min(ancestors1.Length(), ancestors2.Length());
2063 for (uint32_t i = 1; i <= minLengths; ++i) {
2064 if (ancestors1[ancestors1.Length() - i] ==
2065 ancestors2[ancestors2.Length() - i]) {
2066 commonAncestor = ancestors1[ancestors1.Length() - i];
2067 } else {
2068 break;
2071 return commonAncestor;
2074 const nsIFrame* nsLayoutUtils::FindNearestCommonAncestorFrameWithinBlock(
2075 const nsTextFrame* aFrame1, const nsTextFrame* aFrame2) {
2076 MOZ_ASSERT(aFrame1);
2077 MOZ_ASSERT(aFrame2);
2079 const nsIFrame* f1 = aFrame1;
2080 const nsIFrame* f2 = aFrame2;
2082 int n1 = 1;
2083 int n2 = 1;
2085 for (auto f = f1->GetParent();;) {
2086 NS_ASSERTION(f, "All text frames should have a block ancestor");
2087 if (!f) {
2088 return nullptr;
2090 if (f->IsBlockFrameOrSubclass()) {
2091 break;
2093 ++n1;
2094 f = f->GetParent();
2097 for (auto f = f2->GetParent();;) {
2098 NS_ASSERTION(f, "All text frames should have a block ancestor");
2099 if (!f) {
2100 return nullptr;
2102 if (f->IsBlockFrameOrSubclass()) {
2103 break;
2105 ++n2;
2106 f = f->GetParent();
2109 if (n1 > n2) {
2110 std::swap(n1, n2);
2111 std::swap(f1, f2);
2114 while (n2 > n1) {
2115 f2 = f2->GetParent();
2116 --n2;
2119 while (n2 >= 0) {
2120 if (f1 == f2) {
2121 return f1;
2123 f1 = f1->GetParent();
2124 f2 = f2->GetParent();
2125 --n2;
2128 return nullptr;
2131 bool nsLayoutUtils::AuthorSpecifiedBorderBackgroundDisablesTheming(
2132 StyleAppearance aAppearance) {
2133 return aAppearance == StyleAppearance::NumberInput ||
2134 aAppearance == StyleAppearance::Button ||
2135 aAppearance == StyleAppearance::Textfield ||
2136 aAppearance == StyleAppearance::Textarea ||
2137 aAppearance == StyleAppearance::Listbox ||
2138 aAppearance == StyleAppearance::Menulist ||
2139 aAppearance == StyleAppearance::MenulistButton;
2142 static SVGTextFrame* GetContainingSVGTextFrame(const nsIFrame* aFrame) {
2143 if (!aFrame->IsInSVGTextSubtree()) {
2144 return nullptr;
2147 return static_cast<SVGTextFrame*>(nsLayoutUtils::GetClosestFrameOfType(
2148 aFrame->GetParent(), LayoutFrameType::SVGText));
2151 static bool TransformGfxPointFromAncestor(RelativeTo aFrame,
2152 const Point& aPoint,
2153 RelativeTo aAncestor,
2154 Maybe<Matrix4x4Flagged>& aMatrixCache,
2155 Point* aOut) {
2156 SVGTextFrame* text = GetContainingSVGTextFrame(aFrame.mFrame);
2158 if (!aMatrixCache) {
2159 auto matrix = nsLayoutUtils::GetTransformToAncestor(
2160 RelativeTo{text ? text : aFrame.mFrame, aFrame.mViewportType},
2161 aAncestor);
2162 if (matrix.IsSingular()) {
2163 return false;
2165 matrix.Invert();
2166 aMatrixCache.emplace(matrix);
2169 const Matrix4x4Flagged& ctm = *aMatrixCache;
2170 Point4D point = ctm.ProjectPoint(aPoint);
2171 if (!point.HasPositiveWCoord()) {
2172 return false;
2175 *aOut = point.As2DPoint();
2177 if (text) {
2178 *aOut = text->TransformFramePointToTextChild(*aOut, aFrame.mFrame);
2181 return true;
2184 static Point TransformGfxPointToAncestor(
2185 RelativeTo aFrame, const Point& aPoint, RelativeTo aAncestor,
2186 Maybe<Matrix4x4Flagged>& aMatrixCache) {
2187 if (SVGTextFrame* text = GetContainingSVGTextFrame(aFrame.mFrame)) {
2188 Point result =
2189 text->TransformFramePointFromTextChild(aPoint, aFrame.mFrame);
2190 return TransformGfxPointToAncestor(RelativeTo{text}, result, aAncestor,
2191 aMatrixCache);
2193 if (!aMatrixCache) {
2194 aMatrixCache.emplace(
2195 nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor));
2197 return aMatrixCache->ProjectPoint(aPoint).As2DPoint();
2200 static Rect TransformGfxRectToAncestor(
2201 RelativeTo aFrame, const Rect& aRect, RelativeTo aAncestor,
2202 bool* aPreservesAxisAlignedRectangles = nullptr,
2203 Maybe<Matrix4x4Flagged>* aMatrixCache = nullptr,
2204 bool aStopAtStackingContextAndDisplayPortAndOOFFrame = false,
2205 nsIFrame** aOutAncestor = nullptr) {
2206 Rect result;
2207 Matrix4x4Flagged ctm;
2208 if (SVGTextFrame* text = GetContainingSVGTextFrame(aFrame.mFrame)) {
2209 result = text->TransformFrameRectFromTextChild(aRect, aFrame.mFrame);
2211 result = TransformGfxRectToAncestor(
2212 RelativeTo{text}, result, aAncestor, nullptr, aMatrixCache,
2213 aStopAtStackingContextAndDisplayPortAndOOFFrame, aOutAncestor);
2214 if (aPreservesAxisAlignedRectangles) {
2215 // TransformFrameRectFromTextChild could involve any kind of transform, we
2216 // could drill down into it to get an answer out of it but we don't yet.
2217 *aPreservesAxisAlignedRectangles = false;
2219 return result;
2221 if (aMatrixCache && *aMatrixCache) {
2222 // We are given a matrix to use, so use it
2223 ctm = aMatrixCache->value();
2224 } else {
2225 // Else, compute it
2226 uint32_t flags = 0;
2227 if (aStopAtStackingContextAndDisplayPortAndOOFFrame) {
2228 flags |= nsIFrame::STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT;
2230 ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor, flags,
2231 aOutAncestor);
2232 if (aMatrixCache) {
2233 // and put it in the cache, if provided
2234 *aMatrixCache = Some(ctm);
2237 // Fill out the axis-alignment flag
2238 if (aPreservesAxisAlignedRectangles) {
2239 // TransformFrameRectFromTextChild could involve any kind of transform, we
2240 // could drill down into it to get an answer out of it but we don't yet.
2241 Matrix matrix2d;
2242 *aPreservesAxisAlignedRectangles =
2243 ctm.Is2D(&matrix2d) && matrix2d.PreservesAxisAlignedRectangles();
2245 const nsIFrame* ancestor = aOutAncestor ? *aOutAncestor : aAncestor.mFrame;
2246 float factor = ancestor->PresContext()->AppUnitsPerDevPixel();
2247 Rect maxBounds =
2248 Rect(float(nscoord_MIN) / factor * 0.5, float(nscoord_MIN) / factor * 0.5,
2249 float(nscoord_MAX) / factor, float(nscoord_MAX) / factor);
2250 return ctm.TransformAndClipBounds(aRect, maxBounds);
2253 nsLayoutUtils::TransformResult nsLayoutUtils::TransformPoints(
2254 RelativeTo aFromFrame, RelativeTo aToFrame, uint32_t aPointCount,
2255 CSSPoint* aPoints) {
2256 // Conceptually, {ViewportFrame, Visual} is an ancestor of
2257 // {ViewportFrame, Layout}, so factor that into the nearest ancestor
2258 // computation.
2259 RelativeTo nearestCommonAncestor{
2260 FindNearestCommonAncestorFrame(aFromFrame.mFrame, aToFrame.mFrame),
2261 aFromFrame.mViewportType == ViewportType::Visual ||
2262 aToFrame.mViewportType == ViewportType::Visual
2263 ? ViewportType::Visual
2264 : ViewportType::Layout};
2265 if (!nearestCommonAncestor.mFrame) {
2266 return NO_COMMON_ANCESTOR;
2268 CSSToLayoutDeviceScale devPixelsPerCSSPixelFromFrame =
2269 aFromFrame.mFrame->PresContext()->CSSToDevPixelScale();
2270 CSSToLayoutDeviceScale devPixelsPerCSSPixelToFrame =
2271 aToFrame.mFrame->PresContext()->CSSToDevPixelScale();
2272 Maybe<Matrix4x4Flagged> cacheTo;
2273 Maybe<Matrix4x4Flagged> cacheFrom;
2274 for (uint32_t i = 0; i < aPointCount; ++i) {
2275 LayoutDevicePoint devPixels = aPoints[i] * devPixelsPerCSSPixelFromFrame;
2276 // What should the behaviour be if some of the points aren't invertible
2277 // and others are? Just assume all points are for now.
2278 Point toDevPixels =
2279 TransformGfxPointToAncestor(aFromFrame, Point(devPixels.x, devPixels.y),
2280 nearestCommonAncestor, cacheTo);
2281 Point result;
2282 if (!TransformGfxPointFromAncestor(
2283 aToFrame, toDevPixels, nearestCommonAncestor, cacheFrom, &result)) {
2284 return NONINVERTIBLE_TRANSFORM;
2286 // Divide here so that when the devPixelsPerCSSPixels are the same, we get
2287 // the correct answer instead of some inaccuracy multiplying a number by its
2288 // reciprocal.
2289 aPoints[i] =
2290 LayoutDevicePoint(result.x, result.y) / devPixelsPerCSSPixelToFrame;
2292 return TRANSFORM_SUCCEEDED;
2295 nsLayoutUtils::TransformResult nsLayoutUtils::TransformPoint(
2296 RelativeTo aFromFrame, RelativeTo aToFrame, nsPoint& aPoint) {
2297 CSSPoint point = CSSPoint::FromAppUnits(aPoint);
2298 auto result = TransformPoints(aFromFrame, aToFrame, 1, &point);
2299 if (result == TRANSFORM_SUCCEEDED) {
2300 aPoint = CSSPoint::ToAppUnits(point);
2302 return result;
2305 nsLayoutUtils::TransformResult nsLayoutUtils::TransformRect(
2306 const nsIFrame* aFromFrame, const nsIFrame* aToFrame, nsRect& aRect) {
2307 const nsIFrame* nearestCommonAncestor =
2308 FindNearestCommonAncestorFrame(aFromFrame, aToFrame);
2309 if (!nearestCommonAncestor) {
2310 return NO_COMMON_ANCESTOR;
2312 Matrix4x4Flagged downToDest = GetTransformToAncestor(
2313 RelativeTo{aToFrame}, RelativeTo{nearestCommonAncestor});
2314 if (downToDest.IsSingular()) {
2315 return NONINVERTIBLE_TRANSFORM;
2317 downToDest.Invert();
2318 aRect = TransformFrameRectToAncestor(aFromFrame, aRect,
2319 RelativeTo{nearestCommonAncestor});
2321 float devPixelsPerAppUnitFromFrame =
2322 1.0f / nearestCommonAncestor->PresContext()->AppUnitsPerDevPixel();
2323 float devPixelsPerAppUnitToFrame =
2324 1.0f / aToFrame->PresContext()->AppUnitsPerDevPixel();
2325 gfx::Rect toDevPixels = downToDest.ProjectRectBounds(
2326 gfx::Rect(aRect.x * devPixelsPerAppUnitFromFrame,
2327 aRect.y * devPixelsPerAppUnitFromFrame,
2328 aRect.width * devPixelsPerAppUnitFromFrame,
2329 aRect.height * devPixelsPerAppUnitFromFrame),
2330 Rect(-std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame *
2331 0.5f,
2332 -std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame *
2333 0.5f,
2334 std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame,
2335 std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame));
2336 aRect.x = NSToCoordRoundWithClamp(toDevPixels.x / devPixelsPerAppUnitToFrame);
2337 aRect.y = NSToCoordRoundWithClamp(toDevPixels.y / devPixelsPerAppUnitToFrame);
2338 aRect.width =
2339 NSToCoordRoundWithClamp(toDevPixels.width / devPixelsPerAppUnitToFrame);
2340 aRect.height =
2341 NSToCoordRoundWithClamp(toDevPixels.height / devPixelsPerAppUnitToFrame);
2342 return TRANSFORM_SUCCEEDED;
2345 nsRect nsLayoutUtils::GetRectRelativeToFrame(Element* aElement,
2346 nsIFrame* aFrame) {
2347 if (!aElement || !aFrame) {
2348 return nsRect();
2351 nsIFrame* frame = aElement->GetPrimaryFrame();
2352 if (!frame) {
2353 return nsRect();
2356 nsRect rect = frame->GetRectRelativeToSelf();
2357 nsLayoutUtils::TransformResult rv =
2358 nsLayoutUtils::TransformRect(frame, aFrame, rect);
2359 if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) {
2360 return nsRect();
2363 return rect;
2366 bool nsLayoutUtils::ContainsPoint(const nsRect& aRect, const nsPoint& aPoint,
2367 nscoord aInflateSize) {
2368 nsRect rect = aRect;
2369 rect.Inflate(aInflateSize);
2370 return rect.Contains(aPoint);
2373 nsRect nsLayoutUtils::ClampRectToScrollFrames(nsIFrame* aFrame,
2374 const nsRect& aRect) {
2375 nsIFrame* closestScrollFrame =
2376 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::Scroll);
2378 nsRect resultRect = aRect;
2380 while (closestScrollFrame) {
2381 nsIScrollableFrame* sf = do_QueryFrame(closestScrollFrame);
2383 nsRect scrollPortRect = sf->GetScrollPortRect();
2384 nsLayoutUtils::TransformRect(closestScrollFrame, aFrame, scrollPortRect);
2386 resultRect = resultRect.Intersect(scrollPortRect);
2388 // Check whether aRect is visible in the scroll frame or not.
2389 if (resultRect.IsEmpty()) {
2390 break;
2393 // Get next ancestor scroll frame.
2394 closestScrollFrame = nsLayoutUtils::GetClosestFrameOfType(
2395 closestScrollFrame->GetParent(), LayoutFrameType::Scroll);
2398 return resultRect;
2401 nsPoint nsLayoutUtils::TransformAncestorPointToFrame(RelativeTo aFrame,
2402 const nsPoint& aPoint,
2403 RelativeTo aAncestor) {
2404 float factor = aFrame.mFrame->PresContext()->AppUnitsPerDevPixel();
2405 Point result(NSAppUnitsToFloatPixels(aPoint.x, factor),
2406 NSAppUnitsToFloatPixels(aPoint.y, factor));
2408 Maybe<Matrix4x4Flagged> matrixCache;
2409 if (!TransformGfxPointFromAncestor(aFrame, result, aAncestor, matrixCache,
2410 &result)) {
2411 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
2414 return nsPoint(NSFloatPixelsToAppUnits(float(result.x), factor),
2415 NSFloatPixelsToAppUnits(float(result.y), factor));
2418 nsRect nsLayoutUtils::TransformFrameRectToAncestor(
2419 const nsIFrame* aFrame, const nsRect& aRect, RelativeTo aAncestor,
2420 bool* aPreservesAxisAlignedRectangles /* = nullptr */,
2421 Maybe<Matrix4x4Flagged>* aMatrixCache /* = nullptr */,
2422 bool aStopAtStackingContextAndDisplayPortAndOOFFrame /* = false */,
2423 nsIFrame** aOutAncestor /* = nullptr */) {
2424 MOZ_ASSERT(IsAncestorFrameCrossDocInProcess(aAncestor.mFrame, aFrame),
2425 "Fix the caller");
2426 float srcAppUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
2427 Rect result(NSAppUnitsToFloatPixels(aRect.x, srcAppUnitsPerDevPixel),
2428 NSAppUnitsToFloatPixels(aRect.y, srcAppUnitsPerDevPixel),
2429 NSAppUnitsToFloatPixels(aRect.width, srcAppUnitsPerDevPixel),
2430 NSAppUnitsToFloatPixels(aRect.height, srcAppUnitsPerDevPixel));
2431 result = TransformGfxRectToAncestor(
2432 RelativeTo{aFrame}, result, aAncestor, aPreservesAxisAlignedRectangles,
2433 aMatrixCache, aStopAtStackingContextAndDisplayPortAndOOFFrame,
2434 aOutAncestor);
2436 float destAppUnitsPerDevPixel =
2437 aAncestor.mFrame->PresContext()->AppUnitsPerDevPixel();
2438 return nsRect(
2439 NSFloatPixelsToAppUnits(float(result.x), destAppUnitsPerDevPixel),
2440 NSFloatPixelsToAppUnits(float(result.y), destAppUnitsPerDevPixel),
2441 NSFloatPixelsToAppUnits(float(result.width), destAppUnitsPerDevPixel),
2442 NSFloatPixelsToAppUnits(float(result.height), destAppUnitsPerDevPixel));
2445 static LayoutDeviceIntPoint GetWidgetOffset(nsIWidget* aWidget,
2446 nsIWidget*& aRootWidget) {
2447 LayoutDeviceIntPoint offset(0, 0);
2448 while (aWidget->GetWindowType() == widget::WindowType::Child) {
2449 nsIWidget* parent = aWidget->GetParent();
2450 if (!parent) {
2451 break;
2453 LayoutDeviceIntRect bounds = aWidget->GetBounds();
2454 offset += bounds.TopLeft();
2455 aWidget = parent;
2457 aRootWidget = aWidget;
2458 return offset;
2461 LayoutDeviceIntPoint nsLayoutUtils::WidgetToWidgetOffset(nsIWidget* aFrom,
2462 nsIWidget* aTo) {
2463 nsIWidget* fromRoot;
2464 LayoutDeviceIntPoint fromOffset = GetWidgetOffset(aFrom, fromRoot);
2465 nsIWidget* toRoot;
2466 LayoutDeviceIntPoint toOffset = GetWidgetOffset(aTo, toRoot);
2468 if (fromRoot != toRoot) {
2469 fromOffset = aFrom->WidgetToScreenOffset();
2470 toOffset = aTo->WidgetToScreenOffset();
2472 return fromOffset - toOffset;
2475 nsPoint nsLayoutUtils::TranslateWidgetToView(nsPresContext* aPresContext,
2476 nsIWidget* aWidget,
2477 const LayoutDeviceIntPoint& aPt,
2478 nsView* aView) {
2479 nsPoint viewOffset;
2480 nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset);
2481 if (!viewWidget) {
2482 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
2485 LayoutDeviceIntPoint widgetPoint =
2486 aPt + WidgetToWidgetOffset(aWidget, viewWidget);
2487 nsPoint widgetAppUnits(aPresContext->DevPixelsToAppUnits(widgetPoint.x),
2488 aPresContext->DevPixelsToAppUnits(widgetPoint.y));
2489 return widgetAppUnits - viewOffset;
2492 LayoutDeviceIntPoint nsLayoutUtils::TranslateViewToWidget(
2493 nsPresContext* aPresContext, nsView* aView, nsPoint aPt,
2494 ViewportType aViewportType, nsIWidget* aWidget) {
2495 nsPoint viewOffset;
2496 nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset);
2497 if (!viewWidget) {
2498 return LayoutDeviceIntPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
2501 nsPoint pt = (aPt + viewOffset);
2502 // The target coordinates are visual, so perform a layout-to-visual
2503 // conversion if the incoming coordinates are layout.
2504 if (aViewportType == ViewportType::Layout && aPresContext->GetPresShell()) {
2505 pt = ViewportUtils::LayoutToVisual(pt, aPresContext->GetPresShell());
2507 LayoutDeviceIntPoint relativeToViewWidget(
2508 aPresContext->AppUnitsToDevPixels(pt.x),
2509 aPresContext->AppUnitsToDevPixels(pt.y));
2510 return relativeToViewWidget + WidgetToWidgetOffset(viewWidget, aWidget);
2513 StyleClear nsLayoutUtils::CombineClearType(StyleClear aOrigClearType,
2514 StyleClear aNewClearType) {
2515 StyleClear clearType = aOrigClearType;
2516 switch (clearType) {
2517 case StyleClear::Left:
2518 if (StyleClear::Right == aNewClearType ||
2519 StyleClear::Both == aNewClearType) {
2520 clearType = StyleClear::Both;
2522 break;
2523 case StyleClear::Right:
2524 if (StyleClear::Left == aNewClearType ||
2525 StyleClear::Both == aNewClearType) {
2526 clearType = StyleClear::Both;
2528 break;
2529 case StyleClear::None:
2530 if (StyleClear::Left == aNewClearType ||
2531 StyleClear::Right == aNewClearType ||
2532 StyleClear::Both == aNewClearType) {
2533 clearType = aNewClearType;
2535 break;
2536 case StyleClear::Both:
2537 // Do nothing.
2538 break;
2540 return clearType;
2543 #ifdef MOZ_DUMP_PAINTING
2544 # include <stdio.h>
2546 static bool gDumpEventList = false;
2548 // nsLayoutUtils::PaintFrame() can call itself recursively, so rather than
2549 // maintaining a single paint count, we need a stack.
2550 StaticAutoPtr<nsTArray<int>> gPaintCountStack;
2552 struct AutoNestedPaintCount {
2553 AutoNestedPaintCount() { gPaintCountStack->AppendElement(0); }
2554 ~AutoNestedPaintCount() { gPaintCountStack->RemoveLastElement(); }
2557 #endif
2559 nsIFrame* nsLayoutUtils::GetFrameForPoint(
2560 RelativeTo aRelativeTo, nsPoint aPt, const FrameForPointOptions& aOptions) {
2561 AUTO_PROFILER_LABEL("nsLayoutUtils::GetFrameForPoint", LAYOUT);
2563 nsresult rv;
2564 AutoTArray<nsIFrame*, 8> outFrames;
2565 rv = GetFramesForArea(aRelativeTo, nsRect(aPt, nsSize(1, 1)), outFrames,
2566 aOptions);
2567 NS_ENSURE_SUCCESS(rv, nullptr);
2568 return outFrames.SafeElementAt(0);
2571 nsresult nsLayoutUtils::GetFramesForArea(RelativeTo aRelativeTo,
2572 const nsRect& aRect,
2573 nsTArray<nsIFrame*>& aOutFrames,
2574 const FrameForPointOptions& aOptions) {
2575 AUTO_PROFILER_LABEL("nsLayoutUtils::GetFramesForArea", LAYOUT);
2577 nsIFrame* frame = const_cast<nsIFrame*>(aRelativeTo.mFrame);
2579 nsDisplayListBuilder builder(frame, nsDisplayListBuilderMode::EventDelivery,
2580 false);
2581 builder.BeginFrame();
2582 nsDisplayList list(&builder);
2584 if (aOptions.mBits.contains(FrameForPointOption::IgnorePaintSuppression)) {
2585 builder.IgnorePaintSuppression();
2587 if (aOptions.mBits.contains(FrameForPointOption::IgnoreRootScrollFrame)) {
2588 nsIFrame* rootScrollFrame = frame->PresShell()->GetRootScrollFrame();
2589 if (rootScrollFrame) {
2590 builder.SetIgnoreScrollFrame(rootScrollFrame);
2593 if (aRelativeTo.mViewportType == ViewportType::Layout) {
2594 builder.SetIsRelativeToLayoutViewport();
2596 if (aOptions.mBits.contains(FrameForPointOption::IgnoreCrossDoc)) {
2597 builder.SetDescendIntoSubdocuments(false);
2600 if (aOptions.mBits.contains(FrameForPointOption::OnlyVisible)) {
2601 builder.SetHitTestIsForVisibility(aOptions.mVisibleThreshold);
2604 builder.EnterPresShell(frame);
2606 builder.SetVisibleRect(aRect);
2607 builder.SetDirtyRect(aRect);
2609 frame->BuildDisplayListForStackingContext(&builder, &list);
2610 builder.LeavePresShell(frame, nullptr);
2612 #ifdef MOZ_DUMP_PAINTING
2613 if (gDumpEventList) {
2614 fprintf_stderr(stderr, "Event handling --- (%d,%d):\n", aRect.x, aRect.y);
2616 std::stringstream ss;
2617 nsIFrame::PrintDisplayList(&builder, list, ss);
2618 print_stderr(ss);
2620 #endif
2622 nsDisplayItem::HitTestState hitTestState;
2623 list.HitTest(&builder, aRect, &hitTestState, &aOutFrames);
2624 list.DeleteAll(&builder);
2625 builder.EndFrame();
2626 return NS_OK;
2629 mozilla::ParentLayerToScreenScale2D
2630 nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
2631 const nsIFrame* aFrame) {
2632 ParentLayerToScreenScale2D transformToAncestorScale =
2633 ViewAs<ParentLayerToScreenScale2D>(
2634 nsLayoutUtils::GetTransformToAncestorScale(aFrame));
2636 if (BrowserChild* browserChild = BrowserChild::GetFrom(aFrame->PresShell())) {
2637 transformToAncestorScale =
2638 ViewTargetAs<ParentLayerPixel>(
2639 transformToAncestorScale,
2640 PixelCastJustification::PropagatingToChildProcess) *
2641 browserChild->GetEffectsInfo().mTransformToAncestorScale;
2644 return transformToAncestorScale;
2647 // aScrollFrameAsScrollable must be non-nullptr and queryable to an nsIFrame
2648 FrameMetrics nsLayoutUtils::CalculateBasicFrameMetrics(
2649 nsIScrollableFrame* aScrollFrame) {
2650 nsIFrame* frame = do_QueryFrame(aScrollFrame);
2651 MOZ_ASSERT(frame);
2653 // Calculate the metrics necessary for calculating the displayport.
2654 // This code has a lot in common with the code in ComputeFrameMetrics();
2655 // we may want to refactor this at some point.
2656 FrameMetrics metrics;
2657 nsPresContext* presContext = frame->PresContext();
2658 PresShell* presShell = presContext->PresShell();
2659 CSSToLayoutDeviceScale deviceScale = presContext->CSSToDevPixelScale();
2660 float resolution = 1.0f;
2661 bool isRcdRsf = aScrollFrame->IsRootScrollFrameOfDocument() &&
2662 presContext->IsRootContentDocumentCrossProcess();
2663 metrics.SetIsRootContent(isRcdRsf);
2664 if (isRcdRsf) {
2665 // Only the root content document's root scrollable frame should pick up
2666 // the presShell's resolution. All the other frames are 1.0.
2667 resolution = presShell->GetResolution();
2669 LayoutDeviceToLayerScale cumulativeResolution(
2670 LayoutDeviceToLayerScale(presShell->GetCumulativeResolution()));
2672 LayerToParentLayerScale layerToParentLayerScale(1.0f);
2673 metrics.SetDevPixelsPerCSSPixel(deviceScale);
2674 metrics.SetPresShellResolution(resolution);
2676 metrics.SetTransformToAncestorScale(
2677 GetTransformToAncestorScaleCrossProcessForFrameMetrics(frame));
2678 metrics.SetCumulativeResolution(cumulativeResolution);
2679 metrics.SetZoom(deviceScale * cumulativeResolution * layerToParentLayerScale);
2681 // Only the size of the composition bounds is relevant to the
2682 // displayport calculation, not its origin.
2683 nsSize compositionSize =
2684 nsLayoutUtils::CalculateCompositionSizeForFrame(frame);
2685 LayoutDeviceToParentLayerScale compBoundsScale;
2686 if (frame == presShell->GetRootScrollFrame() &&
2687 presContext->IsRootContentDocumentCrossProcess()) {
2688 if (presContext->GetParentPresContext()) {
2689 float res = presContext->GetParentPresContext()
2690 ->PresShell()
2691 ->GetCumulativeResolution();
2692 compBoundsScale = LayoutDeviceToParentLayerScale(res);
2694 } else {
2695 compBoundsScale = cumulativeResolution * layerToParentLayerScale;
2697 metrics.SetCompositionBounds(
2698 LayoutDeviceRect::FromAppUnits(nsRect(nsPoint(0, 0), compositionSize),
2699 presContext->AppUnitsPerDevPixel()) *
2700 compBoundsScale);
2702 metrics.SetBoundingCompositionSize(
2703 nsLayoutUtils::CalculateBoundingCompositionSize(frame, false, metrics));
2705 metrics.SetLayoutViewport(
2706 CSSRect::FromAppUnits(nsRect(aScrollFrame->GetScrollPosition(),
2707 aScrollFrame->GetScrollPortRect().Size())));
2708 metrics.SetVisualScrollOffset(
2709 isRcdRsf ? CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset())
2710 : metrics.GetLayoutViewport().TopLeft());
2712 metrics.SetScrollableRect(CSSRect::FromAppUnits(
2713 nsLayoutUtils::CalculateScrollableRectForFrame(aScrollFrame, nullptr)));
2715 return metrics;
2718 nsIScrollableFrame* nsLayoutUtils::GetAsyncScrollableAncestorFrame(
2719 nsIFrame* aTarget) {
2720 uint32_t flags = nsLayoutUtils::SCROLLABLE_ALWAYS_MATCH_ROOT |
2721 nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE |
2722 nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT;
2723 return nsLayoutUtils::GetNearestScrollableFrame(aTarget, flags);
2726 void nsLayoutUtils::AddExtraBackgroundItems(nsDisplayListBuilder* aBuilder,
2727 nsDisplayList* aList,
2728 nsIFrame* aFrame,
2729 const nsRect& aCanvasArea,
2730 const nsRegion& aVisibleRegion,
2731 nscolor aBackstop) {
2732 if (aFrame->IsPageFrame()) {
2733 // For printing, this function is first called on an nsPageFrame, which
2734 // creates a display list with a PageContent item. The PageContent item's
2735 // paint function calls this function on the nsPageFrame's child which is an
2736 // nsPageContentFrame. We only want to add the canvas background color item
2737 // once, for the nsPageContentFrame.
2738 return;
2740 // Add the canvas background color to the bottom of the list. This
2741 // happens after we've built the list so that AddCanvasBackgroundColorItem
2742 // can monkey with the contents if necessary.
2743 nsRect canvasArea = aVisibleRegion.GetBounds();
2744 canvasArea.IntersectRect(aCanvasArea, canvasArea);
2745 nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
2746 aBuilder, aFrame, canvasArea, canvasArea);
2747 aFrame->PresShell()->AddCanvasBackgroundColorItem(aBuilder, aList, aFrame,
2748 canvasArea, aBackstop);
2751 // #define PRINT_HITTESTINFO_STATS
2752 #ifdef PRINT_HITTESTINFO_STATS
2753 void PrintHitTestInfoStatsInternal(nsDisplayList* aList, int& aTotal,
2754 int& aHitTest, int& aVisible,
2755 int& aSpecial) {
2756 for (nsDisplayItem* i : *aList) {
2757 aTotal++;
2759 if (i->GetChildren()) {
2760 PrintHitTestInfoStatsInternal(i->GetChildren(), aTotal, aHitTest,
2761 aVisible, aSpecial);
2764 if (i->GetType() == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
2765 aHitTest++;
2767 const auto& hitTestInfo = static_cast<nsDisplayCompositorHitTestInfo*>(i)
2768 ->GetHitTestInfo()
2769 .Info();
2771 if (hitTestInfo.size() > 1) {
2772 aSpecial++;
2773 continue;
2776 if (hitTestInfo == CompositorHitTestFlags::eVisibleToHitTest) {
2777 aVisible++;
2778 continue;
2781 aSpecial++;
2786 void PrintHitTestInfoStats(nsDisplayList* aList) {
2787 int total = 0;
2788 int hitTest = 0;
2789 int visible = 0;
2790 int special = 0;
2792 PrintHitTestInfoStatsInternal(aList, total, hitTest, visible, special);
2794 double ratio = (double)hitTest / (double)total;
2796 printf(
2797 "List %p: total items: %d, hit test items: %d, ratio: %f, visible: %d, "
2798 "special: %d\n",
2799 aList, total, hitTest, ratio, visible, special);
2801 #endif
2803 // Apply a batch of effects updates generated during a paint to their
2804 // respective remote browsers.
2805 static void ApplyEffectsUpdates(
2806 const nsTHashMap<nsPtrHashKey<RemoteBrowser>, EffectsInfo>& aUpdates) {
2807 for (const auto& entry : aUpdates) {
2808 auto* browser = entry.GetKey();
2809 const auto& update = entry.GetData();
2810 browser->UpdateEffects(update);
2814 static void DumpBeforePaintDisplayList(UniquePtr<std::stringstream>& aStream,
2815 nsDisplayListBuilder* aBuilder,
2816 nsDisplayList* aList,
2817 const nsRect& aVisibleRect) {
2818 #ifdef MOZ_DUMP_PAINTING
2819 if (gfxEnv::MOZ_DUMP_PAINT_TO_FILE()) {
2820 nsCString string("dump-");
2821 // Include the process ID in the dump file name, to make sure that in an
2822 // e10s setup different processes don't clobber each other's dump files.
2823 string.AppendInt(getpid());
2824 for (int paintCount : *gPaintCountStack) {
2825 string.AppendLiteral("-");
2826 string.AppendInt(paintCount);
2828 string.AppendLiteral(".html");
2829 gfxUtils::sDumpPaintFile = fopen(string.BeginReading(), "w");
2830 } else {
2831 gfxUtils::sDumpPaintFile = stderr;
2833 if (gfxEnv::MOZ_DUMP_PAINT_TO_FILE()) {
2834 *aStream << "<html><head><script>\n"
2835 "var array = {};\n"
2836 "function ViewImage(index) { \n"
2837 " var image = document.getElementById(index);\n"
2838 " if (image.src) {\n"
2839 " image.removeAttribute('src');\n"
2840 " } else {\n"
2841 " image.src = array[index];\n"
2842 " }\n"
2843 "}</script></head><body>";
2845 #endif
2846 *aStream << nsPrintfCString(
2847 "Painting --- before optimization (dirty %d,%d,%d,%d):\n",
2848 aVisibleRect.x, aVisibleRect.y, aVisibleRect.width,
2849 aVisibleRect.height)
2850 .get();
2851 nsIFrame::PrintDisplayList(aBuilder, *aList, *aStream,
2852 gfxEnv::MOZ_DUMP_PAINT_TO_FILE());
2854 if (gfxEnv::MOZ_DUMP_PAINT() || gfxEnv::MOZ_DUMP_PAINT_ITEMS()) {
2855 // Flush stream now to avoid reordering dump output relative to
2856 // messages dumped by PaintRoot below.
2857 fprint_stderr(gfxUtils::sDumpPaintFile, *aStream);
2858 aStream = MakeUnique<std::stringstream>();
2862 static void DumpAfterPaintDisplayList(UniquePtr<std::stringstream>& aStream,
2863 nsDisplayListBuilder* aBuilder,
2864 nsDisplayList* aList) {
2865 *aStream << "Painting --- after optimization:\n";
2866 nsIFrame::PrintDisplayList(aBuilder, *aList, *aStream,
2867 gfxEnv::MOZ_DUMP_PAINT_TO_FILE());
2869 fprint_stderr(gfxUtils::sDumpPaintFile, *aStream);
2871 #ifdef MOZ_DUMP_PAINTING
2872 if (gfxEnv::MOZ_DUMP_PAINT_TO_FILE()) {
2873 *aStream << "</body></html>";
2875 if (gfxEnv::MOZ_DUMP_PAINT_TO_FILE()) {
2876 fclose(gfxUtils::sDumpPaintFile);
2878 #endif
2880 std::stringstream lsStream;
2881 nsIFrame::PrintDisplayList(aBuilder, *aList, lsStream);
2884 struct TemporaryDisplayListBuilder {
2885 TemporaryDisplayListBuilder(nsIFrame* aFrame,
2886 nsDisplayListBuilderMode aBuilderMode,
2887 const bool aBuildCaret)
2888 : mBuilder(aFrame, aBuilderMode, aBuildCaret), mList(&mBuilder) {}
2890 ~TemporaryDisplayListBuilder() { mList.DeleteAll(&mBuilder); }
2892 nsDisplayListBuilder mBuilder;
2893 nsDisplayList mList;
2894 RetainedDisplayListMetrics mMetrics;
2897 void nsLayoutUtils::PaintFrame(gfxContext* aRenderingContext, nsIFrame* aFrame,
2898 const nsRegion& aDirtyRegion, nscolor aBackstop,
2899 nsDisplayListBuilderMode aBuilderMode,
2900 PaintFrameFlags aFlags) {
2901 AUTO_PROFILER_LABEL("nsLayoutUtils::PaintFrame", GRAPHICS);
2903 // Create a static storage counter that is incremented on eacy entry to
2904 // PaintFrame and decremented on exit. We can use this later to determine if
2905 // this is a top-level paint.
2906 static uint32_t paintFrameDepth = 0;
2907 ++paintFrameDepth;
2909 #ifdef MOZ_DUMP_PAINTING
2910 if (!gPaintCountStack) {
2911 gPaintCountStack = new nsTArray<int>();
2912 ClearOnShutdown(&gPaintCountStack);
2914 gPaintCountStack->AppendElement(0);
2916 ++gPaintCountStack->LastElement();
2917 AutoNestedPaintCount nestedPaintCount;
2918 #endif
2920 nsIFrame* displayRoot = GetDisplayRootFrame(aFrame);
2922 if (aFlags & PaintFrameFlags::WidgetLayers) {
2923 nsView* view = aFrame->GetView();
2924 if (!(view && view->GetWidget() && displayRoot == aFrame)) {
2925 aFlags &= ~PaintFrameFlags::WidgetLayers;
2926 NS_ASSERTION(aRenderingContext, "need a rendering context");
2930 nsPresContext* presContext = aFrame->PresContext();
2931 PresShell* presShell = presContext->PresShell();
2933 TimeStamp startBuildDisplayList = TimeStamp::Now();
2934 auto dlTimerId = mozilla::glean::paint::build_displaylist_time.Start();
2936 const bool buildCaret = !(aFlags & PaintFrameFlags::HideCaret);
2938 // Note that isForPainting here does not include the PaintForPrinting builder
2939 // mode; that's OK because there is no point in using retained display lists
2940 // for a print destination.
2941 const bool isForPainting = (aFlags & PaintFrameFlags::WidgetLayers) &&
2942 aBuilderMode == nsDisplayListBuilderMode::Painting;
2944 // Only allow retaining for painting when preffed on, and for root frames
2945 // (since the modified frame tracking is per-root-frame).
2946 const bool retainDisplayList =
2947 isForPainting && AreRetainedDisplayListsEnabled() && !aFrame->GetParent();
2949 RetainedDisplayListBuilder* retainedBuilder = nullptr;
2950 Maybe<TemporaryDisplayListBuilder> temporaryBuilder;
2952 nsDisplayListBuilder* builder = nullptr;
2953 nsDisplayList* list = nullptr;
2954 RetainedDisplayListMetrics* metrics = nullptr;
2956 if (retainDisplayList) {
2957 MOZ_ASSERT(aFrame == displayRoot);
2958 retainedBuilder = aFrame->GetProperty(RetainedDisplayListBuilder::Cached());
2959 if (!retainedBuilder) {
2960 retainedBuilder =
2961 new RetainedDisplayListBuilder(aFrame, aBuilderMode, buildCaret);
2962 aFrame->SetProperty(RetainedDisplayListBuilder::Cached(),
2963 retainedBuilder);
2966 builder = retainedBuilder->Builder();
2967 list = retainedBuilder->List();
2968 metrics = retainedBuilder->Metrics();
2969 } else {
2970 temporaryBuilder.emplace(aFrame, aBuilderMode, buildCaret);
2971 builder = &temporaryBuilder->mBuilder;
2972 list = &temporaryBuilder->mList;
2973 metrics = &temporaryBuilder->mMetrics;
2976 MOZ_ASSERT(builder && list && metrics);
2978 nsAutoString uri;
2979 if (MOZ_LOG_TEST(GetLoggerByProcess(), LogLevel::Info) ||
2980 MOZ_UNLIKELY(gfxUtils::DumpDisplayList()) ||
2981 MOZ_UNLIKELY(gfxEnv::MOZ_DUMP_PAINT())) {
2982 if (Document* doc = presContext->Document()) {
2983 Unused << doc->GetDocumentURI(uri);
2987 nsAutoString frameName, displayRootName;
2988 #ifdef DEBUG_FRAME_DUMP
2989 if (MOZ_LOG_TEST(GetLoggerByProcess(), LogLevel::Info)) {
2990 aFrame->GetFrameName(frameName);
2991 displayRoot->GetFrameName(displayRootName);
2993 #endif
2995 DL_LOGI("PaintFrame: %p (%s), DisplayRoot: %p (%s), Builder: %p, URI: %s",
2996 aFrame, NS_ConvertUTF16toUTF8(frameName).get(), displayRoot,
2997 NS_ConvertUTF16toUTF8(displayRootName).get(), retainedBuilder,
2998 NS_ConvertUTF16toUTF8(uri).get());
3000 metrics->Reset();
3001 metrics->StartBuild();
3003 builder->BeginFrame();
3005 MOZ_ASSERT(paintFrameDepth >= 1);
3006 // If this is a top-level paint, increment the paint sequence number.
3007 if (paintFrameDepth == 1) {
3008 // Increment the paint sequence number for the display list builder.
3009 nsDisplayListBuilder::IncrementPaintSequenceNumber();
3012 if (aFlags & PaintFrameFlags::InTransform) {
3013 builder->SetInTransform(true);
3015 if (aFlags & PaintFrameFlags::SyncDecodeImages) {
3016 builder->SetSyncDecodeImages(true);
3018 if (aFlags & (PaintFrameFlags::WidgetLayers | PaintFrameFlags::ToWindow)) {
3019 builder->SetPaintingToWindow(true);
3021 if (aFlags & PaintFrameFlags::UseHighQualityScaling) {
3022 builder->SetUseHighQualityScaling(true);
3024 if (aFlags & PaintFrameFlags::ForWebRender) {
3025 builder->SetPaintingForWebRender(true);
3027 if (aFlags & PaintFrameFlags::IgnoreSuppression) {
3028 builder->IgnorePaintSuppression();
3031 if (BrowsingContext* bc = presContext->Document()->GetBrowsingContext()) {
3032 builder->SetInActiveDocShell(bc->IsActive());
3035 nsRect rootInkOverflow = aFrame->InkOverflowRectRelativeToSelf();
3037 // If the dynamic toolbar is completely collapsed, the visible rect should
3038 // be expanded to include this area.
3039 const bool hasDynamicToolbar =
3040 presContext->IsRootContentDocumentCrossProcess() &&
3041 presContext->HasDynamicToolbar();
3042 if (hasDynamicToolbar) {
3043 rootInkOverflow.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
3044 presContext, rootInkOverflow.Size()));
3047 // If we are in a remote browser, then apply clipping from ancestor browsers
3048 if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) {
3049 if (!browserChild->IsTopLevel()) {
3050 const nsRect unscaledVisibleRect =
3051 browserChild->GetVisibleRect().valueOr(nsRect());
3052 rootInkOverflow.IntersectRect(rootInkOverflow, unscaledVisibleRect);
3056 builder->ClearHaveScrollableDisplayPort();
3057 if (builder->IsPaintingToWindow() &&
3058 nsLayoutUtils::AsyncPanZoomEnabled(aFrame)) {
3059 DisplayPortUtils::MaybeCreateDisplayPortInFirstScrollFrameEncountered(
3060 aFrame, builder);
3063 nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
3064 if (rootScrollFrame && !aFrame->GetParent()) {
3065 nsIScrollableFrame* rootScrollableFrame =
3066 presShell->GetRootScrollFrameAsScrollable();
3067 MOZ_ASSERT(rootScrollableFrame);
3068 nsRect displayPortBase = rootInkOverflow;
3069 nsRect temp = displayPortBase;
3070 Unused << rootScrollableFrame->DecideScrollableLayer(
3071 builder, &displayPortBase, &temp,
3072 /* aSetBase = */ true);
3075 nsRegion visibleRegion;
3076 if (aFlags & PaintFrameFlags::WidgetLayers) {
3077 // This layer tree will be reused, so we'll need to calculate it
3078 // for the whole "visible" area of the window
3080 // |ignoreViewportScrolling| and |usingDisplayPort| are persistent
3081 // document-rendering state. We rely on PresShell to flush
3082 // retained layers as needed when that persistent state changes.
3083 visibleRegion = rootInkOverflow;
3084 } else {
3085 visibleRegion = aDirtyRegion;
3088 Maybe<nsPoint> originalScrollPosition;
3089 auto maybeResetScrollPosition = MakeScopeExit([&]() {
3090 if (originalScrollPosition && rootScrollFrame) {
3091 nsIScrollableFrame* rootScrollableFrame =
3092 presShell->GetRootScrollFrameAsScrollable();
3093 MOZ_ASSERT(rootScrollableFrame->GetScrolledFrame()->GetPosition() ==
3094 nsPoint());
3095 rootScrollableFrame->GetScrolledFrame()->SetPosition(
3096 *originalScrollPosition);
3100 nsRect canvasArea(nsPoint(0, 0),
3101 aFrame->InkOverflowRectRelativeToSelf().Size());
3102 bool ignoreViewportScrolling =
3103 !aFrame->GetParent() && presShell->IgnoringViewportScrolling();
3105 if (!aFrame->GetParent() && hasDynamicToolbar) {
3106 canvasArea.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
3107 presContext, canvasArea.Size()));
3110 if (ignoreViewportScrolling && rootScrollFrame) {
3111 nsIScrollableFrame* rootScrollableFrame =
3112 presShell->GetRootScrollFrameAsScrollable();
3113 if (aFlags & PaintFrameFlags::ResetViewportScrolling) {
3114 // Temporarily scroll the root scroll frame to 0,0 so that position:fixed
3115 // elements will appear fixed to the top-left of the document. We manually
3116 // set the position of the scrolled frame instead of using ScrollTo, since
3117 // the latter fires scroll listeners, which we don't want.
3118 originalScrollPosition.emplace(
3119 rootScrollableFrame->GetScrolledFrame()->GetPosition());
3120 rootScrollableFrame->GetScrolledFrame()->SetPosition(nsPoint());
3122 if (aFlags & PaintFrameFlags::DocumentRelative) {
3123 // Make visibleRegion and aRenderingContext relative to the
3124 // scrolled frame instead of the root frame.
3125 nsPoint pos = rootScrollableFrame->GetScrollPosition();
3126 visibleRegion.MoveBy(-pos);
3127 if (aRenderingContext) {
3128 gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
3129 pos, presContext->AppUnitsPerDevPixel());
3130 aRenderingContext->SetMatrixDouble(
3131 aRenderingContext->CurrentMatrixDouble().PreTranslate(
3132 devPixelOffset));
3135 builder->SetIgnoreScrollFrame(rootScrollFrame);
3137 nsCanvasFrame* canvasFrame =
3138 do_QueryFrame(rootScrollableFrame->GetScrolledFrame());
3139 if (canvasFrame) {
3140 // Use UnionRect here to ensure that areas where the scrollbars
3141 // were are still filled with the background color.
3142 canvasArea.UnionRect(
3143 canvasArea,
3144 canvasFrame->CanvasArea() + builder->ToReferenceFrame(canvasFrame));
3148 nsRect visibleRect = visibleRegion.GetBounds();
3149 PartialUpdateResult updateState = PartialUpdateResult::Failed;
3152 AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_DisplayListBuilding);
3153 AUTO_PROFILER_TRACING_MARKER("Paint", "DisplayList", GRAPHICS);
3154 PerfStats::AutoMetricRecording<PerfStats::Metric::DisplayListBuilding>
3155 autoRecording;
3157 ViewID id = ScrollableLayerGuid::NULL_SCROLL_ID;
3158 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(
3159 builder);
3161 if (presShell->GetDocument() &&
3162 presShell->GetDocument()->IsRootDisplayDocument() &&
3163 !presShell->GetRootScrollFrame()) {
3164 // In cases where the root document is a XUL document, we want to take
3165 // the ViewID from the root element, as that will be the ViewID of the
3166 // root APZC in the tree. Skip doing this in cases where we know
3167 // nsGfxScrollFrame::BuilDisplayList will do it instead.
3168 if (dom::Element* element =
3169 presShell->GetDocument()->GetDocumentElement()) {
3170 id = nsLayoutUtils::FindOrCreateIDFor(element);
3172 // In some cases we get a root document here on an APZ-enabled window
3173 // that doesn't have the root displayport initialized yet, even though
3174 // the ChromeProcessController is supposed to do it when the widget is
3175 // created. This can happen simply because the ChromeProcessController
3176 // does it on the next spin of the event loop, and we can trigger a
3177 // paint synchronously after window creation but before that runs. In
3178 // that case we should initialize the root displayport here before we do
3179 // the paint.
3180 } else if (XRE_IsParentProcess() && presContext->IsRoot() &&
3181 presShell->GetDocument() != nullptr &&
3182 presShell->GetRootScrollFrame() != nullptr &&
3183 nsLayoutUtils::UsesAsyncScrolling(
3184 presShell->GetRootScrollFrame())) {
3185 if (dom::Element* element =
3186 presShell->GetDocument()->GetDocumentElement()) {
3187 if (!DisplayPortUtils::HasNonMinimalDisplayPort(element)) {
3188 APZCCallbackHelper::InitializeRootDisplayport(presShell);
3193 asrSetter.SetCurrentScrollParentId(id);
3195 builder->SetVisibleRect(visibleRect);
3196 builder->SetIsBuilding(true);
3197 builder->SetAncestorHasApzAwareEventHandler(
3198 gfxPlatform::AsyncPanZoomEnabled() &&
3199 nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(presShell));
3201 // If a pref is toggled that adds or removes display list items,
3202 // we need to rebuild the display list. The pref may be toggled
3203 // manually by the user, or during test setup.
3204 if (retainDisplayList &&
3205 !builder->ShouldRebuildDisplayListDueToPrefChange()) {
3206 // Attempt to do a partial build and merge into the existing list.
3207 // This calls BuildDisplayListForStacking context on a subset of the
3208 // viewport.
3209 updateState = retainedBuilder->AttemptPartialUpdate(aBackstop);
3210 metrics->EndPartialBuild(updateState);
3211 } else {
3212 // Partial updates are disabled.
3213 DL_LOGI("Partial updates are disabled");
3214 metrics->mPartialUpdateResult = PartialUpdateResult::Failed;
3215 metrics->mPartialUpdateFailReason = PartialUpdateFailReason::Disabled;
3218 // Rebuild the full display list if the partial display list build failed.
3219 bool doFullRebuild = updateState == PartialUpdateResult::Failed;
3221 if (StaticPrefs::layout_display_list_build_twice()) {
3222 // Build display list twice to compare partial and full display list
3223 // build times.
3224 metrics->StartBuild();
3225 doFullRebuild = true;
3228 if (doFullRebuild) {
3229 if (retainDisplayList) {
3230 retainedBuilder->ClearRetainedData();
3231 #ifdef DEBUG
3232 mozilla::RDLUtils::AssertFrameSubtreeUnmodified(
3233 builder->RootReferenceFrame());
3234 #endif
3237 list->DeleteAll(builder);
3239 builder->ClearRetainedWindowRegions();
3240 builder->ClearWillChangeBudgets();
3242 builder->EnterPresShell(aFrame);
3243 builder->SetDirtyRect(visibleRect);
3245 DL_LOGI("Starting full display list build, root frame: %p",
3246 builder->RootReferenceFrame());
3248 aFrame->BuildDisplayListForStackingContext(builder, list);
3249 AddExtraBackgroundItems(builder, list, aFrame, canvasArea, visibleRegion,
3250 aBackstop);
3252 builder->LeavePresShell(aFrame, list);
3253 metrics->EndFullBuild();
3255 DL_LOGI("Finished full display list build");
3256 updateState = PartialUpdateResult::Updated;
3259 builder->SetIsBuilding(false);
3260 builder->IncrementPresShellPaintCount(presShell);
3263 MOZ_ASSERT(updateState != PartialUpdateResult::Failed);
3264 builder->Check();
3266 const double geckoDLBuildTime =
3267 (TimeStamp::Now() - startBuildDisplayList).ToMilliseconds();
3268 mozilla::glean::paint::build_displaylist_time.StopAndAccumulate(
3269 std::move(dlTimerId));
3271 bool consoleNeedsDisplayList =
3272 (gfxUtils::DumpDisplayList() || gfxEnv::MOZ_DUMP_PAINT()) &&
3273 builder->IsInActiveDocShell();
3274 #ifdef MOZ_DUMP_PAINTING
3275 FILE* savedDumpFile = gfxUtils::sDumpPaintFile;
3276 #endif
3278 UniquePtr<std::stringstream> ss;
3279 if (consoleNeedsDisplayList) {
3280 ss = MakeUnique<std::stringstream>();
3281 *ss << "Display list for " << uri << "\n";
3282 DumpBeforePaintDisplayList(ss, builder, list, visibleRect);
3285 uint32_t flags = nsDisplayList::PAINT_DEFAULT;
3286 if (aFlags & PaintFrameFlags::WidgetLayers) {
3287 flags |= nsDisplayList::PAINT_USE_WIDGET_LAYERS;
3288 if (!(aFlags & PaintFrameFlags::DocumentRelative)) {
3289 nsIWidget* widget = aFrame->GetNearestWidget();
3290 if (widget) {
3291 // If we're finished building display list items for painting of the
3292 // outermost pres shell, notify the widget about any toolbars we've
3293 // encountered.
3294 widget->UpdateThemeGeometries(builder->GetThemeGeometries());
3298 if (aFlags & PaintFrameFlags::ExistingTransaction) {
3299 flags |= nsDisplayList::PAINT_EXISTING_TRANSACTION;
3301 if (updateState == PartialUpdateResult::NoChange && !aRenderingContext) {
3302 flags |= nsDisplayList::PAINT_IDENTICAL_DISPLAY_LIST;
3305 #ifdef PRINT_HITTESTINFO_STATS
3306 if (XRE_IsContentProcess()) {
3307 PrintHitTestInfoStats(list);
3309 #endif
3311 TimeStamp paintStart = TimeStamp::Now();
3312 list->PaintRoot(builder, aRenderingContext, flags, Some(geckoDLBuildTime));
3313 Telemetry::AccumulateTimeDelta(Telemetry::PAINT_RASTERIZE_TIME, paintStart);
3315 if (builder->IsPaintingToWindow()) {
3316 presShell->EndPaint();
3318 builder->Check();
3320 if (consoleNeedsDisplayList) {
3321 DumpAfterPaintDisplayList(ss, builder, list);
3324 #ifdef MOZ_DUMP_PAINTING
3325 gfxUtils::sDumpPaintFile = savedDumpFile;
3326 #endif
3328 // Update the widget's opaque region information. This sets
3329 // glass boundaries on Windows. Also set up the window dragging region.
3330 if ((aFlags & PaintFrameFlags::WidgetLayers) &&
3331 !(aFlags & PaintFrameFlags::DocumentRelative)) {
3332 if (nsIWidget* widget = aFrame->GetNearestWidget()) {
3333 const nsRegion& opaqueRegion = builder->GetWindowOpaqueRegion();
3334 widget->UpdateOpaqueRegion(LayoutDeviceIntRegion::FromUnknownRegion(
3335 opaqueRegion.ToNearestPixels(presContext->AppUnitsPerDevPixel())));
3336 widget->UpdateWindowDraggingRegion(builder->GetWindowDraggingRegion());
3340 // Apply effects updates if we were actually painting
3341 if (isForPainting) {
3342 ApplyEffectsUpdates(builder->GetEffectUpdates());
3345 builder->Check();
3348 AUTO_PROFILER_TRACING_MARKER("Paint", "DisplayListResources", GRAPHICS);
3350 builder->EndFrame();
3352 if (temporaryBuilder) {
3353 temporaryBuilder.reset();
3357 --paintFrameDepth;
3358 #if 0
3359 if (XRE_IsParentProcess()) {
3360 if (metrics->mPartialUpdateResult == PartialUpdateResult::Failed) {
3361 printf("DL partial update failed: %s, Frame: %p\n",
3362 metrics->FailReasonString(), aFrame);
3363 } else {
3364 printf(
3365 "DL partial build success!"
3366 " new: %d, reused: %d, rebuilt: %d, removed: %d, total: %d\n",
3367 metrics->mNewItems, metrics->mReusedItems, metrics->mRebuiltItems,
3368 metrics->mRemovedItems, metrics->mTotalItems);
3371 #endif
3375 * Uses a binary search for find where the cursor falls in the line of text
3376 * It also keeps track of the part of the string that has already been measured
3377 * so it doesn't have to keep measuring the same text over and over
3379 * @param "aBaseWidth" contains the width in twips of the portion
3380 * of the text that has already been measured, and aBaseInx contains
3381 * the index of the text that has already been measured.
3383 * @param aTextWidth returns the (in twips) the length of the text that falls
3384 * before the cursor aIndex contains the index of the text where the cursor
3385 * falls
3387 bool nsLayoutUtils::BinarySearchForPosition(
3388 DrawTarget* aDrawTarget, nsFontMetrics& aFontMetrics, const char16_t* aText,
3389 int32_t aBaseWidth, int32_t aBaseInx, int32_t aStartInx, int32_t aEndInx,
3390 int32_t aCursorPos, int32_t& aIndex, int32_t& aTextWidth) {
3391 int32_t range = aEndInx - aStartInx;
3392 if ((range == 1) || (range == 2 && NS_IS_HIGH_SURROGATE(aText[aStartInx]))) {
3393 aIndex = aStartInx + aBaseInx;
3394 aTextWidth = nsLayoutUtils::AppUnitWidthOfString(aText, aIndex,
3395 aFontMetrics, aDrawTarget);
3396 return true;
3399 int32_t inx = aStartInx + (range / 2);
3401 // Make sure we don't leave a dangling low surrogate
3402 if (NS_IS_HIGH_SURROGATE(aText[inx - 1])) inx++;
3404 int32_t textWidth = nsLayoutUtils::AppUnitWidthOfString(
3405 aText, inx, aFontMetrics, aDrawTarget);
3407 int32_t fullWidth = aBaseWidth + textWidth;
3408 if (fullWidth == aCursorPos) {
3409 aTextWidth = textWidth;
3410 aIndex = inx;
3411 return true;
3412 } else if (aCursorPos < fullWidth) {
3413 aTextWidth = aBaseWidth;
3414 if (BinarySearchForPosition(aDrawTarget, aFontMetrics, aText, aBaseWidth,
3415 aBaseInx, aStartInx, inx, aCursorPos, aIndex,
3416 aTextWidth)) {
3417 return true;
3419 } else {
3420 aTextWidth = fullWidth;
3421 if (BinarySearchForPosition(aDrawTarget, aFontMetrics, aText, aBaseWidth,
3422 aBaseInx, inx, aEndInx, aCursorPos, aIndex,
3423 aTextWidth)) {
3424 return true;
3427 return false;
3430 void nsLayoutUtils::AddBoxesForFrame(nsIFrame* aFrame,
3431 nsLayoutUtils::BoxCallback* aCallback) {
3432 auto pseudoType = aFrame->Style()->GetPseudoType();
3434 if (pseudoType == PseudoStyleType::tableWrapper) {
3435 AddBoxesForFrame(aFrame->PrincipalChildList().FirstChild(), aCallback);
3436 if (aCallback->mIncludeCaptionBoxForTable) {
3437 nsIFrame* kid =
3438 aFrame->GetChildList(FrameChildListID::Caption).FirstChild();
3439 if (kid) {
3440 AddBoxesForFrame(kid, aCallback);
3443 } else if (pseudoType == PseudoStyleType::mozBlockInsideInlineWrapper ||
3444 pseudoType == PseudoStyleType::mozMathMLAnonymousBlock) {
3445 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
3446 AddBoxesForFrame(kid, aCallback);
3448 } else {
3449 aCallback->AddBox(aFrame);
3453 void nsLayoutUtils::GetAllInFlowBoxes(nsIFrame* aFrame,
3454 BoxCallback* aCallback) {
3455 aCallback->mInTargetContinuation = false;
3456 while (aFrame) {
3457 AddBoxesForFrame(aFrame, aCallback);
3458 aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
3459 aCallback->mInTargetContinuation = true;
3463 nsIFrame* nsLayoutUtils::GetFirstNonAnonymousFrame(nsIFrame* aFrame) {
3464 while (aFrame) {
3465 auto pseudoType = aFrame->Style()->GetPseudoType();
3467 if (pseudoType == PseudoStyleType::tableWrapper) {
3468 nsIFrame* f =
3469 GetFirstNonAnonymousFrame(aFrame->PrincipalChildList().FirstChild());
3470 if (f) {
3471 return f;
3473 nsIFrame* kid =
3474 aFrame->GetChildList(FrameChildListID::Caption).FirstChild();
3475 if (kid) {
3476 f = GetFirstNonAnonymousFrame(kid);
3477 if (f) {
3478 return f;
3481 } else if (pseudoType == PseudoStyleType::mozBlockInsideInlineWrapper ||
3482 pseudoType == PseudoStyleType::mozMathMLAnonymousBlock) {
3483 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
3484 nsIFrame* f = GetFirstNonAnonymousFrame(kid);
3485 if (f) {
3486 return f;
3489 } else {
3490 return aFrame;
3493 aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
3495 return nullptr;
3498 struct BoxToRect : public nsLayoutUtils::BoxCallback {
3499 const nsIFrame* mRelativeTo;
3500 RectCallback* mCallback;
3501 uint32_t mFlags;
3502 // If the frame we're measuring relative to is the root, we know all frames
3503 // are descendants of it, so we don't need to compute the common ancestor
3504 // between a frame and mRelativeTo.
3505 bool mRelativeToIsRoot;
3506 // For the same reason, if the frame we're measuring relative to is the target
3507 // (this is useful for IntersectionObserver), we know all frames are
3508 // descendants of it except if we're in a continuation or ib-split-sibling of
3509 // it.
3510 bool mRelativeToIsTarget;
3512 BoxToRect(const nsIFrame* aTargetFrame, const nsIFrame* aRelativeTo,
3513 RectCallback* aCallback, uint32_t aFlags)
3514 : mRelativeTo(aRelativeTo),
3515 mCallback(aCallback),
3516 mFlags(aFlags),
3517 mRelativeToIsRoot(!aRelativeTo->GetParent()),
3518 mRelativeToIsTarget(aRelativeTo == aTargetFrame) {}
3520 void AddBox(nsIFrame* aFrame) override {
3521 nsRect r;
3522 nsIFrame* outer = SVGUtils::GetOuterSVGFrameAndCoveredRegion(aFrame, &r);
3523 const bool usingSVGOuterFrame = !!outer;
3524 if (!outer) {
3525 outer = aFrame;
3526 switch (mFlags & nsLayoutUtils::RECTS_WHICH_BOX_MASK) {
3527 case nsLayoutUtils::RECTS_USE_CONTENT_BOX:
3528 r = aFrame->GetContentRectRelativeToSelf();
3529 break;
3530 case nsLayoutUtils::RECTS_USE_PADDING_BOX:
3531 r = aFrame->GetPaddingRectRelativeToSelf();
3532 break;
3533 case nsLayoutUtils::RECTS_USE_MARGIN_BOX:
3534 r = aFrame->GetMarginRectRelativeToSelf();
3535 break;
3536 default: // Use the border box
3537 r = aFrame->GetRectRelativeToSelf();
3540 if (mFlags & nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS) {
3541 const bool isAncestorKnown = [&] {
3542 if (mRelativeToIsRoot) {
3543 return true;
3545 if (mRelativeToIsTarget && !mInTargetContinuation) {
3546 return !usingSVGOuterFrame;
3548 return false;
3549 }();
3550 if (isAncestorKnown) {
3551 r = nsLayoutUtils::TransformFrameRectToAncestor(outer, r, mRelativeTo);
3552 } else {
3553 nsLayoutUtils::TransformRect(outer, mRelativeTo, r);
3555 } else {
3556 if (aFrame->PresContext() != mRelativeTo->PresContext()) {
3557 r += outer->GetOffsetToCrossDoc(mRelativeTo);
3558 } else {
3559 r += outer->GetOffsetTo(mRelativeTo);
3562 mCallback->AddRect(r);
3566 struct MOZ_RAII BoxToRectAndText : public BoxToRect {
3567 Sequence<nsString>* mTextList;
3569 BoxToRectAndText(const nsIFrame* aTargetFrame, const nsIFrame* aRelativeTo,
3570 RectCallback* aCallback, Sequence<nsString>* aTextList,
3571 uint32_t aFlags)
3572 : BoxToRect(aTargetFrame, aRelativeTo, aCallback, aFlags),
3573 mTextList(aTextList) {}
3575 static void AccumulateText(nsIFrame* aFrame, nsAString& aResult) {
3576 MOZ_ASSERT(aFrame);
3578 // Get all the text in aFrame and child frames, while respecting
3579 // the content offsets in each of the nsTextFrames.
3580 if (aFrame->IsTextFrame()) {
3581 nsTextFrame* textFrame = static_cast<nsTextFrame*>(aFrame);
3583 nsIFrame::RenderedText renderedText = textFrame->GetRenderedText(
3584 textFrame->GetContentOffset(),
3585 textFrame->GetContentOffset() + textFrame->GetContentLength(),
3586 nsIFrame::TextOffsetType::OffsetsInContentText,
3587 nsIFrame::TrailingWhitespace::DontTrim);
3589 aResult.Append(renderedText.mString);
3592 for (nsIFrame* child = aFrame->PrincipalChildList().FirstChild(); child;
3593 child = child->GetNextSibling()) {
3594 AccumulateText(child, aResult);
3598 void AddBox(nsIFrame* aFrame) override {
3599 BoxToRect::AddBox(aFrame);
3600 if (mTextList) {
3601 nsString* textForFrame = mTextList->AppendElement(fallible);
3602 if (textForFrame) {
3603 AccumulateText(aFrame, *textForFrame);
3609 void nsLayoutUtils::GetAllInFlowRects(nsIFrame* aFrame,
3610 const nsIFrame* aRelativeTo,
3611 RectCallback* aCallback,
3612 uint32_t aFlags) {
3613 BoxToRect converter(aFrame, aRelativeTo, aCallback, aFlags);
3614 GetAllInFlowBoxes(aFrame, &converter);
3617 void nsLayoutUtils::GetAllInFlowRectsAndTexts(nsIFrame* aFrame,
3618 const nsIFrame* aRelativeTo,
3619 RectCallback* aCallback,
3620 Sequence<nsString>* aTextList,
3621 uint32_t aFlags) {
3622 BoxToRectAndText converter(aFrame, aRelativeTo, aCallback, aTextList, aFlags);
3623 GetAllInFlowBoxes(aFrame, &converter);
3626 nsLayoutUtils::RectAccumulator::RectAccumulator() : mSeenFirstRect(false) {}
3628 void nsLayoutUtils::RectAccumulator::AddRect(const nsRect& aRect) {
3629 mResultRect.UnionRect(mResultRect, aRect);
3630 if (!mSeenFirstRect) {
3631 mSeenFirstRect = true;
3632 mFirstRect = aRect;
3636 nsLayoutUtils::RectListBuilder::RectListBuilder(DOMRectList* aList)
3637 : mRectList(aList) {}
3639 void nsLayoutUtils::RectListBuilder::AddRect(const nsRect& aRect) {
3640 RefPtr<DOMRect> rect = new DOMRect(mRectList);
3642 rect->SetLayoutRect(aRect);
3643 mRectList->Append(rect);
3646 nsIFrame* nsLayoutUtils::GetContainingBlockForClientRect(nsIFrame* aFrame) {
3647 return aFrame->PresShell()->GetRootFrame();
3650 nsRect nsLayoutUtils::GetAllInFlowRectsUnion(nsIFrame* aFrame,
3651 const nsIFrame* aRelativeTo,
3652 uint32_t aFlags) {
3653 RectAccumulator accumulator;
3654 GetAllInFlowRects(aFrame, aRelativeTo, &accumulator, aFlags);
3655 return accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect
3656 : accumulator.mResultRect;
3659 nsRect nsLayoutUtils::GetTextShadowRectsUnion(
3660 const nsRect& aTextAndDecorationsRect, nsIFrame* aFrame, uint32_t aFlags) {
3661 const nsStyleText* textStyle = aFrame->StyleText();
3662 auto shadows = textStyle->mTextShadow.AsSpan();
3663 if (shadows.IsEmpty()) {
3664 return aTextAndDecorationsRect;
3667 nsRect resultRect = aTextAndDecorationsRect;
3668 int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel();
3669 for (auto& shadow : shadows) {
3670 nsMargin blur =
3671 nsContextBoxBlur::GetBlurRadiusMargin(shadow.blur.ToAppUnits(), A2D);
3672 if ((aFlags & EXCLUDE_BLUR_SHADOWS) && blur != nsMargin(0, 0, 0, 0))
3673 continue;
3675 nsRect tmpRect(aTextAndDecorationsRect);
3677 tmpRect.MoveBy(
3678 nsPoint(shadow.horizontal.ToAppUnits(), shadow.vertical.ToAppUnits()));
3679 tmpRect.Inflate(blur);
3681 resultRect.UnionRect(resultRect, tmpRect);
3683 return resultRect;
3686 enum ObjectDimensionType { eWidth, eHeight };
3687 static nscoord ComputeMissingDimension(
3688 const nsSize& aDefaultObjectSize, const AspectRatio& aIntrinsicRatio,
3689 const Maybe<nscoord>& aSpecifiedWidth,
3690 const Maybe<nscoord>& aSpecifiedHeight,
3691 ObjectDimensionType aDimensionToCompute) {
3692 // The "default sizing algorithm" computes the missing dimension as follows:
3693 // (source: http://dev.w3.org/csswg/css-images-3/#default-sizing )
3695 // 1. "If the object has an intrinsic aspect ratio, the missing dimension of
3696 // the concrete object size is calculated using the intrinsic aspect
3697 // ratio and the present dimension."
3698 if (aIntrinsicRatio) {
3699 // Fill in the missing dimension using the intrinsic aspect ratio.
3700 if (aDimensionToCompute == eWidth) {
3701 return aIntrinsicRatio.ApplyTo(*aSpecifiedHeight);
3703 return aIntrinsicRatio.Inverted().ApplyTo(*aSpecifiedWidth);
3706 // 2. "Otherwise, if the missing dimension is present in the object's
3707 // intrinsic dimensions, [...]"
3708 // NOTE: *Skipping* this case, because we already know it's not true -- we're
3709 // in this function because the missing dimension is *not* present in
3710 // the object's intrinsic dimensions.
3712 // 3. "Otherwise, the missing dimension of the concrete object size is taken
3713 // from the default object size. "
3714 return (aDimensionToCompute == eWidth) ? aDefaultObjectSize.width
3715 : aDefaultObjectSize.height;
3719 * This computes & returns the concrete object size of replaced content, if
3720 * that content were to be rendered with "object-fit: none". (Or, if the
3721 * element has neither an intrinsic height nor width, this method returns an
3722 * empty Maybe<> object.)
3724 * As specced...
3725 * http://dev.w3.org/csswg/css-images-3/#valdef-object-fit-none
3726 * ..we use "the default sizing algorithm with no specified size,
3727 * and a default object size equal to the replaced element's used width and
3728 * height."
3730 * The default sizing algorithm is described here:
3731 * http://dev.w3.org/csswg/css-images-3/#default-sizing
3732 * Quotes in the function-impl are taken from that ^ spec-text.
3734 * Per its final bulleted section: since there's no specified size,
3735 * we run the default sizing algorithm using the object's intrinsic size in
3736 * place of the specified size. But if the object has neither an intrinsic
3737 * height nor an intrinsic width, then we instead return without populating our
3738 * outparam, and we let the caller figure out the size (using a contain
3739 * constraint).
3741 static Maybe<nsSize> MaybeComputeObjectFitNoneSize(
3742 const nsSize& aDefaultObjectSize, const IntrinsicSize& aIntrinsicSize,
3743 const AspectRatio& aIntrinsicRatio) {
3744 // "If the object has an intrinsic height or width, its size is resolved as
3745 // if its intrinsic dimensions were given as the specified size."
3747 // So, first we check if we have an intrinsic height and/or width:
3748 const Maybe<nscoord>& specifiedWidth = aIntrinsicSize.width;
3749 const Maybe<nscoord>& specifiedHeight = aIntrinsicSize.height;
3751 Maybe<nsSize> noneSize; // (the value we'll return)
3752 if (specifiedWidth || specifiedHeight) {
3753 // We have at least one specified dimension; use whichever dimension is
3754 // specified, and compute the other one using our intrinsic ratio, or (if
3755 // no valid ratio) using the default object size.
3756 noneSize.emplace();
3758 noneSize->width =
3759 specifiedWidth
3760 ? *specifiedWidth
3761 : ComputeMissingDimension(aDefaultObjectSize, aIntrinsicRatio,
3762 specifiedWidth, specifiedHeight, eWidth);
3764 noneSize->height =
3765 specifiedHeight
3766 ? *specifiedHeight
3767 : ComputeMissingDimension(aDefaultObjectSize, aIntrinsicRatio,
3768 specifiedWidth, specifiedHeight, eHeight);
3770 // [else:] "Otherwise [if there's neither an intrinsic height nor width], its
3771 // size is resolved as a contain constraint against the default object size."
3772 // We'll let our caller do that, to share code & avoid redundant
3773 // computations; so, we return w/out populating noneSize.
3774 return noneSize;
3777 // Computes the concrete object size to render into, as described at
3778 // http://dev.w3.org/csswg/css-images-3/#concrete-size-resolution
3779 static nsSize ComputeConcreteObjectSize(const nsSize& aConstraintSize,
3780 const IntrinsicSize& aIntrinsicSize,
3781 const AspectRatio& aIntrinsicRatio,
3782 StyleObjectFit aObjectFit) {
3783 // Handle default behavior (filling the container) w/ fast early return.
3784 // (Also: if there's no valid intrinsic ratio, then we have the "fill"
3785 // behavior & just use the constraint size.)
3786 if (MOZ_LIKELY(aObjectFit == StyleObjectFit::Fill) || !aIntrinsicRatio) {
3787 return aConstraintSize;
3790 // The type of constraint to compute (cover/contain), if needed:
3791 Maybe<nsImageRenderer::FitType> fitType;
3793 Maybe<nsSize> noneSize;
3794 if (aObjectFit == StyleObjectFit::None ||
3795 aObjectFit == StyleObjectFit::ScaleDown) {
3796 noneSize = MaybeComputeObjectFitNoneSize(aConstraintSize, aIntrinsicSize,
3797 aIntrinsicRatio);
3798 if (!noneSize || aObjectFit == StyleObjectFit::ScaleDown) {
3799 // Need to compute a 'CONTAIN' constraint (either for the 'none' size
3800 // itself, or for comparison w/ the 'none' size to resolve 'scale-down'.)
3801 fitType.emplace(nsImageRenderer::CONTAIN);
3803 } else if (aObjectFit == StyleObjectFit::Cover) {
3804 fitType.emplace(nsImageRenderer::COVER);
3805 } else if (aObjectFit == StyleObjectFit::Contain) {
3806 fitType.emplace(nsImageRenderer::CONTAIN);
3809 Maybe<nsSize> constrainedSize;
3810 if (fitType) {
3811 constrainedSize.emplace(nsImageRenderer::ComputeConstrainedSize(
3812 aConstraintSize, aIntrinsicRatio, *fitType));
3815 // Now, we should have all the sizing information that we need.
3816 switch (aObjectFit) {
3817 // skipping StyleObjectFit::Fill; we handled it w/ early-return.
3818 case StyleObjectFit::Contain:
3819 case StyleObjectFit::Cover:
3820 MOZ_ASSERT(constrainedSize);
3821 return *constrainedSize;
3823 case StyleObjectFit::None:
3824 if (noneSize) {
3825 return *noneSize;
3827 MOZ_ASSERT(constrainedSize);
3828 return *constrainedSize;
3830 case StyleObjectFit::ScaleDown:
3831 MOZ_ASSERT(constrainedSize);
3832 if (noneSize) {
3833 constrainedSize->width =
3834 std::min(constrainedSize->width, noneSize->width);
3835 constrainedSize->height =
3836 std::min(constrainedSize->height, noneSize->height);
3838 return *constrainedSize;
3840 default:
3841 MOZ_ASSERT_UNREACHABLE("Unexpected enum value for 'object-fit'");
3842 return aConstraintSize; // fall back to (default) 'fill' behavior
3846 // (Helper for HasInitialObjectFitAndPosition, to check
3847 // each "object-position" coord.)
3848 static bool IsCoord50Pct(const LengthPercentage& aCoord) {
3849 return aCoord.ConvertsToPercentage() && aCoord.ToPercentage() == 0.5f;
3852 // Indicates whether the given nsStylePosition has the initial values
3853 // for the "object-fit" and "object-position" properties.
3854 static bool HasInitialObjectFitAndPosition(const nsStylePosition* aStylePos) {
3855 const Position& objectPos = aStylePos->mObjectPosition;
3857 return aStylePos->mObjectFit == StyleObjectFit::Fill &&
3858 IsCoord50Pct(objectPos.horizontal) && IsCoord50Pct(objectPos.vertical);
3861 /* static */
3862 nsRect nsLayoutUtils::ComputeObjectDestRect(const nsRect& aConstraintRect,
3863 const IntrinsicSize& aIntrinsicSize,
3864 const AspectRatio& aIntrinsicRatio,
3865 const nsStylePosition* aStylePos,
3866 nsPoint* aAnchorPoint) {
3867 // Step 1: Figure out our "concrete object size"
3868 // (the size of the region we'll actually draw our image's pixels into).
3869 nsSize concreteObjectSize =
3870 ComputeConcreteObjectSize(aConstraintRect.Size(), aIntrinsicSize,
3871 aIntrinsicRatio, aStylePos->mObjectFit);
3873 // Step 2: Figure out how to align that region in the element's content-box.
3874 nsPoint imageTopLeftPt, imageAnchorPt;
3875 nsImageRenderer::ComputeObjectAnchorPoint(
3876 aStylePos->mObjectPosition, aConstraintRect.Size(), concreteObjectSize,
3877 &imageTopLeftPt, &imageAnchorPt);
3878 // Right now, we're with respect to aConstraintRect's top-left point. We add
3879 // that point here, to convert to the same broader coordinate space that
3880 // aConstraintRect is in.
3881 imageTopLeftPt += aConstraintRect.TopLeft();
3882 imageAnchorPt += aConstraintRect.TopLeft();
3884 if (aAnchorPoint) {
3885 // Special-case: if our "object-fit" and "object-position" properties have
3886 // their default values ("object-fit: fill; object-position:50% 50%"), then
3887 // we'll override the calculated imageAnchorPt, and instead use the
3888 // object's top-left corner.
3890 // This special case is partly for backwards compatibility (since
3891 // traditionally we've pixel-aligned the top-left corner of e.g. <img>
3892 // elements), and partly because ComputeSnappedDrawingParameters produces
3893 // less error if the anchor point is at the top-left corner. So, all other
3894 // things being equal, we prefer that code path with less error.
3895 if (HasInitialObjectFitAndPosition(aStylePos)) {
3896 *aAnchorPoint = imageTopLeftPt;
3897 } else {
3898 *aAnchorPoint = imageAnchorPt;
3901 return nsRect(imageTopLeftPt, concreteObjectSize);
3904 already_AddRefed<nsFontMetrics> nsLayoutUtils::GetFontMetricsForFrame(
3905 const nsIFrame* aFrame, float aInflation) {
3906 ComputedStyle* computedStyle = aFrame->Style();
3907 uint8_t variantWidth = NS_FONT_VARIANT_WIDTH_NORMAL;
3908 if (computedStyle->IsTextCombined()) {
3909 MOZ_ASSERT(aFrame->IsTextFrame());
3910 auto textFrame = static_cast<const nsTextFrame*>(aFrame);
3911 auto clusters = textFrame->CountGraphemeClusters();
3912 if (clusters == 2) {
3913 variantWidth = NS_FONT_VARIANT_WIDTH_HALF;
3914 } else if (clusters == 3) {
3915 variantWidth = NS_FONT_VARIANT_WIDTH_THIRD;
3916 } else if (clusters == 4) {
3917 variantWidth = NS_FONT_VARIANT_WIDTH_QUARTER;
3920 return GetFontMetricsForComputedStyle(computedStyle, aFrame->PresContext(),
3921 aInflation, variantWidth);
3924 already_AddRefed<nsFontMetrics> nsLayoutUtils::GetFontMetricsForComputedStyle(
3925 const ComputedStyle* aComputedStyle, nsPresContext* aPresContext,
3926 float aInflation, uint8_t aVariantWidth) {
3927 WritingMode wm(aComputedStyle);
3928 const nsStyleFont* styleFont = aComputedStyle->StyleFont();
3929 nsFontMetrics::Params params;
3930 params.language = styleFont->mLanguage;
3931 params.explicitLanguage = styleFont->mExplicitLanguage;
3932 params.orientation = wm.IsVertical() && !wm.IsSideways()
3933 ? nsFontMetrics::eVertical
3934 : nsFontMetrics::eHorizontal;
3935 // pass the user font set object into the device context to
3936 // pass along to CreateFontGroup
3937 params.userFontSet = aPresContext->GetUserFontSet();
3938 params.textPerf = aPresContext->GetTextPerfMetrics();
3939 params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup();
3941 // When aInflation is 1.0 and we don't require width variant, avoid
3942 // making a local copy of the nsFont.
3943 // This also avoids running font.size through floats when it is large,
3944 // which would be lossy. Fortunately, in such cases, aInflation is
3945 // guaranteed to be 1.0f.
3946 if (aInflation == 1.0f && aVariantWidth == NS_FONT_VARIANT_WIDTH_NORMAL) {
3947 return aPresContext->GetMetricsFor(styleFont->mFont, params);
3950 nsFont font = styleFont->mFont;
3951 MOZ_ASSERT(!std::isnan(float(font.size.ToCSSPixels())),
3952 "Style font should never be NaN");
3953 font.size.ScaleBy(aInflation);
3954 if (MOZ_UNLIKELY(std::isnan(float(font.size.ToCSSPixels())))) {
3955 font.size = {0};
3957 font.variantWidth = aVariantWidth;
3958 return aPresContext->GetMetricsFor(font, params);
3961 nsIFrame* nsLayoutUtils::FindChildContainingDescendant(
3962 nsIFrame* aParent, nsIFrame* aDescendantFrame) {
3963 nsIFrame* result = aDescendantFrame;
3965 while (result) {
3966 nsIFrame* parent = result->GetParent();
3967 if (parent == aParent) {
3968 break;
3971 // The frame is not an immediate child of aParent so walk up another level
3972 result = parent;
3975 return result;
3978 nsBlockFrame* nsLayoutUtils::FindNearestBlockAncestor(nsIFrame* aFrame) {
3979 nsIFrame* nextAncestor;
3980 for (nextAncestor = aFrame->GetParent(); nextAncestor;
3981 nextAncestor = nextAncestor->GetParent()) {
3982 nsBlockFrame* block = do_QueryFrame(nextAncestor);
3983 if (block) return block;
3985 return nullptr;
3988 nsIFrame* nsLayoutUtils::GetNonGeneratedAncestor(nsIFrame* aFrame) {
3989 if (!aFrame->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT)) return aFrame;
3991 nsIFrame* f = aFrame;
3992 do {
3993 f = GetParentOrPlaceholderFor(f);
3994 } while (f->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT));
3995 return f;
3998 nsIFrame* nsLayoutUtils::GetParentOrPlaceholderFor(const nsIFrame* aFrame) {
3999 // This condition must match the condition in FindContainingBlocks in
4000 // RetainedDisplayListBuider.cpp, MarkFrameForDisplayIfVisible and
4001 // UnmarkFrameForDisplayIfVisible in nsDisplayList.cpp
4002 if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
4003 !aFrame->GetPrevInFlow()) {
4004 return aFrame->GetProperty(nsIFrame::PlaceholderFrameProperty());
4006 return aFrame->GetParent();
4009 nsIFrame* nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(
4010 const nsIFrame* aFrame) {
4011 nsIFrame* f = GetParentOrPlaceholderFor(aFrame);
4012 if (f) return f;
4013 return GetCrossDocParentFrameInProcess(aFrame);
4016 nsIFrame* nsLayoutUtils::GetDisplayListParent(nsIFrame* aFrame) {
4017 if (aFrame->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
4018 return aFrame->GetParent();
4020 return nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(aFrame);
4023 nsIFrame* nsLayoutUtils::GetPrevContinuationOrIBSplitSibling(
4024 const nsIFrame* aFrame) {
4025 if (nsIFrame* result = aFrame->GetPrevContinuation()) {
4026 return result;
4029 if (aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
4030 // We are the first frame in the continuation chain. Get the ib-split prev
4031 // sibling property stored in us.
4032 return aFrame->GetProperty(nsIFrame::IBSplitPrevSibling());
4035 return nullptr;
4038 nsIFrame* nsLayoutUtils::GetNextContinuationOrIBSplitSibling(
4039 const nsIFrame* aFrame) {
4040 if (nsIFrame* result = aFrame->GetNextContinuation()) {
4041 return result;
4044 if (aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
4045 // We only store the ib-split sibling annotation with the first frame in the
4046 // continuation chain.
4047 return aFrame->FirstContinuation()->GetProperty(nsIFrame::IBSplitSibling());
4050 return nullptr;
4053 nsIFrame* nsLayoutUtils::FirstContinuationOrIBSplitSibling(
4054 const nsIFrame* aFrame) {
4055 nsIFrame* result = aFrame->FirstContinuation();
4057 if (result->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
4058 while (auto* f = result->GetProperty(nsIFrame::IBSplitPrevSibling())) {
4059 result = f;
4063 return result;
4066 nsIFrame* nsLayoutUtils::LastContinuationOrIBSplitSibling(
4067 const nsIFrame* aFrame) {
4068 nsIFrame* result = aFrame->FirstContinuation();
4070 if (result->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
4071 while (auto* f = result->GetProperty(nsIFrame::IBSplitSibling())) {
4072 result = f;
4076 return result->LastContinuation();
4079 bool nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(
4080 const nsIFrame* aFrame) {
4081 if (aFrame->GetPrevContinuation()) {
4082 return false;
4084 if (aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT) &&
4085 aFrame->GetProperty(nsIFrame::IBSplitPrevSibling())) {
4086 return false;
4089 return true;
4092 bool nsLayoutUtils::IsViewportScrollbarFrame(nsIFrame* aFrame) {
4093 if (!aFrame) return false;
4095 nsIFrame* rootScrollFrame = aFrame->PresShell()->GetRootScrollFrame();
4096 if (!rootScrollFrame) return false;
4098 nsIScrollableFrame* rootScrollableFrame = do_QueryFrame(rootScrollFrame);
4099 NS_ASSERTION(rootScrollableFrame, "The root scorollable frame is null");
4101 if (!IsProperAncestorFrame(rootScrollFrame, aFrame)) return false;
4103 nsIFrame* rootScrolledFrame = rootScrollableFrame->GetScrolledFrame();
4104 return !(rootScrolledFrame == aFrame ||
4105 IsProperAncestorFrame(rootScrolledFrame, aFrame));
4109 * Use only for paddings / widths / heights, since it clamps negative calc() to
4110 * 0.
4112 template <typename LengthPercentageLike>
4113 static bool GetAbsoluteCoord(const LengthPercentageLike& aStyle,
4114 nscoord& aResult) {
4115 if (!aStyle.ConvertsToLength()) {
4116 return false;
4118 aResult = std::max(0, aStyle.ToLength());
4119 return true;
4122 static nscoord GetBSizePercentBasisAdjustment(StyleBoxSizing aBoxSizing,
4123 nsIFrame* aFrame,
4124 bool aHorizontalAxis,
4125 bool aResolvesAgainstPaddingBox);
4127 static bool GetPercentBSize(const LengthPercentage& aStyle, nsIFrame* aFrame,
4128 bool aHorizontalAxis, nscoord& aResult);
4130 // Only call on style coords for which GetAbsoluteCoord returned false.
4131 template <typename SizeOrMaxSize>
4132 static bool GetPercentBSize(const SizeOrMaxSize& aStyle, nsIFrame* aFrame,
4133 bool aHorizontalAxis, nscoord& aResult) {
4134 if (!aStyle.IsLengthPercentage()) {
4135 return false;
4137 return GetPercentBSize(aStyle.AsLengthPercentage(), aFrame, aHorizontalAxis,
4138 aResult);
4141 static bool GetPercentBSize(const LengthPercentage& aStyle, nsIFrame* aFrame,
4142 bool aHorizontalAxis, nscoord& aResult) {
4143 if (!aStyle.HasPercent()) {
4144 return false;
4147 MOZ_ASSERT(!aStyle.ConvertsToLength(),
4148 "GetAbsoluteCoord should have handled this");
4150 // During reflow, nsHTMLScrollFrame::ReflowScrolledFrame uses
4151 // SetComputedHeight on the reflow input for its child to propagate its
4152 // computed height to the scrolled content. So here we skip to the scroll
4153 // frame that contains this scrolled content in order to get the same
4154 // behavior as layout when computing percentage heights.
4155 nsIFrame* f = aFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME);
4156 if (!f) {
4157 MOZ_ASSERT_UNREACHABLE("top of frame tree not a containing block");
4158 return false;
4161 WritingMode wm = f->GetWritingMode();
4163 const nsStylePosition* pos = f->StylePosition();
4164 const auto& bSizeCoord = pos->BSize(wm);
4165 nscoord h;
4166 if (!GetAbsoluteCoord(bSizeCoord, h) &&
4167 !GetPercentBSize(bSizeCoord, f, aHorizontalAxis, h)) {
4168 LayoutFrameType fType = f->Type();
4169 if (fType != LayoutFrameType::Viewport &&
4170 fType != LayoutFrameType::Canvas &&
4171 fType != LayoutFrameType::PageContent) {
4172 // There's no basis for the percentage height, so it acts like auto.
4173 // Should we consider a max-height < min-height pair a basis for
4174 // percentage heights? The spec is somewhat unclear, and not doing
4175 // so is simpler and avoids troubling discontinuities in behavior,
4176 // so I'll choose not to. -LDB
4177 return false;
4179 // For the viewport, canvas, and page-content kids, the percentage
4180 // basis is just the parent block-size.
4181 h = f->BSize(wm);
4182 if (h == NS_UNCONSTRAINEDSIZE) {
4183 // We don't have a percentage basis after all
4184 return false;
4188 const auto& maxBSizeCoord = pos->MaxBSize(wm);
4190 nscoord maxh;
4191 if (GetAbsoluteCoord(maxBSizeCoord, maxh) ||
4192 GetPercentBSize(maxBSizeCoord, f, aHorizontalAxis, maxh)) {
4193 if (maxh < h) h = maxh;
4196 const auto& minBSizeCoord = pos->MinBSize(wm);
4198 nscoord minh;
4199 if (GetAbsoluteCoord(minBSizeCoord, minh) ||
4200 GetPercentBSize(minBSizeCoord, f, aHorizontalAxis, minh)) {
4201 if (minh > h) {
4202 h = minh;
4206 // If we're an abspos box, percentages in that case resolve against the
4207 // padding box.
4209 // TODO: This could conceivably cause some problems with fieldsets (which are
4210 // the other place that wants to ignore padding), but solving that here
4211 // without hardcoding a check for f being a fieldset-content frame is a bit of
4212 // a pain.
4213 const bool resolvesAgainstPaddingBox = aFrame->IsAbsolutelyPositioned();
4214 h += GetBSizePercentBasisAdjustment(pos->mBoxSizing, f, aHorizontalAxis,
4215 resolvesAgainstPaddingBox);
4217 aResult = std::max(aStyle.Resolve(std::max(h, 0)), 0);
4218 return true;
4221 // Return true if aStyle can be resolved to a definite value and if so
4222 // return that value in aResult.
4223 static bool GetDefiniteSize(const LengthPercentage& aStyle, nsIFrame* aFrame,
4224 bool aIsInlineAxis,
4225 const Maybe<LogicalSize>& aPercentageBasis,
4226 nscoord* aResult) {
4227 if (aStyle.ConvertsToLength()) {
4228 *aResult = aStyle.ToLength();
4229 return true;
4232 if (!aPercentageBasis) {
4233 return false;
4236 auto wm = aFrame->GetWritingMode();
4237 nscoord pb = aIsInlineAxis ? aPercentageBasis.value().ISize(wm)
4238 : aPercentageBasis.value().BSize(wm);
4239 if (pb == NS_UNCONSTRAINEDSIZE) {
4240 return false;
4242 *aResult = std::max(0, aStyle.Resolve(pb));
4243 return true;
4246 // Return true if aStyle can be resolved to a definite value and if so
4247 // return that value in aResult.
4248 template <typename SizeOrMaxSize>
4249 static bool GetDefiniteSize(const SizeOrMaxSize& aStyle, nsIFrame* aFrame,
4250 bool aIsInlineAxis,
4251 const Maybe<LogicalSize>& aPercentageBasis,
4252 nscoord* aResult) {
4253 if (!aStyle.IsLengthPercentage()) {
4254 return false;
4256 return GetDefiniteSize(aStyle.AsLengthPercentage(), aFrame, aIsInlineAxis,
4257 aPercentageBasis, aResult);
4260 // NOTE: this function will be replaced by GetDefiniteSizeTakenByBoxSizing (bug
4261 // 1363918). Please do not add new uses of this function.
4263 // Get the amount of space to add or subtract out of aFrame's 'block-size' or
4264 // property value due its borders and paddings, given the box-sizing value in
4265 // aBoxSizing.
4267 // aHorizontalAxis is true if our inline direction is horizontal and our block
4268 // direction is vertical. aResolvesAgainstPaddingBox is true if padding should
4269 // be added or not removed.
4270 static nscoord GetBSizePercentBasisAdjustment(StyleBoxSizing aBoxSizing,
4271 nsIFrame* aFrame,
4272 bool aHorizontalAxis,
4273 bool aResolvesAgainstPaddingBox) {
4274 nscoord adjustment = 0;
4275 if (aBoxSizing == StyleBoxSizing::Border) {
4276 const auto& border = aFrame->StyleBorder()->GetComputedBorder();
4277 adjustment -= aHorizontalAxis ? border.TopBottom() : border.LeftRight();
4279 if ((aBoxSizing == StyleBoxSizing::Border) == !aResolvesAgainstPaddingBox) {
4280 const auto& stylePadding = aFrame->StylePadding()->mPadding;
4281 const LengthPercentage& paddingStart =
4282 stylePadding.Get(aHorizontalAxis ? eSideTop : eSideLeft);
4283 const LengthPercentage& paddingEnd =
4284 stylePadding.Get(aHorizontalAxis ? eSideBottom : eSideRight);
4285 nscoord pad;
4286 // XXXbz Calling GetPercentBSize on padding values looks bogus, since
4287 // percent padding is always a percentage of the inline-size of the
4288 // containing block. We should perhaps just treat non-absolute paddings
4289 // here as 0 instead, except that in some cases the width may in fact be
4290 // known. See bug 1231059.
4291 if (GetAbsoluteCoord(paddingStart, pad) ||
4292 GetPercentBSize(paddingStart, aFrame, aHorizontalAxis, pad)) {
4293 adjustment += aResolvesAgainstPaddingBox ? pad : -pad;
4295 if (GetAbsoluteCoord(paddingEnd, pad) ||
4296 GetPercentBSize(paddingEnd, aFrame, aHorizontalAxis, pad)) {
4297 adjustment += aResolvesAgainstPaddingBox ? pad : -pad;
4300 return adjustment;
4303 // Get the amount of space taken out of aFrame's content area due to its
4304 // borders and paddings given the box-sizing value in aBoxSizing. We don't
4305 // get aBoxSizing from the frame because some callers want to compute this for
4306 // specific box-sizing values.
4307 // aIsInlineAxis is true if we're computing for aFrame's inline axis.
4308 // aIgnorePadding is true if padding should be ignored.
4309 static nscoord GetDefiniteSizeTakenByBoxSizing(
4310 StyleBoxSizing aBoxSizing, nsIFrame* aFrame, bool aIsInlineAxis,
4311 bool aIgnorePadding, const Maybe<LogicalSize>& aPercentageBasis) {
4312 nscoord sizeTakenByBoxSizing = 0;
4313 if (MOZ_UNLIKELY(aBoxSizing == StyleBoxSizing::Border)) {
4314 const bool isHorizontalAxis =
4315 aIsInlineAxis == !aFrame->GetWritingMode().IsVertical();
4316 const nsStyleBorder* styleBorder = aFrame->StyleBorder();
4317 sizeTakenByBoxSizing = isHorizontalAxis
4318 ? styleBorder->GetComputedBorder().LeftRight()
4319 : styleBorder->GetComputedBorder().TopBottom();
4320 if (!aIgnorePadding) {
4321 const auto& stylePadding = aFrame->StylePadding()->mPadding;
4322 const LengthPercentage& pStart =
4323 stylePadding.Get(isHorizontalAxis ? eSideLeft : eSideTop);
4324 const LengthPercentage& pEnd =
4325 stylePadding.Get(isHorizontalAxis ? eSideRight : eSideBottom);
4326 nscoord pad;
4327 // XXXbz Calling GetPercentBSize on padding values looks bogus, since
4328 // percent padding is always a percentage of the inline-size of the
4329 // containing block. We should perhaps just treat non-absolute paddings
4330 // here as 0 instead, except that in some cases the width may in fact be
4331 // known. See bug 1231059.
4332 if (GetDefiniteSize(pStart, aFrame, aIsInlineAxis, aPercentageBasis,
4333 &pad) ||
4334 (aPercentageBasis.isNothing() &&
4335 GetPercentBSize(pStart, aFrame, isHorizontalAxis, pad))) {
4336 sizeTakenByBoxSizing += pad;
4338 if (GetDefiniteSize(pEnd, aFrame, aIsInlineAxis, aPercentageBasis,
4339 &pad) ||
4340 (aPercentageBasis.isNothing() &&
4341 GetPercentBSize(pEnd, aFrame, isHorizontalAxis, pad))) {
4342 sizeTakenByBoxSizing += pad;
4346 return sizeTakenByBoxSizing;
4350 * Handles only max-content and min-content, and
4351 * -moz-fit-content for min-width and max-width, since the others
4352 * (-moz-fit-content for width, and -moz-available) have no effect on
4353 * intrinsic widths.
4355 static bool GetIntrinsicCoord(nsIFrame::ExtremumLength aStyle,
4356 gfxContext* aRenderingContext, nsIFrame* aFrame,
4357 Maybe<nscoord> aInlineSizeFromAspectRatio,
4358 nsIFrame::SizeProperty aProperty,
4359 nscoord aContentBoxToBoxSizingDiff,
4360 nscoord& aResult) {
4361 if (aStyle == nsIFrame::ExtremumLength::MozAvailable) {
4362 return false;
4365 if (aStyle == nsIFrame::ExtremumLength::FitContentFunction) {
4366 // fit-content() should be handled by the caller.
4367 return false;
4370 if (aStyle == nsIFrame::ExtremumLength::FitContent) {
4371 switch (aProperty) {
4372 case nsIFrame::SizeProperty::Size:
4373 // handle like 'width: auto'
4374 return false;
4375 case nsIFrame::SizeProperty::MaxSize:
4376 // constrain large 'width' values down to max-content
4377 aStyle = nsIFrame::ExtremumLength::MaxContent;
4378 break;
4379 case nsIFrame::SizeProperty::MinSize:
4380 // constrain small 'width' or 'max-width' values up to min-content
4381 aStyle = nsIFrame::ExtremumLength::MinContent;
4382 break;
4386 NS_ASSERTION(aStyle == nsIFrame::ExtremumLength::MinContent ||
4387 aStyle == nsIFrame::ExtremumLength::MaxContent,
4388 "should have reduced everything remaining to one of these");
4390 // If aFrame is a container for font size inflation, then shrink
4391 // wrapping inside of it should not apply font size inflation.
4392 AutoMaybeDisableFontInflation an(aFrame);
4394 if (aInlineSizeFromAspectRatio) {
4395 aResult = *aInlineSizeFromAspectRatio;
4396 } else if (aStyle == nsIFrame::ExtremumLength::MaxContent) {
4397 aResult = aFrame->GetPrefISize(aRenderingContext);
4398 } else {
4399 aResult = aFrame->GetMinISize(aRenderingContext);
4402 aResult += aContentBoxToBoxSizingDiff;
4404 return true;
4407 template <typename SizeOrMaxSize>
4408 static bool GetIntrinsicCoord(const SizeOrMaxSize& aStyle,
4409 gfxContext* aRenderingContext, nsIFrame* aFrame,
4410 Maybe<nscoord> aInlineSizeFromAspectRatio,
4411 nsIFrame::SizeProperty aProperty,
4412 nscoord aContentBoxToBoxSizingDiff,
4413 nscoord& aResult) {
4414 auto length = nsIFrame::ToExtremumLength(aStyle);
4415 if (!length) {
4416 return false;
4418 return GetIntrinsicCoord(*length, aRenderingContext, aFrame,
4419 aInlineSizeFromAspectRatio, aProperty,
4420 aContentBoxToBoxSizingDiff, aResult);
4423 #undef DEBUG_INTRINSIC_WIDTH
4425 #ifdef DEBUG_INTRINSIC_WIDTH
4426 static int32_t gNoiseIndent = 0;
4427 #endif
4429 static nscoord GetFitContentSizeForMaxOrPreferredSize(
4430 const IntrinsicISizeType aType, const nsIFrame::SizeProperty aProperty,
4431 const nsIFrame* aFrame, const LengthPercentage& aStyleSize,
4432 const nscoord aInitialValue, const nscoord aMinContentSize,
4433 const nscoord aMaxContentSize) {
4434 MOZ_ASSERT(aProperty != nsIFrame::SizeProperty::MinSize);
4436 nscoord size = NS_UNCONSTRAINEDSIZE;
4437 // 1. Treat fit-content()'s arg as a plain LengthPercentage
4438 // However, we have to handle the cyclic percentage contribution first.
4440 // https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution
4441 if (aType == IntrinsicISizeType::MinISize &&
4442 aFrame->IsPercentageResolvedAgainstZero(aStyleSize, aProperty)) {
4443 // Case (c) in the spec.
4444 // FIXME: This doesn't follow the spec for calc(). We should fix this in
4445 // Bug 1463700.
4446 size = 0;
4447 } else if (!GetAbsoluteCoord(aStyleSize, size)) {
4448 // As initial value. Case (a) and (b) in the spec.
4449 size = aInitialValue;
4452 // 2. Clamp size by min-content and max-content.
4453 return std::max(aMinContentSize, std::min(aMaxContentSize, size));
4457 * Add aOffsets which describes what to add on outside of the content box
4458 * aContentSize (controlled by 'box-sizing') and apply min/max properties.
4459 * We have to account for these properties after getting all the offsets
4460 * (margin, border, padding) because percentages do not operate linearly.
4461 * Doing this is ok because although percentages aren't handled linearly,
4462 * they are handled monotonically.
4464 * @param aContentSize the content size calculated so far
4465 (@see IntrinsicForContainer)
4466 * @param aContentMinSize ditto min content size
4467 * @param aStyleSize a 'width' or 'height' property value
4468 * @param aFixedMinSize if aStyleMinSize is a definite size then this points to
4469 * the value, otherwise nullptr
4470 * @param aStyleMinSize a 'min-width' or 'min-height' property value
4471 * @param aFixedMaxSize if aStyleMaxSize is a definite size then this points to
4472 * the value, otherwise nullptr
4473 * @param aStyleMaxSize a 'max-width' or 'max-height' property value
4474 * @param aInlineSizeFromAspectRatio the content-box inline size computed from
4475 * aspect-ratio and the definite block size.
4476 * We use this value to resolve
4477 * {min|max}-content.
4478 * @param aFlags same as for IntrinsicForContainer
4479 * @param aContainerWM the container's WM
4481 static nscoord AddIntrinsicSizeOffset(
4482 gfxContext* aRenderingContext, nsIFrame* aFrame,
4483 const nsIFrame::IntrinsicSizeOffsetData& aOffsets, IntrinsicISizeType aType,
4484 StyleBoxSizing aBoxSizing, nscoord aContentSize, nscoord aContentMinSize,
4485 const StyleSize& aStyleSize, const nscoord* aFixedMinSize,
4486 const StyleSize& aStyleMinSize, const nscoord* aFixedMaxSize,
4487 const StyleMaxSize& aStyleMaxSize,
4488 Maybe<nscoord> aInlineSizeFromAspectRatio, uint32_t aFlags,
4489 PhysicalAxis aAxis) {
4490 nscoord result = aContentSize;
4491 nscoord min = aContentMinSize;
4492 nscoord coordOutsideSize = 0;
4493 nscoord contentBoxToBoxSizingDiff = 0;
4495 if (!(aFlags & nsLayoutUtils::IGNORE_PADDING)) {
4496 coordOutsideSize += aOffsets.padding;
4499 coordOutsideSize += aOffsets.border;
4501 if (aBoxSizing == StyleBoxSizing::Border) {
4502 // Store the padding of the box so we can pass it into
4503 // functions that works with content box-sizing.
4504 contentBoxToBoxSizingDiff = coordOutsideSize;
4506 min += coordOutsideSize;
4507 result = NSCoordSaturatingAdd(result, coordOutsideSize);
4509 coordOutsideSize = 0;
4512 coordOutsideSize += aOffsets.margin;
4514 min += coordOutsideSize;
4516 // Compute min-content/max-content for fit-content().
4517 nscoord minContent = 0;
4518 nscoord maxContent = NS_UNCONSTRAINEDSIZE;
4519 if (aStyleSize.IsFitContentFunction() ||
4520 aStyleMaxSize.IsFitContentFunction() ||
4521 aStyleMinSize.IsFitContentFunction()) {
4522 if (aInlineSizeFromAspectRatio) {
4523 minContent = maxContent = *aInlineSizeFromAspectRatio;
4524 } else {
4525 minContent = aFrame->GetMinISize(aRenderingContext);
4526 maxContent = aFrame->GetPrefISize(aRenderingContext);
4528 minContent += contentBoxToBoxSizingDiff;
4529 maxContent += contentBoxToBoxSizingDiff;
4532 // Compute size.
4533 nscoord size = NS_UNCONSTRAINEDSIZE;
4534 if (aType == IntrinsicISizeType::MinISize &&
4535 aFrame->IsPercentageResolvedAgainstZero(aStyleSize, aStyleMaxSize)) {
4536 // XXX bug 1463700: this doesn't handle calc() according to spec
4537 result = 0; // let |min| handle padding/border/margin
4538 } else if (GetAbsoluteCoord(aStyleSize, size) ||
4539 GetIntrinsicCoord(aStyleSize, aRenderingContext, aFrame,
4540 aInlineSizeFromAspectRatio,
4541 nsIFrame::SizeProperty::Size,
4542 contentBoxToBoxSizingDiff, size)) {
4543 result = size + coordOutsideSize;
4544 } else if (aStyleSize.IsFitContentFunction()) {
4545 // |result| here is the content size or border size, depends on
4546 // StyleBoxSizing. We use it as the initial value when handling the cyclic
4547 // percentage.
4548 nscoord initial = result;
4549 nscoord fitContentFuncSize = GetFitContentSizeForMaxOrPreferredSize(
4550 aType, nsIFrame::SizeProperty::Size, aFrame,
4551 aStyleSize.AsFitContentFunction(), initial, minContent, maxContent);
4552 // Add border and padding.
4553 result = NSCoordSaturatingAdd(fitContentFuncSize, coordOutsideSize);
4554 } else {
4555 result = NSCoordSaturatingAdd(result, coordOutsideSize);
4558 // Compute max-size.
4559 nscoord maxSize = aFixedMaxSize ? *aFixedMaxSize : 0;
4560 if (aFixedMaxSize || GetIntrinsicCoord(aStyleMaxSize, aRenderingContext,
4561 aFrame, aInlineSizeFromAspectRatio,
4562 nsIFrame::SizeProperty::MaxSize,
4563 contentBoxToBoxSizingDiff, maxSize)) {
4564 maxSize += coordOutsideSize;
4565 if (result > maxSize) {
4566 result = maxSize;
4568 } else if (aStyleMaxSize.IsFitContentFunction()) {
4569 nscoord fitContentFuncSize = GetFitContentSizeForMaxOrPreferredSize(
4570 aType, nsIFrame::SizeProperty::MaxSize, aFrame,
4571 aStyleMaxSize.AsFitContentFunction(), NS_UNCONSTRAINEDSIZE, minContent,
4572 maxContent);
4573 maxSize = NSCoordSaturatingAdd(fitContentFuncSize, coordOutsideSize);
4574 if (result > maxSize) {
4575 result = maxSize;
4579 // Compute min-size.
4580 nscoord minSize = aFixedMinSize ? *aFixedMinSize : 0;
4581 if (aFixedMinSize || GetIntrinsicCoord(aStyleMinSize, aRenderingContext,
4582 aFrame, aInlineSizeFromAspectRatio,
4583 nsIFrame::SizeProperty::MinSize,
4584 contentBoxToBoxSizingDiff, minSize)) {
4585 minSize += coordOutsideSize;
4586 if (result < minSize) {
4587 result = minSize;
4589 } else if (aStyleMinSize.IsFitContentFunction()) {
4590 if (!GetAbsoluteCoord(aStyleMinSize.AsFitContentFunction(), minSize)) {
4591 // FIXME: Bug 1463700, we should resolve only the percentage part to 0
4592 // such as min-width: fit-content(calc(50% + 50px)).
4593 minSize = 0;
4595 nscoord fitContentFuncSize =
4596 std::max(minContent, std::min(maxContent, minSize));
4597 minSize = NSCoordSaturatingAdd(fitContentFuncSize, coordOutsideSize);
4598 if (result < minSize) {
4599 result = minSize;
4603 if (result < min) {
4604 result = min;
4607 const nsStyleDisplay* disp = aFrame->StyleDisplay();
4608 if (aFrame->IsThemed(disp)) {
4609 nsPresContext* pc = aFrame->PresContext();
4610 LayoutDeviceIntSize devSize = pc->Theme()->GetMinimumWidgetSize(
4611 pc, aFrame, disp->EffectiveAppearance());
4612 nscoord themeSize = pc->DevPixelsToAppUnits(
4613 aAxis == PhysicalAxis::Vertical ? devSize.height : devSize.width);
4614 // GetMinimumWidgetSize() returns a border-box width.
4615 themeSize += aOffsets.margin;
4616 if (themeSize > result) {
4617 result = themeSize;
4620 return result;
4623 static void AddStateBitToAncestors(nsIFrame* aFrame, nsFrameState aBit) {
4624 for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
4625 if (f->HasAnyStateBits(aBit)) {
4626 break;
4628 f->AddStateBits(aBit);
4632 /* static */
4633 nscoord nsLayoutUtils::IntrinsicForAxis(
4634 PhysicalAxis aAxis, gfxContext* aRenderingContext, nsIFrame* aFrame,
4635 IntrinsicISizeType aType, const Maybe<LogicalSize>& aPercentageBasis,
4636 uint32_t aFlags, nscoord aMarginBoxMinSizeClamp) {
4637 MOZ_ASSERT(aFrame, "null frame");
4638 MOZ_ASSERT(aFrame->GetParent(),
4639 "IntrinsicForAxis called on frame not in tree");
4640 MOZ_ASSERT(aFrame->GetParent()->Type() != LayoutFrameType::GridContainer ||
4641 aPercentageBasis.isSome(),
4642 "grid layout should always pass a percentage basis");
4644 const bool horizontalAxis = MOZ_LIKELY(aAxis == PhysicalAxis::Horizontal);
4645 #ifdef DEBUG_INTRINSIC_WIDTH
4646 nsIFrame::IndentBy(stderr, gNoiseIndent);
4647 aFrame->ListTag(stderr);
4648 printf_stderr(" %s %s intrinsic size for container:\n",
4649 aType == IntrinsicISizeType::MinISize ? "min" : "pref",
4650 horizontalAxis ? "horizontal" : "vertical");
4651 #endif
4653 // If aFrame is a container for font size inflation, then shrink
4654 // wrapping inside of it should not apply font size inflation.
4655 AutoMaybeDisableFontInflation an(aFrame);
4657 // We want the size this frame will contribute to the parent's inline-size,
4658 // so we work in the parent's writing mode; but if aFrame is orthogonal to
4659 // its parent, we'll need to look at its BSize instead of min/pref-ISize.
4660 const nsStylePosition* stylePos = aFrame->StylePosition();
4661 StyleBoxSizing boxSizing = stylePos->mBoxSizing;
4663 StyleSize styleMinISize =
4664 horizontalAxis ? stylePos->mMinWidth : stylePos->mMinHeight;
4665 StyleSize styleISize =
4666 (aFlags & MIN_INTRINSIC_ISIZE)
4667 ? styleMinISize
4668 : (horizontalAxis ? stylePos->mWidth : stylePos->mHeight);
4669 MOZ_ASSERT(!(aFlags & MIN_INTRINSIC_ISIZE) || styleISize.IsAuto() ||
4670 nsIFrame::ToExtremumLength(styleISize),
4671 "should only use MIN_INTRINSIC_ISIZE for intrinsic values");
4672 StyleMaxSize styleMaxISize =
4673 horizontalAxis ? stylePos->mMaxWidth : stylePos->mMaxHeight;
4675 PhysicalAxis ourInlineAxis =
4676 aFrame->GetWritingMode().PhysicalAxis(LogicalAxis::Inline);
4677 const bool isInlineAxis = aAxis == ourInlineAxis;
4679 auto resetIfKeywords = [](StyleSize& aSize, StyleSize& aMinSize,
4680 StyleMaxSize& aMaxSize) {
4681 if (!aSize.IsLengthPercentage()) {
4682 aSize = StyleSize::Auto();
4684 if (!aMinSize.IsLengthPercentage()) {
4685 aMinSize = StyleSize::Auto();
4687 if (!aMaxSize.IsLengthPercentage()) {
4688 aMaxSize = StyleMaxSize::None();
4691 // According to the spec, max-content and min-content should behave as the
4692 // property's initial values in block axis.
4693 // It also make senses to use the initial values for -moz-fit-content and
4694 // -moz-available for intrinsic size in block axis. Therefore, we reset them
4695 // if needed.
4696 if (!isInlineAxis) {
4697 resetIfKeywords(styleISize, styleMinISize, styleMaxISize);
4700 // We build up two values starting with the content box, and then
4701 // adding padding, border and margin. The result is normally
4702 // |result|. Then, when we handle 'width', 'min-width', and
4703 // 'max-width', we use the results we've been building in |min| as a
4704 // minimum, overriding 'min-width'. This ensures two things:
4705 // * that we don't let a value of 'box-sizing' specifying a width
4706 // smaller than the padding/border inside the box-sizing box give
4707 // a content width less than zero
4708 // * that we prevent tables from becoming smaller than their
4709 // intrinsic minimum width
4710 nscoord result = 0, min = 0;
4712 nscoord maxISize;
4713 bool haveFixedMaxISize = GetAbsoluteCoord(styleMaxISize, maxISize);
4714 nscoord minISize;
4716 // Treat "min-width: auto" as 0.
4717 bool haveFixedMinISize;
4718 if (styleMinISize.IsAuto()) {
4719 // NOTE: Technically, "auto" is supposed to behave like "min-content" on
4720 // flex items. However, we don't need to worry about that here, because
4721 // flex items' min-sizes are intentionally ignored until the flex
4722 // container explicitly considers them during space distribution.
4723 minISize = 0;
4724 haveFixedMinISize = true;
4725 } else {
4726 haveFixedMinISize = GetAbsoluteCoord(styleMinISize, minISize);
4729 auto childWM = aFrame->GetWritingMode();
4730 nscoord pmPercentageBasis = NS_UNCONSTRAINEDSIZE;
4731 if (aPercentageBasis.isSome()) {
4732 // The padding/margin percentage basis is the inline-size in the parent's
4733 // writing-mode.
4734 pmPercentageBasis =
4735 aFrame->GetParent()->GetWritingMode().IsOrthogonalTo(childWM)
4736 ? aPercentageBasis->BSize(childWM)
4737 : aPercentageBasis->ISize(childWM);
4739 nsIFrame::IntrinsicSizeOffsetData offsets =
4740 MOZ_LIKELY(isInlineAxis)
4741 ? aFrame->IntrinsicISizeOffsets(pmPercentageBasis)
4742 : aFrame->IntrinsicBSizeOffsets(pmPercentageBasis);
4744 auto getContentBoxSizeToBoxSizingAdjust =
4745 [childWM, &offsets, &aFrame, isInlineAxis,
4746 pmPercentageBasis](const StyleBoxSizing aBoxSizing) {
4747 return aBoxSizing == StyleBoxSizing::Border
4748 ? LogicalSize(childWM,
4749 (isInlineAxis ? offsets
4750 : aFrame->IntrinsicISizeOffsets(
4751 pmPercentageBasis))
4752 .BorderPadding(),
4753 (!isInlineAxis ? offsets
4754 : aFrame->IntrinsicBSizeOffsets(
4755 pmPercentageBasis))
4756 .BorderPadding())
4757 : LogicalSize(childWM);
4760 Maybe<nscoord> inlineSizeFromAspectRatio;
4761 Maybe<LogicalSize> contentBoxSizeToBoxSizingAdjust;
4763 const bool ignorePadding =
4764 (aFlags & IGNORE_PADDING) || aFrame->IsAbsolutelyPositioned();
4766 // If we have a specified width (or a specified 'min-width' greater
4767 // than the specified 'max-width', which works out to the same thing),
4768 // don't even bother getting the frame's intrinsic width, because in
4769 // this case GetAbsoluteCoord(styleISize, w) will always succeed, so
4770 // we'll never need the intrinsic dimensions.
4771 if (styleISize.IsMaxContent() || styleISize.IsMinContent()) {
4772 MOZ_ASSERT(isInlineAxis);
4773 // -moz-fit-content and -moz-available enumerated widths compute intrinsic
4774 // widths just like auto.
4775 // For max-content and min-content, we handle them like
4776 // specified widths, but ignore box-sizing.
4777 boxSizing = StyleBoxSizing::Content;
4778 } else if (!styleISize.ConvertsToLength() &&
4779 !(styleISize.IsFitContentFunction() &&
4780 styleISize.AsFitContentFunction().ConvertsToLength()) &&
4781 !(haveFixedMinISize && haveFixedMaxISize &&
4782 maxISize <= minISize)) {
4783 #ifdef DEBUG_INTRINSIC_WIDTH
4784 ++gNoiseIndent;
4785 #endif
4786 if (MOZ_UNLIKELY(!isInlineAxis)) {
4787 IntrinsicSize intrinsicSize = aFrame->GetIntrinsicSize();
4788 const auto& intrinsicBSize =
4789 horizontalAxis ? intrinsicSize.width : intrinsicSize.height;
4790 if (intrinsicBSize) {
4791 result = *intrinsicBSize;
4792 } else {
4793 // We don't have an intrinsic bsize and we need aFrame's block-dir size.
4794 if (aFlags & BAIL_IF_REFLOW_NEEDED) {
4795 return NS_INTRINSIC_ISIZE_UNKNOWN;
4797 // XXX Unfortunately, we probably don't know this yet, so this is
4798 // wrong... but it's not clear what we should do. If aFrame's inline
4799 // size hasn't been determined yet, we can't necessarily figure out its
4800 // block size either. For now, authors who put orthogonal elements into
4801 // things like buttons or table cells may have to explicitly provide
4802 // sizes rather than expecting intrinsic sizing to work "perfectly" in
4803 // underspecified cases.
4804 result = aFrame->BSize();
4806 } else {
4807 result = aType == IntrinsicISizeType::MinISize
4808 ? aFrame->GetMinISize(aRenderingContext)
4809 : aFrame->GetPrefISize(aRenderingContext);
4811 #ifdef DEBUG_INTRINSIC_WIDTH
4812 --gNoiseIndent;
4813 nsIFrame::IndentBy(stderr, gNoiseIndent);
4814 aFrame->ListTag(stderr);
4815 printf_stderr(" %s %s intrinsic size from frame is %d.\n",
4816 aType == IntrinsicISizeType::MinISize ? "min" : "pref",
4817 horizontalAxis ? "horizontal" : "vertical", result);
4818 #endif
4820 // Handle elements with an intrinsic ratio (or size) and a specified
4821 // height, min-height, or max-height.
4822 // NOTE:
4823 // 1. We treat "min-height:auto" as "0" for the purpose of this code,
4824 // since that's what it means in all cases except for on flex items -- and
4825 // even there, we're supposed to ignore it (i.e. treat it as 0) until the
4826 // flex container explicitly considers it.
4827 // 2. The 'B' in |styleBSize|, |styleMinBSize|, and |styleMaxBSize|
4828 // represents the ratio-determining axis of |aFrame|. It could be the inline
4829 // axis or the block axis of |aFrame|. (So we are calculating the size
4830 // along the ratio-dependent axis in this if-branch.)
4831 StyleSize styleBSize =
4832 horizontalAxis ? stylePos->mHeight : stylePos->mWidth;
4833 StyleSize styleMinBSize =
4834 horizontalAxis ? stylePos->mMinHeight : stylePos->mMinWidth;
4835 StyleMaxSize styleMaxBSize =
4836 horizontalAxis ? stylePos->mMaxHeight : stylePos->mMaxWidth;
4838 // According to the spec, max-content and min-content should behave as the
4839 // property's initial values in block axis.
4840 // It also make senses to use the initial values for -moz-fit-content and
4841 // -moz-available for intrinsic size in block axis. Therefore, we reset them
4842 // if needed.
4843 if (isInlineAxis) {
4844 resetIfKeywords(styleBSize, styleMinBSize, styleMaxBSize);
4847 // If our BSize or min/max-BSize properties are set to values that we can
4848 // resolve and that will impose a constraint when transferred through our
4849 // aspect ratio (if we have one), then compute and apply that constraint.
4851 // (Note: This helper-bool & lambda just let us optimize away the actual
4852 // transferring-and-clamping arithmetic, for the common case where we can
4853 // tell that none of the block-axis size properties establish a meaningful
4854 // transferred constraint.)
4855 const bool mightHaveBlockAxisConstraintToTransfer = [&] {
4856 if (!styleBSize.BehavesLikeInitialValueOnBlockAxis()) {
4857 return true; // BSize property might have a constraint to transfer.
4859 // Check for min-BSize values that would obviously produce zero in the
4860 // transferring logic that follows; zero is trivially-ignorable as a
4861 // transferred lower-bound. (These include the the property's initial
4862 // value, explicit 0, and values that are equivalent to these.)
4863 bool minBSizeHasNoConstraintToTransfer =
4864 styleMinBSize.BehavesLikeInitialValueOnBlockAxis() ||
4865 (styleMinBSize.IsLengthPercentage() &&
4866 styleMinBSize.AsLengthPercentage().IsDefinitelyZero());
4867 if (!minBSizeHasNoConstraintToTransfer) {
4868 return true; // min-BSize property might have a constraint to transfer.
4870 if (!styleMaxBSize.BehavesLikeInitialValueOnBlockAxis()) {
4871 return true; // max-BSize property might have a constraint to transfer.
4873 return false;
4874 }();
4875 if (mightHaveBlockAxisConstraintToTransfer) {
4876 if (AspectRatio ratio = aFrame->GetAspectRatio()) {
4877 AddStateBitToAncestors(
4878 aFrame, NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE);
4880 nscoord bSizeTakenByBoxSizing = GetDefiniteSizeTakenByBoxSizing(
4881 boxSizing, aFrame, !isInlineAxis, ignorePadding, aPercentageBasis);
4882 contentBoxSizeToBoxSizingAdjust.emplace(
4883 getContentBoxSizeToBoxSizingAdjust(boxSizing));
4884 // NOTE: This is only the minContentSize if we've been passed
4885 // MIN_INTRINSIC_ISIZE (which is fine, because this should only be used
4886 // inside a check for that flag).
4887 nscoord minContentSize = result;
4888 nscoord h;
4889 if (GetDefiniteSize(styleBSize, aFrame, !isInlineAxis, aPercentageBasis,
4890 &h) ||
4891 (aPercentageBasis.isNothing() &&
4892 GetPercentBSize(styleBSize, aFrame, horizontalAxis, h))) {
4893 h = std::max(0, h - bSizeTakenByBoxSizing);
4894 // We are computing the size of |aFrame|, so we use the inline & block
4895 // dimensions of |aFrame|.
4896 result = ratio.ComputeRatioDependentSize(
4897 isInlineAxis ? LogicalAxis::Inline : LogicalAxis::Block, childWM,
4898 h, *contentBoxSizeToBoxSizingAdjust);
4899 // We have get the inlineSizeForAspectRatio value, so we don't have to
4900 // recompute this again below.
4901 inlineSizeFromAspectRatio.emplace(result);
4904 if (GetDefiniteSize(styleMaxBSize, aFrame, !isInlineAxis,
4905 aPercentageBasis, &h) ||
4906 (aPercentageBasis.isNothing() &&
4907 GetPercentBSize(styleMaxBSize, aFrame, horizontalAxis, h))) {
4908 h = std::max(0, h - bSizeTakenByBoxSizing);
4909 nscoord maxISize = ratio.ComputeRatioDependentSize(
4910 isInlineAxis ? LogicalAxis::Inline : LogicalAxis::Block, childWM,
4911 h, *contentBoxSizeToBoxSizingAdjust);
4912 if (maxISize < result) {
4913 result = maxISize;
4915 if (maxISize < minContentSize) {
4916 minContentSize = maxISize;
4920 if (GetDefiniteSize(styleMinBSize, aFrame, !isInlineAxis,
4921 aPercentageBasis, &h) ||
4922 (aPercentageBasis.isNothing() &&
4923 GetPercentBSize(styleMinBSize, aFrame, horizontalAxis, h))) {
4924 h = std::max(0, h - bSizeTakenByBoxSizing);
4925 nscoord minISize = ratio.ComputeRatioDependentSize(
4926 isInlineAxis ? LogicalAxis::Inline : LogicalAxis::Block, childWM,
4927 h, *contentBoxSizeToBoxSizingAdjust);
4928 if (minISize > result) {
4929 result = minISize;
4931 if (minISize > minContentSize) {
4932 minContentSize = minISize;
4936 if (MOZ_UNLIKELY(aFlags & nsLayoutUtils::MIN_INTRINSIC_ISIZE) &&
4937 // FIXME: Bug 1715681. Should we use HasReplacedSizing instead
4938 // because IsReplaced is set on some other frames which are
4939 // non-replaced elements, e.g. <select>?
4940 aFrame->IsReplaced()) {
4941 // This is the 'min-width/height:auto' "transferred size" piece of:
4942 // https://drafts.csswg.org/css-flexbox-1/#min-size-auto
4943 // https://drafts.csswg.org/css-grid/#min-size-auto
4944 // Per spec, we handle it only for replaced elements.
4945 result = std::min(result, minContentSize);
4951 if (aFrame->IsTableFrame()) {
4952 // Tables can't shrink smaller than their intrinsic minimum width,
4953 // no matter what.
4954 min = aFrame->GetMinISize(aRenderingContext);
4957 // If we have an aspect-ratio and a definite block size of |aFrame|, we
4958 // resolve the {min|max}-content size by the aspect-ratio and the block size.
4959 // If |aAxis| is not the inline axis of |aFrame|, {min|max}-content should
4960 // behaves as auto, so we don't need this.
4962 // FIXME(emilio): For -moz-available it seems we shouldn't need this.
4964 // https://github.com/w3c/csswg-drafts/issues/5032
4965 // FIXME: Bug 1670151: Use GetAspectRatio() to cover replaced elements (and
4966 // then we can drop the check of eSupportsAspectRatio).
4967 const AspectRatio ar = stylePos->mAspectRatio.ToLayoutRatio();
4968 if (isInlineAxis && ar && nsIFrame::ToExtremumLength(styleISize) &&
4969 aFrame->SupportsAspectRatio() && !inlineSizeFromAspectRatio) {
4970 // This 'B' in |styleBSize| means the block size of |aFrame|. We go into
4971 // this branch only if |aAxis| is the inline axis of |aFrame|.
4972 const StyleSize& styleBSize =
4973 horizontalAxis ? stylePos->mHeight : stylePos->mWidth;
4974 nscoord bSize;
4975 if (GetDefiniteSize(styleBSize, aFrame, !isInlineAxis, aPercentageBasis,
4976 &bSize) ||
4977 (aPercentageBasis.isNothing() &&
4978 GetPercentBSize(styleBSize, aFrame, horizontalAxis, bSize))) {
4979 // We cannot reuse |boxSizing| because it may be updated to content-box
4980 // in the above if-branch.
4981 const StyleBoxSizing boxSizingForAR = stylePos->mBoxSizing;
4982 if (!contentBoxSizeToBoxSizingAdjust) {
4983 contentBoxSizeToBoxSizingAdjust.emplace(
4984 getContentBoxSizeToBoxSizingAdjust(boxSizingForAR));
4986 nscoord bSizeTakenByBoxSizing =
4987 GetDefiniteSizeTakenByBoxSizing(boxSizingForAR, aFrame, !isInlineAxis,
4988 ignorePadding, aPercentageBasis);
4989 bSize -= bSizeTakenByBoxSizing;
4990 inlineSizeFromAspectRatio.emplace(
4991 ar.ComputeRatioDependentSize(LogicalAxis::Inline, childWM, bSize,
4992 *contentBoxSizeToBoxSizingAdjust));
4996 nscoord contentBoxSize = result;
4997 result = AddIntrinsicSizeOffset(
4998 aRenderingContext, aFrame, offsets, aType, boxSizing, result, min,
4999 styleISize, haveFixedMinISize ? &minISize : nullptr, styleMinISize,
5000 haveFixedMaxISize ? &maxISize : nullptr, styleMaxISize,
5001 inlineSizeFromAspectRatio, aFlags, aAxis);
5002 nscoord overflow = result - aMarginBoxMinSizeClamp;
5003 if (MOZ_UNLIKELY(overflow > 0)) {
5004 nscoord newContentBoxSize = std::max(nscoord(0), contentBoxSize - overflow);
5005 result -= contentBoxSize - newContentBoxSize;
5008 #ifdef DEBUG_INTRINSIC_WIDTH
5009 nsIFrame::IndentBy(stderr, gNoiseIndent);
5010 aFrame->ListTag(stderr);
5011 printf_stderr(" %s %s intrinsic size for container is %d twips.\n",
5012 aType == IntrinsicISizeType::MinISize ? "min" : "pref",
5013 horizontalAxis ? "horizontal" : "vertical", result);
5014 #endif
5016 return result;
5019 /* static */
5020 nscoord nsLayoutUtils::IntrinsicForContainer(gfxContext* aRenderingContext,
5021 nsIFrame* aFrame,
5022 IntrinsicISizeType aType,
5023 uint32_t aFlags) {
5024 MOZ_ASSERT(aFrame && aFrame->GetParent());
5025 // We want the size aFrame will contribute to its parent's inline-size.
5026 PhysicalAxis axis =
5027 aFrame->GetParent()->GetWritingMode().PhysicalAxis(LogicalAxis::Inline);
5028 return IntrinsicForAxis(axis, aRenderingContext, aFrame, aType, Nothing(),
5029 aFlags);
5032 /* static */
5033 nscoord nsLayoutUtils::MinSizeContributionForAxis(
5034 PhysicalAxis aAxis, gfxContext* aRC, nsIFrame* aFrame,
5035 IntrinsicISizeType aType, const LogicalSize& aPercentageBasis,
5036 uint32_t aFlags) {
5037 MOZ_ASSERT(aFrame);
5038 MOZ_ASSERT(aFrame->IsFlexOrGridItem(),
5039 "only grid/flex items have this behavior currently");
5041 #ifdef DEBUG_INTRINSIC_WIDTH
5042 nsIFrame::IndentBy(stderr, gNoiseIndent);
5043 aFrame->ListTag(stderr);
5044 printf_stderr(" %s min-isize for %s WM:\n",
5045 aType == IntrinsicISizeType::MinISize ? "min" : "pref",
5046 aAxis == eAxisVertical ? "vertical" : "horizontal");
5047 #endif
5049 // Note: this method is only meant for grid/flex items.
5050 const nsStylePosition* const stylePos = aFrame->StylePosition();
5051 StyleSize size = aAxis == PhysicalAxis::Horizontal ? stylePos->mMinWidth
5052 : stylePos->mMinHeight;
5053 StyleMaxSize maxSize = aAxis == PhysicalAxis::Horizontal
5054 ? stylePos->mMaxWidth
5055 : stylePos->mMaxHeight;
5056 auto childWM = aFrame->GetWritingMode();
5057 PhysicalAxis ourInlineAxis = childWM.PhysicalAxis(LogicalAxis::Inline);
5058 // According to the spec, max-content and min-content should behave as the
5059 // property's initial values in block axis.
5060 // It also make senses to use the initial values for -moz-fit-content and
5061 // -moz-available for intrinsic size in block axis. Therefore, we reset them
5062 // if needed.
5063 if (aAxis != ourInlineAxis) {
5064 if (size.BehavesLikeInitialValueOnBlockAxis()) {
5065 size = StyleSize::Auto();
5067 if (maxSize.BehavesLikeInitialValueOnBlockAxis()) {
5068 maxSize = StyleMaxSize::None();
5072 nscoord minSize;
5073 nscoord* fixedMinSize = nullptr;
5074 if (size.IsAuto()) {
5075 if (aFrame->StyleDisplay()->IsScrollableOverflow()) {
5076 // min-[width|height]:auto with scrollable overflow computes to
5077 // zero.
5078 minSize = 0;
5079 fixedMinSize = &minSize;
5080 } else {
5081 size = aAxis == PhysicalAxis::Horizontal ? stylePos->mWidth
5082 : stylePos->mHeight;
5083 // This is same as above: keywords should behaves as property's initial
5084 // values in block axis.
5085 if (aAxis != ourInlineAxis && size.BehavesLikeInitialValueOnBlockAxis()) {
5086 size = StyleSize::Auto();
5089 if (GetAbsoluteCoord(size, minSize)) {
5090 // We have a definite width/height. This is the "specified size" in:
5091 // https://drafts.csswg.org/css-grid/#min-size-auto
5092 fixedMinSize = &minSize;
5093 } else if (aFrame->IsPercentageResolvedAgainstZero(size, maxSize)) {
5094 // XXX bug 1463700: this doesn't handle calc() according to spec
5095 minSize = 0;
5096 fixedMinSize = &minSize;
5098 // fall through - the caller will have to deal with "transferred size"
5100 } else if (GetAbsoluteCoord(size, minSize)) {
5101 fixedMinSize = &minSize;
5102 } else if (size.IsLengthPercentage()) {
5103 MOZ_ASSERT(size.HasPercent());
5104 minSize = 0;
5105 fixedMinSize = &minSize;
5108 if (!fixedMinSize) {
5109 // Let the caller deal with the "content size" cases.
5110 #ifdef DEBUG_INTRINSIC_WIDTH
5111 nsIFrame::IndentBy(stderr, gNoiseIndent);
5112 aFrame->ListTag(stderr);
5113 printf_stderr(" %s min-isize is indefinite.\n",
5114 aType == IntrinsicISizeType::MinISize ? "min" : "pref");
5115 #endif
5116 return NS_UNCONSTRAINEDSIZE;
5119 // If aFrame is a container for font size inflation, then shrink
5120 // wrapping inside of it should not apply font size inflation.
5121 AutoMaybeDisableFontInflation an(aFrame);
5123 // The padding/margin percentage basis is the inline-size in the parent's
5124 // writing-mode.
5125 nscoord pmPercentageBasis =
5126 aFrame->GetParent()->GetWritingMode().IsOrthogonalTo(childWM)
5127 ? aPercentageBasis.BSize(childWM)
5128 : aPercentageBasis.ISize(childWM);
5129 nsIFrame::IntrinsicSizeOffsetData offsets =
5130 ourInlineAxis == aAxis ? aFrame->IntrinsicISizeOffsets(pmPercentageBasis)
5131 : aFrame->IntrinsicBSizeOffsets(pmPercentageBasis);
5132 nscoord result = 0;
5133 nscoord min = 0;
5134 // Note: aInlineSizeFromAspectRatio is Nothing() here because we don't handle
5135 // "content size" cases here (i.e. |fixedMinSize| is false here).
5136 result = AddIntrinsicSizeOffset(
5137 aRC, aFrame, offsets, aType, stylePos->mBoxSizing, result, min, size,
5138 fixedMinSize, size, nullptr, maxSize, Nothing(), aFlags, aAxis);
5140 #ifdef DEBUG_INTRINSIC_WIDTH
5141 nsIFrame::IndentBy(stderr, gNoiseIndent);
5142 aFrame->ListTag(stderr);
5143 printf_stderr(" %s min-isize is %d twips.\n",
5144 aType == IntrinsicISizeType::MinISize ? "min" : "pref", result);
5145 #endif
5147 return result;
5150 /* static */
5151 nscoord nsLayoutUtils::ComputeBSizeDependentValue(
5152 nscoord aContainingBlockBSize, const LengthPercentageOrAuto& aCoord) {
5153 // XXXldb Some callers explicitly check aContainingBlockBSize
5154 // against NS_UNCONSTRAINEDSIZE *and* unit against eStyleUnit_Percent or
5155 // calc()s containing percents before calling this function.
5156 // However, it would be much more likely to catch problems without
5157 // the unit conditions.
5158 // XXXldb Many callers pass a non-'auto' containing block height when
5159 // according to CSS2.1 they should be passing 'auto'.
5160 NS_ASSERTION(
5161 NS_UNCONSTRAINEDSIZE != aContainingBlockBSize || !aCoord.HasPercent(),
5162 "unexpected containing block block-size");
5164 if (aCoord.IsAuto()) {
5165 return 0;
5168 return aCoord.AsLengthPercentage().Resolve(aContainingBlockBSize);
5171 /* static */
5172 void nsLayoutUtils::MarkDescendantsDirty(nsIFrame* aSubtreeRoot) {
5173 AutoTArray<nsIFrame*, 4> subtrees;
5174 subtrees.AppendElement(aSubtreeRoot);
5176 // dirty descendants, iterating over subtrees that may include
5177 // additional subtrees associated with placeholders
5178 do {
5179 nsIFrame* subtreeRoot = subtrees.PopLastElement();
5181 // Mark all descendants dirty (using an nsTArray stack rather than
5182 // recursion).
5183 // Note that ReflowInput::InitResizeFlags has some similar
5184 // code; see comments there for how and why it differs.
5185 AutoTArray<nsIFrame*, 32> stack;
5186 stack.AppendElement(subtreeRoot);
5188 do {
5189 nsIFrame* f = stack.PopLastElement();
5191 f->MarkIntrinsicISizesDirty();
5193 if (f->IsPlaceholderFrame()) {
5194 nsIFrame* oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
5195 if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {
5196 // We have another distinct subtree we need to mark.
5197 subtrees.AppendElement(oof);
5201 for (const auto& childList : f->ChildLists()) {
5202 for (nsIFrame* kid : childList.mList) {
5203 stack.AppendElement(kid);
5206 } while (stack.Length() != 0);
5207 } while (subtrees.Length() != 0);
5210 /* static */
5211 void nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(
5212 nsIFrame* aFrame) {
5213 AutoTArray<nsIFrame*, 32> stack;
5214 stack.AppendElement(aFrame);
5216 do {
5217 nsIFrame* f = stack.PopLastElement();
5219 if (!f->HasAnyStateBits(
5220 NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
5221 continue;
5223 f->MarkIntrinsicISizesDirty();
5225 for (const auto& childList : f->ChildLists()) {
5226 for (nsIFrame* kid : childList.mList) {
5227 stack.AppendElement(kid);
5230 } while (stack.Length() != 0);
5233 nsSize nsLayoutUtils::ComputeAutoSizeWithIntrinsicDimensions(
5234 nscoord minWidth, nscoord minHeight, nscoord maxWidth, nscoord maxHeight,
5235 nscoord tentWidth, nscoord tentHeight) {
5236 // Now apply min/max-width/height - CSS 2.1 sections 10.4 and 10.7:
5238 if (minWidth > maxWidth) maxWidth = minWidth;
5239 if (minHeight > maxHeight) maxHeight = minHeight;
5241 nscoord heightAtMaxWidth, heightAtMinWidth, widthAtMaxHeight,
5242 widthAtMinHeight;
5244 if (tentWidth > 0) {
5245 heightAtMaxWidth = NSCoordMulDiv(maxWidth, tentHeight, tentWidth);
5246 if (heightAtMaxWidth < minHeight) heightAtMaxWidth = minHeight;
5247 heightAtMinWidth = NSCoordMulDiv(minWidth, tentHeight, tentWidth);
5248 if (heightAtMinWidth > maxHeight) heightAtMinWidth = maxHeight;
5249 } else {
5250 heightAtMaxWidth = heightAtMinWidth =
5251 NS_CSS_MINMAX(tentHeight, minHeight, maxHeight);
5254 if (tentHeight > 0) {
5255 widthAtMaxHeight = NSCoordMulDiv(maxHeight, tentWidth, tentHeight);
5256 if (widthAtMaxHeight < minWidth) widthAtMaxHeight = minWidth;
5257 widthAtMinHeight = NSCoordMulDiv(minHeight, tentWidth, tentHeight);
5258 if (widthAtMinHeight > maxWidth) widthAtMinHeight = maxWidth;
5259 } else {
5260 widthAtMaxHeight = widthAtMinHeight =
5261 NS_CSS_MINMAX(tentWidth, minWidth, maxWidth);
5264 // The table at http://www.w3.org/TR/CSS21/visudet.html#min-max-widths :
5266 nscoord width, height;
5268 if (tentWidth > maxWidth) {
5269 if (tentHeight > maxHeight) {
5270 if (int64_t(maxWidth) * int64_t(tentHeight) <=
5271 int64_t(maxHeight) * int64_t(tentWidth)) {
5272 width = maxWidth;
5273 height = heightAtMaxWidth;
5274 } else {
5275 width = widthAtMaxHeight;
5276 height = maxHeight;
5278 } else {
5279 // This also covers "(w > max-width) and (h < min-height)" since in
5280 // that case (max-width/w < 1), and with (h < min-height):
5281 // max(max-width * h/w, min-height) == min-height
5282 width = maxWidth;
5283 height = heightAtMaxWidth;
5285 } else if (tentWidth < minWidth) {
5286 if (tentHeight < minHeight) {
5287 if (int64_t(minWidth) * int64_t(tentHeight) <=
5288 int64_t(minHeight) * int64_t(tentWidth)) {
5289 width = widthAtMinHeight;
5290 height = minHeight;
5291 } else {
5292 width = minWidth;
5293 height = heightAtMinWidth;
5295 } else {
5296 // This also covers "(w < min-width) and (h > max-height)" since in
5297 // that case (min-width/w > 1), and with (h > max-height):
5298 // min(min-width * h/w, max-height) == max-height
5299 width = minWidth;
5300 height = heightAtMinWidth;
5302 } else {
5303 if (tentHeight > maxHeight) {
5304 width = widthAtMaxHeight;
5305 height = maxHeight;
5306 } else if (tentHeight < minHeight) {
5307 width = widthAtMinHeight;
5308 height = minHeight;
5309 } else {
5310 width = tentWidth;
5311 height = tentHeight;
5315 return nsSize(width, height);
5318 /* static */
5319 nscoord nsLayoutUtils::MinISizeFromInline(nsIFrame* aFrame,
5320 gfxContext* aRenderingContext) {
5321 NS_ASSERTION(!aFrame->IsContainerForFontSizeInflation(),
5322 "should not be container for font size inflation");
5324 nsIFrame::InlineMinISizeData data;
5325 aFrame->AddInlineMinISize(aRenderingContext, &data);
5326 data.ForceBreak();
5327 return data.mPrevLines;
5330 /* static */
5331 nscoord nsLayoutUtils::PrefISizeFromInline(nsIFrame* aFrame,
5332 gfxContext* aRenderingContext) {
5333 NS_ASSERTION(!aFrame->IsContainerForFontSizeInflation(),
5334 "should not be container for font size inflation");
5336 nsIFrame::InlinePrefISizeData data;
5337 aFrame->AddInlinePrefISize(aRenderingContext, &data);
5338 data.ForceBreak();
5339 return data.mPrevLines;
5342 static nscolor DarkenColor(nscolor aColor) {
5343 uint16_t hue, sat, value;
5344 uint8_t alpha;
5346 // convert the RBG to HSV so we can get the lightness (which is the v)
5347 NS_RGB2HSV(aColor, hue, sat, value, alpha);
5349 // The goal here is to send white to black while letting colored
5350 // stuff stay colored... So we adopt the following approach.
5351 // Something with sat = 0 should end up with value = 0. Something
5352 // with a high sat can end up with a high value and it's ok.... At
5353 // the same time, we don't want to make things lighter. Do
5354 // something simple, since it seems to work.
5355 if (value > sat) {
5356 value = sat;
5357 // convert this color back into the RGB color space.
5358 NS_HSV2RGB(aColor, hue, sat, value, alpha);
5360 return aColor;
5363 // Check whether we should darken text/decoration colors. We need to do this if
5364 // background images and colors are being suppressed, because that means
5365 // light text will not be visible against the (presumed light-colored)
5366 // background.
5367 static bool ShouldDarkenColors(nsIFrame* aFrame) {
5368 nsPresContext* pc = aFrame->PresContext();
5369 if (pc->GetBackgroundColorDraw() || pc->GetBackgroundImageDraw()) {
5370 return false;
5372 return aFrame->StyleVisibility()->mPrintColorAdjust !=
5373 StylePrintColorAdjust::Exact;
5376 nscolor nsLayoutUtils::DarkenColorIfNeeded(nsIFrame* aFrame, nscolor aColor) {
5377 return ShouldDarkenColors(aFrame) ? DarkenColor(aColor) : aColor;
5380 gfxFloat nsLayoutUtils::GetSnappedBaselineY(nsIFrame* aFrame,
5381 gfxContext* aContext, nscoord aY,
5382 nscoord aAscent) {
5383 gfxFloat appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
5384 gfxFloat baseline = gfxFloat(aY) + aAscent;
5385 gfxRect putativeRect(0, baseline / appUnitsPerDevUnit, 1, 1);
5386 if (!aContext->UserToDevicePixelSnapped(
5387 putativeRect, gfxContext::SnapOption::IgnoreScale)) {
5388 return baseline;
5390 return aContext->DeviceToUser(putativeRect.TopLeft()).y * appUnitsPerDevUnit;
5393 gfxFloat nsLayoutUtils::GetSnappedBaselineX(nsIFrame* aFrame,
5394 gfxContext* aContext, nscoord aX,
5395 nscoord aAscent) {
5396 gfxFloat appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
5397 gfxFloat baseline = gfxFloat(aX) + aAscent;
5398 gfxRect putativeRect(baseline / appUnitsPerDevUnit, 0, 1, 1);
5399 if (!aContext->UserToDevicePixelSnapped(
5400 putativeRect, gfxContext::SnapOption::IgnoreScale)) {
5401 return baseline;
5403 return aContext->DeviceToUser(putativeRect.TopLeft()).x * appUnitsPerDevUnit;
5406 // Hard limit substring lengths to 8000 characters ... this lets us statically
5407 // size the cluster buffer array in FindSafeLength
5408 #define MAX_GFX_TEXT_BUF_SIZE 8000
5410 static int32_t FindSafeLength(const char16_t* aString, uint32_t aLength,
5411 uint32_t aMaxChunkLength) {
5412 if (aLength <= aMaxChunkLength) return aLength;
5414 int32_t len = aMaxChunkLength;
5416 // Ensure that we don't break inside a surrogate pair
5417 while (len > 0 && NS_IS_LOW_SURROGATE(aString[len])) {
5418 len--;
5420 if (len == 0) {
5421 // We don't want our caller to go into an infinite loop, so don't
5422 // return zero. It's hard to imagine how we could actually get here
5423 // unless there are languages that allow clusters of arbitrary size.
5424 // If there are and someone feeds us a 500+ character cluster, too
5425 // bad.
5426 return aMaxChunkLength;
5428 return len;
5431 static int32_t GetMaxChunkLength(nsFontMetrics& aFontMetrics) {
5432 return std::min(aFontMetrics.GetMaxStringLength(), MAX_GFX_TEXT_BUF_SIZE);
5435 nscoord nsLayoutUtils::AppUnitWidthOfString(const char16_t* aString,
5436 uint32_t aLength,
5437 nsFontMetrics& aFontMetrics,
5438 DrawTarget* aDrawTarget) {
5439 uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
5440 nscoord width = 0;
5441 while (aLength > 0) {
5442 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
5443 width += aFontMetrics.GetWidth(aString, len, aDrawTarget);
5444 aLength -= len;
5445 aString += len;
5447 return width;
5450 nscoord nsLayoutUtils::AppUnitWidthOfStringBidi(const char16_t* aString,
5451 uint32_t aLength,
5452 const nsIFrame* aFrame,
5453 nsFontMetrics& aFontMetrics,
5454 gfxContext& aContext) {
5455 nsPresContext* presContext = aFrame->PresContext();
5456 if (presContext->BidiEnabled()) {
5457 mozilla::intl::BidiEmbeddingLevel level =
5458 nsBidiPresUtils::BidiLevelFromStyle(aFrame->Style());
5459 return nsBidiPresUtils::MeasureTextWidth(
5460 aString, aLength, level, presContext, aContext, aFontMetrics);
5462 aFontMetrics.SetTextRunRTL(false);
5463 aFontMetrics.SetVertical(aFrame->GetWritingMode().IsVertical());
5464 aFontMetrics.SetTextOrientation(aFrame->StyleVisibility()->mTextOrientation);
5465 return nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics,
5466 aContext.GetDrawTarget());
5469 bool nsLayoutUtils::StringWidthIsGreaterThan(const nsString& aString,
5470 nsFontMetrics& aFontMetrics,
5471 DrawTarget* aDrawTarget,
5472 nscoord aWidth) {
5473 const char16_t* string = aString.get();
5474 uint32_t length = aString.Length();
5475 uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
5476 nscoord width = 0;
5477 while (length > 0) {
5478 int32_t len = FindSafeLength(string, length, maxChunkLength);
5479 width += aFontMetrics.GetWidth(string, len, aDrawTarget);
5480 if (width > aWidth) {
5481 return true;
5483 length -= len;
5484 string += len;
5486 return false;
5489 nsBoundingMetrics nsLayoutUtils::AppUnitBoundsOfString(
5490 const char16_t* aString, uint32_t aLength, nsFontMetrics& aFontMetrics,
5491 DrawTarget* aDrawTarget) {
5492 uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
5493 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
5494 // Assign directly in the first iteration. This ensures that
5495 // negative ascent/descent can be returned and the left bearing
5496 // is properly initialized.
5497 nsBoundingMetrics totalMetrics =
5498 aFontMetrics.GetBoundingMetrics(aString, len, aDrawTarget);
5499 aLength -= len;
5500 aString += len;
5502 while (aLength > 0) {
5503 len = FindSafeLength(aString, aLength, maxChunkLength);
5504 nsBoundingMetrics metrics =
5505 aFontMetrics.GetBoundingMetrics(aString, len, aDrawTarget);
5506 totalMetrics += metrics;
5507 aLength -= len;
5508 aString += len;
5510 return totalMetrics;
5513 void nsLayoutUtils::DrawString(const nsIFrame* aFrame,
5514 nsFontMetrics& aFontMetrics,
5515 gfxContext* aContext, const char16_t* aString,
5516 int32_t aLength, nsPoint aPoint,
5517 ComputedStyle* aComputedStyle,
5518 DrawStringFlags aFlags) {
5519 nsresult rv = NS_ERROR_FAILURE;
5521 // If caller didn't pass a style, use the frame's.
5522 if (!aComputedStyle) {
5523 aComputedStyle = aFrame->Style();
5526 if (aFlags & DrawStringFlags::ForceHorizontal) {
5527 aFontMetrics.SetVertical(false);
5528 } else {
5529 aFontMetrics.SetVertical(WritingMode(aComputedStyle).IsVertical());
5532 aFontMetrics.SetTextOrientation(
5533 aComputedStyle->StyleVisibility()->mTextOrientation);
5535 nsPresContext* presContext = aFrame->PresContext();
5536 if (presContext->BidiEnabled()) {
5537 mozilla::intl::BidiEmbeddingLevel level =
5538 nsBidiPresUtils::BidiLevelFromStyle(aComputedStyle);
5539 rv = nsBidiPresUtils::RenderText(aString, aLength, level, presContext,
5540 *aContext, aContext->GetDrawTarget(),
5541 aFontMetrics, aPoint.x, aPoint.y);
5543 if (NS_FAILED(rv)) {
5544 aFontMetrics.SetTextRunRTL(false);
5545 DrawUniDirString(aString, aLength, aPoint, aFontMetrics, *aContext);
5549 void nsLayoutUtils::DrawUniDirString(const char16_t* aString, uint32_t aLength,
5550 const nsPoint& aPoint,
5551 nsFontMetrics& aFontMetrics,
5552 gfxContext& aContext) {
5553 nscoord x = aPoint.x;
5554 nscoord y = aPoint.y;
5556 uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
5557 if (aLength <= maxChunkLength) {
5558 aFontMetrics.DrawString(aString, aLength, x, y, &aContext,
5559 aContext.GetDrawTarget());
5560 return;
5563 bool isRTL = aFontMetrics.GetTextRunRTL();
5565 // If we're drawing right to left, we must start at the end.
5566 if (isRTL) {
5567 x += nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics,
5568 aContext.GetDrawTarget());
5571 while (aLength > 0) {
5572 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
5573 nscoord width =
5574 aFontMetrics.GetWidth(aString, len, aContext.GetDrawTarget());
5575 if (isRTL) {
5576 x -= width;
5578 aFontMetrics.DrawString(aString, len, x, y, &aContext,
5579 aContext.GetDrawTarget());
5580 if (!isRTL) {
5581 x += width;
5583 aLength -= len;
5584 aString += len;
5588 /* static */
5589 void nsLayoutUtils::PaintTextShadow(
5590 const nsIFrame* aFrame, gfxContext* aContext, const nsRect& aTextRect,
5591 const nsRect& aDirtyRect, const nscolor& aForegroundColor,
5592 TextShadowCallback aCallback, void* aCallbackData) {
5593 const nsStyleText* textStyle = aFrame->StyleText();
5594 auto shadows = textStyle->mTextShadow.AsSpan();
5595 if (shadows.IsEmpty()) {
5596 return;
5599 // Text shadow happens with the last value being painted at the back,
5600 // ie. it is painted first.
5601 gfxContext* aDestCtx = aContext;
5602 for (auto& shadow : Reversed(shadows)) {
5603 nsPoint shadowOffset(shadow.horizontal.ToAppUnits(),
5604 shadow.vertical.ToAppUnits());
5605 nscoord blurRadius = std::max(shadow.blur.ToAppUnits(), 0);
5607 nsRect shadowRect(aTextRect);
5608 shadowRect.MoveBy(shadowOffset);
5610 nsPresContext* presCtx = aFrame->PresContext();
5611 nsContextBoxBlur contextBoxBlur;
5613 nscolor shadowColor = shadow.color.CalcColor(aForegroundColor);
5615 // Webrender just needs the shadow details
5616 if (auto* textDrawer = aContext->GetTextDrawer()) {
5617 wr::Shadow wrShadow;
5619 wrShadow.offset = {
5620 presCtx->AppUnitsToFloatDevPixels(shadow.horizontal.ToAppUnits()),
5621 presCtx->AppUnitsToFloatDevPixels(shadow.vertical.ToAppUnits())};
5623 wrShadow.blur_radius = presCtx->AppUnitsToFloatDevPixels(blurRadius);
5624 wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
5626 // Gecko already inflates the bounding rect of text shadows,
5627 // so tell WR not to inflate again.
5628 bool inflate = false;
5629 textDrawer->AppendShadow(wrShadow, inflate);
5630 continue;
5633 gfxContext* shadowContext = contextBoxBlur.Init(
5634 shadowRect, 0, blurRadius, presCtx->AppUnitsPerDevPixel(), aDestCtx,
5635 aDirtyRect, nullptr,
5636 nsContextBoxBlur::DISABLE_HARDWARE_ACCELERATION_BLUR);
5637 if (!shadowContext) continue;
5639 aDestCtx->Save();
5640 aDestCtx->NewPath();
5641 aDestCtx->SetColor(sRGBColor::FromABGR(shadowColor));
5643 // The callback will draw whatever we want to blur as a shadow.
5644 aCallback(shadowContext, shadowOffset, shadowColor, aCallbackData);
5646 contextBoxBlur.DoPaint();
5647 aDestCtx->Restore();
5651 /* static */
5652 nscoord nsLayoutUtils::GetCenteredFontBaseline(nsFontMetrics* aFontMetrics,
5653 nscoord aLineHeight,
5654 bool aIsInverted) {
5655 nscoord fontAscent =
5656 aIsInverted ? aFontMetrics->MaxDescent() : aFontMetrics->MaxAscent();
5657 nscoord fontHeight = aFontMetrics->MaxHeight();
5659 nscoord leading = aLineHeight - fontHeight;
5660 return fontAscent + leading / 2;
5663 /* static */
5664 bool nsLayoutUtils::GetFirstLineBaseline(WritingMode aWritingMode,
5665 const nsIFrame* aFrame,
5666 nscoord* aResult) {
5667 LinePosition position;
5668 if (!GetFirstLinePosition(aWritingMode, aFrame, &position)) return false;
5669 *aResult = position.mBaseline;
5670 return true;
5673 /* static */
5674 bool nsLayoutUtils::GetFirstLinePosition(WritingMode aWM,
5675 const nsIFrame* aFrame,
5676 LinePosition* aResult) {
5677 if (aFrame->StyleDisplay()->IsContainLayout()) {
5678 return false;
5680 const nsBlockFrame* block = do_QueryFrame(aFrame);
5681 if (!block) {
5682 // For the first-line baseline we also have to check for a table, and if
5683 // so, use the baseline of its first row.
5684 LayoutFrameType fType = aFrame->Type();
5685 if (fType == LayoutFrameType::TableWrapper ||
5686 fType == LayoutFrameType::FlexContainer ||
5687 fType == LayoutFrameType::GridContainer) {
5688 if ((fType == LayoutFrameType::GridContainer &&
5689 aFrame->HasAnyStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE)) ||
5690 (fType == LayoutFrameType::FlexContainer &&
5691 aFrame->HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) ||
5692 (fType == LayoutFrameType::TableWrapper &&
5693 static_cast<const nsTableWrapperFrame*>(aFrame)->GetRowCount() ==
5694 0)) {
5695 // empty grid/flex/table container
5696 aResult->mBStart = 0;
5697 aResult->mBaseline = Baseline::SynthesizeBOffsetFromBorderBox(
5698 aFrame, aWM, BaselineSharingGroup::First);
5699 aResult->mBEnd = aFrame->BSize(aWM);
5700 return true;
5702 if (fType == LayoutFrameType::TableWrapper &&
5703 aFrame->GetWritingMode().IsOrthogonalTo(aWM)) {
5704 // For tables, the upcoming GetLogicalBaseline call would determine the
5705 // table's baseline from its first row that has a baseline. However:
5706 // this doesn't make sense for an orthogonal writing mode, so in that
5707 // case we report no baseline instead. The table wrapper and its rows
5708 // should flow the same way, so we can bail out early, but this logic
5709 // wouldn't be correct to transplant into other places in the codebase
5710 // (Depending on how bug 1786633 is resolved).
5711 return false;
5713 aResult->mBStart = 0;
5714 aResult->mBaseline = aFrame->GetLogicalBaseline(aWM);
5715 // This is what we want for the list bullet caller; not sure if
5716 // other future callers will want the same.
5717 aResult->mBEnd = aFrame->BSize(aWM);
5718 return true;
5721 // For first-line baselines, we have to consider scroll frames.
5722 if (nsIScrollableFrame* sFrame =
5723 do_QueryFrame(const_cast<nsIFrame*>(aFrame))) {
5724 LinePosition kidPosition;
5725 if (GetFirstLinePosition(aWM, sFrame->GetScrolledFrame(), &kidPosition)) {
5726 // Consider only the border (Padding is ignored, since
5727 // `-moz-scrolled-content` inherits and handles the padding) that
5728 // contributes to the kid's position, not the scrolling, so we get the
5729 // initial position.
5730 *aResult = kidPosition + aFrame->GetLogicalUsedBorder(aWM).BStart(aWM);
5731 // Don't want to move the line's block positioning, but the baseline
5732 // needs to be clamped (See bug 1791069).
5733 aResult->mBaseline = std::clamp(aResult->mBaseline, 0,
5734 aFrame->GetLogicalSize(aWM).BSize(aWM));
5735 return true;
5737 return false;
5740 if (fType == LayoutFrameType::FieldSet) {
5741 LinePosition kidPosition;
5742 // Get the first baseline from the fieldset content, not from the legend.
5743 nsIFrame* kid = static_cast<const nsFieldSetFrame*>(aFrame)->GetInner();
5744 if (kid && GetFirstLinePosition(aWM, kid, &kidPosition)) {
5745 *aResult = kidPosition +
5746 kid->GetLogicalNormalPosition(aWM, aFrame->GetSize()).B(aWM);
5747 return true;
5749 return false;
5752 if (fType == LayoutFrameType::ColumnSet) {
5753 // Note(dshin): This is basically the same as
5754 // `nsColumnSetFrame::GetNaturalBaselineBOffset`, but with line start and
5755 // end, all stored in `LinePosition`. Field value apart from baseline is
5756 // used in one other place
5757 // (`nsBlockFrame`) - if that goes away, this becomes a duplication that
5758 // should be removed.
5759 LinePosition kidPosition;
5760 for (const auto* kid : aFrame->PrincipalChildList()) {
5761 LinePosition position;
5762 if (!GetFirstLinePosition(aWM, kid, &position)) {
5763 continue;
5765 if (position.mBaseline < kidPosition.mBaseline) {
5766 kidPosition = position;
5769 if (kidPosition.mBaseline != nscoord_MAX) {
5770 *aResult = kidPosition;
5771 return true;
5775 // No baseline.
5776 return false;
5779 for (const auto& line : block->Lines()) {
5780 if (line.IsBlock()) {
5781 const nsIFrame* kid = line.mFirstChild;
5782 LinePosition kidPosition;
5783 if (GetFirstLinePosition(aWM, kid, &kidPosition)) {
5784 // XXX Not sure if this is the correct value to use for container
5785 // width here. It will only be used in vertical-rl layout,
5786 // which we don't have full support and testing for yet.
5787 const auto& containerSize = line.mContainerSize;
5788 *aResult = kidPosition +
5789 kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
5790 return true;
5792 } else {
5793 // XXX Is this the right test? We have some bogus empty lines
5794 // floating around, but IsEmpty is perhaps too weak.
5795 if (0 != line.BSize() || !line.IsEmpty()) {
5796 nscoord bStart = line.BStart();
5797 aResult->mBStart = bStart;
5798 aResult->mBaseline = bStart + line.GetLogicalAscent();
5799 aResult->mBEnd = bStart + line.BSize();
5800 return true;
5804 return false;
5807 /* static */
5808 bool nsLayoutUtils::GetLastLineBaseline(WritingMode aWM, const nsIFrame* aFrame,
5809 nscoord* aResult) {
5810 if (aFrame->StyleDisplay()->IsContainLayout()) {
5811 return false;
5814 const nsBlockFrame* block = do_QueryFrame(aFrame);
5815 if (!block) {
5816 if (nsIScrollableFrame* sFrame = do_QueryFrame(aFrame)) {
5817 // Use the baseline position only if the last line's baseline is within
5818 // the scrolling frame's box in the initial position.
5819 const auto* scrolledFrame = sFrame->GetScrolledFrame();
5820 if (!GetLastLineBaseline(aWM, scrolledFrame, aResult)) {
5821 return false;
5823 // Go from scrolled frame to scrollable frame position.
5824 *aResult += aFrame->GetLogicalUsedBorder(aWM).BStart(aWM);
5825 const auto maxBaseline = aFrame->GetLogicalSize(aWM).BSize(aWM);
5826 // Clamp the last baseline to border (See bug 1791069).
5827 *aResult = std::clamp(*aResult, 0, maxBaseline);
5828 return true;
5831 // No need to duplicate the baseline logic (Unlike `GetFirstLinePosition`,
5832 // we don't need to return any other value apart from baseline), just defer
5833 // to `GetNaturalBaselineBOffset`. Technically, we could do this at
5834 // `ColumnSetWrapperFrame` level, but this keeps it symmetric to
5835 // `GetFirstLinePosition`.
5836 if (aFrame->IsColumnSetFrame()) {
5837 const auto baseline = aFrame->GetNaturalBaselineBOffset(
5838 aWM, BaselineSharingGroup::Last, BaselineExportContext::Other);
5839 if (!baseline) {
5840 return false;
5842 *aResult = aFrame->BSize(aWM) - *baseline;
5843 return true;
5845 // No baseline.
5846 return false;
5849 for (nsBlockFrame::ConstReverseLineIterator line = block->LinesRBegin(),
5850 line_end = block->LinesREnd();
5851 line != line_end; ++line) {
5852 if (line->IsBlock()) {
5853 nsIFrame* kid = line->mFirstChild;
5854 nscoord kidBaseline;
5855 const nsSize& containerSize = line->mContainerSize;
5856 if (GetLastLineBaseline(aWM, kid, &kidBaseline)) {
5857 // Ignore relative positioning for baseline calculations
5858 *aResult = kidBaseline +
5859 kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
5860 return true;
5862 if (kid->IsScrollFrame()) {
5863 // Defer to nsIFrame::GetLogicalBaseline (which synthesizes a baseline
5864 // from the margin-box).
5865 kidBaseline = kid->GetLogicalBaseline(aWM);
5866 *aResult = kidBaseline +
5867 kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
5868 return true;
5870 } else {
5871 // XXX Is this the right test? We have some bogus empty lines
5872 // floating around, but IsEmpty is perhaps too weak.
5873 if (line->BSize() != 0 || !line->IsEmpty()) {
5874 *aResult = line->BStart() + line->GetLogicalAscent();
5875 return true;
5879 return false;
5882 static nscoord CalculateBlockContentBEnd(WritingMode aWM,
5883 nsBlockFrame* aFrame) {
5884 MOZ_ASSERT(aFrame, "null ptr");
5886 nscoord contentBEnd = 0;
5888 for (const auto& line : aFrame->Lines()) {
5889 if (line.IsBlock()) {
5890 nsIFrame* child = line.mFirstChild;
5891 const auto& containerSize = line.mContainerSize;
5892 nscoord offset =
5893 child->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
5894 contentBEnd =
5895 std::max(contentBEnd,
5896 nsLayoutUtils::CalculateContentBEnd(aWM, child) + offset);
5897 } else {
5898 contentBEnd = std::max(contentBEnd, line.BEnd());
5901 return contentBEnd;
5904 /* static */
5905 nscoord nsLayoutUtils::CalculateContentBEnd(WritingMode aWM, nsIFrame* aFrame) {
5906 MOZ_ASSERT(aFrame, "null ptr");
5908 nscoord contentBEnd = aFrame->BSize(aWM);
5910 // We want scrollable overflow rather than visual because this
5911 // calculation is intended to affect layout.
5912 LogicalSize overflowSize(aWM, aFrame->ScrollableOverflowRect().Size());
5913 if (overflowSize.BSize(aWM) > contentBEnd) {
5914 FrameChildListIDs skip = {FrameChildListID::Overflow,
5915 FrameChildListID::ExcessOverflowContainers,
5916 FrameChildListID::OverflowOutOfFlow};
5917 nsBlockFrame* blockFrame = do_QueryFrame(aFrame);
5918 if (blockFrame) {
5919 contentBEnd =
5920 std::max(contentBEnd, CalculateBlockContentBEnd(aWM, blockFrame));
5921 skip += FrameChildListID::Principal;
5923 for (const auto& [list, listID] : aFrame->ChildLists()) {
5924 if (!skip.contains(listID)) {
5925 for (nsIFrame* child : list) {
5926 nscoord offset =
5927 child->GetLogicalNormalPosition(aWM, aFrame->GetSize()).B(aWM);
5928 contentBEnd =
5929 std::max(contentBEnd, CalculateContentBEnd(aWM, child) + offset);
5934 return contentBEnd;
5937 /* static */
5938 nsIFrame* nsLayoutUtils::GetClosestLayer(nsIFrame* aFrame) {
5939 nsIFrame* layer;
5940 for (layer = aFrame; layer; layer = layer->GetParent()) {
5941 if (layer->IsAbsPosContainingBlock() ||
5942 (layer->GetParent() && layer->GetParent()->IsScrollFrame()))
5943 break;
5945 if (layer) return layer;
5946 return aFrame->PresShell()->GetRootFrame();
5949 SamplingFilter nsLayoutUtils::GetSamplingFilterForFrame(nsIFrame* aForFrame) {
5950 switch (aForFrame->UsedImageRendering()) {
5951 case StyleImageRendering::Smooth:
5952 case StyleImageRendering::Optimizequality:
5953 return SamplingFilter::LINEAR;
5954 case StyleImageRendering::CrispEdges:
5955 case StyleImageRendering::Optimizespeed:
5956 case StyleImageRendering::Pixelated:
5957 return SamplingFilter::POINT;
5958 case StyleImageRendering::Auto:
5959 return SamplingFilter::GOOD;
5961 MOZ_ASSERT_UNREACHABLE("Unknown image-rendering value");
5962 return SamplingFilter::GOOD;
5966 * Given an image being drawn into an appunit coordinate system, and
5967 * a point in that coordinate system, map the point back into image
5968 * pixel space.
5969 * @param aSize the size of the image, in pixels
5970 * @param aDest the rectangle that the image is being mapped into
5971 * @param aPt a point in the same coordinate system as the rectangle
5973 static gfxPoint MapToFloatImagePixels(const gfxSize& aSize,
5974 const gfxRect& aDest,
5975 const gfxPoint& aPt) {
5976 return gfxPoint(((aPt.x - aDest.X()) * aSize.width) / aDest.Width(),
5977 ((aPt.y - aDest.Y()) * aSize.height) / aDest.Height());
5981 * Given an image being drawn into an pixel-based coordinate system, and
5982 * a point in image space, map the point into the pixel-based coordinate
5983 * system.
5984 * @param aSize the size of the image, in pixels
5985 * @param aDest the rectangle that the image is being mapped into
5986 * @param aPt a point in image space
5988 static gfxPoint MapToFloatUserPixels(const gfxSize& aSize, const gfxRect& aDest,
5989 const gfxPoint& aPt) {
5990 return gfxPoint(aPt.x * aDest.Width() / aSize.width + aDest.X(),
5991 aPt.y * aDest.Height() / aSize.height + aDest.Y());
5994 /* static */
5995 gfxRect nsLayoutUtils::RectToGfxRect(const nsRect& aRect,
5996 int32_t aAppUnitsPerDevPixel) {
5997 return gfxRect(gfxFloat(aRect.x) / aAppUnitsPerDevPixel,
5998 gfxFloat(aRect.y) / aAppUnitsPerDevPixel,
5999 gfxFloat(aRect.width) / aAppUnitsPerDevPixel,
6000 gfxFloat(aRect.height) / aAppUnitsPerDevPixel);
6003 struct SnappedImageDrawingParameters {
6004 // A transform from image space to device space.
6005 gfxMatrix imageSpaceToDeviceSpace;
6006 // The size at which the image should be drawn (which may not be its
6007 // intrinsic size due to, for example, HQ scaling).
6008 nsIntSize size;
6009 // The region in tiled image space which will be drawn, with an associated
6010 // region to which sampling should be restricted.
6011 ImageRegion region;
6012 // The default viewport size for SVG images, which we use unless a different
6013 // one has been explicitly specified. This is the same as |size| except that
6014 // it does not take into account any transformation on the gfxContext we're
6015 // drawing to - for example, CSS transforms are not taken into account.
6016 CSSIntSize svgViewportSize;
6017 // Whether there's anything to draw at all.
6018 bool shouldDraw;
6020 SnappedImageDrawingParameters()
6021 : region(ImageRegion::Empty()), shouldDraw(false) {}
6023 SnappedImageDrawingParameters(const gfxMatrix& aImageSpaceToDeviceSpace,
6024 const nsIntSize& aSize,
6025 const ImageRegion& aRegion,
6026 const CSSIntSize& aSVGViewportSize)
6027 : imageSpaceToDeviceSpace(aImageSpaceToDeviceSpace),
6028 size(aSize),
6029 region(aRegion),
6030 svgViewportSize(aSVGViewportSize),
6031 shouldDraw(true) {}
6035 * Given two axis-aligned rectangles, returns the transformation that maps the
6036 * first onto the second.
6038 * @param aFrom The rect to be transformed.
6039 * @param aTo The rect that aFrom should be mapped onto by the transformation.
6041 static gfxMatrix TransformBetweenRects(const gfxRect& aFrom,
6042 const gfxRect& aTo) {
6043 MatrixScalesDouble scale(aTo.width / aFrom.width, aTo.height / aFrom.height);
6044 gfxPoint translation(aTo.x - aFrom.x * scale.xScale,
6045 aTo.y - aFrom.y * scale.yScale);
6046 return gfxMatrix(scale.xScale, 0, 0, scale.yScale, translation.x,
6047 translation.y);
6050 static nsRect TileNearRect(const nsRect& aAnyTile, const nsRect& aTargetRect) {
6051 nsPoint distance = aTargetRect.TopLeft() - aAnyTile.TopLeft();
6052 return aAnyTile + nsPoint(distance.x / aAnyTile.width * aAnyTile.width,
6053 distance.y / aAnyTile.height * aAnyTile.height);
6056 static gfxFloat StableRound(gfxFloat aValue) {
6057 // Values slightly less than 0.5 should round up like 0.5 would; we're
6058 // assuming they were meant to be 0.5.
6059 return floor(aValue + 0.5001);
6062 static gfxPoint StableRound(const gfxPoint& aPoint) {
6063 return gfxPoint(StableRound(aPoint.x), StableRound(aPoint.y));
6067 * Given a set of input parameters, compute certain output parameters
6068 * for drawing an image with the image snapping algorithm.
6069 * See https://wiki.mozilla.org/Gecko:Image_Snapping_and_Rendering
6071 * @see nsLayoutUtils::DrawImage() for the descriptions of input parameters
6073 static SnappedImageDrawingParameters ComputeSnappedImageDrawingParameters(
6074 gfxContext* aCtx, int32_t aAppUnitsPerDevPixel, const nsRect aDest,
6075 const nsRect aFill, const nsPoint aAnchor, const nsRect aDirty,
6076 imgIContainer* aImage, const SamplingFilter aSamplingFilter,
6077 uint32_t aImageFlags, ExtendMode aExtendMode) {
6078 if (aDest.IsEmpty() || aFill.IsEmpty())
6079 return SnappedImageDrawingParameters();
6081 // Avoid unnecessarily large offsets.
6082 bool doTile = !aDest.Contains(aFill);
6083 nsRect appUnitDest =
6084 doTile ? TileNearRect(aDest, aFill.Intersect(aDirty)) : aDest;
6085 nsPoint anchor = aAnchor + (appUnitDest.TopLeft() - aDest.TopLeft());
6087 gfxRect devPixelDest =
6088 nsLayoutUtils::RectToGfxRect(appUnitDest, aAppUnitsPerDevPixel);
6089 gfxRect devPixelFill =
6090 nsLayoutUtils::RectToGfxRect(aFill, aAppUnitsPerDevPixel);
6091 gfxRect devPixelDirty =
6092 nsLayoutUtils::RectToGfxRect(aDirty, aAppUnitsPerDevPixel);
6094 gfxMatrix currentMatrix = aCtx->CurrentMatrixDouble();
6095 gfxRect fill = devPixelFill;
6096 gfxRect dest = devPixelDest;
6097 bool didSnap;
6098 // Snap even if we have a scale in the context. But don't snap if
6099 // we have something that's not translation+scale, or if the scale flips in
6100 // the X or Y direction, because snapped image drawing can't handle that yet.
6101 if (!currentMatrix.HasNonAxisAlignedTransform() && currentMatrix._11 > 0.0 &&
6102 currentMatrix._22 > 0.0 &&
6103 aCtx->UserToDevicePixelSnapped(fill,
6104 gfxContext::SnapOption::IgnoreScale) &&
6105 aCtx->UserToDevicePixelSnapped(dest,
6106 gfxContext::SnapOption::IgnoreScale)) {
6107 // We snapped. On this code path, |fill| and |dest| take into account
6108 // currentMatrix's transform.
6109 didSnap = true;
6110 } else {
6111 // We didn't snap. On this code path, |fill| and |dest| do not take into
6112 // account currentMatrix's transform.
6113 didSnap = false;
6114 fill = devPixelFill;
6115 dest = devPixelDest;
6118 // If we snapped above, |dest| already takes into account |currentMatrix|'s
6119 // scale and has integer coordinates. If not, we need these properties to
6120 // compute the optimal drawn image size, so compute |snappedDestSize| here.
6121 gfxSize snappedDestSize = dest.Size();
6122 auto scaleFactors = currentMatrix.ScaleFactors();
6123 if (!didSnap) {
6124 snappedDestSize.Scale(scaleFactors.xScale, scaleFactors.yScale);
6125 snappedDestSize.width = NS_round(snappedDestSize.width);
6126 snappedDestSize.height = NS_round(snappedDestSize.height);
6129 // We need to be sure that this is at least one pixel in width and height,
6130 // or we'll end up drawing nothing even if we have a nonempty fill.
6131 snappedDestSize.width = std::max(snappedDestSize.width, 1.0);
6132 snappedDestSize.height = std::max(snappedDestSize.height, 1.0);
6134 // Bail if we're not going to end up drawing anything.
6135 if (fill.IsEmpty()) {
6136 return SnappedImageDrawingParameters();
6139 nsIntSize intImageSize = aImage->OptimalImageSizeForDest(
6140 snappedDestSize, imgIContainer::FRAME_CURRENT, aSamplingFilter,
6141 aImageFlags);
6143 nsIntSize svgViewportSize;
6144 if (scaleFactors.xScale == 1.0 && scaleFactors.yScale == 1.0) {
6145 // intImageSize is scaled by currentMatrix. But since there are no scale
6146 // factors in currentMatrix, it is safe to assign intImageSize to
6147 // svgViewportSize directly.
6148 svgViewportSize = intImageSize;
6149 } else {
6150 // We should not take into account any transformation of currentMatrix
6151 // when computing svg viewport size. Since currentMatrix contains scale
6152 // factors, we need to recompute SVG viewport by unscaled devPixelDest.
6153 svgViewportSize = aImage->OptimalImageSizeForDest(
6154 devPixelDest.Size(), imgIContainer::FRAME_CURRENT, aSamplingFilter,
6155 aImageFlags);
6158 gfxSize imageSize(intImageSize.width, intImageSize.height);
6160 // Compute the set of pixels that would be sampled by an ideal rendering
6161 gfxPoint subimageTopLeft =
6162 MapToFloatImagePixels(imageSize, devPixelDest, devPixelFill.TopLeft());
6163 gfxPoint subimageBottomRight = MapToFloatImagePixels(
6164 imageSize, devPixelDest, devPixelFill.BottomRight());
6165 gfxRect subimage;
6166 subimage.MoveTo(NSToIntFloor(subimageTopLeft.x),
6167 NSToIntFloor(subimageTopLeft.y));
6168 subimage.SizeTo(NSToIntCeil(subimageBottomRight.x) - subimage.x,
6169 NSToIntCeil(subimageBottomRight.y) - subimage.y);
6171 if (subimage.IsEmpty()) {
6172 // Bail if the subimage is empty (we're not going to be drawing anything).
6173 return SnappedImageDrawingParameters();
6176 gfxMatrix transform;
6177 gfxMatrix invTransform;
6179 bool anchorAtUpperLeft =
6180 anchor.x == appUnitDest.x && anchor.y == appUnitDest.y;
6181 bool exactlyOneImageCopy = aFill.IsEqualEdges(appUnitDest);
6182 if (anchorAtUpperLeft && exactlyOneImageCopy) {
6183 // The simple case: we can ignore the anchor point and compute the
6184 // transformation from the sampled region (the subimage) to the fill rect.
6185 // This approach is preferable when it works since it tends to produce
6186 // less numerical error.
6187 transform = TransformBetweenRects(subimage, fill);
6188 invTransform = TransformBetweenRects(fill, subimage);
6189 } else {
6190 // The more complicated case: we compute the transformation from the
6191 // image rect positioned at the image space anchor point to the dest rect
6192 // positioned at the device space anchor point.
6194 // Compute the anchor point in both device space and image space. This
6195 // code assumes that pixel-based devices have one pixel per device unit!
6196 gfxPoint anchorPoint(gfxFloat(anchor.x) / aAppUnitsPerDevPixel,
6197 gfxFloat(anchor.y) / aAppUnitsPerDevPixel);
6198 gfxPoint imageSpaceAnchorPoint =
6199 MapToFloatImagePixels(imageSize, devPixelDest, anchorPoint);
6201 if (didSnap) {
6202 imageSpaceAnchorPoint = StableRound(imageSpaceAnchorPoint);
6203 anchorPoint = imageSpaceAnchorPoint;
6204 anchorPoint = MapToFloatUserPixels(imageSize, devPixelDest, anchorPoint);
6205 anchorPoint = currentMatrix.TransformPoint(anchorPoint);
6206 anchorPoint = StableRound(anchorPoint);
6209 // Compute an unsnapped version of the dest rect's size. We continue to
6210 // follow the pattern that we take |currentMatrix| into account only if
6211 // |didSnap| is true.
6212 gfxSize unsnappedDestSize =
6213 didSnap ? devPixelDest.Size() * currentMatrix.ScaleFactors()
6214 : devPixelDest.Size();
6216 gfxRect anchoredDestRect(anchorPoint, unsnappedDestSize);
6217 gfxRect anchoredImageRect(imageSpaceAnchorPoint, imageSize);
6219 // Calculate anchoredDestRect with snapped fill rect when the devPixelFill
6220 // rect corresponds to just a single tile in that direction
6221 if (fill.Width() != devPixelFill.Width() &&
6222 devPixelDest.x == devPixelFill.x &&
6223 devPixelDest.XMost() == devPixelFill.XMost()) {
6224 anchoredDestRect.width = fill.width;
6226 if (fill.Height() != devPixelFill.Height() &&
6227 devPixelDest.y == devPixelFill.y &&
6228 devPixelDest.YMost() == devPixelFill.YMost()) {
6229 anchoredDestRect.height = fill.height;
6232 transform = TransformBetweenRects(anchoredImageRect, anchoredDestRect);
6233 invTransform = TransformBetweenRects(anchoredDestRect, anchoredImageRect);
6236 // If the transform is not a straight translation by integers, then
6237 // filtering will occur, and restricting the fill rect to the dirty rect
6238 // would change the values computed for edge pixels, which we can't allow.
6239 // Also, if 'didSnap' is false then rounding out 'devPixelDirty' might not
6240 // produce pixel-aligned coordinates, which would also break the values
6241 // computed for edge pixels.
6242 if (didSnap && !invTransform.HasNonIntegerTranslation()) {
6243 // This form of Transform is safe to call since non-axis-aligned
6244 // transforms wouldn't be snapped.
6245 devPixelDirty = currentMatrix.TransformRect(devPixelDirty);
6246 devPixelDirty.RoundOut();
6247 fill = fill.Intersect(devPixelDirty);
6249 if (fill.IsEmpty()) return SnappedImageDrawingParameters();
6251 gfxRect imageSpaceFill(didSnap ? invTransform.TransformRect(fill)
6252 : invTransform.TransformBounds(fill));
6254 // If we didn't snap, we need to post-multiply the matrix on the context to
6255 // get the final matrix we'll draw with, because we didn't take it into
6256 // account when computing the matrices above.
6257 if (!didSnap) {
6258 transform = transform * currentMatrix;
6261 ExtendMode extendMode = (aImageFlags & imgIContainer::FLAG_CLAMP)
6262 ? ExtendMode::CLAMP
6263 : aExtendMode;
6264 // We were passed in the default extend mode but need to tile.
6265 if (extendMode == ExtendMode::CLAMP && doTile) {
6266 MOZ_ASSERT(!(aImageFlags & imgIContainer::FLAG_CLAMP));
6267 extendMode = ExtendMode::REPEAT;
6270 ImageRegion region = ImageRegion::CreateWithSamplingRestriction(
6271 imageSpaceFill, subimage, extendMode);
6273 return SnappedImageDrawingParameters(
6274 transform, intImageSize, region,
6275 CSSIntSize(svgViewportSize.width, svgViewportSize.height));
6278 static ImgDrawResult DrawImageInternal(
6279 gfxContext& aContext, nsPresContext* aPresContext, imgIContainer* aImage,
6280 const SamplingFilter aSamplingFilter, const nsRect& aDest,
6281 const nsRect& aFill, const nsPoint& aAnchor, const nsRect& aDirty,
6282 const SVGImageContext& aSVGContext, uint32_t aImageFlags,
6283 ExtendMode aExtendMode = ExtendMode::CLAMP, float aOpacity = 1.0) {
6284 ImgDrawResult result = ImgDrawResult::SUCCESS;
6286 aImageFlags |= imgIContainer::FLAG_ASYNC_NOTIFY;
6288 if (aPresContext->Type() == nsPresContext::eContext_Print) {
6289 // We want vector images to be passed on as vector commands, not a raster
6290 // image.
6291 aImageFlags |= imgIContainer::FLAG_BYPASS_SURFACE_CACHE;
6293 if (aDest.Contains(aFill)) {
6294 aImageFlags |= imgIContainer::FLAG_CLAMP;
6296 int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
6298 SnappedImageDrawingParameters params = ComputeSnappedImageDrawingParameters(
6299 &aContext, appUnitsPerDevPixel, aDest, aFill, aAnchor, aDirty, aImage,
6300 aSamplingFilter, aImageFlags, aExtendMode);
6302 if (!params.shouldDraw) {
6303 return result;
6307 gfxContextMatrixAutoSaveRestore contextMatrixRestorer(&aContext);
6309 aContext.SetMatrixDouble(params.imageSpaceToDeviceSpace);
6311 SVGImageContext newContext = aSVGContext;
6312 if (!aSVGContext.GetViewportSize()) {
6313 newContext.SetViewportSize(Some(params.svgViewportSize));
6316 result = aImage->Draw(&aContext, params.size, params.region,
6317 imgIContainer::FRAME_CURRENT, aSamplingFilter,
6318 newContext, aImageFlags, aOpacity);
6321 return result;
6324 /* static */
6325 ImgDrawResult nsLayoutUtils::DrawSingleUnscaledImage(
6326 gfxContext& aContext, nsPresContext* aPresContext, imgIContainer* aImage,
6327 const SamplingFilter aSamplingFilter, const nsPoint& aDest,
6328 const nsRect* aDirty, const SVGImageContext& aSVGContext,
6329 uint32_t aImageFlags, const nsRect* aSourceArea) {
6330 CSSIntSize imageSize;
6331 aImage->GetWidth(&imageSize.width);
6332 aImage->GetHeight(&imageSize.height);
6333 aImage->GetResolution().ApplyTo(imageSize.width, imageSize.height);
6335 if (imageSize.width < 1 || imageSize.height < 1) {
6336 NS_WARNING("Image width or height is non-positive");
6337 return ImgDrawResult::TEMPORARY_ERROR;
6340 nsSize size(CSSPixel::ToAppUnits(imageSize));
6341 nsRect source;
6342 if (aSourceArea) {
6343 source = *aSourceArea;
6344 } else {
6345 source.SizeTo(size);
6348 nsRect dest(aDest - source.TopLeft(), size);
6349 nsRect fill(aDest, source.Size());
6350 // Ensure that only a single image tile is drawn. If aSourceArea extends
6351 // outside the image bounds, we want to honor the aSourceArea-to-aDest
6352 // translation but we don't want to actually tile the image.
6353 fill.IntersectRect(fill, dest);
6354 return DrawImageInternal(aContext, aPresContext, aImage, aSamplingFilter,
6355 dest, fill, aDest, aDirty ? *aDirty : dest,
6356 aSVGContext, aImageFlags);
6359 /* static */
6360 ImgDrawResult nsLayoutUtils::DrawSingleImage(
6361 gfxContext& aContext, nsPresContext* aPresContext, imgIContainer* aImage,
6362 SamplingFilter aSamplingFilter, const nsRect& aDest, const nsRect& aDirty,
6363 const SVGImageContext& aSVGContext, uint32_t aImageFlags,
6364 const nsPoint* aAnchorPoint) {
6365 // NOTE(emilio): We can hardcode resolution to 1 here, since we're interested
6366 // in the actual image pixels, for snapping purposes, not on the adjusted
6367 // size.
6368 CSSIntSize pixelImageSize(ComputeSizeForDrawingWithFallback(
6369 aImage, ImageResolution(), aDest.Size()));
6370 if (pixelImageSize.width < 1 || pixelImageSize.height < 1) {
6371 NS_ASSERTION(pixelImageSize.width >= 0 && pixelImageSize.height >= 0,
6372 "Image width or height is negative");
6373 return ImgDrawResult::SUCCESS; // no point in drawing a zero size image
6376 const nsSize imageSize(CSSPixel::ToAppUnits(pixelImageSize));
6377 const nsRect source(nsPoint(), imageSize);
6378 const nsRect dest = GetWholeImageDestination(imageSize, source, aDest);
6380 // Ensure that only a single image tile is drawn. If aSourceArea extends
6381 // outside the image bounds, we want to honor the aSourceArea-to-aDest
6382 // transform but we don't want to actually tile the image.
6383 nsRect fill;
6384 fill.IntersectRect(aDest, dest);
6385 return DrawImageInternal(aContext, aPresContext, aImage, aSamplingFilter,
6386 dest, fill,
6387 aAnchorPoint ? *aAnchorPoint : fill.TopLeft(),
6388 aDirty, aSVGContext, aImageFlags);
6391 /* static */
6392 void nsLayoutUtils::ComputeSizeForDrawing(
6393 imgIContainer* aImage, const ImageResolution& aResolution,
6394 /* outparam */ CSSIntSize& aImageSize,
6395 /* outparam */ AspectRatio& aIntrinsicRatio,
6396 /* outparam */ bool& aGotWidth,
6397 /* outparam */ bool& aGotHeight) {
6398 aGotWidth = NS_SUCCEEDED(aImage->GetWidth(&aImageSize.width));
6399 aGotHeight = NS_SUCCEEDED(aImage->GetHeight(&aImageSize.height));
6400 Maybe<AspectRatio> intrinsicRatio = aImage->GetIntrinsicRatio();
6401 aIntrinsicRatio = intrinsicRatio.valueOr(AspectRatio());
6403 if (aGotWidth) {
6404 aResolution.ApplyXTo(aImageSize.width);
6406 if (aGotHeight) {
6407 aResolution.ApplyYTo(aImageSize.height);
6410 if (!(aGotWidth && aGotHeight) && intrinsicRatio.isNothing()) {
6411 // We hit an error (say, because the image failed to load or couldn't be
6412 // decoded) and should return zero size.
6413 aGotWidth = aGotHeight = true;
6414 aImageSize = CSSIntSize(0, 0);
6418 /* static */
6419 CSSIntSize nsLayoutUtils::ComputeSizeForDrawingWithFallback(
6420 imgIContainer* aImage, const ImageResolution& aResolution,
6421 const nsSize& aFallbackSize) {
6422 CSSIntSize imageSize;
6423 AspectRatio imageRatio;
6424 bool gotHeight, gotWidth;
6425 ComputeSizeForDrawing(aImage, aResolution, imageSize, imageRatio, gotWidth,
6426 gotHeight);
6428 // If we didn't get both width and height, try to compute them using the
6429 // intrinsic ratio of the image.
6430 if (gotWidth != gotHeight) {
6431 if (!gotWidth) {
6432 if (imageRatio) {
6433 imageSize.width = imageRatio.ApplyTo(imageSize.height);
6434 gotWidth = true;
6436 } else {
6437 if (imageRatio) {
6438 imageSize.height = imageRatio.Inverted().ApplyTo(imageSize.width);
6439 gotHeight = true;
6444 // If we still don't have a width or height, just use the fallback size the
6445 // caller provided.
6446 if (!gotWidth) {
6447 imageSize.width =
6448 nsPresContext::AppUnitsToIntCSSPixels(aFallbackSize.width);
6450 if (!gotHeight) {
6451 imageSize.height =
6452 nsPresContext::AppUnitsToIntCSSPixels(aFallbackSize.height);
6455 return imageSize;
6458 /* static */ LayerIntRect SnapRectForImage(
6459 const gfx::Matrix& aTransform, const gfx::MatrixScales& aScaleFactors,
6460 const LayoutDeviceRect& aRect) {
6461 // Attempt to snap pixels, the same as ComputeSnappedImageDrawingParameters.
6462 // Any changes to the algorithm here will need to be reflected there.
6463 bool snapped = false;
6464 LayerIntRect snapRect;
6465 if (!aTransform.HasNonAxisAlignedTransform() && aTransform._11 > 0.0 &&
6466 aTransform._22 > 0.0) {
6467 gfxRect rect(gfxPoint(aRect.X(), aRect.Y()),
6468 gfxSize(aRect.Width(), aRect.Height()));
6470 gfxPoint p1 =
6471 ThebesPoint(aTransform.TransformPoint(ToPoint(rect.TopLeft())));
6472 gfxPoint p2 =
6473 ThebesPoint(aTransform.TransformPoint(ToPoint(rect.TopRight())));
6474 gfxPoint p3 =
6475 ThebesPoint(aTransform.TransformPoint(ToPoint(rect.BottomRight())));
6477 if (p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y)) {
6478 p1.Round();
6479 p3.Round();
6481 IntPoint p1i(int32_t(p1.x), int32_t(p1.y));
6482 IntPoint p3i(int32_t(p3.x), int32_t(p3.y));
6484 snapRect.MoveTo(std::min(p1i.x, p3i.x), std::min(p1i.y, p3i.y));
6485 snapRect.SizeTo(std::max(p1i.x, p3i.x) - snapRect.X(),
6486 std::max(p1i.y, p3i.y) - snapRect.Y());
6487 snapped = true;
6491 if (!snapped) {
6492 // If we couldn't snap directly with the transform, we need to go best
6493 // effort in layer pixels.
6494 snapRect = RoundedToInt(
6495 aRect * LayoutDeviceToLayerScale2D::FromUnknownScale(aScaleFactors));
6498 // An empty size is unacceptable so we ensure our suggested size is at least
6499 // 1 pixel wide/tall.
6500 if (snapRect.Width() < 1) {
6501 snapRect.SetWidth(1);
6503 if (snapRect.Height() < 1) {
6504 snapRect.SetHeight(1);
6506 return snapRect;
6509 /* static */
6510 IntSize nsLayoutUtils::ComputeImageContainerDrawingParameters(
6511 imgIContainer* aImage, nsIFrame* aForFrame,
6512 const LayoutDeviceRect& aDestRect, const LayoutDeviceRect& aFillRect,
6513 const StackingContextHelper& aSc, uint32_t aFlags,
6514 SVGImageContext& aSVGContext, Maybe<ImageIntRegion>& aRegion) {
6515 MOZ_ASSERT(aImage);
6516 MOZ_ASSERT(aForFrame);
6518 MatrixScales scaleFactors = aSc.GetInheritedScale();
6519 SamplingFilter samplingFilter =
6520 nsLayoutUtils::GetSamplingFilterForFrame(aForFrame);
6522 // Compute our SVG context parameters, if any. Don't replace the viewport
6523 // size if it was already set, prefer what the caller gave.
6524 SVGImageContext::MaybeStoreContextPaint(aSVGContext, aForFrame, aImage);
6525 if ((scaleFactors.xScale != 1.0 || scaleFactors.yScale != 1.0) &&
6526 aImage->GetType() == imgIContainer::TYPE_VECTOR &&
6527 (!aSVGContext.GetViewportSize())) {
6528 gfxSize gfxDestSize(aDestRect.Width(), aDestRect.Height());
6529 IntSize viewportSize = aImage->OptimalImageSizeForDest(
6530 gfxDestSize, imgIContainer::FRAME_CURRENT, samplingFilter, aFlags);
6532 CSSIntSize cssViewportSize(viewportSize.width, viewportSize.height);
6533 aSVGContext.SetViewportSize(Some(cssViewportSize));
6536 const gfx::Matrix& itm = aSc.GetInheritedTransform();
6537 LayerIntRect destRect = SnapRectForImage(itm, scaleFactors, aDestRect);
6539 // Since we always decode entire raster images, we only care about the
6540 // ImageIntRegion for vector images when we are recording blobs, for which we
6541 // may only draw part of in some cases.
6542 if ((aImage->GetType() != imgIContainer::TYPE_VECTOR) ||
6543 !(aFlags & imgIContainer::FLAG_RECORD_BLOB)) {
6544 // If the transform scale of our stacking context helper is being animated
6545 // on the compositor then the transform will have the current value of the
6546 // scale, but the scale factors will have max value of the scale animation.
6547 // So we want to ask for a decoded image that can fulfill that larger size.
6548 int32_t scaleWidth = int32_t(ceil(aDestRect.Width() * scaleFactors.xScale));
6549 if (scaleWidth > destRect.width + 2) {
6550 destRect.width = scaleWidth;
6552 int32_t scaleHeight =
6553 int32_t(ceil(aDestRect.Height() * scaleFactors.yScale));
6554 if (scaleHeight > destRect.height + 2) {
6555 destRect.height = scaleHeight;
6558 return aImage->OptimalImageSizeForDest(
6559 gfxSize(destRect.Width(), destRect.Height()),
6560 imgIContainer::FRAME_CURRENT, samplingFilter, aFlags);
6563 // We only use the region rect with blob recordings. This is because when we
6564 // rasterize an SVG image in process, we always create a complete
6565 // rasterization of the whole image which can be given to any caller, while
6566 // we support partial rasterization with the blob recordings.
6567 if (aFlags & imgIContainer::FLAG_RECORD_BLOB) {
6568 // If the dest rect contains the fill rect, then we are only displaying part
6569 // of the vector image. We need to calculate the restriction region to avoid
6570 // drawing more than we need, and sampling outside the desired bounds.
6571 LayerIntRect clipRect = SnapRectForImage(itm, scaleFactors, aFillRect);
6572 if (destRect.Contains(clipRect)) {
6573 LayerIntRect restrictRect = destRect.Intersect(clipRect);
6574 restrictRect.MoveBy(-destRect.TopLeft());
6576 if (restrictRect.Width() < 1) {
6577 restrictRect.SetWidth(1);
6579 if (restrictRect.Height() < 1) {
6580 restrictRect.SetHeight(1);
6583 if (restrictRect.X() != 0 || restrictRect.Y() != 0 ||
6584 restrictRect.Size() != destRect.Size()) {
6585 IntRect sampleRect = restrictRect.ToUnknownRect();
6586 aRegion = Some(ImageIntRegion::CreateWithSamplingRestriction(
6587 sampleRect, sampleRect, ExtendMode::CLAMP));
6592 // VectorImage::OptimalImageSizeForDest will just round up, but we already
6593 // have an integer size.
6594 return destRect.Size().ToUnknownSize();
6597 /* static */
6598 nsPoint nsLayoutUtils::GetBackgroundFirstTilePos(const nsPoint& aDest,
6599 const nsPoint& aFill,
6600 const nsSize& aRepeatSize) {
6601 return nsPoint(NSToIntFloor(float(aFill.x - aDest.x) / aRepeatSize.width) *
6602 aRepeatSize.width,
6603 NSToIntFloor(float(aFill.y - aDest.y) / aRepeatSize.height) *
6604 aRepeatSize.height) +
6605 aDest;
6608 /* static */
6609 ImgDrawResult nsLayoutUtils::DrawBackgroundImage(
6610 gfxContext& aContext, nsIFrame* aForFrame, nsPresContext* aPresContext,
6611 imgIContainer* aImage, SamplingFilter aSamplingFilter, const nsRect& aDest,
6612 const nsRect& aFill, const nsSize& aRepeatSize, const nsPoint& aAnchor,
6613 const nsRect& aDirty, uint32_t aImageFlags, ExtendMode aExtendMode,
6614 float aOpacity) {
6615 AUTO_PROFILER_LABEL("nsLayoutUtils::DrawBackgroundImage",
6616 GRAPHICS_Rasterization);
6618 CSSIntSize destCSSSize{nsPresContext::AppUnitsToIntCSSPixels(aDest.width),
6619 nsPresContext::AppUnitsToIntCSSPixels(aDest.height)};
6621 SVGImageContext svgContext(Some(destCSSSize));
6622 SVGImageContext::MaybeStoreContextPaint(svgContext, aForFrame, aImage);
6624 /* Fast path when there is no need for image spacing */
6625 if (aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height) {
6626 return DrawImageInternal(aContext, aPresContext, aImage, aSamplingFilter,
6627 aDest, aFill, aAnchor, aDirty, svgContext,
6628 aImageFlags, aExtendMode, aOpacity);
6631 const nsPoint firstTilePos =
6632 GetBackgroundFirstTilePos(aDest.TopLeft(), aFill.TopLeft(), aRepeatSize);
6633 const nscoord xMost = aFill.XMost();
6634 const nscoord repeatWidth = aRepeatSize.width;
6635 const nscoord yMost = aFill.YMost();
6636 const nscoord repeatHeight = aRepeatSize.height;
6637 nsRect dest(0, 0, aDest.width, aDest.height);
6638 nsPoint anchor = aAnchor;
6639 for (nscoord x = firstTilePos.x; x < xMost; x += repeatWidth) {
6640 for (nscoord y = firstTilePos.y; y < yMost; y += repeatHeight) {
6641 dest.x = x;
6642 dest.y = y;
6643 ImgDrawResult result = DrawImageInternal(
6644 aContext, aPresContext, aImage, aSamplingFilter, dest, dest, anchor,
6645 aDirty, svgContext, aImageFlags, ExtendMode::CLAMP, aOpacity);
6646 anchor.y += repeatHeight;
6647 if (result != ImgDrawResult::SUCCESS) {
6648 return result;
6651 anchor.x += repeatWidth;
6652 anchor.y = aAnchor.y;
6655 return ImgDrawResult::SUCCESS;
6658 /* static */
6659 ImgDrawResult nsLayoutUtils::DrawImage(
6660 gfxContext& aContext, ComputedStyle* aComputedStyle,
6661 nsPresContext* aPresContext, imgIContainer* aImage,
6662 const SamplingFilter aSamplingFilter, const nsRect& aDest,
6663 const nsRect& aFill, const nsPoint& aAnchor, const nsRect& aDirty,
6664 uint32_t aImageFlags, float aOpacity) {
6665 SVGImageContext svgContext;
6666 SVGImageContext::MaybeStoreContextPaint(svgContext, *aPresContext,
6667 *aComputedStyle, aImage);
6669 return DrawImageInternal(aContext, aPresContext, aImage, aSamplingFilter,
6670 aDest, aFill, aAnchor, aDirty, svgContext,
6671 aImageFlags, ExtendMode::CLAMP, aOpacity);
6674 /* static */
6675 nsRect nsLayoutUtils::GetWholeImageDestination(const nsSize& aWholeImageSize,
6676 const nsRect& aImageSourceArea,
6677 const nsRect& aDestArea) {
6678 double scaleX = double(aDestArea.width) / aImageSourceArea.width;
6679 double scaleY = double(aDestArea.height) / aImageSourceArea.height;
6680 nscoord destOffsetX = NSToCoordRound(aImageSourceArea.x * scaleX);
6681 nscoord destOffsetY = NSToCoordRound(aImageSourceArea.y * scaleY);
6682 nscoord wholeSizeX = NSToCoordRound(aWholeImageSize.width * scaleX);
6683 nscoord wholeSizeY = NSToCoordRound(aWholeImageSize.height * scaleY);
6684 return nsRect(aDestArea.TopLeft() - nsPoint(destOffsetX, destOffsetY),
6685 nsSize(wholeSizeX, wholeSizeY));
6688 /* static */
6689 already_AddRefed<imgIContainer> nsLayoutUtils::OrientImage(
6690 imgIContainer* aContainer, const StyleImageOrientation& aOrientation) {
6691 MOZ_ASSERT(aContainer, "Should have an image container");
6692 nsCOMPtr<imgIContainer> img(aContainer);
6694 switch (aOrientation) {
6695 case StyleImageOrientation::FromImage:
6696 break;
6697 case StyleImageOrientation::None:
6698 img = ImageOps::Unorient(img);
6699 break;
6702 return img.forget();
6705 /* static */
6706 bool nsLayoutUtils::ImageRequestUsesCORS(imgIRequest* aRequest) {
6707 int32_t corsMode = mozilla::CORS_NONE;
6708 return NS_SUCCEEDED(aRequest->GetCORSMode(&corsMode)) &&
6709 corsMode != mozilla::CORS_NONE;
6712 static bool NonZeroCorner(const LengthPercentage& aLength) {
6713 // Since negative results are clamped to 0, check > 0.
6714 return aLength.Resolve(nscoord_MAX) > 0 || aLength.Resolve(0) > 0;
6717 /* static */
6718 bool nsLayoutUtils::HasNonZeroCorner(const BorderRadius& aCorners) {
6719 for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
6720 if (NonZeroCorner(aCorners.Get(corner))) return true;
6722 return false;
6725 // aCorner is a "full corner" value, i.e. eCornerTopLeft etc.
6726 static bool IsCornerAdjacentToSide(uint8_t aCorner, Side aSide) {
6727 static_assert((int)eSideTop == eCornerTopLeft, "Check for Full Corner");
6728 static_assert((int)eSideRight == eCornerTopRight, "Check for Full Corner");
6729 static_assert((int)eSideBottom == eCornerBottomRight,
6730 "Check for Full Corner");
6731 static_assert((int)eSideLeft == eCornerBottomLeft, "Check for Full Corner");
6732 static_assert((int)eSideTop == ((eCornerTopRight - 1) & 3),
6733 "Check for Full Corner");
6734 static_assert((int)eSideRight == ((eCornerBottomRight - 1) & 3),
6735 "Check for Full Corner");
6736 static_assert((int)eSideBottom == ((eCornerBottomLeft - 1) & 3),
6737 "Check for Full Corner");
6738 static_assert((int)eSideLeft == ((eCornerTopLeft - 1) & 3),
6739 "Check for Full Corner");
6741 return aSide == aCorner || aSide == ((aCorner - 1) & 3);
6744 /* static */
6745 bool nsLayoutUtils::HasNonZeroCornerOnSide(const BorderRadius& aCorners,
6746 Side aSide) {
6747 static_assert(eCornerTopLeftX / 2 == eCornerTopLeft,
6748 "Check for Non Zero on side");
6749 static_assert(eCornerTopLeftY / 2 == eCornerTopLeft,
6750 "Check for Non Zero on side");
6751 static_assert(eCornerTopRightX / 2 == eCornerTopRight,
6752 "Check for Non Zero on side");
6753 static_assert(eCornerTopRightY / 2 == eCornerTopRight,
6754 "Check for Non Zero on side");
6755 static_assert(eCornerBottomRightX / 2 == eCornerBottomRight,
6756 "Check for Non Zero on side");
6757 static_assert(eCornerBottomRightY / 2 == eCornerBottomRight,
6758 "Check for Non Zero on side");
6759 static_assert(eCornerBottomLeftX / 2 == eCornerBottomLeft,
6760 "Check for Non Zero on side");
6761 static_assert(eCornerBottomLeftY / 2 == eCornerBottomLeft,
6762 "Check for Non Zero on side");
6764 for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
6765 // corner is a "half corner" value, so dividing by two gives us a
6766 // "full corner" value.
6767 if (NonZeroCorner(aCorners.Get(corner)) &&
6768 IsCornerAdjacentToSide(corner / 2, aSide))
6769 return true;
6771 return false;
6774 /* static */
6775 widget::TransparencyMode nsLayoutUtils::GetFrameTransparency(
6776 nsIFrame* aBackgroundFrame, nsIFrame* aCSSRootFrame) {
6777 if (!aCSSRootFrame->StyleEffects()->IsOpaque()) {
6778 return TransparencyMode::Transparent;
6781 if (HasNonZeroCorner(aCSSRootFrame->StyleBorder()->mBorderRadius)) {
6782 return TransparencyMode::Transparent;
6785 nsITheme::Transparency transparency;
6786 if (aCSSRootFrame->IsThemed(&transparency)) {
6787 return transparency == nsITheme::eTransparent
6788 ? TransparencyMode::Transparent
6789 : TransparencyMode::Opaque;
6792 // We need an uninitialized window to be treated as opaque because doing
6793 // otherwise breaks window display effects on some platforms, specifically
6794 // Vista. (bug 450322)
6795 if (aBackgroundFrame->IsViewportFrame() &&
6796 !aBackgroundFrame->PrincipalChildList().FirstChild()) {
6797 return TransparencyMode::Opaque;
6800 const ComputedStyle* bgSC = nsCSSRendering::FindBackground(aBackgroundFrame);
6801 if (!bgSC) {
6802 return TransparencyMode::Transparent;
6804 const nsStyleBackground* bg = bgSC->StyleBackground();
6805 if (NS_GET_A(bg->BackgroundColor(bgSC)) < 255 ||
6806 // bottom layer's clip is used for the color
6807 bg->BottomLayer().mClip != StyleGeometryBox::BorderBox) {
6808 return TransparencyMode::Transparent;
6810 return TransparencyMode::Opaque;
6813 /* static */
6814 bool nsLayoutUtils::IsPopup(const nsIFrame* aFrame) {
6815 // Optimization: the frame can't possibly be a popup if it has no view.
6816 if (!aFrame->HasView()) {
6817 NS_ASSERTION(!aFrame->IsMenuPopupFrame(), "popup frame must have a view");
6818 return false;
6820 return aFrame->IsMenuPopupFrame();
6823 /* static */
6824 nsIFrame* nsLayoutUtils::GetDisplayRootFrame(nsIFrame* aFrame) {
6825 return const_cast<nsIFrame*>(
6826 nsLayoutUtils::GetDisplayRootFrame(const_cast<const nsIFrame*>(aFrame)));
6829 /* static */
6830 const nsIFrame* nsLayoutUtils::GetDisplayRootFrame(const nsIFrame* aFrame) {
6831 // We could use GetRootPresContext() here if the
6832 // NS_FRAME_IN_POPUP frame bit is set.
6833 const nsIFrame* f = aFrame;
6834 for (;;) {
6835 if (!f->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
6836 f = f->PresShell()->GetRootFrame();
6837 if (!f) {
6838 return aFrame;
6840 } else if (IsPopup(f)) {
6841 return f;
6843 nsIFrame* parent = GetCrossDocParentFrameInProcess(f);
6844 if (!parent) return f;
6845 f = parent;
6849 /* static */
6850 nsIFrame* nsLayoutUtils::GetReferenceFrame(nsIFrame* aFrame) {
6851 nsIFrame* f = aFrame;
6852 for (;;) {
6853 if (f->IsTransformed() || IsPopup(f)) {
6854 return f;
6856 nsIFrame* parent = GetCrossDocParentFrameInProcess(f);
6857 if (!parent) {
6858 return f;
6860 f = parent;
6864 /* static */ gfx::ShapedTextFlags nsLayoutUtils::GetTextRunFlagsForStyle(
6865 const ComputedStyle* aComputedStyle, nsPresContext* aPresContext,
6866 const nsStyleFont* aStyleFont, const nsStyleText* aStyleText,
6867 nscoord aLetterSpacing) {
6868 gfx::ShapedTextFlags result = gfx::ShapedTextFlags();
6869 if (aLetterSpacing != 0 ||
6870 aStyleText->mTextJustify == StyleTextJustify::InterCharacter) {
6871 result |= gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES;
6873 if (aStyleText->mMozControlCharacterVisibility ==
6874 StyleMozControlCharacterVisibility::Hidden) {
6875 result |= gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS;
6877 switch (aComputedStyle->StyleText()->mTextRendering) {
6878 case StyleTextRendering::Optimizespeed:
6879 result |= gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED;
6880 break;
6881 case StyleTextRendering::Auto:
6882 if (aPresContext &&
6883 aStyleFont->mFont.size.ToCSSPixels() <
6884 aPresContext->DevPixelsToFloatCSSPixels(
6885 StaticPrefs::browser_display_auto_quality_min_font_size())) {
6886 result |= gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED;
6888 break;
6889 default:
6890 break;
6892 return result | GetTextRunOrientFlagsForStyle(aComputedStyle);
6895 /* static */ gfx::ShapedTextFlags nsLayoutUtils::GetTextRunOrientFlagsForStyle(
6896 const ComputedStyle* aComputedStyle) {
6897 auto writingMode = aComputedStyle->StyleVisibility()->mWritingMode;
6898 switch (writingMode) {
6899 case StyleWritingModeProperty::HorizontalTb:
6900 return gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL;
6902 case StyleWritingModeProperty::VerticalLr:
6903 case StyleWritingModeProperty::VerticalRl:
6904 switch (aComputedStyle->StyleVisibility()->mTextOrientation) {
6905 case StyleTextOrientation::Mixed:
6906 return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED;
6907 case StyleTextOrientation::Upright:
6908 return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
6909 case StyleTextOrientation::Sideways:
6910 return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
6911 default:
6912 MOZ_ASSERT_UNREACHABLE("unknown text-orientation");
6913 return gfx::ShapedTextFlags();
6916 case StyleWritingModeProperty::SidewaysLr:
6917 return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT;
6919 case StyleWritingModeProperty::SidewaysRl:
6920 return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
6922 default:
6923 MOZ_ASSERT_UNREACHABLE("unknown writing-mode");
6924 return gfx::ShapedTextFlags();
6928 /* static */
6929 void nsLayoutUtils::GetRectDifferenceStrips(const nsRect& aR1,
6930 const nsRect& aR2, nsRect* aHStrip,
6931 nsRect* aVStrip) {
6932 NS_ASSERTION(aR1.TopLeft() == aR2.TopLeft(),
6933 "expected rects at the same position");
6934 nsRect unionRect(aR1.x, aR1.y, std::max(aR1.width, aR2.width),
6935 std::max(aR1.height, aR2.height));
6936 nscoord VStripStart = std::min(aR1.width, aR2.width);
6937 nscoord HStripStart = std::min(aR1.height, aR2.height);
6938 *aVStrip = unionRect;
6939 aVStrip->x += VStripStart;
6940 aVStrip->width -= VStripStart;
6941 *aHStrip = unionRect;
6942 aHStrip->y += HStripStart;
6943 aHStrip->height -= HStripStart;
6946 nsDeviceContext* nsLayoutUtils::GetDeviceContextForScreenInfo(
6947 nsPIDOMWindowOuter* aWindow) {
6948 if (!aWindow) {
6949 return nullptr;
6952 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
6953 while (docShell) {
6954 // Now make sure our size is up to date. That will mean that the device
6955 // context does the right thing on multi-monitor systems when we return it
6956 // to the caller. It will also make sure that our prescontext has been
6957 // created, if we're supposed to have one.
6958 nsCOMPtr<nsPIDOMWindowOuter> win = docShell->GetWindow();
6959 if (!win) {
6960 // No reason to go on
6961 return nullptr;
6964 win->EnsureSizeAndPositionUpToDate();
6966 RefPtr<nsPresContext> presContext = docShell->GetPresContext();
6967 if (presContext) {
6968 nsDeviceContext* context = presContext->DeviceContext();
6969 if (context) {
6970 return context;
6974 nsCOMPtr<nsIDocShellTreeItem> parentItem;
6975 docShell->GetInProcessParent(getter_AddRefs(parentItem));
6976 docShell = do_QueryInterface(parentItem);
6979 return nullptr;
6982 /* static */
6983 bool nsLayoutUtils::IsReallyFixedPos(const nsIFrame* aFrame) {
6984 MOZ_ASSERT(aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed,
6985 "IsReallyFixedPos called on non-'position:fixed' frame");
6986 return MayBeReallyFixedPos(aFrame);
6989 /* static */
6990 bool nsLayoutUtils::MayBeReallyFixedPos(const nsIFrame* aFrame) {
6991 MOZ_ASSERT(aFrame->GetParent(),
6992 "MayBeReallyFixedPos called on frame not in tree");
6993 LayoutFrameType parentType = aFrame->GetParent()->Type();
6994 return parentType == LayoutFrameType::Viewport ||
6995 parentType == LayoutFrameType::PageContent;
6998 /* static */
6999 bool nsLayoutUtils::IsInPositionFixedSubtree(const nsIFrame* aFrame) {
7000 for (const nsIFrame* f = aFrame; f; f = f->GetParent()) {
7001 if (f->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
7002 nsLayoutUtils::IsReallyFixedPos(f)) {
7003 return true;
7006 return false;
7009 static RefPtr<SourceSurface> ScaleSourceSurface(SourceSurface& aSurface,
7010 const IntSize& aTargetSize) {
7011 const IntSize surfaceSize = aSurface.GetSize();
7013 MOZ_ASSERT(surfaceSize != aTargetSize);
7014 MOZ_ASSERT(!surfaceSize.IsEmpty());
7015 MOZ_ASSERT(!aTargetSize.IsEmpty());
7017 RefPtr<DrawTarget> dt = Factory::CreateDrawTarget(
7018 gfxVars::ContentBackend(), aTargetSize, aSurface.GetFormat());
7020 if (!dt || !dt->IsValid()) {
7021 return nullptr;
7024 dt->DrawSurface(&aSurface, Rect(Point(), Size(aTargetSize)),
7025 Rect(Point(), Size(surfaceSize)));
7026 return dt->GetBackingSurface();
7029 SurfaceFromElementResult nsLayoutUtils::SurfaceFromOffscreenCanvas(
7030 OffscreenCanvas* aOffscreenCanvas, uint32_t aSurfaceFlags,
7031 RefPtr<DrawTarget>& aTarget) {
7032 SurfaceFromElementResult result;
7034 IntSize size = aOffscreenCanvas->GetWidthHeight();
7035 if (size.IsEmpty()) {
7036 return result;
7039 result.mSourceSurface =
7040 aOffscreenCanvas->GetSurfaceSnapshot(&result.mAlphaType);
7041 if (!result.mSourceSurface) {
7042 // If the element doesn't have a context then we won't get a snapshot. The
7043 // canvas spec wants us to not error and just draw nothing, so return an
7044 // empty surface.
7045 result.mSize = size;
7046 result.mAlphaType = gfxAlphaType::Opaque;
7047 RefPtr<DrawTarget> ref =
7048 aTarget ? aTarget : gfxPlatform::ThreadLocalScreenReferenceDrawTarget();
7049 if (ref->CanCreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8)) {
7050 RefPtr<DrawTarget> dt =
7051 ref->CreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8);
7052 if (dt) {
7053 result.mSourceSurface = dt->Snapshot();
7056 } else {
7057 result.mSize = result.mSourceSurface->GetSize();
7059 // If we want an exact sized surface, then we need to scale if we don't
7060 // match the intrinsic size.
7061 const bool exactSize = aSurfaceFlags & SFE_EXACT_SIZE_SURFACE;
7062 if (exactSize && size != result.mSize) {
7063 result.mSize = size;
7064 result.mSourceSurface = ScaleSourceSurface(*result.mSourceSurface, size);
7067 if (aTarget && result.mSourceSurface) {
7068 RefPtr<SourceSurface> opt =
7069 aTarget->OptimizeSourceSurface(result.mSourceSurface);
7070 if (opt) {
7071 result.mSourceSurface = opt;
7076 result.mHasSize = true;
7077 result.mIntrinsicSize = size;
7078 result.mIsWriteOnly = aOffscreenCanvas->IsWriteOnly();
7080 nsIGlobalObject* global = aOffscreenCanvas->GetParentObject();
7081 if (global) {
7082 result.mPrincipal = global->PrincipalOrNull();
7085 return result;
7088 SurfaceFromElementResult nsLayoutUtils::SurfaceFromVideoFrame(
7089 VideoFrame* aVideoFrame, uint32_t aSurfaceFlags,
7090 RefPtr<DrawTarget>& aTarget) {
7091 SurfaceFromElementResult result;
7093 RefPtr<layers::Image> layersImage = aVideoFrame->GetImage();
7094 if (!layersImage) {
7095 return result;
7098 IntSize codedSize = aVideoFrame->NativeCodedSize();
7099 IntRect visibleRect = aVideoFrame->NativeVisibleRect();
7100 IntSize displaySize = aVideoFrame->NativeDisplaySize();
7102 MOZ_ASSERT(layersImage->GetSize() == codedSize);
7103 IntRect codedRect(IntPoint(0, 0), codedSize);
7105 if (visibleRect.IsEqualEdges(codedRect) && displaySize == codedSize) {
7106 // The display and coded rects are identical, which means we can just use
7107 // the image as is.
7108 result.mLayersImage = std::move(layersImage);
7109 result.mSize = codedSize;
7110 result.mIntrinsicSize = codedSize;
7111 } else if (aSurfaceFlags & SFE_ALLOW_UNCROPPED_UNSCALED) {
7112 // The caller supports cropping/scaling.
7113 result.mLayersImage = std::move(layersImage);
7114 result.mCropRect = Some(visibleRect);
7115 result.mSize = codedSize;
7116 result.mIntrinsicSize = displaySize;
7117 } else {
7118 // The caller does not support cropping/scaling. We need to on its behalf.
7119 RefPtr<SourceSurface> surface = layersImage->GetAsSourceSurface();
7120 if (!surface) {
7121 return result;
7124 RefPtr<DrawTarget> ref = aTarget
7125 ? aTarget
7126 : gfxPlatform::GetPlatform()
7127 ->ThreadLocalScreenReferenceDrawTarget();
7128 if (!ref->CanCreateSimilarDrawTarget(displaySize,
7129 SurfaceFormat::B8G8R8A8)) {
7130 return result;
7133 RefPtr<DrawTarget> dt =
7134 ref->CreateSimilarDrawTarget(displaySize, SurfaceFormat::B8G8R8A8);
7135 if (!dt) {
7136 return result;
7139 gfx::Rect dstRect(0, 0, displaySize.Width(), displaySize.Height());
7140 gfx::Rect srcRect(visibleRect.X(), visibleRect.Y(), visibleRect.Width(),
7141 visibleRect.Height());
7142 dt->DrawSurface(surface, dstRect, srcRect);
7143 result.mSourceSurface = dt->Snapshot();
7144 if (NS_WARN_IF(!result.mSourceSurface)) {
7145 return result;
7148 result.mSize = displaySize;
7149 result.mIntrinsicSize = displaySize;
7152 result.mAlphaType = gfxAlphaType::Premult;
7153 Nullable<VideoPixelFormat> format = aVideoFrame->GetFormat();
7154 if (!format.IsNull()) {
7155 switch (format.Value()) {
7156 case VideoPixelFormat::I420:
7157 case VideoPixelFormat::I422:
7158 case VideoPixelFormat::I444:
7159 case VideoPixelFormat::NV12:
7160 case VideoPixelFormat::RGBX:
7161 case VideoPixelFormat::BGRX:
7162 result.mAlphaType = gfxAlphaType::Opaque;
7163 break;
7164 default:
7165 break;
7169 result.mHasSize = true;
7171 // We shouldn't have a VideoFrame if either of these is true.
7172 result.mHadCrossOriginRedirects = false;
7173 result.mIsWriteOnly = false;
7175 nsIGlobalObject* global = aVideoFrame->GetParentObject();
7176 if (global) {
7177 result.mPrincipal = global->PrincipalOrNull();
7180 if (aTarget) {
7181 // They gave us a DrawTarget to optimize for, so even though we may have a
7182 // layers::Image, we should unconditionally try to grab a SourceSurface and
7183 // try to optimize it.
7184 if (result.mLayersImage) {
7185 MOZ_ASSERT(!result.mSourceSurface);
7186 result.mSourceSurface = result.mLayersImage->GetAsSourceSurface();
7189 if (result.mSourceSurface) {
7190 RefPtr<SourceSurface> opt =
7191 aTarget->OptimizeSourceSurface(result.mSourceSurface);
7192 if (opt) {
7193 result.mSourceSurface = std::move(opt);
7198 return result;
7201 SurfaceFromElementResult nsLayoutUtils::SurfaceFromImageBitmap(
7202 mozilla::dom::ImageBitmap* aImageBitmap, uint32_t aSurfaceFlags) {
7203 return aImageBitmap->SurfaceFrom(aSurfaceFlags);
7206 SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
7207 nsIImageLoadingContent* aElement, const Maybe<int32_t>& aResizeWidth,
7208 const Maybe<int32_t>& aResizeHeight, uint32_t aSurfaceFlags,
7209 RefPtr<DrawTarget>& aTarget) {
7210 SurfaceFromElementResult result;
7211 nsresult rv;
7213 nsCOMPtr<imgIRequest> imgRequest;
7214 rv = aElement->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
7215 getter_AddRefs(imgRequest));
7216 if (NS_FAILED(rv)) {
7217 return result;
7220 if (!imgRequest) {
7221 // There's no image request. This is either because a request for
7222 // a non-empty URI failed, or the URI is the empty string.
7223 nsCOMPtr<nsIURI> currentURI;
7224 aElement->GetCurrentURI(getter_AddRefs(currentURI));
7225 if (!currentURI) {
7226 // Treat the empty URI as available instead of broken state.
7227 result.mHasSize = true;
7229 return result;
7232 uint32_t status;
7233 imgRequest->GetImageStatus(&status);
7234 result.mHasSize = status & imgIRequest::STATUS_SIZE_AVAILABLE;
7235 if ((status & imgIRequest::STATUS_LOAD_COMPLETE) == 0) {
7236 // Spec says to use GetComplete, but that only works on
7237 // HTMLImageElement, and we support all sorts of other stuff
7238 // here. Do this for now pending spec clarification.
7239 result.mIsStillLoading = (status & imgIRequest::STATUS_ERROR) == 0;
7240 return result;
7243 nsCOMPtr<nsIPrincipal> principal;
7244 rv = imgRequest->GetImagePrincipal(getter_AddRefs(principal));
7245 if (NS_FAILED(rv)) {
7246 return result;
7249 nsCOMPtr<imgIContainer> imgContainer;
7250 rv = imgRequest->GetImage(getter_AddRefs(imgContainer));
7251 if (NS_FAILED(rv)) {
7252 return result;
7255 nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
7257 // Ensure that the image is oriented the same way as it's displayed
7258 // if the image request is of the same origin.
7259 auto orientation =
7260 content->GetPrimaryFrame() &&
7261 !(aSurfaceFlags & SFE_ORIENTATION_FROM_IMAGE)
7262 ? content->GetPrimaryFrame()->StyleVisibility()->UsedImageOrientation(
7263 imgRequest)
7264 : nsStyleVisibility::UsedImageOrientation(
7265 imgRequest, StyleImageOrientation::FromImage);
7266 imgContainer = OrientImage(imgContainer, orientation);
7268 const bool noRasterize = aSurfaceFlags & SFE_NO_RASTERIZING_VECTORS;
7270 uint32_t whichFrame = aSurfaceFlags & SFE_WANT_FIRST_FRAME_IF_IMAGE
7271 ? (uint32_t)imgIContainer::FRAME_FIRST
7272 : (uint32_t)imgIContainer::FRAME_CURRENT;
7273 const bool exactSize = aSurfaceFlags & SFE_EXACT_SIZE_SURFACE;
7275 uint32_t frameFlags =
7276 imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY;
7277 if (aSurfaceFlags & SFE_NO_COLORSPACE_CONVERSION)
7278 frameFlags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION;
7279 if (aSurfaceFlags & SFE_ALLOW_NON_PREMULT) {
7280 frameFlags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
7283 int32_t imgWidth, imgHeight;
7284 HTMLImageElement* element = HTMLImageElement::FromNodeOrNull(content);
7285 if (aSurfaceFlags & SFE_USE_ELEMENT_SIZE_IF_VECTOR && element &&
7286 imgContainer->GetType() == imgIContainer::TYPE_VECTOR) {
7287 // We're holding a strong ref to "element" via "content".
7288 imgWidth = MOZ_KnownLive(element)->Width();
7289 imgHeight = MOZ_KnownLive(element)->Height();
7290 } else {
7291 auto res = imgContainer->GetResolution();
7292 rv = imgContainer->GetWidth(&imgWidth);
7293 if (NS_SUCCEEDED(rv)) {
7294 res.ApplyXTo(imgWidth);
7295 } else if (aResizeWidth.isSome()) {
7296 imgWidth = *aResizeWidth;
7297 } else {
7298 // As stated in css-sizing-3 Intrinsic Sizes, the fallback size of
7299 // 300 x 150 for the width and height as needed.
7301 // See https://drafts.csswg.org/css-sizing-3/#intrinsic-sizes
7302 imgWidth = kFallbackIntrinsicWidthInPixels;
7304 rv = imgContainer->GetHeight(&imgHeight);
7305 if (NS_SUCCEEDED(rv)) {
7306 res.ApplyYTo(imgHeight);
7307 } else if (aResizeHeight.isSome()) {
7308 imgHeight = *aResizeHeight;
7309 } else {
7310 // As stated in css-sizing-3 Intrinsic Sizes, the fallback size of
7311 // 300 x 150 for the width and height as needed.
7313 // See https://drafts.csswg.org/css-sizing-3/#intrinsic-sizes
7314 imgHeight = kFallbackIntrinsicHeightInPixels;
7317 result.mSize = result.mIntrinsicSize = IntSize(imgWidth, imgHeight);
7319 if (!noRasterize || imgContainer->GetType() == imgIContainer::TYPE_RASTER) {
7320 result.mSourceSurface =
7321 imgContainer->GetFrameAtSize(result.mSize, whichFrame, frameFlags);
7322 if (!result.mSourceSurface) {
7323 return result;
7325 IntSize surfSize = result.mSourceSurface->GetSize();
7326 if (exactSize && surfSize != result.mSize) {
7327 result.mSourceSurface =
7328 ScaleSourceSurface(*result.mSourceSurface, result.mSize);
7329 if (!result.mSourceSurface) {
7330 return result;
7332 } else {
7333 result.mSize = surfSize;
7335 // The surface we return is likely to be cached. We don't want to have to
7336 // convert to a surface that's compatible with aTarget each time it's used
7337 // (that would result in terrible performance), so we convert once here
7338 // upfront if aTarget is specified.
7339 if (aTarget) {
7340 RefPtr<SourceSurface> optSurface =
7341 aTarget->OptimizeSourceSurface(result.mSourceSurface);
7342 if (optSurface) {
7343 result.mSourceSurface = optSurface;
7347 const auto& format = result.mSourceSurface->GetFormat();
7348 if (IsOpaque(format)) {
7349 result.mAlphaType = gfxAlphaType::Opaque;
7350 } else if (frameFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA) {
7351 result.mAlphaType = gfxAlphaType::NonPremult;
7352 } else {
7353 result.mAlphaType = gfxAlphaType::Premult;
7355 } else {
7356 result.mDrawInfo.mImgContainer = imgContainer;
7357 result.mDrawInfo.mWhichFrame = whichFrame;
7358 result.mDrawInfo.mDrawingFlags = frameFlags;
7361 result.mCORSUsed = nsLayoutUtils::ImageRequestUsesCORS(imgRequest);
7363 bool hadCrossOriginRedirects = true;
7364 imgRequest->GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
7366 result.mPrincipal = std::move(principal);
7367 result.mHadCrossOriginRedirects = hadCrossOriginRedirects;
7368 result.mImageRequest = std::move(imgRequest);
7369 result.mIsWriteOnly = CanvasUtils::CheckWriteOnlySecurity(
7370 result.mCORSUsed, result.mPrincipal, result.mHadCrossOriginRedirects);
7372 return result;
7375 SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
7376 HTMLImageElement* aElement, uint32_t aSurfaceFlags,
7377 RefPtr<DrawTarget>& aTarget) {
7378 return SurfaceFromElement(static_cast<nsIImageLoadingContent*>(aElement),
7379 Nothing(), Nothing(), aSurfaceFlags, aTarget);
7382 SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
7383 HTMLCanvasElement* aElement, uint32_t aSurfaceFlags,
7384 RefPtr<DrawTarget>& aTarget) {
7385 SurfaceFromElementResult result;
7387 IntSize size = aElement->GetSize();
7388 if (size.IsEmpty()) {
7389 return result;
7392 auto pAlphaType = &result.mAlphaType;
7393 if (!(aSurfaceFlags & SFE_ALLOW_NON_PREMULT)) {
7394 pAlphaType =
7395 nullptr; // Coersce GetSurfaceSnapshot to give us Opaque/Premult only.
7397 result.mSourceSurface = aElement->GetSurfaceSnapshot(pAlphaType, aTarget);
7398 if (!result.mSourceSurface) {
7399 // If the element doesn't have a context then we won't get a snapshot. The
7400 // canvas spec wants us to not error and just draw nothing, so return an
7401 // empty surface.
7402 result.mSize = size;
7403 result.mAlphaType = gfxAlphaType::Opaque;
7404 RefPtr<DrawTarget> ref =
7405 aTarget ? aTarget
7406 : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
7407 if (ref->CanCreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8)) {
7408 RefPtr<DrawTarget> dt =
7409 ref->CreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8);
7410 if (dt) {
7411 result.mSourceSurface = dt->Snapshot();
7414 } else {
7415 result.mSize = result.mSourceSurface->GetSize();
7417 // If we want an exact sized surface, then we need to scale if we don't
7418 // match the intrinsic size.
7419 const bool exactSize = aSurfaceFlags & SFE_EXACT_SIZE_SURFACE;
7420 if (exactSize && size != result.mSize) {
7421 result.mSize = size;
7422 result.mSourceSurface = ScaleSourceSurface(*result.mSourceSurface, size);
7425 if (aTarget && result.mSourceSurface) {
7426 RefPtr<SourceSurface> opt =
7427 aTarget->OptimizeSourceSurface(result.mSourceSurface);
7428 if (opt) {
7429 result.mSourceSurface = opt;
7434 // Ensure that any future changes to the canvas trigger proper invalidation,
7435 // in case this is being used by -moz-element()
7436 aElement->MarkContextClean();
7438 result.mHasSize = true;
7439 result.mIntrinsicSize = size;
7440 result.mPrincipal = aElement->NodePrincipal();
7441 result.mHadCrossOriginRedirects = false;
7442 result.mIsWriteOnly = aElement->IsWriteOnly();
7444 return result;
7447 SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
7448 HTMLVideoElement* aElement, uint32_t aSurfaceFlags,
7449 RefPtr<DrawTarget>& aTarget, bool aOptimizeSourceSurface) {
7450 SurfaceFromElementResult result;
7451 result.mAlphaType = gfxAlphaType::Opaque; // Assume opaque.
7453 if (aElement->ContainsRestrictedContent()) {
7454 return result;
7457 uint16_t readyState = aElement->ReadyState();
7458 if (readyState == HAVE_NOTHING || readyState == HAVE_METADATA) {
7459 result.mIsStillLoading = true;
7460 return result;
7463 // If it doesn't have a principal, just bail
7464 nsCOMPtr<nsIPrincipal> principal = aElement->GetCurrentVideoPrincipal();
7465 if (!principal) {
7466 return result;
7469 result.mLayersImage = aElement->GetCurrentImage();
7470 if (!result.mLayersImage) {
7471 return result;
7474 result.mCORSUsed = aElement->GetCORSMode() != CORS_NONE;
7475 result.mHasSize = true;
7476 result.mSize = result.mLayersImage->GetSize();
7477 result.mIntrinsicSize =
7478 gfx::IntSize(aElement->VideoWidth(), aElement->VideoHeight());
7479 result.mPrincipal = std::move(principal);
7480 result.mHadCrossOriginRedirects = aElement->HadCrossOriginRedirects();
7481 result.mIsWriteOnly = CanvasUtils::CheckWriteOnlySecurity(
7482 result.mCORSUsed, result.mPrincipal, result.mHadCrossOriginRedirects);
7484 if (aTarget && aOptimizeSourceSurface) {
7485 // They gave us a DrawTarget to optimize for, so even though we have a
7486 // layers::Image, we should unconditionally try to grab a SourceSurface and
7487 // try to optimize it.
7488 if ((result.mSourceSurface = result.mLayersImage->GetAsSourceSurface())) {
7489 RefPtr<SourceSurface> opt =
7490 aTarget->OptimizeSourceSurface(result.mSourceSurface);
7491 if (opt) {
7492 result.mSourceSurface = opt;
7497 return result;
7500 SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
7501 dom::Element* aElement, const Maybe<int32_t>& aResizeWidth,
7502 const Maybe<int32_t>& aResizeHeight, uint32_t aSurfaceFlags,
7503 RefPtr<DrawTarget>& aTarget) {
7504 // If it's a <canvas>, we may be able to just grab its internal surface
7505 if (HTMLCanvasElement* canvas = HTMLCanvasElement::FromNodeOrNull(aElement)) {
7506 return SurfaceFromElement(canvas, aSurfaceFlags, aTarget);
7509 // Maybe it's <video>?
7510 if (HTMLVideoElement* video = HTMLVideoElement::FromNodeOrNull(aElement)) {
7511 return SurfaceFromElement(video, aSurfaceFlags, aTarget);
7514 // Finally, check if it's a normal image
7515 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement);
7517 if (!imageLoader) {
7518 return SurfaceFromElementResult();
7521 return SurfaceFromElement(imageLoader, aResizeWidth, aResizeHeight,
7522 aSurfaceFlags, aTarget);
7525 /* static */
7526 Element* nsLayoutUtils::GetEditableRootContentByContentEditable(
7527 Document* aDocument) {
7528 // If the document is in designMode we should return nullptr.
7529 if (!aDocument || aDocument->IsInDesignMode()) {
7530 return nullptr;
7533 // contenteditable only works with HTML document.
7534 // XXXbz should this test IsHTMLOrXHTML(), or just IsHTML()?
7535 if (!aDocument->IsHTMLOrXHTML()) {
7536 return nullptr;
7539 Element* rootElement = aDocument->GetRootElement();
7540 if (rootElement && rootElement->IsEditable()) {
7541 return rootElement;
7544 // If there is no editable root element, check its <body> element.
7545 // Note that the body element could be <frameset> element.
7546 Element* bodyElement = aDocument->GetBody();
7547 if (bodyElement && bodyElement->IsEditable()) {
7548 return bodyElement;
7550 return nullptr;
7553 #ifdef DEBUG
7554 /* static */
7555 void nsLayoutUtils::AssertNoDuplicateContinuations(
7556 nsIFrame* aContainer, const nsFrameList& aFrameList) {
7557 for (nsIFrame* f : aFrameList) {
7558 // Check only later continuations of f; we deal with checking the
7559 // earlier continuations when we hit those earlier continuations in
7560 // the frame list.
7561 for (nsIFrame* c = f; (c = c->GetNextInFlow());) {
7562 NS_ASSERTION(c->GetParent() != aContainer || !aFrameList.ContainsFrame(c),
7563 "Two continuations of the same frame in the same "
7564 "frame list");
7569 // Is one of aFrame's ancestors a letter frame?
7570 static bool IsInLetterFrame(nsIFrame* aFrame) {
7571 for (nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) {
7572 if (f->IsLetterFrame()) {
7573 return true;
7576 return false;
7579 /* static */
7580 void nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(nsIFrame* aSubtreeRoot) {
7581 NS_ASSERTION(aSubtreeRoot->GetPrevInFlow(),
7582 "frame tree not empty, but caller reported complete status");
7584 // Also assert that text frames map no text.
7585 auto [start, end] = aSubtreeRoot->GetOffsets();
7586 // In some cases involving :first-letter, we'll partially unlink a
7587 // continuation in the middle of a continuation chain from its
7588 // previous and next continuations before destroying it, presumably so
7589 // that we don't also destroy the later continuations. Once we've
7590 // done this, GetOffsets returns incorrect values.
7591 // For examples, see list of tests in
7592 // https://bugzilla.mozilla.org/show_bug.cgi?id=619021#c29
7593 NS_ASSERTION(start == end || IsInLetterFrame(aSubtreeRoot),
7594 "frame tree not empty, but caller reported complete status");
7596 for (const auto& childList : aSubtreeRoot->ChildLists()) {
7597 for (nsIFrame* child : childList.mList) {
7598 nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(child);
7602 #endif
7604 static void GetFontFacesForFramesInner(
7605 nsIFrame* aFrame, nsLayoutUtils::UsedFontFaceList& aResult,
7606 nsLayoutUtils::UsedFontFaceTable& aFontFaces, uint32_t aMaxRanges,
7607 bool aSkipCollapsedWhitespace) {
7608 MOZ_ASSERT(aFrame, "NULL frame pointer");
7610 if (aFrame->IsTextFrame()) {
7611 if (!aFrame->GetPrevContinuation()) {
7612 nsLayoutUtils::GetFontFacesForText(aFrame, 0, INT32_MAX, true, aResult,
7613 aFontFaces, aMaxRanges,
7614 aSkipCollapsedWhitespace);
7616 return;
7619 FrameChildListID childLists[] = {FrameChildListID::Principal,
7620 FrameChildListID::Popup};
7621 for (size_t i = 0; i < ArrayLength(childLists); ++i) {
7622 for (nsIFrame* child : aFrame->GetChildList(childLists[i])) {
7623 child = nsPlaceholderFrame::GetRealFrameFor(child);
7624 GetFontFacesForFramesInner(child, aResult, aFontFaces, aMaxRanges,
7625 aSkipCollapsedWhitespace);
7630 /* static */
7631 nsresult nsLayoutUtils::GetFontFacesForFrames(nsIFrame* aFrame,
7632 UsedFontFaceList& aResult,
7633 UsedFontFaceTable& aFontFaces,
7634 uint32_t aMaxRanges,
7635 bool aSkipCollapsedWhitespace) {
7636 MOZ_ASSERT(aFrame, "NULL frame pointer");
7638 while (aFrame) {
7639 GetFontFacesForFramesInner(aFrame, aResult, aFontFaces, aMaxRanges,
7640 aSkipCollapsedWhitespace);
7641 aFrame = GetNextContinuationOrIBSplitSibling(aFrame);
7644 return NS_OK;
7647 static void AddFontsFromTextRun(gfxTextRun* aTextRun, nsTextFrame* aFrame,
7648 gfxSkipCharsIterator& aSkipIter,
7649 const gfxTextRun::Range& aRange,
7650 nsLayoutUtils::UsedFontFaceList& aResult,
7651 nsLayoutUtils::UsedFontFaceTable& aFontFaces,
7652 uint32_t aMaxRanges) {
7653 nsIContent* content = aFrame->GetContent();
7654 int32_t contentLimit =
7655 aFrame->GetContentOffset() + aFrame->GetInFlowContentLength();
7656 for (gfxTextRun::GlyphRunIterator glyphRuns(aTextRun, aRange);
7657 !glyphRuns.AtEnd(); glyphRuns.NextRun()) {
7658 gfxFontEntry* fe = glyphRuns.GlyphRun()->mFont->GetFontEntry();
7659 // if we have already listed this face, just make sure the match type is
7660 // recorded
7661 InspectorFontFace* fontFace = aFontFaces.Get(fe);
7662 if (fontFace) {
7663 fontFace->AddMatchType(glyphRuns.GlyphRun()->mMatchType);
7664 } else {
7665 // A new font entry we haven't seen before
7666 fontFace = new InspectorFontFace(fe, aTextRun->GetFontGroup(),
7667 glyphRuns.GlyphRun()->mMatchType);
7668 aFontFaces.InsertOrUpdate(fe, fontFace);
7669 aResult.AppendElement(fontFace);
7672 // Add this glyph run to the fontFace's list of ranges, unless we have
7673 // already collected as many as wanted.
7674 if (fontFace->RangeCount() < aMaxRanges) {
7675 int32_t start =
7676 aSkipIter.ConvertSkippedToOriginal(glyphRuns.StringStart());
7677 int32_t end = aSkipIter.ConvertSkippedToOriginal(glyphRuns.StringEnd());
7679 // Mapping back from textrun offsets ("skipped" offsets that reflect the
7680 // text after whitespace collapsing, etc) to DOM content offsets in the
7681 // original text is ambiguous, because many original characters can
7682 // map to a single skipped offset. aSkipIter.ConvertSkippedToOriginal()
7683 // will return an "original" offset that corresponds to the *end* of
7684 // a collapsed run of characters in this case; but that might extend
7685 // beyond the current content node if the textrun mapped multiple nodes.
7686 // So we clamp the end offset to keep it valid for the content node
7687 // that corresponds to the current textframe.
7688 end = std::min(end, contentLimit);
7690 if (end > start) {
7691 RefPtr<nsRange> range =
7692 nsRange::Create(content, start, content, end, IgnoreErrors());
7693 NS_WARNING_ASSERTION(range,
7694 "nsRange::Create() failed to create valid range");
7695 if (range) {
7696 fontFace->AddRange(range);
7703 /* static */
7704 void nsLayoutUtils::GetFontFacesForText(nsIFrame* aFrame, int32_t aStartOffset,
7705 int32_t aEndOffset,
7706 bool aFollowContinuations,
7707 UsedFontFaceList& aResult,
7708 UsedFontFaceTable& aFontFaces,
7709 uint32_t aMaxRanges,
7710 bool aSkipCollapsedWhitespace) {
7711 MOZ_ASSERT(aFrame, "NULL frame pointer");
7713 if (!aFrame->IsTextFrame()) {
7714 return;
7717 if (!aFrame->StyleVisibility()->IsVisible()) {
7718 return;
7721 nsTextFrame* curr = static_cast<nsTextFrame*>(aFrame);
7722 do {
7723 int32_t fstart = std::max(curr->GetContentOffset(), aStartOffset);
7724 int32_t fend = std::min(curr->GetContentEnd(), aEndOffset);
7725 if (fstart >= fend) {
7726 curr = static_cast<nsTextFrame*>(curr->GetNextContinuation());
7727 continue;
7730 // curr is overlapping with the offset we want
7731 gfxSkipCharsIterator iter = curr->EnsureTextRun(nsTextFrame::eInflated);
7732 gfxTextRun* textRun = curr->GetTextRun(nsTextFrame::eInflated);
7733 if (!textRun) {
7734 NS_WARNING("failed to get textRun, low memory?");
7735 return;
7738 // include continuations in the range that share the same textrun
7739 nsTextFrame* next = nullptr;
7740 if (aFollowContinuations && fend < aEndOffset) {
7741 next = static_cast<nsTextFrame*>(curr->GetNextContinuation());
7742 while (next && next->GetTextRun(nsTextFrame::eInflated) == textRun) {
7743 fend = std::min(next->GetContentEnd(), aEndOffset);
7744 next = fend < aEndOffset
7745 ? static_cast<nsTextFrame*>(next->GetNextContinuation())
7746 : nullptr;
7750 if (!aSkipCollapsedWhitespace || (curr->HasAnyNoncollapsedCharacters() &&
7751 curr->HasNonSuppressedText())) {
7752 gfxTextRun::Range range(iter.ConvertOriginalToSkipped(fstart),
7753 iter.ConvertOriginalToSkipped(fend));
7754 AddFontsFromTextRun(textRun, curr, iter, range, aResult, aFontFaces,
7755 aMaxRanges);
7758 curr = next;
7759 } while (aFollowContinuations && curr);
7762 /* static */
7763 size_t nsLayoutUtils::SizeOfTextRunsForFrames(nsIFrame* aFrame,
7764 MallocSizeOf aMallocSizeOf,
7765 bool clear) {
7766 MOZ_ASSERT(aFrame, "NULL frame pointer");
7768 size_t total = 0;
7770 if (aFrame->IsTextFrame()) {
7771 nsTextFrame* textFrame = static_cast<nsTextFrame*>(aFrame);
7772 for (uint32_t i = 0; i < 2; ++i) {
7773 gfxTextRun* run = textFrame->GetTextRun(
7774 (i != 0) ? nsTextFrame::eInflated : nsTextFrame::eNotInflated);
7775 if (run) {
7776 if (clear) {
7777 run->ResetSizeOfAccountingFlags();
7778 } else {
7779 total += run->MaybeSizeOfIncludingThis(aMallocSizeOf);
7783 return total;
7786 for (const auto& childList : aFrame->ChildLists()) {
7787 for (nsIFrame* f : childList.mList) {
7788 total += SizeOfTextRunsForFrames(f, aMallocSizeOf, clear);
7791 return total;
7794 /* static */
7795 void nsLayoutUtils::RecomputeSmoothScrollDefault() {
7796 if (nsContentUtils::ShouldResistFingerprinting(
7797 "We use the global RFP pref to maintain consistent scroll behavior "
7798 "in the browser.",
7799 RFPTarget::CSSPrefersReducedMotion)) {
7800 // When resist fingerprinting is enabled, we should not default disable
7801 // smooth scrolls when the user prefers-reduced-motion to avoid leaking
7802 // the value of the OS pref to sites.
7803 Preferences::SetBool(StaticPrefs::GetPrefName_general_smoothScroll(), true,
7804 PrefValueKind::Default);
7805 } else {
7806 // We want prefers-reduced-motion to determine the default
7807 // value of the general.smoothScroll pref. If the user
7808 // changed the pref we want to respect the change.
7809 Preferences::SetBool(
7810 StaticPrefs::GetPrefName_general_smoothScroll(),
7811 !LookAndFeel::GetInt(LookAndFeel::IntID::PrefersReducedMotion, 0),
7812 PrefValueKind::Default);
7816 /* static */
7817 bool nsLayoutUtils::IsSmoothScrollingEnabled() {
7818 return StaticPrefs::general_smoothScroll();
7821 /* static */
7822 void nsLayoutUtils::Initialize() {
7823 nsComputedDOMStyle::RegisterPrefChangeCallbacks();
7826 /* static */
7827 void nsLayoutUtils::Shutdown() {
7828 if (sContentMap) {
7829 sContentMap = nullptr;
7832 nsComputedDOMStyle::UnregisterPrefChangeCallbacks();
7835 /* static */
7836 void nsLayoutUtils::RegisterImageRequest(nsPresContext* aPresContext,
7837 imgIRequest* aRequest,
7838 bool* aRequestRegistered) {
7839 if (!aPresContext) {
7840 return;
7843 if (aRequestRegistered && *aRequestRegistered) {
7844 // Our request is already registered with the refresh driver, so
7845 // no need to register it again.
7846 return;
7849 if (aRequest) {
7850 aPresContext->RefreshDriver()->AddImageRequest(aRequest);
7851 if (aRequestRegistered) {
7852 *aRequestRegistered = true;
7857 /* static */
7858 void nsLayoutUtils::RegisterImageRequestIfAnimated(nsPresContext* aPresContext,
7859 imgIRequest* aRequest,
7860 bool* aRequestRegistered) {
7861 if (!aPresContext) {
7862 return;
7865 if (aRequestRegistered && *aRequestRegistered) {
7866 // Our request is already registered with the refresh driver, so
7867 // no need to register it again.
7868 return;
7871 if (aRequest) {
7872 nsCOMPtr<imgIContainer> image;
7873 if (NS_SUCCEEDED(aRequest->GetImage(getter_AddRefs(image)))) {
7874 // Check to verify that the image is animated. If so, then add it to the
7875 // list of images tracked by the refresh driver.
7876 bool isAnimated = false;
7877 nsresult rv = image->GetAnimated(&isAnimated);
7878 if (NS_SUCCEEDED(rv) && isAnimated) {
7879 aPresContext->RefreshDriver()->AddImageRequest(aRequest);
7880 if (aRequestRegistered) {
7881 *aRequestRegistered = true;
7888 /* static */
7889 void nsLayoutUtils::DeregisterImageRequest(nsPresContext* aPresContext,
7890 imgIRequest* aRequest,
7891 bool* aRequestRegistered) {
7892 if (!aPresContext) {
7893 return;
7896 // Deregister our imgIRequest with the refresh driver to
7897 // complete tear-down, but only if it has been registered
7898 if (aRequestRegistered && !*aRequestRegistered) {
7899 return;
7902 if (aRequest) {
7903 nsCOMPtr<imgIContainer> image;
7904 if (NS_SUCCEEDED(aRequest->GetImage(getter_AddRefs(image)))) {
7905 aPresContext->RefreshDriver()->RemoveImageRequest(aRequest);
7907 if (aRequestRegistered) {
7908 *aRequestRegistered = false;
7914 /* static */
7915 void nsLayoutUtils::PostRestyleEvent(Element* aElement,
7916 RestyleHint aRestyleHint,
7917 nsChangeHint aMinChangeHint) {
7918 if (Document* doc = aElement->GetComposedDoc()) {
7919 if (nsPresContext* presContext = doc->GetPresContext()) {
7920 presContext->RestyleManager()->PostRestyleEvent(aElement, aRestyleHint,
7921 aMinChangeHint);
7926 nsSetAttrRunnable::nsSetAttrRunnable(Element* aElement, nsAtom* aAttrName,
7927 const nsAString& aValue)
7928 : mozilla::Runnable("nsSetAttrRunnable"),
7929 mElement(aElement),
7930 mAttrName(aAttrName),
7931 mValue(aValue) {
7932 NS_ASSERTION(aElement && aAttrName, "Missing stuff, prepare to crash");
7935 nsSetAttrRunnable::nsSetAttrRunnable(Element* aElement, nsAtom* aAttrName,
7936 int32_t aValue)
7937 : mozilla::Runnable("nsSetAttrRunnable"),
7938 mElement(aElement),
7939 mAttrName(aAttrName) {
7940 NS_ASSERTION(aElement && aAttrName, "Missing stuff, prepare to crash");
7941 mValue.AppendInt(aValue);
7944 NS_IMETHODIMP
7945 nsSetAttrRunnable::Run() {
7946 return mElement->SetAttr(kNameSpaceID_None, mAttrName, mValue, true);
7949 nsUnsetAttrRunnable::nsUnsetAttrRunnable(Element* aElement, nsAtom* aAttrName)
7950 : mozilla::Runnable("nsUnsetAttrRunnable"),
7951 mElement(aElement),
7952 mAttrName(aAttrName) {
7953 NS_ASSERTION(aElement && aAttrName, "Missing stuff, prepare to crash");
7956 NS_IMETHODIMP
7957 nsUnsetAttrRunnable::Run() {
7958 return mElement->UnsetAttr(kNameSpaceID_None, mAttrName, true);
7962 * Compute the minimum font size inside of a container with the given
7963 * width, such that **when the user zooms the container to fill the full
7964 * width of the device**, the fonts satisfy our minima.
7966 static nscoord MinimumFontSizeFor(nsPresContext* aPresContext,
7967 WritingMode aWritingMode,
7968 nscoord aContainerISize) {
7969 PresShell* presShell = aPresContext->PresShell();
7971 uint32_t emPerLine = presShell->FontSizeInflationEmPerLine();
7972 uint32_t minTwips = presShell->FontSizeInflationMinTwips();
7973 if (emPerLine == 0 && minTwips == 0) {
7974 return 0;
7977 nscoord byLine = 0, byInch = 0;
7978 if (emPerLine != 0) {
7979 byLine = aContainerISize / emPerLine;
7981 if (minTwips != 0) {
7982 // REVIEW: Is this giving us app units and sizes *not* counting
7983 // viewport scaling?
7984 gfxSize screenSize = aPresContext->ScreenSizeInchesForFontInflation();
7985 float deviceISizeInches =
7986 aWritingMode.IsVertical() ? screenSize.height : screenSize.width;
7987 byInch =
7988 NSToCoordRound(aContainerISize / (deviceISizeInches * 1440 / minTwips));
7990 return std::max(byLine, byInch);
7993 /* static */
7994 float nsLayoutUtils::FontSizeInflationInner(const nsIFrame* aFrame,
7995 nscoord aMinFontSize) {
7996 // Note that line heights should be inflated by the same ratio as the
7997 // font size of the same text; thus we operate only on the font size
7998 // even when we're scaling a line height.
7999 nscoord styleFontSize = aFrame->StyleFont()->mFont.size.ToAppUnits();
8000 if (styleFontSize <= 0) {
8001 // Never scale zero font size.
8002 return 1.0;
8005 if (aMinFontSize <= 0) {
8006 // No need to scale.
8007 return 1.0;
8010 // If between this current frame and its font inflation container there is a
8011 // non-inline element with fixed width or height, then we should not inflate
8012 // fonts for this frame.
8013 for (const nsIFrame* f = aFrame; f && !f->IsContainerForFontSizeInflation();
8014 f = f->GetParent()) {
8015 nsIContent* content = f->GetContent();
8016 LayoutFrameType fType = f->Type();
8017 nsIFrame* parent = f->GetParent();
8018 // Also, if there is more than one frame corresponding to a single
8019 // content node, we want the outermost one.
8020 if (!(parent && parent->GetContent() == content) &&
8021 // ignore width/height on inlines since they don't apply
8022 fType != LayoutFrameType::Inline &&
8023 // ignore width on radios and checkboxes since we enlarge them and
8024 // they have width/height in ua.css
8025 fType != LayoutFrameType::CheckboxRadio) {
8026 // ruby annotations should have the same inflation as its
8027 // grandparent, which is the ruby frame contains the annotation.
8028 if (fType == LayoutFrameType::RubyText) {
8029 MOZ_ASSERT(parent && parent->IsRubyTextContainerFrame());
8030 nsIFrame* grandparent = parent->GetParent();
8031 MOZ_ASSERT(grandparent && grandparent->IsRubyFrame());
8032 return FontSizeInflationFor(grandparent);
8034 WritingMode wm = f->GetWritingMode();
8035 const auto& stylePosISize = f->StylePosition()->ISize(wm);
8036 const auto& stylePosBSize = f->StylePosition()->BSize(wm);
8037 if (!stylePosISize.IsAuto() ||
8038 !stylePosBSize.BehavesLikeInitialValueOnBlockAxis()) {
8039 return 1.0;
8044 int32_t interceptParam = StaticPrefs::font_size_inflation_mappingIntercept();
8045 float maxRatio = (float)StaticPrefs::font_size_inflation_maxRatio() / 100.0f;
8047 float ratio = float(styleFontSize) / float(aMinFontSize);
8048 float inflationRatio;
8050 // Given a minimum inflated font size m, a specified font size s, we want to
8051 // find the inflated font size i and then return the ratio of i to s (i/s).
8052 if (interceptParam >= 0) {
8053 // Since the mapping intercept parameter P is greater than zero, we use it
8054 // to determine the point where our mapping function intersects the i=s
8055 // line. This means that we have an equation of the form:
8057 // i = m + s*(P/2)/(1 + P/2), if s <= (1 + P/2)*m
8058 // i = s, if s >= (1 + P/2)*m
8060 float intercept = 1 + float(interceptParam) / 2.0f;
8061 if (ratio >= intercept) {
8062 // If we're already at 1+P/2 or more times the minimum, don't scale.
8063 return 1.0;
8066 // The point (intercept, intercept) is where the part of the i vs. s graph
8067 // that's not slope 1 meets the i=s line. (This part of the
8068 // graph is a line from (0, m), to that point). We calculate the
8069 // intersection point to be ((1+P/2)m, (1+P/2)m), where P is the
8070 // intercept parameter above. We then need to return i/s.
8071 inflationRatio = (1.0f + (ratio * (intercept - 1) / intercept)) / ratio;
8072 } else {
8073 // This is the case where P is negative. We essentially want to implement
8074 // the case for P=infinity here, so we make i = s + m, which means that
8075 // i/s = s/s + m/s = 1 + 1/ratio
8076 inflationRatio = 1 + 1.0f / ratio;
8079 if (maxRatio > 1.0 && inflationRatio > maxRatio) {
8080 return maxRatio;
8081 } else {
8082 return inflationRatio;
8086 static bool ShouldInflateFontsForContainer(const nsIFrame* aFrame) {
8087 // We only want to inflate fonts for text that is in a place
8088 // with room to expand. The question is what the best heuristic for
8089 // that is...
8090 // For now, we're going to use NS_FRAME_IN_CONSTRAINED_BSIZE, which
8091 // indicates whether the frame is inside something with a constrained
8092 // block-size (propagating down the tree), but the propagation stops when
8093 // we hit overflow-y [or -x, for vertical mode]: scroll or auto.
8094 const nsStyleText* styleText = aFrame->StyleText();
8096 return styleText->mTextSizeAdjust != StyleTextSizeAdjust::None &&
8097 !aFrame->HasAnyStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE) &&
8098 // We also want to disable font inflation for containers that have
8099 // preformatted text.
8100 // MathML cells need special treatment. See bug 1002526 comment 56.
8101 (styleText->WhiteSpaceCanWrap(aFrame) || aFrame->IsMathMLFrame());
8104 nscoord nsLayoutUtils::InflationMinFontSizeFor(const nsIFrame* aFrame) {
8105 nsPresContext* presContext = aFrame->PresContext();
8106 if (!FontSizeInflationEnabled(presContext) ||
8107 presContext->mInflationDisabledForShrinkWrap) {
8108 return 0;
8111 for (const nsIFrame* f = aFrame; f; f = f->GetParent()) {
8112 if (f->IsContainerForFontSizeInflation()) {
8113 if (!ShouldInflateFontsForContainer(f)) {
8114 return 0;
8117 nsFontInflationData* data =
8118 nsFontInflationData::FindFontInflationDataFor(aFrame);
8119 // FIXME: The need to null-check here is sort of a bug, and might
8120 // lead to incorrect results.
8121 if (!data || !data->InflationEnabled()) {
8122 return 0;
8125 return MinimumFontSizeFor(aFrame->PresContext(), aFrame->GetWritingMode(),
8126 data->UsableISize());
8130 MOZ_ASSERT(false, "root should always be container");
8132 return 0;
8135 float nsLayoutUtils::FontSizeInflationFor(const nsIFrame* aFrame) {
8136 if (aFrame->IsInSVGTextSubtree()) {
8137 const nsIFrame* container = aFrame;
8138 while (!container->IsSVGTextFrame()) {
8139 container = container->GetParent();
8141 NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame");
8142 return static_cast<const SVGTextFrame*>(container)
8143 ->GetFontSizeScaleFactor();
8146 if (!FontSizeInflationEnabled(aFrame->PresContext())) {
8147 return 1.0f;
8150 return FontSizeInflationInner(aFrame, InflationMinFontSizeFor(aFrame));
8153 /* static */
8154 bool nsLayoutUtils::FontSizeInflationEnabled(nsPresContext* aPresContext) {
8155 PresShell* presShell = aPresContext->GetPresShell();
8156 if (!presShell) {
8157 return false;
8159 return presShell->FontSizeInflationEnabled();
8162 /* static */
8163 nsRect nsLayoutUtils::GetBoxShadowRectForFrame(nsIFrame* aFrame,
8164 const nsSize& aFrameSize) {
8165 auto boxShadows = aFrame->StyleEffects()->mBoxShadow.AsSpan();
8166 if (boxShadows.IsEmpty()) {
8167 return nsRect();
8170 nsRect inputRect(nsPoint(0, 0), aFrameSize);
8172 // According to the CSS spec, box-shadow should be based on the border box.
8173 // However, that looks broken when the background extends outside the border
8174 // box, as can be the case with native theming. To fix that we expand the
8175 // area that we shadow to include the bounds of any native theme drawing.
8176 const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
8177 nsITheme::Transparency transparency;
8178 if (aFrame->IsThemed(styleDisplay, &transparency)) {
8179 // For opaque (rectangular) theme widgets we can take the generic
8180 // border-box path with border-radius disabled.
8181 if (transparency != nsITheme::eOpaque) {
8182 nsPresContext* presContext = aFrame->PresContext();
8183 presContext->Theme()->GetWidgetOverflow(
8184 presContext->DeviceContext(), aFrame,
8185 styleDisplay->EffectiveAppearance(), &inputRect);
8189 nsRect shadows;
8190 int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel();
8191 for (auto& shadow : boxShadows) {
8192 nsRect tmpRect = inputRect;
8194 // inset shadows are never painted outside the frame
8195 if (shadow.inset) {
8196 continue;
8199 tmpRect.MoveBy(nsPoint(shadow.base.horizontal.ToAppUnits(),
8200 shadow.base.vertical.ToAppUnits()));
8201 tmpRect.Inflate(shadow.spread.ToAppUnits());
8202 tmpRect.Inflate(nsContextBoxBlur::GetBlurRadiusMargin(
8203 shadow.base.blur.ToAppUnits(), A2D));
8204 shadows.UnionRect(shadows, tmpRect);
8206 return shadows;
8209 /* static */
8210 bool nsLayoutUtils::GetDocumentViewerSize(
8211 const nsPresContext* aPresContext, LayoutDeviceIntSize& aOutSize,
8212 SubtractDynamicToolbar aSubtractDynamicToolbar) {
8213 nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
8214 if (!docShell) {
8215 return false;
8218 nsCOMPtr<nsIDocumentViewer> viewer;
8219 docShell->GetDocViewer(getter_AddRefs(viewer));
8220 if (!viewer) {
8221 return false;
8224 nsIntRect bounds;
8225 viewer->GetBounds(bounds);
8227 if (aPresContext->IsRootContentDocumentCrossProcess() &&
8228 aSubtractDynamicToolbar == SubtractDynamicToolbar::Yes &&
8229 aPresContext->HasDynamicToolbar() && !bounds.IsEmpty()) {
8230 MOZ_ASSERT(aPresContext->IsRootContentDocumentCrossProcess());
8231 bounds.height -= aPresContext->GetDynamicToolbarMaxHeight();
8232 // Collapse the size in the case the dynamic toolbar max height is greater
8233 // than the content bound height so that hopefully embedders of GeckoView
8234 // may notice they set wrong dynamic toolbar max height.
8235 if (bounds.height < 0) {
8236 bounds.height = 0;
8240 aOutSize = LayoutDeviceIntRect::FromUnknownRect(bounds).Size();
8241 return true;
8244 bool nsLayoutUtils::UpdateCompositionBoundsForRCDRSF(
8245 ParentLayerRect& aCompBounds, const nsPresContext* aPresContext,
8246 IncludeDynamicToolbar aIncludeDynamicToolbar) {
8247 SubtractDynamicToolbar shouldSubtractDynamicToolbar =
8248 aIncludeDynamicToolbar == IncludeDynamicToolbar::Force
8249 ? SubtractDynamicToolbar::No
8250 : aPresContext->IsRootContentDocumentCrossProcess() &&
8251 aPresContext->HasDynamicToolbar()
8252 ? SubtractDynamicToolbar::Yes
8253 : SubtractDynamicToolbar::No;
8255 if (shouldSubtractDynamicToolbar == SubtractDynamicToolbar::Yes) {
8256 if (RefPtr<MobileViewportManager> MVM =
8257 aPresContext->PresShell()->GetMobileViewportManager()) {
8258 // Convert the intrinsic composition size to app units here since
8259 // the returned size of below CalculateScrollableRectForFrame call has
8260 // been already converted/rounded to app units.
8261 nsSize intrinsicCompositionSize =
8262 CSSSize::ToAppUnits(MVM->GetIntrinsicCompositionSize());
8264 if (nsIScrollableFrame* rootScrollableFrame =
8265 aPresContext->PresShell()->GetRootScrollFrameAsScrollable()) {
8266 // Expand the composition size to include the area initially covered by
8267 // the dynamic toolbar only if the content is taller than the intrinsic
8268 // composition size (i.e. the dynamic toolbar should be able to move
8269 // only if the content is vertically scrollable).
8270 if (intrinsicCompositionSize.height <
8271 CalculateScrollableRectForFrame(rootScrollableFrame, nullptr)
8272 .Height()) {
8273 shouldSubtractDynamicToolbar = SubtractDynamicToolbar::No;
8279 LayoutDeviceIntSize contentSize;
8280 if (!GetDocumentViewerSize(aPresContext, contentSize,
8281 shouldSubtractDynamicToolbar)) {
8282 return false;
8284 aCompBounds.SizeTo(ViewAs<ParentLayerPixel>(
8285 LayoutDeviceSize(contentSize),
8286 PixelCastJustification::LayoutDeviceIsParentLayerForRCDRSF));
8287 return true;
8290 /* static */
8291 nsMargin nsLayoutUtils::ScrollbarAreaToExcludeFromCompositionBoundsFor(
8292 const nsIFrame* aScrollFrame) {
8293 if (!aScrollFrame || !aScrollFrame->GetScrollTargetFrame()) {
8294 return nsMargin();
8296 nsPresContext* presContext = aScrollFrame->PresContext();
8297 PresShell* presShell = presContext->GetPresShell();
8298 if (!presShell) {
8299 return nsMargin();
8301 bool isRootScrollFrame = aScrollFrame == presShell->GetRootScrollFrame();
8302 bool isRootContentDocRootScrollFrame =
8303 isRootScrollFrame && presContext->IsRootContentDocumentCrossProcess();
8304 if (!isRootContentDocRootScrollFrame) {
8305 return nsMargin();
8307 if (presContext->UseOverlayScrollbars()) {
8308 return nsMargin();
8310 nsIScrollableFrame* scrollableFrame = aScrollFrame->GetScrollTargetFrame();
8311 if (!scrollableFrame) {
8312 return nsMargin();
8314 return scrollableFrame->GetActualScrollbarSizes(
8315 nsIScrollableFrame::ScrollbarSizesOptions::
8316 INCLUDE_VISUAL_VIEWPORT_SCROLLBARS);
8319 /* static */
8320 nsSize nsLayoutUtils::CalculateCompositionSizeForFrame(
8321 nsIFrame* aFrame, bool aSubtractScrollbars,
8322 const nsSize* aOverrideScrollPortSize,
8323 IncludeDynamicToolbar aIncludeDynamicToolbar) {
8324 // If we have a scrollable frame, restrict the composition bounds to its
8325 // scroll port. The scroll port excludes the frame borders and the scroll
8326 // bars, which we don't want to be part of the composition bounds.
8327 nsIScrollableFrame* scrollableFrame = aFrame->GetScrollTargetFrame();
8328 nsRect rect = scrollableFrame ? scrollableFrame->GetScrollPortRect()
8329 : aFrame->GetRect();
8330 nsSize size =
8331 aOverrideScrollPortSize ? *aOverrideScrollPortSize : rect.Size();
8333 nsPresContext* presContext = aFrame->PresContext();
8334 PresShell* presShell = presContext->PresShell();
8336 bool isRootContentDocRootScrollFrame =
8337 presContext->IsRootContentDocumentCrossProcess() &&
8338 aFrame == presShell->GetRootScrollFrame();
8339 if (isRootContentDocRootScrollFrame) {
8340 ParentLayerRect compBounds;
8341 if (UpdateCompositionBoundsForRCDRSF(compBounds, presContext,
8342 aIncludeDynamicToolbar)) {
8343 int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
8344 size = nsSize(compBounds.width * auPerDevPixel,
8345 compBounds.height * auPerDevPixel);
8349 if (aSubtractScrollbars) {
8350 nsMargin margins = ScrollbarAreaToExcludeFromCompositionBoundsFor(aFrame);
8351 size.width -= margins.LeftRight();
8352 size.height -= margins.TopBottom();
8355 return size;
8358 /* static */
8359 CSSSize nsLayoutUtils::CalculateBoundingCompositionSize(
8360 const nsIFrame* aFrame, bool aIsRootContentDocRootScrollFrame,
8361 const FrameMetrics& aMetrics) {
8362 if (aIsRootContentDocRootScrollFrame) {
8363 return ViewAs<LayerPixel>(
8364 aMetrics.GetCompositionBounds().Size(),
8365 PixelCastJustification::ParentLayerToLayerForRootComposition) *
8366 LayerToScreenScale(1.0f) / aMetrics.DisplayportPixelsPerCSSPixel();
8368 nsPresContext* presContext = aFrame->PresContext();
8369 ScreenSize rootCompositionSize;
8370 nsPresContext* rootPresContext =
8371 presContext->GetInProcessRootContentDocumentPresContext();
8372 if (!rootPresContext) {
8373 rootPresContext = presContext->GetRootPresContext();
8375 PresShell* rootPresShell = nullptr;
8376 if (rootPresContext) {
8377 rootPresShell = rootPresContext->PresShell();
8378 if (nsIFrame* rootFrame = rootPresShell->GetRootFrame()) {
8379 ParentLayerRect compBounds;
8380 if (UpdateCompositionBoundsForRCDRSF(compBounds, rootPresContext)) {
8381 rootCompositionSize = ViewAs<ScreenPixel>(
8382 compBounds.Size(),
8383 PixelCastJustification::ScreenIsParentLayerForRoot);
8384 } else {
8385 // LayoutDeviceToScreenScale2D =
8386 // LayoutDeviceToParentLayerScale *
8387 // ParentLayerToScreenScale2D
8388 LayoutDeviceToScreenScale2D cumulativeResolution =
8389 LayoutDeviceToParentLayerScale(
8390 rootPresShell->GetCumulativeResolution()) *
8391 GetTransformToAncestorScaleCrossProcessForFrameMetrics(rootFrame);
8393 int32_t rootAUPerDevPixel = rootPresContext->AppUnitsPerDevPixel();
8394 rootCompositionSize = (LayoutDeviceRect::FromAppUnits(
8395 rootFrame->GetRect(), rootAUPerDevPixel) *
8396 cumulativeResolution)
8397 .Size();
8400 } else {
8401 nsIWidget* widget = aFrame->GetNearestWidget();
8402 LayoutDeviceIntRect widgetBounds = widget->GetBounds();
8403 rootCompositionSize = ScreenSize(ViewAs<ScreenPixel>(
8404 widgetBounds.Size(),
8405 PixelCastJustification::LayoutDeviceIsScreenForBounds));
8408 // Adjust composition size for the size of scroll bars.
8409 nsIFrame* rootRootScrollFrame =
8410 rootPresShell ? rootPresShell->GetRootScrollFrame() : nullptr;
8411 nsMargin scrollbarMargins =
8412 ScrollbarAreaToExcludeFromCompositionBoundsFor(rootRootScrollFrame);
8413 LayoutDeviceMargin margins = LayoutDeviceMargin::FromAppUnits(
8414 scrollbarMargins, rootPresContext->AppUnitsPerDevPixel());
8415 // Scrollbars are not subject to resolution scaling, so LD pixels = layer
8416 // pixels for them.
8417 rootCompositionSize.width -= margins.LeftRight();
8418 rootCompositionSize.height -= margins.TopBottom();
8420 CSSSize result =
8421 rootCompositionSize / aMetrics.DisplayportPixelsPerCSSPixel();
8423 // If this is a nested content process, the in-process root content document's
8424 // composition size may still be arbitrarily large, so bound it further by
8425 // how much of the in-process RCD is visible in the top-level (cross-process
8426 // RCD) viewport.
8427 if (rootPresShell) {
8428 if (BrowserChild* bc = BrowserChild::GetFrom(rootPresShell)) {
8429 if (const auto& visibleRect =
8430 bc->GetTopLevelViewportVisibleRectInSelfCoords()) {
8431 CSSSize cssVisibleRect =
8432 visibleRect->Size() / rootPresContext->CSSToDevPixelScale();
8433 result = Min(result, cssVisibleRect);
8438 return result;
8441 /* static */
8442 nsRect nsLayoutUtils::CalculateScrollableRectForFrame(
8443 const nsIScrollableFrame* aScrollableFrame, const nsIFrame* aRootFrame) {
8444 nsRect contentBounds;
8445 if (aScrollableFrame) {
8446 contentBounds = aScrollableFrame->GetScrollRange();
8448 nsPoint scrollPosition = aScrollableFrame->GetScrollPosition();
8449 if (aScrollableFrame->GetScrollStyles().mVertical ==
8450 StyleOverflow::Hidden) {
8451 contentBounds.y = scrollPosition.y;
8452 contentBounds.height = 0;
8454 if (aScrollableFrame->GetScrollStyles().mHorizontal ==
8455 StyleOverflow::Hidden) {
8456 contentBounds.x = scrollPosition.x;
8457 contentBounds.width = 0;
8460 contentBounds.width += aScrollableFrame->GetScrollPortRect().width;
8461 contentBounds.height += aScrollableFrame->GetScrollPortRect().height;
8462 } else {
8463 contentBounds = aRootFrame->GetRect();
8464 // Clamp to (0, 0) if there is no corresponding scrollable frame for the
8465 // given |aRootFrame|.
8466 contentBounds.MoveTo(0, 0);
8468 return contentBounds;
8471 /* static */
8472 nsRect nsLayoutUtils::CalculateExpandedScrollableRect(nsIFrame* aFrame) {
8473 nsRect scrollableRect = CalculateScrollableRectForFrame(
8474 aFrame->GetScrollTargetFrame(), aFrame->PresShell()->GetRootFrame());
8475 nsSize compSize = CalculateCompositionSizeForFrame(aFrame);
8477 if (aFrame == aFrame->PresShell()->GetRootScrollFrame()) {
8478 // the composition size for the root scroll frame does not include the
8479 // local resolution, so we adjust.
8480 float res = aFrame->PresShell()->GetResolution();
8481 compSize.width = NSToCoordRound(compSize.width / res);
8482 compSize.height = NSToCoordRound(compSize.height / res);
8485 if (scrollableRect.width < compSize.width) {
8486 scrollableRect.x =
8487 std::max(0, scrollableRect.x - (compSize.width - scrollableRect.width));
8488 scrollableRect.width = compSize.width;
8491 if (scrollableRect.height < compSize.height) {
8492 scrollableRect.y = std::max(
8493 0, scrollableRect.y - (compSize.height - scrollableRect.height));
8494 scrollableRect.height = compSize.height;
8496 return scrollableRect;
8499 /* static */
8500 void nsLayoutUtils::DoLogTestDataForPaint(WebRenderLayerManager* aManager,
8501 ViewID aScrollId,
8502 const std::string& aKey,
8503 const std::string& aValue) {
8504 MOZ_ASSERT(nsLayoutUtils::IsAPZTestLoggingEnabled(), "don't call me");
8505 aManager->LogTestDataForCurrentPaint(aScrollId, aKey, aValue);
8508 void nsLayoutUtils::LogAdditionalTestData(nsDisplayListBuilder* aBuilder,
8509 const std::string& aKey,
8510 const std::string& aValue) {
8511 WebRenderLayerManager* manager = aBuilder->GetWidgetLayerManager(nullptr);
8512 if (!manager) {
8513 return;
8515 manager->LogAdditionalTestData(aKey, aValue);
8518 /* static */
8519 bool nsLayoutUtils::IsAPZTestLoggingEnabled() {
8520 return StaticPrefs::apz_test_logging_enabled();
8523 ////////////////////////////////////////
8524 // SurfaceFromElementResult
8526 SurfaceFromElementResult::SurfaceFromElementResult()
8527 // Use safe default values here
8528 : mHadCrossOriginRedirects(false),
8529 mIsWriteOnly(true),
8530 mIsStillLoading(false),
8531 mHasSize(false),
8532 mCORSUsed(false),
8533 mAlphaType(gfxAlphaType::Opaque) {}
8535 const RefPtr<mozilla::gfx::SourceSurface>&
8536 SurfaceFromElementResult::GetSourceSurface() {
8537 if (!mSourceSurface && mLayersImage) {
8538 mSourceSurface = mLayersImage->GetAsSourceSurface();
8541 return mSourceSurface;
8544 ////////////////////////////////////////
8546 bool nsLayoutUtils::IsNonWrapperBlock(nsIFrame* aFrame) {
8547 MOZ_ASSERT(aFrame);
8548 return aFrame->IsBlockFrameOrSubclass() && !aFrame->IsBlockWrapper();
8551 AutoMaybeDisableFontInflation::AutoMaybeDisableFontInflation(nsIFrame* aFrame) {
8552 // FIXME: Now that inflation calculations are based on the flow
8553 // root's NCA's (nearest common ancestor of its inflatable
8554 // descendants) width, we could probably disable inflation in
8555 // fewer cases than we currently do.
8556 // MathML cells need special treatment. See bug 1002526 comment 56.
8557 if (aFrame->IsContainerForFontSizeInflation() && !aFrame->IsMathMLFrame()) {
8558 mPresContext = aFrame->PresContext();
8559 mOldValue = mPresContext->mInflationDisabledForShrinkWrap;
8560 mPresContext->mInflationDisabledForShrinkWrap = true;
8561 } else {
8562 // indicate we have nothing to restore
8563 mPresContext = nullptr;
8564 mOldValue = false;
8568 AutoMaybeDisableFontInflation::~AutoMaybeDisableFontInflation() {
8569 if (mPresContext) {
8570 mPresContext->mInflationDisabledForShrinkWrap = mOldValue;
8574 namespace mozilla {
8576 Rect NSRectToRect(const nsRect& aRect, double aAppUnitsPerPixel) {
8577 // Note that by making aAppUnitsPerPixel a double we're doing floating-point
8578 // division using a larger type and avoiding rounding error.
8579 return Rect(Float(aRect.x / aAppUnitsPerPixel),
8580 Float(aRect.y / aAppUnitsPerPixel),
8581 Float(aRect.width / aAppUnitsPerPixel),
8582 Float(aRect.height / aAppUnitsPerPixel));
8585 Rect NSRectToSnappedRect(const nsRect& aRect, double aAppUnitsPerPixel,
8586 const gfx::DrawTarget& aSnapDT) {
8587 // Note that by making aAppUnitsPerPixel a double we're doing floating-point
8588 // division using a larger type and avoiding rounding error.
8589 Rect rect(Float(aRect.x / aAppUnitsPerPixel),
8590 Float(aRect.y / aAppUnitsPerPixel),
8591 Float(aRect.width / aAppUnitsPerPixel),
8592 Float(aRect.height / aAppUnitsPerPixel));
8593 MaybeSnapToDevicePixels(rect, aSnapDT, true);
8594 return rect;
8596 // Similar to a snapped rect, except an axis is left unsnapped if the snapping
8597 // process results in a length of 0.
8598 Rect NSRectToNonEmptySnappedRect(const nsRect& aRect, double aAppUnitsPerPixel,
8599 const gfx::DrawTarget& aSnapDT) {
8600 // Note that by making aAppUnitsPerPixel a double we're doing floating-point
8601 // division using a larger type and avoiding rounding error.
8602 Rect rect(Float(aRect.x / aAppUnitsPerPixel),
8603 Float(aRect.y / aAppUnitsPerPixel),
8604 Float(aRect.width / aAppUnitsPerPixel),
8605 Float(aRect.height / aAppUnitsPerPixel));
8606 MaybeSnapToDevicePixels(rect, aSnapDT, true, false);
8607 return rect;
8610 void StrokeLineWithSnapping(const nsPoint& aP1, const nsPoint& aP2,
8611 int32_t aAppUnitsPerDevPixel,
8612 DrawTarget& aDrawTarget, const Pattern& aPattern,
8613 const StrokeOptions& aStrokeOptions,
8614 const DrawOptions& aDrawOptions) {
8615 Point p1 = NSPointToPoint(aP1, aAppUnitsPerDevPixel);
8616 Point p2 = NSPointToPoint(aP2, aAppUnitsPerDevPixel);
8617 SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget,
8618 aStrokeOptions.mLineWidth);
8619 aDrawTarget.StrokeLine(p1, p2, aPattern, aStrokeOptions, aDrawOptions);
8622 } // namespace mozilla
8624 /* static */
8625 void nsLayoutUtils::SetBSizeFromFontMetrics(const nsIFrame* aFrame,
8626 ReflowOutput& aMetrics,
8627 const LogicalMargin& aFramePadding,
8628 WritingMode aLineWM,
8629 WritingMode aFrameWM) {
8630 RefPtr<nsFontMetrics> fm =
8631 nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
8633 if (fm) {
8634 // Compute final height of the frame.
8636 // Do things the standard css2 way -- though it's hard to find it
8637 // in the css2 spec! It's actually found in the css1 spec section
8638 // 4.4 (you will have to read between the lines to really see
8639 // it).
8641 // The height of our box is the sum of our font size plus the top
8642 // and bottom border and padding. The height of children do not
8643 // affect our height.
8644 aMetrics.SetBlockStartAscent(
8645 aLineWM.IsAlphabeticalBaseline()
8646 ? aLineWM.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent()
8647 : fm->MaxHeight() / 2);
8648 aMetrics.BSize(aLineWM) = fm->MaxHeight();
8649 } else {
8650 NS_WARNING("Cannot get font metrics - defaulting sizes to 0");
8651 aMetrics.SetBlockStartAscent(aMetrics.BSize(aLineWM) = 0);
8653 aMetrics.SetBlockStartAscent(aMetrics.BlockStartAscent() +
8654 aFramePadding.BStart(aFrameWM));
8655 aMetrics.BSize(aLineWM) += aFramePadding.BStartEnd(aFrameWM);
8658 /* static */
8659 // _BOUNDARY because Dispatch() with `targets` must not handle the event.
8660 MOZ_CAN_RUN_SCRIPT_BOUNDARY bool
8661 nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(
8662 PresShell* aPresShell) {
8663 if (RefPtr<Document> doc = aPresShell->GetDocument()) {
8664 WidgetEvent event(true, eVoidEvent);
8665 nsTArray<EventTarget*> targets;
8666 nsresult rv = EventDispatcher::Dispatch(doc, nullptr, &event, nullptr,
8667 nullptr, nullptr, &targets);
8668 NS_ENSURE_SUCCESS(rv, false);
8669 for (size_t i = 0; i < targets.Length(); i++) {
8670 if (targets[i]->IsApzAware()) {
8671 return true;
8675 return false;
8678 /* static */
8679 bool nsLayoutUtils::CanScrollOriginClobberApz(ScrollOrigin aScrollOrigin) {
8680 switch (aScrollOrigin) {
8681 case ScrollOrigin::None:
8682 case ScrollOrigin::NotSpecified:
8683 case ScrollOrigin::Apz:
8684 case ScrollOrigin::Restore:
8685 return false;
8686 default:
8687 return true;
8691 /* static */
8692 ScrollMetadata nsLayoutUtils::ComputeScrollMetadata(
8693 const nsIFrame* aForFrame, const nsIFrame* aScrollFrame,
8694 nsIContent* aContent, const nsIFrame* aItemFrame,
8695 const nsPoint& aOffsetToReferenceFrame,
8696 WebRenderLayerManager* aLayerManager, ViewID aScrollParentId,
8697 const nsSize& aScrollPortSize, bool aIsRootContent) {
8698 const nsPresContext* presContext = aForFrame->PresContext();
8699 int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
8701 PresShell* presShell = presContext->GetPresShell();
8702 ScrollMetadata metadata;
8703 FrameMetrics& metrics = metadata.GetMetrics();
8704 metrics.SetLayoutViewport(
8705 CSSRect(CSSPoint(), CSSSize::FromAppUnits(aScrollPortSize)));
8707 nsIDocShell* docShell = presContext->GetDocShell();
8708 const BrowsingContext* bc =
8709 docShell ? docShell->GetBrowsingContext() : nullptr;
8710 bool isTouchEventsEnabled =
8711 bc &&
8712 bc->TouchEventsOverride() == mozilla::dom::TouchEventsOverride::Enabled;
8714 if (bc && bc->InRDMPane() && isTouchEventsEnabled) {
8715 metadata.SetIsRDMTouchSimulationActive(true);
8718 ViewID scrollId = ScrollableLayerGuid::NULL_SCROLL_ID;
8719 if (aContent) {
8720 if (void* paintRequestTime =
8721 aContent->GetProperty(nsGkAtoms::paintRequestTime)) {
8722 metrics.SetPaintRequestTime(*static_cast<TimeStamp*>(paintRequestTime));
8723 aContent->RemoveProperty(nsGkAtoms::paintRequestTime);
8725 scrollId = nsLayoutUtils::FindOrCreateIDFor(aContent);
8726 nsRect dp;
8727 if (DisplayPortUtils::GetDisplayPort(aContent, &dp)) {
8728 metrics.SetDisplayPort(CSSRect::FromAppUnits(dp));
8729 DisplayPortUtils::MarkDisplayPortAsPainted(aContent);
8732 metrics.SetHasNonZeroDisplayPortMargins(false);
8733 if (DisplayPortMarginsPropertyData* currentData =
8734 static_cast<DisplayPortMarginsPropertyData*>(
8735 aContent->GetProperty(nsGkAtoms::DisplayPortMargins))) {
8736 if (currentData->mMargins.mMargins != ScreenMargin()) {
8737 metrics.SetHasNonZeroDisplayPortMargins(true);
8741 // Note: GetProperty() will return nullptr both in the case where
8742 // the property hasn't been set, and in the case where the property
8743 // has been set to false (in which case the property value is
8744 // `reinterpret_cast<void*>(false)` which is nullptr.
8745 if (aContent->GetProperty(nsGkAtoms::forceMousewheelAutodir)) {
8746 metadata.SetForceMousewheelAutodir(true);
8749 if (aContent->GetProperty(nsGkAtoms::forceMousewheelAutodirHonourRoot)) {
8750 metadata.SetForceMousewheelAutodirHonourRoot(true);
8753 if (IsAPZTestLoggingEnabled()) {
8754 LogTestDataForPaint(aLayerManager, scrollId, "displayport",
8755 metrics.GetDisplayPort());
8758 metrics.SetMinimalDisplayPort(
8759 aContent->GetProperty(nsGkAtoms::MinimalDisplayPort));
8762 nsIScrollableFrame* scrollableFrame = nullptr;
8763 if (aScrollFrame) scrollableFrame = aScrollFrame->GetScrollTargetFrame();
8765 metrics.SetScrollableRect(
8766 CSSRect::FromAppUnits(nsLayoutUtils::CalculateScrollableRectForFrame(
8767 scrollableFrame, aForFrame)));
8769 if (scrollableFrame) {
8770 CSSPoint layoutScrollOffset =
8771 CSSPoint::FromAppUnits(scrollableFrame->GetScrollPosition());
8772 CSSPoint visualScrollOffset =
8773 aIsRootContent
8774 ? CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset())
8775 : layoutScrollOffset;
8776 metrics.SetVisualScrollOffset(visualScrollOffset);
8777 // APZ sometimes reads this even if we haven't set a visual scroll
8778 // update type (specifically, in the isFirstPaint case), so always
8779 // set it.
8780 metrics.SetVisualDestination(visualScrollOffset);
8782 if (aIsRootContent) {
8783 if (aLayerManager->GetIsFirstPaint() &&
8784 presShell->IsVisualViewportOffsetSet()) {
8785 // Restore the visual viewport offset to the copy stored on the
8786 // main thread.
8787 presShell->ScrollToVisual(presShell->GetVisualViewportOffset(),
8788 FrameMetrics::eRestore, ScrollMode::Instant);
8792 if (scrollableFrame->IsRootScrollFrameOfDocument()) {
8793 if (const Maybe<PresShell::VisualScrollUpdate>& visualUpdate =
8794 presShell->GetPendingVisualScrollUpdate()) {
8795 metrics.SetVisualDestination(
8796 CSSPoint::FromAppUnits(visualUpdate->mVisualScrollOffset));
8797 metrics.SetVisualScrollUpdateType(visualUpdate->mUpdateType);
8798 presShell->AcknowledgePendingVisualScrollUpdate();
8802 if (aIsRootContent) {
8803 // Expand the layout viewport to the size including the area covered by
8804 // the dynamic toolbar in the case where the dynamic toolbar is being
8805 // used, otherwise when the dynamic toolbar transitions on the compositor,
8806 // the layout viewport will be smaller than the visual viewport on the
8807 // compositor, thus the layout viewport offset will be forced to be moved
8808 // in FrameMetrics::KeepLayoutViewportEnclosingVisualViewport.
8809 if (presContext->HasDynamicToolbar()) {
8810 CSSRect viewport = metrics.GetLayoutViewport();
8811 viewport.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
8812 presContext, viewport.Size()));
8813 metrics.SetLayoutViewport(viewport);
8815 // We need to set 'fixed margins' to adjust 'fixed margins' value on the
8816 // composiutor in the case where the dynamic toolbar is completely
8817 // hidden because the margin value on the compositor is offset from the
8818 // position where the dynamic toolbar is completely VISIBLE but now the
8819 // toolbar is completely HIDDEN we need to adjust the difference on the
8820 // compositor.
8821 if (presContext->GetDynamicToolbarState() ==
8822 DynamicToolbarState::Collapsed) {
8823 metrics.SetFixedLayerMargins(ScreenMargin(
8824 0, 0,
8825 ScreenCoord(presContext->GetDynamicToolbarHeight() -
8826 presContext->GetDynamicToolbarMaxHeight()),
8827 0));
8832 metrics.SetScrollGeneration(scrollableFrame->CurrentScrollGeneration());
8834 CSSRect viewport = metrics.GetLayoutViewport();
8835 viewport.MoveTo(layoutScrollOffset);
8836 metrics.SetLayoutViewport(viewport);
8838 nsSize lineScrollAmount = scrollableFrame->GetLineScrollAmount();
8839 LayoutDeviceIntSize lineScrollAmountInDevPixels =
8840 LayoutDeviceIntSize::FromAppUnitsRounded(
8841 lineScrollAmount, presContext->AppUnitsPerDevPixel());
8842 metadata.SetLineScrollAmount(lineScrollAmountInDevPixels);
8844 nsSize pageScrollAmount = scrollableFrame->GetPageScrollAmount();
8845 LayoutDeviceIntSize pageScrollAmountInDevPixels =
8846 LayoutDeviceIntSize::FromAppUnitsRounded(
8847 pageScrollAmount, presContext->AppUnitsPerDevPixel());
8848 metadata.SetPageScrollAmount(pageScrollAmountInDevPixels);
8850 if (aScrollFrame->GetParent()) {
8851 metadata.SetDisregardedDirection(
8852 WheelHandlingUtils::GetDisregardedWheelScrollDirection(
8853 aScrollFrame->GetParent()));
8856 metadata.SetSnapInfo(scrollableFrame->GetScrollSnapInfo());
8857 metadata.SetOverscrollBehavior(
8858 scrollableFrame->GetOverscrollBehaviorInfo());
8859 metadata.SetScrollUpdates(scrollableFrame->GetScrollUpdates());
8862 // If we have the scrollparent being the same as the scroll id, the
8863 // compositor-side code could get into an infinite loop while building the
8864 // overscroll handoff chain.
8865 MOZ_ASSERT(aScrollParentId == ScrollableLayerGuid::NULL_SCROLL_ID ||
8866 scrollId != aScrollParentId);
8867 metrics.SetScrollId(scrollId);
8868 metrics.SetIsRootContent(aIsRootContent);
8869 metadata.SetScrollParentId(aScrollParentId);
8871 const nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
8872 bool isRootScrollFrame = aScrollFrame == rootScrollFrame;
8873 Document* document = presShell->GetDocument();
8875 if (scrollId != ScrollableLayerGuid::NULL_SCROLL_ID &&
8876 !presContext->GetParentPresContext()) {
8877 if ((aScrollFrame && isRootScrollFrame)) {
8878 metadata.SetIsLayersIdRoot(true);
8879 } else {
8880 MOZ_ASSERT(document, "A non-root-scroll frame must be in a document");
8881 if (aContent == document->GetDocumentElement()) {
8882 metadata.SetIsLayersIdRoot(true);
8887 // Get whether the root content is RTL(E.g. it's true either if
8888 // "writing-mode: vertical-rl", or if
8889 // "writing-mode: horizontal-tb; direction: rtl;" in CSS).
8890 // For the concept of this and the reason why we need to get this kind of
8891 // information, see the definition of |mIsAutoDirRootContentRTL| in struct
8892 // |ScrollMetadata|.
8893 const Element* bodyElement = document ? document->GetBodyElement() : nullptr;
8894 const nsIFrame* primaryFrame =
8895 bodyElement ? bodyElement->GetPrimaryFrame() : rootScrollFrame;
8896 if (!primaryFrame) {
8897 primaryFrame = rootScrollFrame;
8899 if (primaryFrame) {
8900 WritingMode writingModeOfRootScrollFrame = primaryFrame->GetWritingMode();
8901 WritingMode::BlockDir blockDirOfRootScrollFrame =
8902 writingModeOfRootScrollFrame.GetBlockDir();
8903 WritingMode::InlineDir inlineDirOfRootScrollFrame =
8904 writingModeOfRootScrollFrame.GetInlineDir();
8905 if (blockDirOfRootScrollFrame == WritingMode::BlockDir::eBlockRL ||
8906 (blockDirOfRootScrollFrame == WritingMode::BlockDir::eBlockTB &&
8907 inlineDirOfRootScrollFrame == WritingMode::InlineDir::eInlineRTL)) {
8908 metadata.SetIsAutoDirRootContentRTL(true);
8912 // Only the root scrollable frame for a given presShell should pick up
8913 // the presShell's resolution. All the other frames are 1.0.
8914 if (isRootScrollFrame) {
8915 metrics.SetPresShellResolution(presShell->GetResolution());
8916 } else {
8917 metrics.SetPresShellResolution(1.0f);
8920 if (presShell->IsResolutionUpdated()) {
8921 metadata.SetResolutionUpdated(true);
8924 // The cumulative resolution is the resolution at which the scroll frame's
8925 // content is actually rendered. It includes the pres shell resolutions of
8926 // all the pres shells from here up to the root, as well as any css-driven
8927 // resolution. We don't need to compute it as it's already stored in the
8928 // container parameters... except if we're in WebRender in which case we
8929 // don't have a aContainerParameters. In that case we're also not rasterizing
8930 // in Gecko anyway, so the only resolution we care about here is the presShell
8931 // resolution which we need to propagate to WebRender.
8932 metrics.SetCumulativeResolution(
8933 LayoutDeviceToLayerScale(presShell->GetCumulativeResolution()));
8935 metrics.SetTransformToAncestorScale(
8936 GetTransformToAncestorScaleCrossProcessForFrameMetrics(
8937 aScrollFrame ? aScrollFrame : aForFrame));
8938 metrics.SetDevPixelsPerCSSPixel(presContext->CSSToDevPixelScale());
8940 // Initially, AsyncPanZoomController should render the content to the screen
8941 // at the painted resolution.
8942 const LayerToParentLayerScale layerToParentLayerScale(1.0f);
8943 metrics.SetZoom(metrics.GetCumulativeResolution() *
8944 metrics.GetDevPixelsPerCSSPixel() * layerToParentLayerScale);
8946 // Calculate the composition bounds as the size of the scroll frame and
8947 // its origin relative to the reference frame.
8948 // If aScrollFrame is null, we are in a document without a root scroll frame,
8949 // so it's a xul document. In this case, use the size of the viewport frame.
8950 const nsIFrame* frameForCompositionBoundsCalculation =
8951 aScrollFrame ? aScrollFrame : aForFrame;
8952 nsRect compositionBounds(
8953 frameForCompositionBoundsCalculation->GetOffsetToCrossDoc(aItemFrame) +
8954 aOffsetToReferenceFrame,
8955 frameForCompositionBoundsCalculation->GetSize());
8956 if (scrollableFrame) {
8957 // If we have a scrollable frame, restrict the composition bounds to its
8958 // scroll port. The scroll port excludes the frame borders and the scroll
8959 // bars, which we don't want to be part of the composition bounds.
8960 nsRect scrollPort = scrollableFrame->GetScrollPortRect();
8961 compositionBounds = nsRect(
8962 compositionBounds.TopLeft() + scrollPort.TopLeft(), scrollPort.Size());
8964 ParentLayerRect frameBounds =
8965 LayoutDeviceRect::FromAppUnits(compositionBounds, auPerDevPixel) *
8966 metrics.GetCumulativeResolution() * layerToParentLayerScale;
8968 // For the root scroll frame of the root content document (RCD-RSF), the above
8969 // calculation will yield the size of the viewport frame as the composition
8970 // bounds, which doesn't actually correspond to what is visible when
8971 // nsIDOMWindowUtils::setCSSViewport has been called to modify the visible
8972 // area of the prescontext that the viewport frame is reflowed into. In that
8973 // case if our document has a widget then the widget's bounds will correspond
8974 // to what is visible. If we don't have a widget the root view's bounds
8975 // correspond to what would be visible because they don't get modified by
8976 // setCSSViewport.
8977 bool isRootContentDocRootScrollFrame =
8978 isRootScrollFrame && presContext->IsRootContentDocumentCrossProcess();
8979 if (isRootContentDocRootScrollFrame) {
8980 UpdateCompositionBoundsForRCDRSF(frameBounds, presContext);
8981 if (RefPtr<MobileViewportManager> MVM =
8982 presContext->PresShell()->GetMobileViewportManager()) {
8983 metrics.SetCompositionSizeWithoutDynamicToolbar(
8984 MVM->GetCompositionSizeWithoutDynamicToolbar());
8988 metrics.SetCompositionBoundsWidthIgnoringScrollbars(frameBounds.width);
8990 nsMargin sizes = ScrollbarAreaToExcludeFromCompositionBoundsFor(aScrollFrame);
8991 // Scrollbars are not subject to resolution scaling, so LD pixels = layer
8992 // pixels for them.
8993 ParentLayerMargin boundMargins =
8994 LayoutDeviceMargin::FromAppUnits(sizes, auPerDevPixel) *
8995 LayoutDeviceToParentLayerScale(1.0f);
8996 frameBounds.Deflate(boundMargins);
8998 metrics.SetCompositionBounds(frameBounds);
9000 metrics.SetBoundingCompositionSize(
9001 nsLayoutUtils::CalculateBoundingCompositionSize(
9002 aScrollFrame ? aScrollFrame : aForFrame,
9003 isRootContentDocRootScrollFrame, metrics));
9005 if (StaticPrefs::apz_printtree() || StaticPrefs::apz_test_logging_enabled()) {
9006 if (const nsIContent* content =
9007 frameForCompositionBoundsCalculation->GetContent()) {
9008 nsAutoString contentDescription;
9009 if (content->IsElement()) {
9010 content->AsElement()->Describe(contentDescription);
9011 } else {
9012 contentDescription.AssignLiteral("(not an element)");
9014 metadata.SetContentDescription(
9015 NS_LossyConvertUTF16toASCII(contentDescription));
9016 if (IsAPZTestLoggingEnabled()) {
9017 LogTestDataForPaint(aLayerManager, scrollId, "contentDescription",
9018 metadata.GetContentDescription().get());
9023 metrics.SetPresShellId(presShell->GetPresShellId());
9025 // If the scroll frame's content is marked 'scrollgrab', record this
9026 // in the FrameMetrics so APZ knows to provide the scroll grabbing
9027 // behaviour.
9028 if (aScrollFrame &&
9029 nsContentUtils::HasScrollgrab(aScrollFrame->GetContent())) {
9030 metadata.SetHasScrollgrab(true);
9033 if (ShouldDisableApzForElement(aContent)) {
9034 metadata.SetForceDisableApz(true);
9037 metadata.SetIsPaginatedPresentation(presContext->Type() !=
9038 nsPresContext::eContext_Galley);
9040 return metadata;
9043 /*static*/
9044 Maybe<ScrollMetadata> nsLayoutUtils::GetRootMetadata(
9045 nsDisplayListBuilder* aBuilder, WebRenderLayerManager* aLayerManager,
9046 const std::function<bool(ViewID& aScrollId)>& aCallback) {
9047 nsIFrame* frame = aBuilder->RootReferenceFrame();
9048 nsPresContext* presContext = frame->PresContext();
9049 PresShell* presShell = presContext->PresShell();
9050 Document* document = presShell->GetDocument();
9052 // There is one case where we want the root container layer to have metrics.
9053 // If the parent process is using XUL windows, there is no root scrollframe,
9054 // and without explicitly creating metrics there will be no guaranteed
9055 // top-level APZC.
9056 bool addMetrics = XRE_IsParentProcess() && !presShell->GetRootScrollFrame();
9058 // Add metrics if there are none in the layer tree with the id (create an id
9059 // if there isn't one already) of the root scroll frame/root content.
9060 bool ensureMetricsForRootId = nsLayoutUtils::AsyncPanZoomEnabled(frame) &&
9061 aBuilder->IsPaintingToWindow() &&
9062 !presContext->GetParentPresContext();
9064 nsIContent* content = nullptr;
9065 nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
9066 if (rootScrollFrame) {
9067 content = rootScrollFrame->GetContent();
9068 } else {
9069 // If there is no root scroll frame, pick the document element instead.
9070 // The only case we don't want to do this is in non-APZ fennec, where
9071 // we want the root xul document to get a null scroll id so that the root
9072 // content document gets the first non-null scroll id.
9073 content = document->GetDocumentElement();
9076 if (ensureMetricsForRootId && content) {
9077 ViewID scrollId = nsLayoutUtils::FindOrCreateIDFor(content);
9078 if (aCallback(scrollId)) {
9079 ensureMetricsForRootId = false;
9083 if (addMetrics || ensureMetricsForRootId) {
9084 bool isRootContent = presContext->IsRootContentDocumentCrossProcess();
9086 nsSize scrollPortSize = frame->GetSize();
9087 if (isRootContent && rootScrollFrame) {
9088 nsIScrollableFrame* scrollableFrame =
9089 rootScrollFrame->GetScrollTargetFrame();
9090 scrollPortSize = scrollableFrame->GetScrollPortRect().Size();
9092 return Some(nsLayoutUtils::ComputeScrollMetadata(
9093 frame, rootScrollFrame, content, frame,
9094 aBuilder->ToReferenceFrame(frame), aLayerManager,
9095 ScrollableLayerGuid::NULL_SCROLL_ID, scrollPortSize, isRootContent));
9098 return Nothing();
9101 /* static */
9102 void nsLayoutUtils::TransformToAncestorAndCombineRegions(
9103 const nsRegion& aRegion, nsIFrame* aFrame, const nsIFrame* aAncestorFrame,
9104 nsRegion* aPreciseTargetDest, nsRegion* aImpreciseTargetDest,
9105 Maybe<Matrix4x4Flagged>* aMatrixCache, const DisplayItemClip* aClip) {
9106 if (aRegion.IsEmpty()) {
9107 return;
9109 bool isPrecise;
9110 RegionBuilder<nsRegion> transformedRegion;
9111 for (nsRegion::RectIterator it = aRegion.RectIter(); !it.Done(); it.Next()) {
9112 nsRect transformed = TransformFrameRectToAncestor(
9113 aFrame, it.Get(), aAncestorFrame, &isPrecise, aMatrixCache);
9114 if (aClip) {
9115 transformed = aClip->ApplyNonRoundedIntersection(transformed);
9116 if (aClip->GetRoundedRectCount() > 0) {
9117 isPrecise = false;
9120 transformedRegion.OrWith(transformed);
9122 nsRegion* dest = isPrecise ? aPreciseTargetDest : aImpreciseTargetDest;
9123 dest->OrWith(transformedRegion.ToRegion());
9124 // If the region becomes too complex this has a large performance impact.
9125 // We limit its complexity here.
9126 if (dest->GetNumRects() > 12) {
9127 dest->SimplifyOutward(6);
9128 if (isPrecise) {
9129 aPreciseTargetDest->OrWith(*aImpreciseTargetDest);
9130 *aImpreciseTargetDest = std::move(*aPreciseTargetDest);
9131 aImpreciseTargetDest->SimplifyOutward(6);
9132 *aPreciseTargetDest = nsRegion();
9137 /* static */
9138 bool nsLayoutUtils::ShouldUseNoFramesSheet(Document* aDocument) {
9139 bool allowSubframes = true;
9140 nsIDocShell* docShell = aDocument->GetDocShell();
9141 if (docShell) {
9142 docShell->GetAllowSubframes(&allowSubframes);
9144 return !allowSubframes;
9147 /* static */
9148 void nsLayoutUtils::GetFrameTextContent(nsIFrame* aFrame, nsAString& aResult) {
9149 aResult.Truncate();
9150 AppendFrameTextContent(aFrame, aResult);
9153 /* static */
9154 void nsLayoutUtils::AppendFrameTextContent(nsIFrame* aFrame,
9155 nsAString& aResult) {
9156 if (aFrame->IsTextFrame()) {
9157 auto* const textFrame = static_cast<nsTextFrame*>(aFrame);
9158 const auto offset = AssertedCast<uint32_t>(textFrame->GetContentOffset());
9159 const auto length = AssertedCast<uint32_t>(textFrame->GetContentLength());
9160 textFrame->TextFragment()->AppendTo(aResult, offset, length);
9161 } else {
9162 for (nsIFrame* child : aFrame->PrincipalChildList()) {
9163 AppendFrameTextContent(child, aResult);
9168 /* static */
9169 nsRect nsLayoutUtils::GetSelectionBoundingRect(const Selection* aSel) {
9170 nsRect res;
9171 // Bounding client rect may be empty after calling GetBoundingClientRect
9172 // when range is collapsed. So we get caret's rect when range is
9173 // collapsed.
9174 if (aSel->IsCollapsed()) {
9175 nsIFrame* frame = nsCaret::GetGeometry(aSel, &res);
9176 if (frame) {
9177 nsIFrame* relativeTo = GetContainingBlockForClientRect(frame);
9178 res = TransformFrameRectToAncestor(frame, res, relativeTo);
9180 } else {
9181 RectAccumulator accumulator;
9182 const uint32_t rangeCount = aSel->RangeCount();
9183 for (const uint32_t idx : IntegerRange(rangeCount)) {
9184 MOZ_ASSERT(aSel->RangeCount() == rangeCount);
9185 nsRange* range = aSel->GetRangeAt(idx);
9186 nsRange::CollectClientRectsAndText(
9187 &accumulator, nullptr, range, range->GetStartContainer(),
9188 range->StartOffset(), range->GetEndContainer(), range->EndOffset(),
9189 true, false);
9191 res = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect
9192 : accumulator.mResultRect;
9195 return res;
9198 /* static */
9199 nsBlockFrame* nsLayoutUtils::GetFloatContainingBlock(nsIFrame* aFrame) {
9200 nsIFrame* ancestor = aFrame->GetParent();
9201 while (ancestor && !ancestor->IsFloatContainingBlock()) {
9202 ancestor = ancestor->GetParent();
9204 MOZ_ASSERT(!ancestor || ancestor->IsBlockFrameOrSubclass(),
9205 "Float containing block can only be block frame");
9206 return static_cast<nsBlockFrame*>(ancestor);
9209 // The implementations of this calculation are adapted from
9210 // Element::GetBoundingClientRect().
9211 /* static */
9212 CSSRect nsLayoutUtils::GetBoundingContentRect(
9213 const nsIContent* aContent, const nsIScrollableFrame* aRootScrollFrame,
9214 Maybe<CSSRect>* aOutNearestScrollClip) {
9215 if (nsIFrame* frame = aContent->GetPrimaryFrame()) {
9216 return GetBoundingFrameRect(frame, aRootScrollFrame, aOutNearestScrollClip);
9218 return CSSRect();
9221 /* static */
9222 CSSRect nsLayoutUtils::GetBoundingFrameRect(
9223 nsIFrame* aFrame, const nsIScrollableFrame* aRootScrollFrame,
9224 Maybe<CSSRect>* aOutNearestScrollClip) {
9225 CSSRect result;
9226 nsIFrame* relativeTo = aRootScrollFrame->GetScrolledFrame();
9227 result = CSSRect::FromAppUnits(nsLayoutUtils::GetAllInFlowRectsUnion(
9228 aFrame, relativeTo, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS));
9230 // If the element is contained in a scrollable frame that is not
9231 // the root scroll frame, make sure to clip the result so that it is
9232 // not larger than the containing scrollable frame's bounds.
9233 nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
9234 aFrame, SCROLLABLE_INCLUDE_HIDDEN | SCROLLABLE_FIXEDPOS_FINDS_ROOT);
9235 if (scrollFrame && scrollFrame != aRootScrollFrame) {
9236 nsIFrame* subFrame = do_QueryFrame(scrollFrame);
9237 MOZ_ASSERT(subFrame);
9238 // Get the bounds of the scroll frame in the same coordinate space
9239 // as |result|.
9240 nsRect subFrameRect = subFrame->GetRectRelativeToSelf();
9241 TransformResult res =
9242 nsLayoutUtils::TransformRect(subFrame, relativeTo, subFrameRect);
9243 MOZ_ASSERT(res == TRANSFORM_SUCCEEDED || res == NONINVERTIBLE_TRANSFORM);
9244 if (res == TRANSFORM_SUCCEEDED) {
9245 CSSRect subFrameRectCSS = CSSRect::FromAppUnits(subFrameRect);
9246 if (aOutNearestScrollClip) {
9247 *aOutNearestScrollClip = Some(subFrameRectCSS);
9250 result = subFrameRectCSS.Intersect(result);
9253 return result;
9256 /* static */
9257 bool nsLayoutUtils::IsTransformed(nsIFrame* aForFrame, nsIFrame* aTopFrame) {
9258 for (nsIFrame* f = aForFrame; f != aTopFrame; f = f->GetParent()) {
9259 if (f->IsTransformed()) {
9260 return true;
9263 return false;
9266 /*static*/
9267 CSSPoint nsLayoutUtils::GetCumulativeApzCallbackTransform(nsIFrame* aFrame) {
9268 CSSPoint delta;
9269 if (!aFrame) {
9270 return delta;
9272 nsIFrame* frame = aFrame;
9273 nsCOMPtr<nsIContent> lastContent;
9274 bool seenRcdRsf = false;
9276 // Helper lambda to apply the callback transform for a single frame.
9277 auto applyCallbackTransformForFrame = [&](nsIFrame* frame) {
9278 if (frame) {
9279 nsCOMPtr<nsIContent> content = frame->GetContent();
9280 if (content && (content != lastContent)) {
9281 void* property = content->GetProperty(nsGkAtoms::apzCallbackTransform);
9282 if (property) {
9283 delta += *static_cast<CSSPoint*>(property);
9286 lastContent = content;
9290 while (frame) {
9291 // Apply the callback transform for the current frame.
9292 applyCallbackTransformForFrame(frame);
9294 // Keep track of whether we've encountered the RCD-RSF's content element.
9295 nsPresContext* pc = frame->PresContext();
9296 if (pc->IsRootContentDocumentCrossProcess()) {
9297 if (PresShell* shell = pc->GetPresShell()) {
9298 if (nsIFrame* rsf = shell->GetRootScrollFrame()) {
9299 if (frame->GetContent() == rsf->GetContent()) {
9300 seenRcdRsf = true;
9306 // If we reach the RCD's viewport frame, but have not encountered
9307 // the RCD-RSF, we were inside fixed content in the RCD.
9308 // We still want to apply the RCD-RSF's callback transform because
9309 // it contains the offset between the visual and layout viewports
9310 // which applies to fixed content as well.
9311 ViewportFrame* viewportFrame = do_QueryFrame(frame);
9312 if (viewportFrame) {
9313 if (pc->IsRootContentDocumentCrossProcess() && !seenRcdRsf) {
9314 applyCallbackTransformForFrame(pc->PresShell()->GetRootScrollFrame());
9318 // Proceed to the parent frame.
9319 frame = GetCrossDocParentFrameInProcess(frame);
9321 return delta;
9324 static nsSize ComputeMaxSizeForPartialPrerender(nsIFrame* aFrame,
9325 nsSize aMaxSize) {
9326 Matrix4x4Flagged transform = nsLayoutUtils::GetTransformToAncestor(
9327 RelativeTo{aFrame},
9328 RelativeTo{nsLayoutUtils::GetDisplayRootFrame(aFrame)});
9330 Matrix transform2D;
9331 if (!transform.Is2D(&transform2D)) {
9332 return aMaxSize;
9335 gfx::Rect result(0, 0, aMaxSize.width, aMaxSize.height);
9336 auto scale = transform2D.ScaleFactors();
9337 if (scale.xScale != 0 && scale.yScale != 0) {
9338 result.width /= scale.xScale;
9339 result.height /= scale.yScale;
9342 // Don't apply translate.
9343 transform2D._31 = 0.0f;
9344 transform2D._32 = 0.0f;
9346 // Don't apply scale.
9347 if (scale.xScale != 0 && scale.yScale != 0) {
9348 transform2D._11 /= scale.xScale;
9349 transform2D._12 /= scale.xScale;
9350 transform2D._21 /= scale.yScale;
9351 transform2D._22 /= scale.yScale;
9354 // Theoretically we should use transform2D.Inverse() here but in this case
9355 // |transform2D| is a pure rotation matrix, no scaling, no translate at all,
9356 // so that the result bound's width and height would be pretty much same
9357 // as the one rotated by the inverse matrix.
9358 result = transform2D.TransformBounds(result);
9359 return nsSize(
9360 result.width < (float)nscoord_MAX ? result.width : nscoord_MAX,
9361 result.height < (float)nscoord_MAX ? result.height : nscoord_MAX);
9364 /* static */
9365 nsRect nsLayoutUtils::ComputePartialPrerenderArea(
9366 nsIFrame* aFrame, const nsRect& aDirtyRect, const nsRect& aOverflow,
9367 const nsSize& aPrerenderSize) {
9368 nsSize maxSizeForPartialPrerender =
9369 ComputeMaxSizeForPartialPrerender(aFrame, aPrerenderSize);
9370 // Simple calculation for now: center the pre-render area on the dirty rect,
9371 // and clamp to the overflow area. Later we can do more advanced things like
9372 // redistributing from one axis to another, or from one side to another.
9373 nscoord xExcess =
9374 std::max(maxSizeForPartialPrerender.width - aDirtyRect.width, 0);
9375 nscoord yExcess =
9376 std::max(maxSizeForPartialPrerender.height - aDirtyRect.height, 0);
9377 nsRect result = aDirtyRect;
9378 result.Inflate(xExcess / 2, yExcess / 2);
9379 return result.MoveInsideAndClamp(aOverflow);
9382 static bool LineHasNonEmptyContentWorker(nsIFrame* aFrame) {
9383 // Look for non-empty frames, but ignore inline and br frames.
9384 // For inline frames, descend into the children, if any.
9385 if (aFrame->IsInlineFrame()) {
9386 for (nsIFrame* child : aFrame->PrincipalChildList()) {
9387 if (LineHasNonEmptyContentWorker(child)) {
9388 return true;
9391 } else {
9392 if (!aFrame->IsBrFrame() && !aFrame->IsEmpty()) {
9393 return true;
9396 return false;
9399 static bool LineHasNonEmptyContent(nsLineBox* aLine) {
9400 int32_t count = aLine->GetChildCount();
9401 for (nsIFrame* frame = aLine->mFirstChild; count > 0;
9402 --count, frame = frame->GetNextSibling()) {
9403 if (LineHasNonEmptyContentWorker(frame)) {
9404 return true;
9407 return false;
9410 /* static */
9411 bool nsLayoutUtils::IsInvisibleBreak(nsINode* aNode,
9412 nsIFrame** aNextLineFrame) {
9413 if (aNextLineFrame) {
9414 *aNextLineFrame = nullptr;
9417 if (!aNode->IsElement() || !aNode->IsEditable()) {
9418 return false;
9420 nsIFrame* frame = aNode->AsElement()->GetPrimaryFrame();
9421 if (!frame || !frame->IsBrFrame()) {
9422 return false;
9425 nsContainerFrame* f = frame->GetParent();
9426 while (f && f->IsLineParticipant()) {
9427 f = f->GetParent();
9429 nsBlockFrame* blockAncestor = do_QueryFrame(f);
9430 if (!blockAncestor) {
9431 // The container frame doesn't support line breaking.
9432 return false;
9435 bool valid = false;
9436 nsBlockInFlowLineIterator iter(blockAncestor, frame, &valid);
9437 if (!valid) {
9438 return false;
9441 bool lineNonEmpty = LineHasNonEmptyContent(iter.GetLine());
9442 if (!lineNonEmpty) {
9443 return false;
9446 while (iter.Next()) {
9447 auto currentLine = iter.GetLine();
9448 // Completely skip empty lines.
9449 if (!currentLine->IsEmpty()) {
9450 // If we come across an inline line, the BR has caused a visible line
9451 // break.
9452 if (currentLine->IsInline()) {
9453 if (aNextLineFrame) {
9454 *aNextLineFrame = currentLine->mFirstChild;
9456 return false;
9458 break;
9462 return lineNonEmpty;
9465 /* static */
9466 nsRect nsLayoutUtils::ComputeSVGOriginBox(SVGViewportElement* aElement) {
9467 if (!aElement) {
9468 return {};
9471 if (aElement->HasViewBox()) {
9472 // Return the "origin box", which is defined as a rect positioned at the
9473 // origin, but with the width and height given by the viewBox attribute
9475 // https://drafts.csswg.org/css-box-3/#valdef-box-view-box
9477 // For more discussion see
9478 // https://github.com/web-platform-tests/interop/issues/509
9479 const SVGViewBox& value = aElement->GetAnimatedViewBox()->GetAnimValue();
9480 return nsRect(0, 0, nsPresContext::CSSPixelsToAppUnits(value.width),
9481 nsPresContext::CSSPixelsToAppUnits(value.height));
9484 // No viewBox is specified, uses the nearest SVG viewport as reference
9485 // box.
9486 auto viewportSize = aElement->GetViewportSize();
9487 return nsRect(0, 0, nsPresContext::CSSPixelsToAppUnits(viewportSize.width),
9488 nsPresContext::CSSPixelsToAppUnits(viewportSize.height));
9491 /* static */
9492 nsRect nsLayoutUtils::ComputeSVGReferenceRect(
9493 nsIFrame* aFrame, StyleGeometryBox aGeometryBox,
9494 MayHaveNonScalingStrokeCyclicDependency aMayHaveCyclicDependency) {
9495 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement());
9496 nsRect r;
9498 switch (aGeometryBox) {
9499 case StyleGeometryBox::StrokeBox: {
9500 // XXX Bug 1299876
9501 // The size of stroke-box is not correct if this graphic element has
9502 // specific stroke-linejoin or stroke-linecap.
9503 const uint32_t flags = SVGUtils::eBBoxIncludeFillGeometry |
9504 SVGUtils::eBBoxIncludeStroke |
9505 (bool(aMayHaveCyclicDependency)
9506 ? SVGUtils::eAvoidCycleIfNonScalingStroke
9507 : 0);
9508 gfxRect bbox = SVGUtils::GetBBox(aFrame, flags);
9509 r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, AppUnitsPerCSSPixel());
9510 break;
9512 case StyleGeometryBox::ViewBox: {
9513 SVGViewportElement* viewportElement =
9514 SVGElement::FromNode(aFrame->GetContent())->GetCtx();
9515 if (!viewportElement) {
9516 // We should not render without a viewport so return an empty rect.
9517 break;
9519 r = nsLayoutUtils::ComputeSVGOriginBox(viewportElement);
9520 break;
9522 case StyleGeometryBox::FillBox: {
9523 gfxRect bbox =
9524 SVGUtils::GetBBox(aFrame, SVGUtils::eBBoxIncludeFillGeometry);
9525 r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, AppUnitsPerCSSPixel());
9526 break;
9528 default: {
9529 MOZ_ASSERT_UNREACHABLE("unsupported SVG box");
9530 break;
9534 return r;
9537 /* static */
9538 nsRect nsLayoutUtils::ComputeHTMLReferenceRect(const nsIFrame* aFrame,
9539 StyleGeometryBox aGeometryBox) {
9540 nsRect r;
9542 switch (aGeometryBox) {
9543 case StyleGeometryBox::ContentBox:
9544 r = aFrame->GetContentRectRelativeToSelf();
9545 break;
9546 case StyleGeometryBox::PaddingBox:
9547 r = aFrame->GetPaddingRectRelativeToSelf();
9548 break;
9549 case StyleGeometryBox::MarginBox:
9550 r = aFrame->GetMarginRectRelativeToSelf();
9551 break;
9552 case StyleGeometryBox::BorderBox:
9553 r = aFrame->GetRectRelativeToSelf();
9554 break;
9555 default:
9556 MOZ_ASSERT_UNREACHABLE("unsupported CSS box");
9557 break;
9560 return r;
9563 static StyleGeometryBox ShapeBoxToGeometryBox(const StyleShapeBox& aBox) {
9564 switch (aBox) {
9565 case StyleShapeBox::BorderBox:
9566 return StyleGeometryBox::BorderBox;
9567 case StyleShapeBox::ContentBox:
9568 return StyleGeometryBox::ContentBox;
9569 case StyleShapeBox::MarginBox:
9570 return StyleGeometryBox::MarginBox;
9571 case StyleShapeBox::PaddingBox:
9572 return StyleGeometryBox::PaddingBox;
9574 MOZ_ASSERT_UNREACHABLE("Unknown shape box type");
9575 return StyleGeometryBox::MarginBox;
9578 static StyleGeometryBox ClipPathBoxToGeometryBox(
9579 const StyleShapeGeometryBox& aBox) {
9580 using Tag = StyleShapeGeometryBox::Tag;
9581 switch (aBox.tag) {
9582 case Tag::ShapeBox:
9583 return ShapeBoxToGeometryBox(aBox.AsShapeBox());
9584 case Tag::ElementDependent:
9585 return StyleGeometryBox::NoBox;
9586 case Tag::FillBox:
9587 return StyleGeometryBox::FillBox;
9588 case Tag::StrokeBox:
9589 return StyleGeometryBox::StrokeBox;
9590 case Tag::ViewBox:
9591 return StyleGeometryBox::ViewBox;
9593 MOZ_ASSERT_UNREACHABLE("Unknown shape box type");
9594 return StyleGeometryBox::NoBox;
9597 // The mapping is from
9598 // https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box
9599 /* static */
9600 nsRect nsLayoutUtils::ComputeClipPathGeometryBox(
9601 nsIFrame* aFrame, const StyleShapeGeometryBox& aBox) {
9602 StyleGeometryBox box = ClipPathBoxToGeometryBox(aBox);
9604 if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
9605 // For SVG elements without associated CSS layout box, the used value for
9606 // content-box and padding-box is fill-box and for border-box and margin-box
9607 // is stroke-box.
9608 switch (box) {
9609 case StyleGeometryBox::ContentBox:
9610 case StyleGeometryBox::PaddingBox:
9611 case StyleGeometryBox::FillBox:
9612 return ComputeSVGReferenceRect(aFrame, StyleGeometryBox::FillBox);
9613 case StyleGeometryBox::NoBox:
9614 case StyleGeometryBox::BorderBox:
9615 case StyleGeometryBox::MarginBox:
9616 case StyleGeometryBox::StrokeBox:
9617 return ComputeSVGReferenceRect(aFrame, StyleGeometryBox::StrokeBox);
9618 case StyleGeometryBox::ViewBox:
9619 return ComputeSVGReferenceRect(aFrame, StyleGeometryBox::ViewBox);
9620 default:
9621 MOZ_ASSERT_UNREACHABLE("Unknown clip-path geometry box");
9622 // Use default, border-box (as stroke-box in SVG layout).
9623 return ComputeSVGReferenceRect(aFrame, StyleGeometryBox::StrokeBox);
9627 // For elements with associated CSS layout box, the used value for fill-box is
9628 // content-box and for stroke-box and view-box is border-box.
9629 switch (box) {
9630 case StyleGeometryBox::FillBox:
9631 case StyleGeometryBox::ContentBox:
9632 return ComputeHTMLReferenceRect(aFrame, StyleGeometryBox::ContentBox);
9633 case StyleGeometryBox::NoBox:
9634 case StyleGeometryBox::StrokeBox:
9635 case StyleGeometryBox::ViewBox:
9636 case StyleGeometryBox::BorderBox:
9637 return ComputeHTMLReferenceRect(aFrame, StyleGeometryBox::BorderBox);
9638 case StyleGeometryBox::PaddingBox:
9639 return ComputeHTMLReferenceRect(aFrame, StyleGeometryBox::PaddingBox);
9640 case StyleGeometryBox::MarginBox:
9641 return ComputeHTMLReferenceRect(aFrame, StyleGeometryBox::MarginBox);
9642 default:
9643 MOZ_ASSERT_UNREACHABLE("Unknown clip-path geometry box");
9644 // Use default, border-box.
9645 return ComputeHTMLReferenceRect(aFrame, StyleGeometryBox::BorderBox);
9649 /* static */
9650 nsPoint nsLayoutUtils::ComputeOffsetToUserSpace(nsDisplayListBuilder* aBuilder,
9651 nsIFrame* aFrame) {
9652 nsPoint offsetToBoundingBox =
9653 aBuilder->ToReferenceFrame(aFrame) -
9654 SVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
9655 if (!aFrame->IsSVGFrame()) {
9656 // Snap the offset if the reference frame is not a SVG frame, since other
9657 // frames will be snapped to pixel when rendering.
9658 offsetToBoundingBox =
9659 nsPoint(aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
9660 offsetToBoundingBox.x),
9661 aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
9662 offsetToBoundingBox.y));
9665 // During SVG painting, the offset computed here is applied to the gfxContext
9666 // "ctx" used to paint the mask. After applying only "offsetToBoundingBox",
9667 // "ctx" would have its origin at the top left corner of frame's bounding box
9668 // (over all continuations).
9669 // However, SVG painting needs the origin to be located at the origin of the
9670 // SVG frame's "user space", i.e. the space in which, for example, the
9671 // frame's BBox lives.
9672 // SVG geometry frames and foreignObject frames apply their own offsets, so
9673 // their position is relative to their user space. So for these frame types,
9674 // if we want "ctx" to be in user space, we first need to subtract the
9675 // frame's position so that SVG painting can later add it again and the
9676 // frame is painted in the right place.
9677 gfxPoint toUserSpaceGfx =
9678 SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
9679 nsPoint toUserSpace =
9680 nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
9681 nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
9683 return (offsetToBoundingBox - toUserSpace);
9686 /* static */
9687 already_AddRefed<nsFontMetrics> nsLayoutUtils::GetMetricsFor(
9688 nsPresContext* aPresContext, bool aIsVertical,
9689 const nsStyleFont* aStyleFont, Length aFontSize, bool aUseUserFontSet) {
9690 nsFont font = aStyleFont->mFont;
9691 font.size = aFontSize;
9692 gfxFont::Orientation orientation =
9693 aIsVertical ? nsFontMetrics::eVertical : nsFontMetrics::eHorizontal;
9694 nsFontMetrics::Params params;
9695 params.language = aStyleFont->mLanguage;
9696 params.explicitLanguage = aStyleFont->mExplicitLanguage;
9697 params.orientation = orientation;
9698 params.userFontSet =
9699 aUseUserFontSet ? aPresContext->GetUserFontSet() : nullptr;
9700 params.textPerf = aPresContext->GetTextPerfMetrics();
9701 params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup();
9702 return aPresContext->GetMetricsFor(font, params);
9705 /* static */
9706 void nsLayoutUtils::ComputeSystemFont(nsFont* aSystemFont,
9707 LookAndFeel::FontID aFontID,
9708 const nsFont& aDefaultVariableFont,
9709 const Document* aDocument) {
9710 gfxFontStyle fontStyle;
9711 nsAutoString systemFontName;
9712 if (!LookAndFeel::GetFont(aFontID, systemFontName, fontStyle)) {
9713 return;
9715 systemFontName.Trim("\"'");
9716 NS_ConvertUTF16toUTF8 nameu8(systemFontName);
9717 Servo_FontFamily_ForSystemFont(&nameu8, &aSystemFont->family);
9718 aSystemFont->style = fontStyle.style;
9719 aSystemFont->family.is_system_font = fontStyle.systemFont;
9720 aSystemFont->weight = fontStyle.weight;
9721 aSystemFont->stretch = fontStyle.stretch;
9722 aSystemFont->size = Length::FromPixels(fontStyle.size);
9724 // aSystemFont->langGroup = fontStyle.langGroup;
9726 switch (StyleFontSizeAdjust::Tag(fontStyle.sizeAdjustBasis)) {
9727 case StyleFontSizeAdjust::Tag::None:
9728 aSystemFont->sizeAdjust = StyleFontSizeAdjust::None();
9729 break;
9730 case StyleFontSizeAdjust::Tag::ExHeight:
9731 aSystemFont->sizeAdjust =
9732 StyleFontSizeAdjust::ExHeight(fontStyle.sizeAdjust);
9733 break;
9734 case StyleFontSizeAdjust::Tag::CapHeight:
9735 aSystemFont->sizeAdjust =
9736 StyleFontSizeAdjust::CapHeight(fontStyle.sizeAdjust);
9737 break;
9738 case StyleFontSizeAdjust::Tag::ChWidth:
9739 aSystemFont->sizeAdjust =
9740 StyleFontSizeAdjust::ChWidth(fontStyle.sizeAdjust);
9741 break;
9742 case StyleFontSizeAdjust::Tag::IcWidth:
9743 aSystemFont->sizeAdjust =
9744 StyleFontSizeAdjust::IcWidth(fontStyle.sizeAdjust);
9745 break;
9746 case StyleFontSizeAdjust::Tag::IcHeight:
9747 aSystemFont->sizeAdjust =
9748 StyleFontSizeAdjust::IcHeight(fontStyle.sizeAdjust);
9749 break;
9752 if (aFontID == LookAndFeel::FontID::MozField ||
9753 aFontID == LookAndFeel::FontID::MozButton ||
9754 aFontID == LookAndFeel::FontID::MozList) {
9755 // For textfields, buttons and selects, we use whatever font is defined by
9756 // the system. Which it appears (and the assumption is) it is always a
9757 // proportional font. Then we always use 2 points smaller than what the
9758 // browser has defined as the default proportional font.
9760 // This matches historical Windows behavior and other browsers.
9761 auto newSize =
9762 aDefaultVariableFont.size.ToCSSPixels() - CSSPixel::FromPoints(2.0f);
9763 aSystemFont->size = Length::FromPixels(std::max(float(newSize), 0.0f));
9767 /* static */
9768 bool nsLayoutUtils::ShouldHandleMetaViewport(const Document* aDocument) {
9769 BrowsingContext* bc = aDocument->GetBrowsingContext();
9770 return StaticPrefs::dom_meta_viewport_enabled() || (bc && bc->InRDMPane());
9773 /* static */
9774 ComputedStyle* nsLayoutUtils::StyleForScrollbar(
9775 const nsIFrame* aScrollbarPart) {
9776 // Get the closest content node which is not an anonymous scrollbar
9777 // part. It should be the originating element of the scrollbar part.
9778 nsIContent* content = aScrollbarPart->GetContent();
9779 // Note that the content may be a normal element with scrollbar part
9780 // value specified for its -moz-appearance, so don't rely on it being
9781 // a native anonymous. Also note that we have to check the node name
9782 // because anonymous element like generated content may originate a
9783 // scrollbar.
9784 MOZ_ASSERT(content, "No content for the scrollbar part?");
9785 while (content && content->IsInNativeAnonymousSubtree() &&
9786 content->IsAnyOfXULElements(
9787 nsGkAtoms::scrollbar, nsGkAtoms::scrollbarbutton,
9788 nsGkAtoms::scrollcorner, nsGkAtoms::slider, nsGkAtoms::thumb)) {
9789 content = content->GetParent();
9791 MOZ_ASSERT(content, "Native anonymous element with no originating node?");
9792 // Use the style from the primary frame of the content.
9793 // Note: it is important to use the primary frame rather than an
9794 // ancestor frame of the scrollbar part for the correct handling of
9795 // viewport scrollbar. The content of the scroll frame of the viewport
9796 // is the root element, but its style inherits from the viewport.
9797 // Since we need to use the style of root element for the viewport
9798 // scrollbar, we have to get the style from the primary frame.
9799 if (nsIFrame* primaryFrame = content->GetPrimaryFrame()) {
9800 return primaryFrame->Style();
9802 // If the element doesn't have primary frame, get the computed style
9803 // from the element directly. This can happen on viewport, because
9804 // the scrollbar of viewport may be shown when the root element has
9805 // > display: none; overflow: scroll;
9806 MOZ_ASSERT(
9807 content == aScrollbarPart->PresContext()->Document()->GetRootElement(),
9808 "Root element is the only case for this fallback "
9809 "path to be triggered");
9810 RefPtr<ComputedStyle> style =
9811 ServoStyleSet::ResolveServoStyle(*content->AsElement());
9812 // Dropping the strong reference is fine because the style should be
9813 // held strongly by the element.
9814 return style.get();
9817 enum class FramePosition : uint8_t {
9818 Unknown,
9819 InView,
9820 OutOfView,
9823 // NOTE: Returns a pair of Nothing() and `FramePosition::Unknown` if |aFrame|
9824 // is not in out-of-process or if we haven't received enough information from
9825 // APZ.
9826 static std::pair<Maybe<ScreenRect>, FramePosition> GetFrameVisibleRectOnScreen(
9827 const nsIFrame* aFrame) {
9828 // We actually want the in-process top prescontext here.
9829 nsPresContext* topContextInProcess =
9830 aFrame->PresContext()->GetInProcessRootContentDocumentPresContext();
9831 if (!topContextInProcess) {
9832 // We are in chrome process.
9833 return std::make_pair(Nothing(), FramePosition::Unknown);
9836 if (topContextInProcess->Document()->IsTopLevelContentDocument()) {
9837 // We are in the top of content document.
9838 return std::make_pair(Nothing(), FramePosition::Unknown);
9841 nsIDocShell* docShell = topContextInProcess->GetDocShell();
9842 BrowserChild* browserChild = BrowserChild::GetFrom(docShell);
9843 if (!browserChild) {
9844 // We are not in out-of-process iframe.
9845 return std::make_pair(Nothing(), FramePosition::Unknown);
9848 if (!browserChild->GetEffectsInfo().IsVisible()) {
9849 // There is no visible rect on this iframe at all.
9850 return std::make_pair(Some(ScreenRect()), FramePosition::Unknown);
9853 Maybe<ScreenRect> visibleRect =
9854 browserChild->GetTopLevelViewportVisibleRectInBrowserCoords();
9855 if (!visibleRect) {
9856 // We are unsure if we haven't received the transformed rectangle of the
9857 // iframe's visible area.
9858 return std::make_pair(Nothing(), FramePosition::Unknown);
9861 nsIFrame* rootFrame = topContextInProcess->PresShell()->GetRootFrame();
9862 nsRect transformedToIFrame = nsLayoutUtils::TransformFrameRectToAncestor(
9863 aFrame, aFrame->InkOverflowRectRelativeToSelf(), rootFrame);
9865 LayoutDeviceRect rectInLayoutDevicePixel = LayoutDeviceRect::FromAppUnits(
9866 transformedToIFrame, topContextInProcess->AppUnitsPerDevPixel());
9868 ScreenRect transformedToRoot = ViewAs<ScreenPixel>(
9869 browserChild->GetChildToParentConversionMatrix().TransformBounds(
9870 rectInLayoutDevicePixel),
9871 PixelCastJustification::ContentProcessIsLayerInUiProcess);
9873 FramePosition position = FramePosition::Unknown;
9874 // we need to check whether the transformed rect is outside the iframe
9875 // visible rect or not because in some cases the rect size is (0x0), thus
9876 // the intersection between the transformed rect and the iframe visible rect
9877 // would also be (0x0), then we can't tell whether the given nsIFrame is
9878 // inside the iframe visible rect or not by calling BaseRect::IsEmpty for the
9879 // intersection.
9880 if (transformedToRoot.x > visibleRect->XMost() ||
9881 transformedToRoot.y > visibleRect->YMost() ||
9882 visibleRect->x > transformedToRoot.XMost() ||
9883 visibleRect->y > transformedToRoot.YMost()) {
9884 position = FramePosition::OutOfView;
9885 } else {
9886 position = FramePosition::InView;
9889 return std::make_pair(Some(visibleRect->Intersect(transformedToRoot)),
9890 position);
9893 // static
9894 bool nsLayoutUtils::FrameIsScrolledOutOfViewInCrossProcess(
9895 const nsIFrame* aFrame) {
9896 auto [visibleRect, framePosition] = GetFrameVisibleRectOnScreen(aFrame);
9897 if (visibleRect.isNothing()) {
9898 return false;
9901 return visibleRect->IsEmpty() && framePosition != FramePosition::InView;
9904 // static
9905 bool nsLayoutUtils::FrameIsMostlyScrolledOutOfViewInCrossProcess(
9906 const nsIFrame* aFrame, nscoord aMargin) {
9907 auto [visibleRect, framePosition] = GetFrameVisibleRectOnScreen(aFrame);
9908 (void)framePosition;
9909 if (visibleRect.isNothing()) {
9910 return false;
9913 nsPresContext* topContextInProcess =
9914 aFrame->PresContext()->GetInProcessRootContentDocumentPresContext();
9915 MOZ_ASSERT(topContextInProcess);
9917 nsIDocShell* docShell = topContextInProcess->GetDocShell();
9918 BrowserChild* browserChild = BrowserChild::GetFrom(docShell);
9919 MOZ_ASSERT(browserChild);
9921 auto scale =
9922 browserChild->GetChildToParentConversionMatrix().As2D().ScaleFactors();
9923 const CSSCoord cssMargin = CSSPixel::FromAppUnits(aMargin);
9924 ScreenSize margin =
9925 CSSSize(cssMargin, cssMargin) * ViewAs<CSSToScreenScale2D>(scale);
9927 return visibleRect->width < margin.width ||
9928 visibleRect->height < margin.height;
9931 // static
9932 nsSize nsLayoutUtils::ExpandHeightForViewportUnits(nsPresContext* aPresContext,
9933 const nsSize& aSize) {
9934 nsSize sizeForViewportUnits = aPresContext->GetSizeForViewportUnits();
9936 // |aSize| might be the size expanded to the minimum-scale size whereas the
9937 // size for viewport units is not scaled so that we need to expand the |aSize|
9938 // height by multiplying by the ratio of the viewport units height to the
9939 // visible area height.
9940 float vhExpansionRatio = (float)sizeForViewportUnits.height /
9941 aPresContext->GetVisibleArea().height;
9943 MOZ_ASSERT(aSize.height <= NSCoordSaturatingNonnegativeMultiply(
9944 aSize.height, vhExpansionRatio));
9945 return nsSize(aSize.width, NSCoordSaturatingNonnegativeMultiply(
9946 aSize.height, vhExpansionRatio));
9949 template <typename SizeType>
9950 /* static */ SizeType ExpandHeightForDynamicToolbarImpl(
9951 const nsPresContext* aPresContext, const SizeType& aSize) {
9952 MOZ_ASSERT(aPresContext);
9954 LayoutDeviceIntSize displaySize;
9955 if (RefPtr<MobileViewportManager> MVM =
9956 aPresContext->PresShell()->GetMobileViewportManager()) {
9957 displaySize = MVM->DisplaySize();
9958 } else if (!nsLayoutUtils::GetDocumentViewerSize(aPresContext, displaySize)) {
9959 return aSize;
9962 float toolbarHeightRatio =
9963 mozilla::ScreenCoord(aPresContext->GetDynamicToolbarMaxHeight()) /
9964 mozilla::ViewAs<mozilla::ScreenPixel>(
9965 displaySize,
9966 mozilla::PixelCastJustification::LayoutDeviceIsScreenForBounds)
9967 .height;
9969 SizeType expandedSize = aSize;
9970 static_assert(std::is_same_v<nsSize, SizeType> ||
9971 std::is_same_v<CSSSize, SizeType>);
9972 if constexpr (std::is_same_v<nsSize, SizeType>) {
9973 expandedSize.height =
9974 NSCoordSaturatingAdd(aSize.height, aSize.height * toolbarHeightRatio);
9975 } else if (std::is_same_v<CSSSize, SizeType>) {
9976 expandedSize.height = aSize.height + aSize.height * toolbarHeightRatio;
9978 return expandedSize;
9981 CSSSize nsLayoutUtils::ExpandHeightForDynamicToolbar(
9982 const nsPresContext* aPresContext, const CSSSize& aSize) {
9983 return ExpandHeightForDynamicToolbarImpl(aPresContext, aSize);
9985 nsSize nsLayoutUtils::ExpandHeightForDynamicToolbar(
9986 const nsPresContext* aPresContext, const nsSize& aSize) {
9987 return ExpandHeightForDynamicToolbarImpl(aPresContext, aSize);