1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "nsLayoutUtils.h"
8
9 #include <algorithm>
10 #include <limits>
11
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 "Layers.h"
29 #include "LayoutLogging.h"
30 #include "MobileViewportManager.h"
31 #include "mozilla/AccessibleCaretEventHub.h"
32 #include "mozilla/ArrayUtils.h"
33 #include "mozilla/BasicEvents.h"
34 #include "mozilla/ClearOnShutdown.h"
35 #include "mozilla/DisplayPortUtils.h"
36 #include "mozilla/GeckoBindings.h"
37 #include "mozilla/glean/GleanMetrics.h"
38 #include "mozilla/dom/AnonymousContent.h"
39 #include "mozilla/dom/BrowserChild.h"
40 #include "mozilla/dom/CanvasUtils.h"
41 #include "mozilla/dom/Document.h"
42 #include "mozilla/dom/DocumentInlines.h"
43 #include "mozilla/dom/DOMRect.h"
44 #include "mozilla/dom/DOMStringList.h"
45 #include "mozilla/dom/Element.h"
46 #include "mozilla/dom/HTMLBodyElement.h"
47 #include "mozilla/dom/HTMLCanvasElement.h"
48 #include "mozilla/dom/HTMLImageElement.h"
49 #include "mozilla/dom/HTMLMediaElementBinding.h"
50 #include "mozilla/dom/HTMLVideoElement.h"
51 #include "mozilla/dom/InspectorFontFace.h"
52 #include "mozilla/dom/KeyframeEffect.h"
53 #include "mozilla/dom/SVGViewportElement.h"
54 #include "mozilla/dom/UIEvent.h"
55 #include "mozilla/intl/BidiEmbeddingLevel.h"
56 #include "mozilla/EffectCompositor.h"
57 #include "mozilla/EffectSet.h"
58 #include "mozilla/EventDispatcher.h"
59 #include "mozilla/EventStateManager.h"
60 #include "mozilla/FloatingPoint.h"
61 #include "mozilla/gfx/2D.h"
62 #include "mozilla/gfx/gfxVars.h"
63 #include "mozilla/gfx/PathHelpers.h"
64 #include "mozilla/IntegerRange.h"
65 #include "mozilla/layers/APZCCallbackHelper.h"
66 #include "mozilla/layers/APZPublicUtils.h" // for apz::CalculatePendingDisplayPort
67 #include "mozilla/layers/CompositorBridgeChild.h"
68 #include "mozilla/layers/PAPZ.h"
69 #include "mozilla/layers/StackingContextHelper.h"
70 #include "mozilla/layers/WebRenderLayerManager.h"
71 #include "mozilla/Likely.h"
72 #include "mozilla/LookAndFeel.h"
73 #include "mozilla/Maybe.h"
74 #include "mozilla/MemoryReporting.h"
75 #include "mozilla/PerfStats.h"
76 #include "mozilla/Preferences.h"
77 #include "mozilla/PresShell.h"
78 #include "mozilla/ProfilerLabels.h"
79 #include "mozilla/ProfilerMarkers.h"
80 #include "mozilla/RestyleManager.h"
81 #include "mozilla/ScopeExit.h"
82 #include "mozilla/ScrollOrigin.h"
83 #include "mozilla/ServoStyleSet.h"
84 #include "mozilla/ServoStyleSetInlines.h"
85 #include "mozilla/StaticPrefs_apz.h"
86 #include "mozilla/StaticPrefs_dom.h"
87 #include "mozilla/StaticPrefs_font.h"
88 #include "mozilla/StaticPrefs_gfx.h"
89 #include "mozilla/StaticPrefs_image.h"
90 #include "mozilla/StaticPrefs_layers.h"
91 #include "mozilla/StaticPrefs_layout.h"
92 #include "mozilla/StaticPtr.h"
93 #include "mozilla/StyleAnimationValue.h"
94 #include "mozilla/SVGImageContext.h"
95 #include "mozilla/SVGIntegrationUtils.h"
96 #include "mozilla/SVGTextFrame.h"
97 #include "mozilla/SVGUtils.h"
98 #include "mozilla/Telemetry.h"
99 #include "mozilla/ToString.h"
100 #include "mozilla/Unused.h"
101 #include "mozilla/ViewportFrame.h"
102 #include "mozilla/ViewportUtils.h"
103 #include "mozilla/WheelHandlingHelper.h" // for WheelHandlingUtils
104 #include "nsAnimationManager.h"
105 #include "nsAtom.h"
106 #include "nsBidiPresUtils.h"
107 #include "nsBlockFrame.h"
108 #include "nsCanvasFrame.h"
109 #include "nsCaret.h"
110 #include "nsCharTraits.h"
111 #include "nsCOMPtr.h"
112 #include "nsComputedDOMStyle.h"
113 #include "nsCSSAnonBoxes.h"
114 #include "nsCSSColorUtils.h"
115 #include "nsCSSFrameConstructor.h"
116 #include "nsCSSProps.h"
117 #include "nsCSSPseudoElements.h"
118 #include "nsCSSRendering.h"
119 #include "nsTHashMap.h"
120 #include "nsDeckFrame.h"
121 #include "nsDisplayList.h"
122 #include "nsFlexContainerFrame.h"
123 #include "nsFontInflationData.h"
124 #include "nsFontMetrics.h"
125 #include "nsFrameList.h"
126 #include "nsFrameSelection.h"
127 #include "nsGenericHTMLElement.h"
128 #include "nsGkAtoms.h"
129 #include "nsICanvasRenderingContextInternal.h"
130 #include "nsIContent.h"
131 #include "nsIContentInlines.h"
132 #include "nsIContentViewer.h"
133 #include "nsIDocShell.h"
134 #include "nsIFrameInlines.h"
135 #include "nsIImageLoadingContent.h"
136 #include "nsIInterfaceRequestorUtils.h"
137 #include "nsIScrollableFrame.h"
138 #include "nsIWidget.h"
139 #include "nsListControlFrame.h"
140 #include "nsPIDOMWindow.h"
141 #include "nsPlaceholderFrame.h"
142 #include "nsPresContext.h"
143 #include "nsPresContextInlines.h"
144 #include "nsRefreshDriver.h"
145 #include "nsRegion.h"
146 #include "nsStyleConsts.h"
147 #include "nsStyleStructInlines.h"
148 #include "nsStyleTransformMatrix.h"
149 #include "nsSubDocumentFrame.h"
150 #include "nsTableWrapperFrame.h"
151 #include "nsTArray.h"
152 #include "nsTextFragment.h"
153 #include "nsTextFrame.h"
154 #include "nsTransitionManager.h"
155 #include "nsView.h"
156 #include "nsViewManager.h"
157 #include "prenv.h"
158 #include "RegionBuilder.h"
159 #include "RetainedDisplayListBuilder.h"
160 #include "TextDrawTarget.h"
161 #include "UnitTransforms.h"
162 #include "ViewportFrame.h"
163
164 #include "nsXULPopupManager.h"
165
166 // Make sure getpid() works.
167 #ifdef XP_WIN
168 # include <process.h>
169 # define getpid _getpid
170 #else
171 # include <unistd.h>
172 #endif
173
174 using namespace mozilla;
175 using namespace mozilla::dom;
176 using namespace mozilla::image;
177 using namespace mozilla::layers;
178 using namespace mozilla::layout;
179 using namespace mozilla::gfx;
180 using mozilla::dom::HTMLMediaElement_Binding::HAVE_METADATA;
181 using mozilla::dom::HTMLMediaElement_Binding::HAVE_NOTHING;
182
183 #ifdef DEBUG
184 // TODO: remove, see bug 598468.
185 bool nsLayoutUtils::gPreventAssertInCompareTreePosition = false;
186 #endif // DEBUG
187
188 typedef ScrollableLayerGuid::ViewID ViewID;
189 typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox;
190
191 static ViewID sScrollIdCounter = ScrollableLayerGuid::START_SCROLL_ID;
192
193 typedef nsTHashMap<nsUint64HashKey, nsIContent*> ContentMap;
194 static StaticAutoPtr<ContentMap> sContentMap;
195
GetContentMap()196 static ContentMap& GetContentMap() {
197 if (!sContentMap) {
198 sContentMap = new ContentMap();
199 }
200 return *sContentMap;
201 }
202
203 template <typename TestType>
HasMatchingAnimations(EffectSet & aEffects,TestType && aTest)204 static bool HasMatchingAnimations(EffectSet& aEffects, TestType&& aTest) {
205 for (KeyframeEffect* effect : aEffects) {
206 if (!effect->GetAnimation() || !effect->GetAnimation()->IsRelevant()) {
207 continue;
208 }
209
210 if (aTest(*effect, aEffects)) {
211 return true;
212 }
213 }
214
215 return false;
216 }
217
218 template <typename TestType>
HasMatchingAnimations(const nsIFrame * aFrame,const nsCSSPropertyIDSet & aPropertySet,TestType && aTest)219 static bool HasMatchingAnimations(const nsIFrame* aFrame,
220 const nsCSSPropertyIDSet& aPropertySet,
221 TestType&& aTest) {
222 MOZ_ASSERT(aFrame);
223
224 if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()) &&
225 !aFrame->MayHaveOpacityAnimation()) {
226 return false;
227 }
228
229 if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) &&
230 !aFrame->MayHaveTransformAnimation()) {
231 return false;
232 }
233
234 EffectSet* effectSet = EffectSet::GetEffectSetForFrame(aFrame, aPropertySet);
235 if (!effectSet) {
236 return false;
237 }
238
239 return HasMatchingAnimations(*effectSet, aTest);
240 }
241
242 /* static */
HasAnimationOfPropertySet(const nsIFrame * aFrame,const nsCSSPropertyIDSet & aPropertySet)243 bool nsLayoutUtils::HasAnimationOfPropertySet(
244 const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet) {
245 return HasMatchingAnimations(
246 aFrame, aPropertySet,
247 [&aPropertySet](KeyframeEffect& aEffect, const EffectSet&) {
248 return aEffect.HasAnimationOfPropertySet(aPropertySet);
249 });
250 }
251
252 /* static */
HasAnimationOfPropertySet(const nsIFrame * aFrame,const nsCSSPropertyIDSet & aPropertySet,EffectSet * aEffectSet)253 bool nsLayoutUtils::HasAnimationOfPropertySet(
254 const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet,
255 EffectSet* aEffectSet) {
256 MOZ_ASSERT(
257 !aEffectSet ||
258 EffectSet::GetEffectSetForFrame(aFrame, aPropertySet) == aEffectSet,
259 "The EffectSet, if supplied, should match what we would otherwise fetch");
260
261 if (!aEffectSet) {
262 return nsLayoutUtils::HasAnimationOfPropertySet(aFrame, aPropertySet);
263 }
264
265 if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) &&
266 !aEffectSet->MayHaveTransformAnimation()) {
267 return false;
268 }
269
270 if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()) &&
271 !aEffectSet->MayHaveOpacityAnimation()) {
272 return false;
273 }
274
275 return HasMatchingAnimations(
276 *aEffectSet,
277 [&aPropertySet](KeyframeEffect& aEffect, const EffectSet& aEffectSet) {
278 return aEffect.HasAnimationOfPropertySet(aPropertySet);
279 });
280 }
281
282 /* static */
HasAnimationOfTransformAndMotionPath(const nsIFrame * aFrame)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()));
293 }
294
295 /* static */
HasEffectiveAnimation(const nsIFrame * aFrame,const nsCSSPropertyIDSet & aPropertySet)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);
303 });
304 }
305
306 /* static */
GetAnimationPropertiesForCompositor(const nsIFrame * aStyleFrame)307 nsCSSPropertyIDSet nsLayoutUtils::GetAnimationPropertiesForCompositor(
308 const nsIFrame* aStyleFrame) {
309 nsCSSPropertyIDSet properties;
310
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::GetEffectSetForStyleFrame(aStyleFrame);
315 if (!effects) {
316 return properties;
317 }
318
319 AnimationPerformanceWarning::Type warning;
320 if (!EffectCompositor::AllowCompositorAnimationsOnFrame(aStyleFrame,
321 warning)) {
322 return properties;
323 }
324
325 for (const KeyframeEffect* effect : *effects) {
326 properties |= effect->GetPropertiesForCompositor(*effects, aStyleFrame);
327 }
328
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();
336 }
337
338 return properties;
339 }
340
GetSuitableScale(float aMaxScale,float aMinScale,nscoord aVisibleDimension,nscoord aDisplayDimension)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;
358 }
359 return std::max(std::min(aMaxScale, displayVisibleRatio), aMinScale);
360 }
361
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<Size, Size>;
365
UpdateMinMaxScale(const nsIFrame * aFrame,const AnimationValue & aValue,MinAndMaxScale & aMinAndMaxScale)366 static inline void UpdateMinMaxScale(const nsIFrame* aFrame,
367 const AnimationValue& aValue,
368 MinAndMaxScale& aMinAndMaxScale) {
369 Size size = aValue.GetScaleValue(aFrame);
370 Size& minScale = aMinAndMaxScale.first;
371 Size& maxScale = aMinAndMaxScale.second;
372
373 minScale = Min(minScale, size);
374 maxScale = Max(maxScale, size);
375 }
376
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.
381 //
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" };
385 //
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.
GetMinAndMaxScaleForAnimationProperty(const nsIFrame * aFrame,const nsTArray<RefPtr<dom::Animation>> & aAnimations)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(Size(std::numeric_limits<float>::max(),
398 std::numeric_limits<float>::max()),
399 Size(std::numeric_limits<float>::min(),
400 std::numeric_limits<float>::min()));
401 Array<MinAndMaxScale, 2> minAndMaxScales(defaultValue, defaultValue);
402
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());
409
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 != eCSSProperty_transform &&
415 prop.mProperty != eCSSProperty_scale) {
416 continue;
417 }
418
419 // 0: eCSSProperty_transform.
420 // 1: eCSSProperty_scale.
421 MinAndMaxScale& scales =
422 minAndMaxScales[prop.mProperty == eCSSProperty_transform ? 0 : 1];
423
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);
429 }
430
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);
436 }
437
438 if (segment.HasReplaceableToValue()) {
439 UpdateMinMaxScale(aFrame, segment.mToValue, scales);
440 }
441 }
442 }
443 }
444
445 return minAndMaxScales;
446 }
447
ComputeSuitableScaleForAnimation(const nsIFrame * aFrame,const nsSize & aVisibleSize,const nsSize & aDisplaySize)448 Size 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});
455
456 if (compositorAnimations.IsEmpty()) {
457 return Size(1.0, 1.0);
458 }
459
460 const Array<MinAndMaxScale, 2> minAndMaxScales =
461 GetMinAndMaxScaleForAnimationProperty(aFrame, compositorAnimations);
462
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 Size maxScale(std::numeric_limits<float>::min(),
468 std::numeric_limits<float>::min());
469 Size minScale(std::numeric_limits<float>::max(),
470 std::numeric_limits<float>::max());
471
472 auto isUnset = [](const Size& aMax, const Size& aMin) {
473 return aMax.width == std::numeric_limits<float>::min() &&
474 aMax.height == std::numeric_limits<float>::min() &&
475 aMin.width == std::numeric_limits<float>::max() &&
476 aMin.height == std::numeric_limits<float>::max();
477 };
478
479 // Iterate the slots to get the final scale value.
480 for (const auto& pair : minAndMaxScales) {
481 const Size& currMinScale = pair.first;
482 const Size& currMaxScale = pair.second;
483
484 if (isUnset(currMaxScale, currMinScale)) {
485 // We don't have this animation property, so skip.
486 continue;
487 }
488
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;
500 }
501 }
502
503 if (isUnset(maxScale, minScale)) {
504 // We didn't encounter any transform-like property.
505 return Size(1.0, 1.0);
506 }
507
508 return Size(GetSuitableScale(maxScale.width, minScale.width,
509 aVisibleSize.width, aDisplaySize.width),
510 GetSuitableScale(maxScale.height, minScale.height,
511 aVisibleSize.height, aDisplaySize.height));
512 }
513
AreAsyncAnimationsEnabled()514 bool nsLayoutUtils::AreAsyncAnimationsEnabled() {
515 return StaticPrefs::layers_offmainthreadcomposition_async_animations() &&
516 gfxPlatform::OffMainThreadCompositingEnabled();
517 }
518
AreRetainedDisplayListsEnabled()519 bool nsLayoutUtils::AreRetainedDisplayListsEnabled() {
520 #ifdef MOZ_WIDGET_ANDROID
521 return StaticPrefs::layout_display_list_retain();
522 #else
523 if (XRE_IsContentProcess()) {
524 return StaticPrefs::layout_display_list_retain();
525 }
526
527 if (XRE_IsE10sParentProcess()) {
528 return StaticPrefs::layout_display_list_retain_chrome();
529 }
530
531 // Retained display lists require e10s.
532 return false;
533 #endif
534 }
535
DisplayRootHasRetainedDisplayListBuilder(nsIFrame * aFrame)536 bool nsLayoutUtils::DisplayRootHasRetainedDisplayListBuilder(nsIFrame* aFrame) {
537 const nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame);
538 MOZ_ASSERT(displayRoot);
539 return displayRoot->HasProperty(RetainedDisplayListBuilder::Cached());
540 }
541
GPUImageScalingEnabled()542 bool nsLayoutUtils::GPUImageScalingEnabled() {
543 static bool sGPUImageScalingEnabled;
544 static bool sGPUImageScalingPrefInitialised = false;
545
546 if (!sGPUImageScalingPrefInitialised) {
547 sGPUImageScalingPrefInitialised = true;
548 sGPUImageScalingEnabled =
549 Preferences::GetBool("layout.gpu-image-scaling.enabled", false);
550 }
551
552 return sGPUImageScalingEnabled;
553 }
554
UnionChildOverflow(nsIFrame * aFrame,OverflowAreas & aOverflowAreas,FrameChildListIDs aSkipChildLists)555 void nsLayoutUtils::UnionChildOverflow(nsIFrame* aFrame,
556 OverflowAreas& aOverflowAreas,
557 FrameChildListIDs aSkipChildLists) {
558 // Iterate over all children except pop-ups.
559 FrameChildListIDs skip(aSkipChildLists);
560 skip += {nsIFrame::kSelectPopupList, nsIFrame::kPopupList};
561
562 for (const auto& [list, listID] : aFrame->ChildLists()) {
563 if (skip.contains(listID)) {
564 continue;
565 }
566 for (nsIFrame* child : list) {
567 aOverflowAreas.UnionWith(
568 child->GetActualAndNormalOverflowAreasRelativeToParent());
569 }
570 }
571 }
572
DestroyViewID(void * aObject,nsAtom * aPropertyName,void * aPropertyValue,void * aData)573 static void DestroyViewID(void* aObject, nsAtom* aPropertyName,
574 void* aPropertyValue, void* aData) {
575 ViewID* id = static_cast<ViewID*>(aPropertyValue);
576 GetContentMap().Remove(*id);
577 delete id;
578 }
579
580 /**
581 * A namespace class for static layout utilities.
582 */
583
FindIDFor(const nsIContent * aContent,ViewID * aOutViewId)584 bool nsLayoutUtils::FindIDFor(const nsIContent* aContent, ViewID* aOutViewId) {
585 void* scrollIdProperty = aContent->GetProperty(nsGkAtoms::RemoteId);
586 if (scrollIdProperty) {
587 *aOutViewId = *static_cast<ViewID*>(scrollIdProperty);
588 return true;
589 }
590 return false;
591 }
592
FindOrCreateIDFor(nsIContent * aContent)593 ViewID nsLayoutUtils::FindOrCreateIDFor(nsIContent* aContent) {
594 ViewID scrollId;
595
596 if (!FindIDFor(aContent, &scrollId)) {
597 scrollId = sScrollIdCounter++;
598 aContent->SetProperty(nsGkAtoms::RemoteId, new ViewID(scrollId),
599 DestroyViewID);
600 GetContentMap().InsertOrUpdate(scrollId, aContent);
601 }
602
603 return scrollId;
604 }
605
FindContentFor(ViewID aId)606 nsIContent* nsLayoutUtils::FindContentFor(ViewID aId) {
607 MOZ_ASSERT(aId != ScrollableLayerGuid::NULL_SCROLL_ID,
608 "Cannot find a content element in map for null IDs.");
609 nsIContent* content;
610 bool exists = GetContentMap().Get(aId, &content);
611
612 if (exists) {
613 return content;
614 } else {
615 return nullptr;
616 }
617 }
618
GetScrollFrameFromContent(nsIContent * aContent)619 nsIFrame* nsLayoutUtils::GetScrollFrameFromContent(nsIContent* aContent) {
620 nsIFrame* frame = aContent->GetPrimaryFrame();
621 if (aContent->OwnerDoc()->GetRootElement() == aContent) {
622 PresShell* presShell = frame ? frame->PresShell() : nullptr;
623 if (!presShell) {
624 presShell = aContent->OwnerDoc()->GetPresShell();
625 }
626 // We want the scroll frame, the root scroll frame differs from all
627 // others in that the primary frame is not the scroll frame.
628 nsIFrame* rootScrollFrame =
629 presShell ? presShell->GetRootScrollFrame() : nullptr;
630 if (rootScrollFrame) {
631 frame = rootScrollFrame;
632 }
633 }
634 return frame;
635 }
636
FindScrollableFrameFor(nsIContent * aContent)637 nsIScrollableFrame* nsLayoutUtils::FindScrollableFrameFor(
638 nsIContent* aContent) {
639 nsIFrame* scrollFrame = GetScrollFrameFromContent(aContent);
640 return scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr;
641 }
642
FindScrollableFrameFor(ViewID aId)643 nsIScrollableFrame* nsLayoutUtils::FindScrollableFrameFor(ViewID aId) {
644 nsIContent* content = FindContentFor(aId);
645 if (!content) {
646 return nullptr;
647 }
648
649 return FindScrollableFrameFor(content);
650 }
651
FindIDForScrollableFrame(nsIScrollableFrame * aScrollable)652 ViewID nsLayoutUtils::FindIDForScrollableFrame(
653 nsIScrollableFrame* aScrollable) {
654 if (!aScrollable) {
655 return ScrollableLayerGuid::NULL_SCROLL_ID;
656 }
657
658 nsIFrame* scrollFrame = do_QueryFrame(aScrollable);
659 nsIContent* scrollContent = scrollFrame->GetContent();
660
661 ScrollableLayerGuid::ViewID scrollId;
662 if (scrollContent && nsLayoutUtils::FindIDFor(scrollContent, &scrollId)) {
663 return scrollId;
664 }
665
666 return ScrollableLayerGuid::NULL_SCROLL_ID;
667 }
668
UsesAsyncScrolling(nsIFrame * aFrame)669 bool nsLayoutUtils::UsesAsyncScrolling(nsIFrame* aFrame) {
670 #ifdef MOZ_WIDGET_ANDROID
671 // We always have async scrolling for android
672 return true;
673 #endif
674
675 return AsyncPanZoomEnabled(aFrame);
676 }
677
AsyncPanZoomEnabled(const nsIFrame * aFrame)678 bool nsLayoutUtils::AsyncPanZoomEnabled(const nsIFrame* aFrame) {
679 // We use this as a shortcut, since if the compositor will never use APZ,
680 // no widget will either.
681 if (!gfxPlatform::AsyncPanZoomEnabled()) {
682 return false;
683 }
684
685 const nsIFrame* frame = nsLayoutUtils::GetDisplayRootFrame(aFrame);
686 nsIWidget* widget = frame->GetNearestWidget();
687 if (!widget) {
688 return false;
689 }
690 return widget->AsyncPanZoomEnabled();
691 }
692
AllowZoomingForDocument(const mozilla::dom::Document * aDocument)693 bool nsLayoutUtils::AllowZoomingForDocument(
694 const mozilla::dom::Document* aDocument) {
695 if (aDocument->GetPresShell() &&
696 !aDocument->GetPresShell()->AsyncPanZoomEnabled()) {
697 return false;
698 }
699 // True if we allow zooming for all documents on this platform, or if we are
700 // in RDM and handling meta viewports, which force zoom under some
701 // circumstances.
702 BrowsingContext* bc = aDocument ? aDocument->GetBrowsingContext() : nullptr;
703 return StaticPrefs::apz_allow_zooming() ||
704 (bc && bc->InRDMPane() &&
705 nsLayoutUtils::ShouldHandleMetaViewport(aDocument));
706 }
707
HasVisibleAnonymousContents(Document * aDoc)708 static bool HasVisibleAnonymousContents(Document* aDoc) {
709 for (RefPtr<AnonymousContent>& ac : aDoc->GetAnonymousContents()) {
710 // We check to see if the anonymous content node has a frame. If it doesn't,
711 // that means that's not visible to the user because e.g. it's display:none.
712 // For now we assume that if it has a frame, it is visible. We might be able
713 // to refine this further by adding complexity if it turns out this
714 // condition results in a lot of false positives.
715 if (ac->ContentNode().GetPrimaryFrame()) {
716 return true;
717 }
718 }
719 return false;
720 }
721
ShouldDisableApzForElement(nsIContent * aContent)722 bool nsLayoutUtils::ShouldDisableApzForElement(nsIContent* aContent) {
723 if (!aContent) {
724 return false;
725 }
726
727 if (aContent->GetProperty(nsGkAtoms::apzDisabled)) {
728 return true;
729 }
730
731 Document* doc = aContent->GetComposedDoc();
732 if (PresShell* rootPresShell =
733 APZCCallbackHelper::GetRootContentDocumentPresShellForContent(
734 aContent)) {
735 if (Document* rootDoc = rootPresShell->GetDocument()) {
736 nsIContent* rootContent =
737 rootPresShell->GetRootScrollFrame()
738 ? rootPresShell->GetRootScrollFrame()->GetContent()
739 : rootDoc->GetDocumentElement();
740 // For the AccessibleCaret and other anonymous contents: disable APZ on
741 // any scrollable subframes that are not the root scrollframe of a
742 // document, if the document has any visible anonymous contents.
743 //
744 // If we find this is triggering in too many scenarios then we might
745 // want to tighten this check further. The main use cases for which we
746 // want to disable APZ as of this writing are listed in bug 1316318.
747 if (aContent != rootContent && HasVisibleAnonymousContents(rootDoc)) {
748 return true;
749 }
750 }
751 }
752
753 if (!doc) {
754 return false;
755 }
756
757 if (PresShell* presShell = doc->GetPresShell()) {
758 if (RefPtr<AccessibleCaretEventHub> eventHub =
759 presShell->GetAccessibleCaretEventHub()) {
760 // Disable APZ for all elements if AccessibleCaret tells us to do so.
761 if (eventHub->ShouldDisableApz()) {
762 return true;
763 }
764 }
765 }
766
767 return StaticPrefs::apz_disable_for_scroll_linked_effects() &&
768 doc->HasScrollLinkedEffect();
769 }
770
NotifyPaintSkipTransaction(ViewID aScrollId)771 void nsLayoutUtils::NotifyPaintSkipTransaction(ViewID aScrollId) {
772 if (nsIScrollableFrame* scrollFrame =
773 nsLayoutUtils::FindScrollableFrameFor(aScrollId)) {
774 #ifdef DEBUG
775 nsIFrame* f = do_QueryFrame(scrollFrame);
776 MOZ_ASSERT(f && f->PresShell() && !f->PresShell()->IsResolutionUpdated());
777 #endif
778 scrollFrame->NotifyApzTransaction();
779 }
780 }
781
LastContinuationWithChild(nsContainerFrame * aFrame)782 nsContainerFrame* nsLayoutUtils::LastContinuationWithChild(
783 nsContainerFrame* aFrame) {
784 MOZ_ASSERT(aFrame, "NULL frame pointer");
785 for (auto f = aFrame->LastContinuation(); f; f = f->GetPrevContinuation()) {
786 for (const auto& childList : f->ChildLists()) {
787 if (MOZ_LIKELY(!childList.mList.IsEmpty())) {
788 return static_cast<nsContainerFrame*>(f);
789 }
790 }
791 }
792 return aFrame;
793 }
794
795 // static
GetChildListNameFor(nsIFrame * aChildFrame)796 FrameChildListID nsLayoutUtils::GetChildListNameFor(nsIFrame* aChildFrame) {
797 nsIFrame::ChildListID id = nsIFrame::kPrincipalList;
798
799 MOZ_DIAGNOSTIC_ASSERT(!aChildFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
800
801 if (aChildFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
802 nsIFrame* pif = aChildFrame->GetPrevInFlow();
803 if (pif->GetParent() == aChildFrame->GetParent()) {
804 id = nsIFrame::kExcessOverflowContainersList;
805 } else {
806 id = nsIFrame::kOverflowContainersList;
807 }
808 } else {
809 LayoutFrameType childType = aChildFrame->Type();
810 if (LayoutFrameType::MenuPopup == childType) {
811 nsIFrame* parent = aChildFrame->GetParent();
812 MOZ_ASSERT(parent, "nsMenuPopupFrame can't be the root frame");
813 if (parent) {
814 if (parent->IsPopupSetFrame()) {
815 id = nsIFrame::kPopupList;
816 } else {
817 nsIFrame* firstPopup =
818 parent->GetChildList(nsIFrame::kPopupList).FirstChild();
819 MOZ_ASSERT(
820 !firstPopup || !firstPopup->GetNextSibling(),
821 "We assume popupList only has one child, but it has more.");
822 id = firstPopup == aChildFrame ? nsIFrame::kPopupList
823 : nsIFrame::kPrincipalList;
824 }
825 } else {
826 id = nsIFrame::kPrincipalList;
827 }
828 } else if (LayoutFrameType::TableColGroup == childType) {
829 id = nsIFrame::kColGroupList;
830 } else if (aChildFrame->IsTableCaption()) {
831 id = nsIFrame::kCaptionList;
832 } else {
833 id = nsIFrame::kPrincipalList;
834 }
835 }
836
837 #ifdef DEBUG
838 // Verify that the frame is actually in that child list or in the
839 // corresponding overflow list.
840 nsContainerFrame* parent = aChildFrame->GetParent();
841 bool found = parent->GetChildList(id).ContainsFrame(aChildFrame);
842 if (!found) {
843 found = parent->GetChildList(nsIFrame::kOverflowList)
844 .ContainsFrame(aChildFrame);
845 MOZ_ASSERT(found, "not in child list");
846 }
847 #endif
848
849 return id;
850 }
851
GetPseudo(const nsIContent * aContent,nsAtom * aPseudoProperty)852 static Element* GetPseudo(const nsIContent* aContent, nsAtom* aPseudoProperty) {
853 MOZ_ASSERT(aPseudoProperty == nsGkAtoms::beforePseudoProperty ||
854 aPseudoProperty == nsGkAtoms::afterPseudoProperty ||
855 aPseudoProperty == nsGkAtoms::markerPseudoProperty);
856 if (!aContent->MayHaveAnonymousChildren()) {
857 return nullptr;
858 }
859 return static_cast<Element*>(aContent->GetProperty(aPseudoProperty));
860 }
861
862 /*static*/
GetBeforePseudo(const nsIContent * aContent)863 Element* nsLayoutUtils::GetBeforePseudo(const nsIContent* aContent) {
864 return GetPseudo(aContent, nsGkAtoms::beforePseudoProperty);
865 }
866
867 /*static*/
GetBeforeFrame(const nsIContent * aContent)868 nsIFrame* nsLayoutUtils::GetBeforeFrame(const nsIContent* aContent) {
869 Element* pseudo = GetBeforePseudo(aContent);
870 return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
871 }
872
873 /*static*/
GetAfterPseudo(const nsIContent * aContent)874 Element* nsLayoutUtils::GetAfterPseudo(const nsIContent* aContent) {
875 return GetPseudo(aContent, nsGkAtoms::afterPseudoProperty);
876 }
877
878 /*static*/
GetAfterFrame(const nsIContent * aContent)879 nsIFrame* nsLayoutUtils::GetAfterFrame(const nsIContent* aContent) {
880 Element* pseudo = GetAfterPseudo(aContent);
881 return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
882 }
883
884 /*static*/
GetMarkerPseudo(const nsIContent * aContent)885 Element* nsLayoutUtils::GetMarkerPseudo(const nsIContent* aContent) {
886 return GetPseudo(aContent, nsGkAtoms::markerPseudoProperty);
887 }
888
889 /*static*/
GetMarkerFrame(const nsIContent * aContent)890 nsIFrame* nsLayoutUtils::GetMarkerFrame(const nsIContent* aContent) {
891 Element* pseudo = GetMarkerPseudo(aContent);
892 return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
893 }
894
895 #ifdef ACCESSIBILITY
GetMarkerSpokenText(const nsIContent * aContent,nsAString & aText)896 void nsLayoutUtils::GetMarkerSpokenText(const nsIContent* aContent,
897 nsAString& aText) {
898 MOZ_ASSERT(aContent && aContent->IsGeneratedContentContainerForMarker());
899
900 aText.Truncate();
901
902 nsIFrame* frame = aContent->GetPrimaryFrame();
903 if (!frame) {
904 return;
905 }
906
907 if (frame->StyleContent()->ContentCount() > 0) {
908 for (nsIFrame* child : frame->PrincipalChildList()) {
909 nsIFrame::RenderedText text = child->GetRenderedText();
910 aText += text.mString;
911 }
912 return;
913 }
914
915 if (!frame->StyleList()->mListStyleImage.IsNone()) {
916 // ::marker is an image, so use default bullet character.
917 static const char16_t kDiscMarkerString[] = {0x2022, ' ', 0};
918 aText.AssignLiteral(kDiscMarkerString);
919 return;
920 }
921
922 frame->PresContext()
923 ->FrameConstructor()
924 ->CounterManager()
925 ->GetSpokenCounterText(frame, aText);
926 }
927 #endif
928
929 // static
GetClosestFrameOfType(nsIFrame * aFrame,LayoutFrameType aFrameType,nsIFrame * aStopAt)930 nsIFrame* nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame,
931 LayoutFrameType aFrameType,
932 nsIFrame* aStopAt) {
933 for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
934 if (frame->Type() == aFrameType) {
935 return frame;
936 }
937 if (frame == aStopAt) {
938 break;
939 }
940 }
941 return nullptr;
942 }
943
944 /* static */
GetPageFrame(nsIFrame * aFrame)945 nsIFrame* nsLayoutUtils::GetPageFrame(nsIFrame* aFrame) {
946 return GetClosestFrameOfType(aFrame, LayoutFrameType::Page);
947 }
948
949 /* static */
GetStyleFrame(nsIFrame * aPrimaryFrame)950 nsIFrame* nsLayoutUtils::GetStyleFrame(nsIFrame* aPrimaryFrame) {
951 MOZ_ASSERT(aPrimaryFrame);
952 if (aPrimaryFrame->IsTableWrapperFrame()) {
953 nsIFrame* inner = aPrimaryFrame->PrincipalChildList().FirstChild();
954 // inner may be null, if aPrimaryFrame is mid-destruction
955 return inner;
956 }
957
958 return aPrimaryFrame;
959 }
960
GetStyleFrame(const nsIFrame * aPrimaryFrame)961 const nsIFrame* nsLayoutUtils::GetStyleFrame(const nsIFrame* aPrimaryFrame) {
962 return nsLayoutUtils::GetStyleFrame(const_cast<nsIFrame*>(aPrimaryFrame));
963 }
964
GetStyleFrame(const nsIContent * aContent)965 nsIFrame* nsLayoutUtils::GetStyleFrame(const nsIContent* aContent) {
966 nsIFrame* frame = aContent->GetPrimaryFrame();
967 if (!frame) {
968 return nullptr;
969 }
970
971 return nsLayoutUtils::GetStyleFrame(frame);
972 }
973
UnthemedScrollbarSize(StyleScrollbarWidth aWidth)974 CSSIntCoord nsLayoutUtils::UnthemedScrollbarSize(StyleScrollbarWidth aWidth) {
975 switch (aWidth) {
976 case StyleScrollbarWidth::Auto:
977 return 12;
978 case StyleScrollbarWidth::Thin:
979 return 6;
980 case StyleScrollbarWidth::None:
981 return 0;
982 }
983 return 0;
984 }
985
986 /* static */
GetPrimaryFrameFromStyleFrame(nsIFrame * aStyleFrame)987 nsIFrame* nsLayoutUtils::GetPrimaryFrameFromStyleFrame(nsIFrame* aStyleFrame) {
988 nsIFrame* parent = aStyleFrame->GetParent();
989 return parent && parent->IsTableWrapperFrame() ? parent : aStyleFrame;
990 }
991
992 /* static */
GetPrimaryFrameFromStyleFrame(const nsIFrame * aStyleFrame)993 const nsIFrame* nsLayoutUtils::GetPrimaryFrameFromStyleFrame(
994 const nsIFrame* aStyleFrame) {
995 return nsLayoutUtils::GetPrimaryFrameFromStyleFrame(
996 const_cast<nsIFrame*>(aStyleFrame));
997 }
998
999 /*static*/
IsPrimaryStyleFrame(const nsIFrame * aFrame)1000 bool nsLayoutUtils::IsPrimaryStyleFrame(const nsIFrame* aFrame) {
1001 if (aFrame->IsTableWrapperFrame()) {
1002 return false;
1003 }
1004
1005 const nsIFrame* parent = aFrame->GetParent();
1006 if (parent && parent->IsTableWrapperFrame()) {
1007 return parent->PrincipalChildList().FirstChild() == aFrame;
1008 }
1009
1010 return aFrame->IsPrimaryFrame();
1011 }
1012
GetFloatFromPlaceholder(nsIFrame * aFrame)1013 nsIFrame* nsLayoutUtils::GetFloatFromPlaceholder(nsIFrame* aFrame) {
1014 NS_ASSERTION(aFrame->IsPlaceholderFrame(), "Must have a placeholder here");
1015 if (aFrame->HasAnyStateBits(PLACEHOLDER_FOR_FLOAT)) {
1016 nsIFrame* outOfFlowFrame =
1017 nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
1018 NS_ASSERTION(outOfFlowFrame && outOfFlowFrame->IsFloating(),
1019 "How did that happen?");
1020 return outOfFlowFrame;
1021 }
1022
1023 return nullptr;
1024 }
1025
1026 // static
GetCrossDocParentFrameInProcess(const nsIFrame * aFrame,nsPoint * aCrossDocOffset)1027 nsIFrame* nsLayoutUtils::GetCrossDocParentFrameInProcess(
1028 const nsIFrame* aFrame, nsPoint* aCrossDocOffset) {
1029 nsIFrame* p = aFrame->GetParent();
1030 if (p) {
1031 return p;
1032 }
1033
1034 nsView* v = aFrame->GetView();
1035 if (!v) {
1036 return nullptr;
1037 }
1038 v = v->GetParent(); // anonymous inner view
1039 if (!v) {
1040 return nullptr;
1041 }
1042 v = v->GetParent(); // subdocumentframe's view
1043 if (!v) {
1044 return nullptr;
1045 }
1046
1047 p = v->GetFrame();
1048 if (p && aCrossDocOffset) {
1049 nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(p);
1050 MOZ_ASSERT(subdocumentFrame);
1051 *aCrossDocOffset += subdocumentFrame->GetExtraOffset();
1052 }
1053
1054 return p;
1055 }
1056
1057 // static
GetCrossDocParentFrame(const nsIFrame * aFrame,nsPoint * aCrossDocOffset)1058 nsIFrame* nsLayoutUtils::GetCrossDocParentFrame(const nsIFrame* aFrame,
1059 nsPoint* aCrossDocOffset) {
1060 return GetCrossDocParentFrameInProcess(aFrame, aCrossDocOffset);
1061 }
1062
1063 // static
IsProperAncestorFrameCrossDoc(const nsIFrame * aAncestorFrame,const nsIFrame * aFrame,const nsIFrame * aCommonAncestor)1064 bool nsLayoutUtils::IsProperAncestorFrameCrossDoc(
1065 const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
1066 const nsIFrame* aCommonAncestor) {
1067 if (aFrame == aAncestorFrame) return false;
1068 return IsAncestorFrameCrossDoc(aAncestorFrame, aFrame, aCommonAncestor);
1069 }
1070
1071 // static
IsProperAncestorFrameCrossDocInProcess(const nsIFrame * aAncestorFrame,const nsIFrame * aFrame,const nsIFrame * aCommonAncestor)1072 bool nsLayoutUtils::IsProperAncestorFrameCrossDocInProcess(
1073 const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
1074 const nsIFrame* aCommonAncestor) {
1075 if (aFrame == aAncestorFrame) return false;
1076 return IsAncestorFrameCrossDocInProcess(aAncestorFrame, aFrame,
1077 aCommonAncestor);
1078 }
1079
1080 // static
IsAncestorFrameCrossDoc(const nsIFrame * aAncestorFrame,const nsIFrame * aFrame,const nsIFrame * aCommonAncestor)1081 bool nsLayoutUtils::IsAncestorFrameCrossDoc(const nsIFrame* aAncestorFrame,
1082 const nsIFrame* aFrame,
1083 const nsIFrame* aCommonAncestor) {
1084 for (const nsIFrame* f = aFrame; f != aCommonAncestor;
1085 f = GetCrossDocParentFrameInProcess(f)) {
1086 if (f == aAncestorFrame) return true;
1087 }
1088 return aCommonAncestor == aAncestorFrame;
1089 }
1090
1091 // static
IsAncestorFrameCrossDocInProcess(const nsIFrame * aAncestorFrame,const nsIFrame * aFrame,const nsIFrame * aCommonAncestor)1092 bool nsLayoutUtils::IsAncestorFrameCrossDocInProcess(
1093 const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
1094 const nsIFrame* aCommonAncestor) {
1095 for (const nsIFrame* f = aFrame; f != aCommonAncestor;
1096 f = GetCrossDocParentFrameInProcess(f)) {
1097 if (f == aAncestorFrame) return true;
1098 }
1099 return aCommonAncestor == aAncestorFrame;
1100 }
1101
1102 // static
IsProperAncestorFrame(const nsIFrame * aAncestorFrame,const nsIFrame * aFrame,const nsIFrame * aCommonAncestor)1103 bool nsLayoutUtils::IsProperAncestorFrame(const nsIFrame* aAncestorFrame,
1104 const nsIFrame* aFrame,
1105 const nsIFrame* aCommonAncestor) {
1106 if (aFrame == aAncestorFrame) return false;
1107 for (const nsIFrame* f = aFrame; f != aCommonAncestor; f = f->GetParent()) {
1108 if (f == aAncestorFrame) return true;
1109 }
1110 return aCommonAncestor == aAncestorFrame;
1111 }
1112
1113 // static
DoCompareTreePosition(nsIContent * aContent1,nsIContent * aContent2,int32_t aIf1Ancestor,int32_t aIf2Ancestor,const nsIContent * aCommonAncestor)1114 int32_t nsLayoutUtils::DoCompareTreePosition(
1115 nsIContent* aContent1, nsIContent* aContent2, int32_t aIf1Ancestor,
1116 int32_t aIf2Ancestor, const nsIContent* aCommonAncestor) {
1117 MOZ_ASSERT(aIf1Ancestor == -1 || aIf1Ancestor == 0 || aIf1Ancestor == 1);
1118 MOZ_ASSERT(aIf2Ancestor == -1 || aIf2Ancestor == 0 || aIf2Ancestor == 1);
1119 MOZ_ASSERT(aContent1, "aContent1 must not be null");
1120 MOZ_ASSERT(aContent2, "aContent2 must not be null");
1121
1122 AutoTArray<nsINode*, 32> content1Ancestors;
1123 nsINode* c1;
1124 for (c1 = aContent1; c1 && c1 != aCommonAncestor;
1125 c1 = c1->GetParentOrShadowHostNode()) {
1126 content1Ancestors.AppendElement(c1);
1127 }
1128 if (!c1 && aCommonAncestor) {
1129 // So, it turns out aCommonAncestor was not an ancestor of c1. Oops.
1130 // Never mind. We can continue as if aCommonAncestor was null.
1131 aCommonAncestor = nullptr;
1132 }
1133
1134 AutoTArray<nsINode*, 32> content2Ancestors;
1135 nsINode* c2;
1136 for (c2 = aContent2; c2 && c2 != aCommonAncestor;
1137 c2 = c2->GetParentOrShadowHostNode()) {
1138 content2Ancestors.AppendElement(c2);
1139 }
1140 if (!c2 && aCommonAncestor) {
1141 // So, it turns out aCommonAncestor was not an ancestor of c2.
1142 // We need to retry with no common ancestor hint.
1143 return DoCompareTreePosition(aContent1, aContent2, aIf1Ancestor,
1144 aIf2Ancestor, nullptr);
1145 }
1146
1147 int last1 = content1Ancestors.Length() - 1;
1148 int last2 = content2Ancestors.Length() - 1;
1149 nsINode* content1Ancestor = nullptr;
1150 nsINode* content2Ancestor = nullptr;
1151 while (last1 >= 0 && last2 >= 0 &&
1152 ((content1Ancestor = content1Ancestors.ElementAt(last1)) ==
1153 (content2Ancestor = content2Ancestors.ElementAt(last2)))) {
1154 last1--;
1155 last2--;
1156 }
1157
1158 if (last1 < 0) {
1159 if (last2 < 0) {
1160 NS_ASSERTION(aContent1 == aContent2, "internal error?");
1161 return 0;
1162 }
1163 // aContent1 is an ancestor of aContent2
1164 return aIf1Ancestor;
1165 }
1166
1167 if (last2 < 0) {
1168 // aContent2 is an ancestor of aContent1
1169 return aIf2Ancestor;
1170 }
1171
1172 // content1Ancestor != content2Ancestor, so they must be siblings with the
1173 // same parent
1174 nsINode* parent = content1Ancestor->GetParentOrShadowHostNode();
1175 #ifdef DEBUG
1176 // TODO: remove the uglyness, see bug 598468.
1177 NS_ASSERTION(gPreventAssertInCompareTreePosition || parent,
1178 "no common ancestor at all???");
1179 #endif // DEBUG
1180 if (!parent) { // different documents??
1181 return 0;
1182 }
1183
1184 const Maybe<uint32_t> index1 = parent->ComputeIndexOf(content1Ancestor);
1185 const Maybe<uint32_t> index2 = parent->ComputeIndexOf(content2Ancestor);
1186
1187 // None of the nodes are anonymous, just do a regular comparison.
1188 if (index1.isSome() && index2.isSome()) {
1189 return static_cast<int32_t>(static_cast<int64_t>(*index1) - *index2);
1190 }
1191
1192 // Otherwise handle pseudo-element and anonymous content ordering.
1193 //
1194 // ::marker -> ::before -> anon siblings -> regular siblings -> ::after
1195 auto PseudoIndex = [](const nsINode* aNode,
1196 const Maybe<uint32_t>& aNodeIndex) -> int32_t {
1197 if (aNodeIndex.isSome()) {
1198 return 1; // Not a pseudo.
1199 }
1200 if (aNode->IsContent()) {
1201 if (aNode->AsContent()->IsGeneratedContentContainerForMarker()) {
1202 return -2;
1203 }
1204 if (aNode->AsContent()->IsGeneratedContentContainerForBefore()) {
1205 return -1;
1206 }
1207 if (aNode->AsContent()->IsGeneratedContentContainerForAfter()) {
1208 return 2;
1209 }
1210 }
1211 return 0;
1212 };
1213
1214 return PseudoIndex(content1Ancestor, index1) -
1215 PseudoIndex(content2Ancestor, index2);
1216 }
1217
1218 // static
FillAncestors(nsIFrame * aFrame,nsIFrame * aStopAtAncestor,nsTArray<nsIFrame * > * aAncestors)1219 nsIFrame* nsLayoutUtils::FillAncestors(nsIFrame* aFrame,
1220 nsIFrame* aStopAtAncestor,
1221 nsTArray<nsIFrame*>* aAncestors) {
1222 while (aFrame && aFrame != aStopAtAncestor) {
1223 aAncestors->AppendElement(aFrame);
1224 aFrame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
1225 }
1226 return aFrame;
1227 }
1228
1229 // Return true if aFrame1 is after aFrame2
IsFrameAfter(nsIFrame * aFrame1,nsIFrame * aFrame2)1230 static bool IsFrameAfter(nsIFrame* aFrame1, nsIFrame* aFrame2) {
1231 nsIFrame* f = aFrame2;
1232 do {
1233 f = f->GetNextSibling();
1234 if (f == aFrame1) return true;
1235 } while (f);
1236 return false;
1237 }
1238
1239 // static
DoCompareTreePosition(nsIFrame * aFrame1,nsIFrame * aFrame2,int32_t aIf1Ancestor,int32_t aIf2Ancestor,nsIFrame * aCommonAncestor)1240 int32_t nsLayoutUtils::DoCompareTreePosition(nsIFrame* aFrame1,
1241 nsIFrame* aFrame2,
1242 int32_t aIf1Ancestor,
1243 int32_t aIf2Ancestor,
1244 nsIFrame* aCommonAncestor) {
1245 MOZ_ASSERT(aIf1Ancestor == -1 || aIf1Ancestor == 0 || aIf1Ancestor == 1);
1246 MOZ_ASSERT(aIf2Ancestor == -1 || aIf2Ancestor == 0 || aIf2Ancestor == 1);
1247 MOZ_ASSERT(aFrame1, "aFrame1 must not be null");
1248 MOZ_ASSERT(aFrame2, "aFrame2 must not be null");
1249
1250 AutoTArray<nsIFrame*, 20> frame2Ancestors;
1251 nsIFrame* nonCommonAncestor =
1252 FillAncestors(aFrame2, aCommonAncestor, &frame2Ancestors);
1253
1254 return DoCompareTreePosition(aFrame1, aFrame2, frame2Ancestors, aIf1Ancestor,
1255 aIf2Ancestor,
1256 nonCommonAncestor ? aCommonAncestor : nullptr);
1257 }
1258
1259 // static
DoCompareTreePosition(nsIFrame * aFrame1,nsIFrame * aFrame2,nsTArray<nsIFrame * > & aFrame2Ancestors,int32_t aIf1Ancestor,int32_t aIf2Ancestor,nsIFrame * aCommonAncestor)1260 int32_t nsLayoutUtils::DoCompareTreePosition(
1261 nsIFrame* aFrame1, nsIFrame* aFrame2, nsTArray<nsIFrame*>& aFrame2Ancestors,
1262 int32_t aIf1Ancestor, int32_t aIf2Ancestor, nsIFrame* aCommonAncestor) {
1263 MOZ_ASSERT(aIf1Ancestor == -1 || aIf1Ancestor == 0 || aIf1Ancestor == 1);
1264 MOZ_ASSERT(aIf2Ancestor == -1 || aIf2Ancestor == 0 || aIf2Ancestor == 1);
1265 MOZ_ASSERT(aFrame1, "aFrame1 must not be null");
1266 MOZ_ASSERT(aFrame2, "aFrame2 must not be null");
1267
1268 nsPresContext* presContext = aFrame1->PresContext();
1269 if (presContext != aFrame2->PresContext()) {
1270 NS_ERROR("no common ancestor at all, different documents");
1271 return 0;
1272 }
1273
1274 AutoTArray<nsIFrame*, 20> frame1Ancestors;
1275 if (aCommonAncestor &&
1276 !FillAncestors(aFrame1, aCommonAncestor, &frame1Ancestors)) {
1277 // We reached the root of the frame tree ... if aCommonAncestor was set,
1278 // it is wrong
1279 return DoCompareTreePosition(aFrame1, aFrame2, aIf1Ancestor, aIf2Ancestor,
1280 nullptr);
1281 }
1282
1283 int32_t last1 = int32_t(frame1Ancestors.Length()) - 1;
1284 int32_t last2 = int32_t(aFrame2Ancestors.Length()) - 1;
1285 while (last1 >= 0 && last2 >= 0 &&
1286 frame1Ancestors[last1] == aFrame2Ancestors[last2]) {
1287 last1--;
1288 last2--;
1289 }
1290
1291 if (last1 < 0) {
1292 if (last2 < 0) {
1293 NS_ASSERTION(aFrame1 == aFrame2, "internal error?");
1294 return 0;
1295 }
1296 // aFrame1 is an ancestor of aFrame2
1297 return aIf1Ancestor;
1298 }
1299
1300 if (last2 < 0) {
1301 // aFrame2 is an ancestor of aFrame1
1302 return aIf2Ancestor;
1303 }
1304
1305 nsIFrame* ancestor1 = frame1Ancestors[last1];
1306 nsIFrame* ancestor2 = aFrame2Ancestors[last2];
1307 // Now we should be able to walk sibling chains to find which one is first
1308 if (IsFrameAfter(ancestor2, ancestor1)) return -1;
1309 if (IsFrameAfter(ancestor1, ancestor2)) return 1;
1310 NS_WARNING("Frames were in different child lists???");
1311 return 0;
1312 }
1313
1314 // static
GetLastSibling(nsIFrame * aFrame)1315 nsIFrame* nsLayoutUtils::GetLastSibling(nsIFrame* aFrame) {
1316 if (!aFrame) {
1317 return nullptr;
1318 }
1319
1320 nsIFrame* next;
1321 while ((next = aFrame->GetNextSibling()) != nullptr) {
1322 aFrame = next;
1323 }
1324 return aFrame;
1325 }
1326
1327 // static
FindSiblingViewFor(nsView * aParentView,nsIFrame * aFrame)1328 nsView* nsLayoutUtils::FindSiblingViewFor(nsView* aParentView,
1329 nsIFrame* aFrame) {
1330 nsIFrame* parentViewFrame = aParentView->GetFrame();
1331 nsIContent* parentViewContent =
1332 parentViewFrame ? parentViewFrame->GetContent() : nullptr;
1333 for (nsView* insertBefore = aParentView->GetFirstChild(); insertBefore;
1334 insertBefore = insertBefore->GetNextSibling()) {
1335 nsIFrame* f = insertBefore->GetFrame();
1336 if (!f) {
1337 // this view could be some anonymous view attached to a meaningful parent
1338 for (nsView* searchView = insertBefore->GetParent(); searchView;
1339 searchView = searchView->GetParent()) {
1340 f = searchView->GetFrame();
1341 if (f) {
1342 break;
1343 }
1344 }
1345 NS_ASSERTION(f, "Can't find a frame anywhere!");
1346 }
1347 if (!f || !aFrame->GetContent() || !f->GetContent() ||
1348 CompareTreePosition(aFrame->GetContent(), f->GetContent(),
1349 parentViewContent) > 0) {
1350 // aFrame's content is after f's content (or we just don't know),
1351 // so put our view before f's view
1352 return insertBefore;
1353 }
1354 }
1355 return nullptr;
1356 }
1357
1358 // static
GetScrollableFrameFor(const nsIFrame * aScrolledFrame)1359 nsIScrollableFrame* nsLayoutUtils::GetScrollableFrameFor(
1360 const nsIFrame* aScrolledFrame) {
1361 nsIFrame* frame = aScrolledFrame->GetParent();
1362 nsIScrollableFrame* sf = do_QueryFrame(frame);
1363 return (sf && sf->GetScrolledFrame() == aScrolledFrame) ? sf : nullptr;
1364 }
1365
1366 /* static */
GetSideBitsForFixedPositionContent(const nsIFrame * aFixedPosFrame)1367 SideBits nsLayoutUtils::GetSideBitsForFixedPositionContent(
1368 const nsIFrame* aFixedPosFrame) {
1369 SideBits sides = SideBits::eNone;
1370 if (aFixedPosFrame) {
1371 const nsStylePosition* position = aFixedPosFrame->StylePosition();
1372 if (!position->mOffset.Get(eSideRight).IsAuto()) {
1373 sides |= SideBits::eRight;
1374 }
1375 if (!position->mOffset.Get(eSideLeft).IsAuto()) {
1376 sides |= SideBits::eLeft;
1377 }
1378 if (!position->mOffset.Get(eSideBottom).IsAuto()) {
1379 sides |= SideBits::eBottom;
1380 }
1381 if (!position->mOffset.Get(eSideTop).IsAuto()) {
1382 sides |= SideBits::eTop;
1383 }
1384 }
1385 return sides;
1386 }
1387
ScrollIdForRootScrollFrame(nsPresContext * aPresContext)1388 ScrollableLayerGuid::ViewID nsLayoutUtils::ScrollIdForRootScrollFrame(
1389 nsPresContext* aPresContext) {
1390 ViewID id = ScrollableLayerGuid::NULL_SCROLL_ID;
1391 if (nsIFrame* rootScrollFrame =
1392 aPresContext->PresShell()->GetRootScrollFrame()) {
1393 if (nsIContent* content = rootScrollFrame->GetContent()) {
1394 id = FindOrCreateIDFor(content);
1395 }
1396 }
1397 return id;
1398 }
1399
1400 // static
GetNearestScrollableFrameForDirection(nsIFrame * aFrame,ScrollDirections aDirections)1401 nsIScrollableFrame* nsLayoutUtils::GetNearestScrollableFrameForDirection(
1402 nsIFrame* aFrame, ScrollDirections aDirections) {
1403 NS_ASSERTION(
1404 aFrame, "GetNearestScrollableFrameForDirection expects a non-null frame");
1405 // FIXME Bug 1714720 : This nearest scroll target is not going to work over
1406 // process boundaries, in such cases we need to hand over in APZ side.
1407 for (nsIFrame* f = aFrame; f;
1408 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
1409 nsIScrollableFrame* scrollableFrame = do_QueryFrame(f);
1410 if (scrollableFrame) {
1411 ScrollDirections directions =
1412 scrollableFrame->GetAvailableScrollingDirectionsForUserInputEvents();
1413 if (aDirections.contains(ScrollDirection::eVertical)) {
1414 if (directions.contains(ScrollDirection::eVertical)) {
1415 return scrollableFrame;
1416 }
1417 }
1418 if (aDirections.contains(ScrollDirection::eHorizontal)) {
1419 if (directions.contains(ScrollDirection::eHorizontal)) {
1420 return scrollableFrame;
1421 }
1422 }
1423 }
1424 }
1425 return nullptr;
1426 }
1427
GetNearestScrollableOrOverflowClipFrame(nsIFrame * aFrame,uint32_t aFlags,const std::function<bool (const nsIFrame * aCurrentFrame)> & aClipFrameCheck=nullptr)1428 static nsIFrame* GetNearestScrollableOrOverflowClipFrame(
1429 nsIFrame* aFrame, uint32_t aFlags,
1430 const std::function<bool(const nsIFrame* aCurrentFrame)>& aClipFrameCheck =
1431 nullptr) {
1432 MOZ_ASSERT(
1433 aFrame,
1434 "GetNearestScrollableOrOverflowClipFrame expects a non-null frame");
1435
1436 auto GetNextFrame = [aFlags](const nsIFrame* aFrame) -> nsIFrame* {
1437 if (aFlags & nsLayoutUtils::SCROLLABLE_FOLLOW_OOF_TO_PLACEHOLDER) {
1438 return (aFlags & nsLayoutUtils::SCROLLABLE_SAME_DOC)
1439 ? nsLayoutUtils::GetParentOrPlaceholderFor(aFrame)
1440 : nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(aFrame);
1441 }
1442 return (aFlags & nsLayoutUtils::SCROLLABLE_SAME_DOC)
1443 ? aFrame->GetParent()
1444 : nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame);
1445 };
1446
1447 for (nsIFrame* f = aFrame; f; f = GetNextFrame(f)) {
1448 if (aClipFrameCheck && aClipFrameCheck(f)) {
1449 return f;
1450 }
1451
1452 if ((aFlags & nsLayoutUtils::SCROLLABLE_STOP_AT_PAGE) && f->IsPageFrame()) {
1453 break;
1454 }
1455 if (nsIScrollableFrame* scrollableFrame = do_QueryFrame(f)) {
1456 if (aFlags & nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE) {
1457 if (scrollableFrame->WantAsyncScroll()) {
1458 return f;
1459 }
1460 } else {
1461 ScrollStyles ss = scrollableFrame->GetScrollStyles();
1462 if ((aFlags & nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN) ||
1463 ss.mVertical != StyleOverflow::Hidden ||
1464 ss.mHorizontal != StyleOverflow::Hidden) {
1465 return f;
1466 }
1467 }
1468 if (aFlags & nsLayoutUtils::SCROLLABLE_ALWAYS_MATCH_ROOT) {
1469 PresShell* presShell = f->PresShell();
1470 if (presShell->GetRootScrollFrame() == f && presShell->GetDocument() &&
1471 presShell->GetDocument()->IsRootDisplayDocument()) {
1472 return f;
1473 }
1474 }
1475 }
1476 if ((aFlags & nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT) &&
1477 f->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
1478 nsLayoutUtils::IsReallyFixedPos(f)) {
1479 return f->PresShell()->GetRootScrollFrame();
1480 }
1481 }
1482 return nullptr;
1483 }
1484
1485 // static
GetNearestScrollableFrame(nsIFrame * aFrame,uint32_t aFlags)1486 nsIScrollableFrame* nsLayoutUtils::GetNearestScrollableFrame(nsIFrame* aFrame,
1487 uint32_t aFlags) {
1488 nsIFrame* found = GetNearestScrollableOrOverflowClipFrame(aFrame, aFlags);
1489 if (!found) {
1490 return nullptr;
1491 }
1492
1493 return do_QueryFrame(found);
1494 }
1495
1496 // static
GetNearestOverflowClipFrame(nsIFrame * aFrame)1497 nsIFrame* nsLayoutUtils::GetNearestOverflowClipFrame(nsIFrame* aFrame) {
1498 return GetNearestScrollableOrOverflowClipFrame(
1499 aFrame, SCROLLABLE_SAME_DOC | SCROLLABLE_INCLUDE_HIDDEN,
1500 [](const nsIFrame* currentFrame) -> bool {
1501 // In cases of SVG Inner/Outer frames it basically clips descendants
1502 // unless overflow: visible is explicitly specified.
1503 LayoutFrameType type = currentFrame->Type();
1504 return ((type == LayoutFrameType::SVGOuterSVG ||
1505 type == LayoutFrameType::SVGInnerSVG) &&
1506 (currentFrame->StyleDisplay()->mOverflowX !=
1507 StyleOverflow::Visible &&
1508 currentFrame->StyleDisplay()->mOverflowY !=
1509 StyleOverflow::Visible));
1510 });
1511 }
1512
1513 // static
GetScrolledRect(nsIFrame * aScrolledFrame,const nsRect & aScrolledFrameOverflowArea,const nsSize & aScrollPortSize,StyleDirection aDirection)1514 nsRect nsLayoutUtils::GetScrolledRect(nsIFrame* aScrolledFrame,
1515 const nsRect& aScrolledFrameOverflowArea,
1516 const nsSize& aScrollPortSize,
1517 StyleDirection aDirection) {
1518 WritingMode wm = aScrolledFrame->GetWritingMode();
1519 // Potentially override the frame's direction to use the direction found
1520 // by ScrollFrameHelper::GetScrolledFrameDir()
1521 wm.SetDirectionFromBidiLevel(aDirection == StyleDirection::Rtl
1522 ? mozilla::intl::BidiEmbeddingLevel::RTL()
1523 : mozilla::intl::BidiEmbeddingLevel::LTR());
1524
1525 nscoord x1 = aScrolledFrameOverflowArea.x,
1526 x2 = aScrolledFrameOverflowArea.XMost(),
1527 y1 = aScrolledFrameOverflowArea.y,
1528 y2 = aScrolledFrameOverflowArea.YMost();
1529
1530 const bool isHorizontalWM = !wm.IsVertical();
1531 const bool isVerticalWM = wm.IsVertical();
1532 bool isInlineFlowFromTopOrLeft = !wm.IsInlineReversed();
1533 bool isBlockFlowFromTopOrLeft = isHorizontalWM || wm.IsVerticalLR();
1534
1535 if (aScrolledFrame->IsFlexContainerFrame()) {
1536 // In a flex container, the children flow (and overflow) along the flex
1537 // container's main axis and cross axis. These are analogous to the
1538 // inline/block axes, and by default they correspond exactly to those axes;
1539 // but the flex container's CSS (e.g. flex-direction: column-reverse) may
1540 // have swapped and/or reversed them, and we need to account for that here.
1541 FlexboxAxisInfo info(aScrolledFrame);
1542 if (info.mIsRowOriented) {
1543 // The flex container's inline axis is the main axis.
1544 isInlineFlowFromTopOrLeft =
1545 isInlineFlowFromTopOrLeft == !info.mIsMainAxisReversed;
1546 isBlockFlowFromTopOrLeft =
1547 isBlockFlowFromTopOrLeft == !info.mIsCrossAxisReversed;
1548 } else {
1549 // The flex container's block axis is the main axis.
1550 isBlockFlowFromTopOrLeft =
1551 isBlockFlowFromTopOrLeft == !info.mIsMainAxisReversed;
1552 isInlineFlowFromTopOrLeft =
1553 isInlineFlowFromTopOrLeft == !info.mIsCrossAxisReversed;
1554 }
1555 }
1556
1557 // Clamp the horizontal start-edge (x1 or x2, depending whether the logical
1558 // axis that corresponds to horizontal progresses from left-to-right or
1559 // right-to-left).
1560 if ((isHorizontalWM && isInlineFlowFromTopOrLeft) ||
1561 (isVerticalWM && isBlockFlowFromTopOrLeft)) {
1562 if (x1 < 0) {
1563 x1 = 0;
1564 }
1565 } else {
1566 if (x2 > aScrollPortSize.width) {
1567 x2 = aScrollPortSize.width;
1568 }
1569 // When the scrolled frame chooses a size larger than its available width
1570 // (because its padding alone is larger than the available width), we need
1571 // to keep the start-edge of the scroll frame anchored to the start-edge of
1572 // the scrollport.
1573 // When the scrolled frame is RTL, this means moving it in our left-based
1574 // coordinate system, so we need to compensate for its extra width here by
1575 // effectively repositioning the frame.
1576 nscoord extraWidth =
1577 std::max(0, aScrolledFrame->GetSize().width - aScrollPortSize.width);
1578 x2 += extraWidth;
1579 }
1580
1581 // Similarly, clamp the vertical start-edge (y1 or y2, depending whether the
1582 // logical axis that corresponds to vertical progresses from top-to-bottom or
1583 // buttom-to-top).
1584 if ((isHorizontalWM && isBlockFlowFromTopOrLeft) ||
1585 (isVerticalWM && isInlineFlowFromTopOrLeft)) {
1586 if (y1 < 0) {
1587 y1 = 0;
1588 }
1589 } else {
1590 if (y2 > aScrollPortSize.height) {
1591 y2 = aScrollPortSize.height;
1592 }
1593 nscoord extraHeight =
1594 std::max(0, aScrolledFrame->GetSize().height - aScrollPortSize.height);
1595 y2 += extraHeight;
1596 }
1597
1598 return nsRect(x1, y1, x2 - x1, y2 - y1);
1599 }
1600
1601 // static
HasPseudoStyle(nsIContent * aContent,ComputedStyle * aComputedStyle,PseudoStyleType aPseudoElement,nsPresContext * aPresContext)1602 bool nsLayoutUtils::HasPseudoStyle(nsIContent* aContent,
1603 ComputedStyle* aComputedStyle,
1604 PseudoStyleType aPseudoElement,
1605 nsPresContext* aPresContext) {
1606 MOZ_ASSERT(aPresContext, "Must have a prescontext");
1607
1608 RefPtr<ComputedStyle> pseudoContext;
1609 if (aContent) {
1610 pseudoContext = aPresContext->StyleSet()->ProbePseudoElementStyle(
1611 *aContent->AsElement(), aPseudoElement, aComputedStyle);
1612 }
1613 return pseudoContext != nullptr;
1614 }
1615
GetDOMEventCoordinatesRelativeTo(Event * aDOMEvent,nsIFrame * aFrame)1616 nsPoint nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(Event* aDOMEvent,
1617 nsIFrame* aFrame) {
1618 if (!aDOMEvent) return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1619 WidgetEvent* event = aDOMEvent->WidgetEventPtr();
1620 if (!event) return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1621 return GetEventCoordinatesRelativeTo(event, RelativeTo{aFrame});
1622 }
1623
IsValidCoordinateTypeEvent(const WidgetEvent * aEvent)1624 static bool IsValidCoordinateTypeEvent(const WidgetEvent* aEvent) {
1625 if (!aEvent) {
1626 return false;
1627 }
1628 return aEvent->mClass == eMouseEventClass ||
1629 aEvent->mClass == eMouseScrollEventClass ||
1630 aEvent->mClass == eWheelEventClass ||
1631 aEvent->mClass == eDragEventClass ||
1632 aEvent->mClass == eSimpleGestureEventClass ||
1633 aEvent->mClass == ePointerEventClass ||
1634 aEvent->mClass == eGestureNotifyEventClass ||
1635 aEvent->mClass == eTouchEventClass ||
1636 aEvent->mClass == eQueryContentEventClass;
1637 }
1638
GetEventCoordinatesRelativeTo(const WidgetEvent * aEvent,RelativeTo aFrame)1639 nsPoint nsLayoutUtils::GetEventCoordinatesRelativeTo(const WidgetEvent* aEvent,
1640 RelativeTo aFrame) {
1641 if (!IsValidCoordinateTypeEvent(aEvent)) {
1642 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1643 }
1644
1645 return GetEventCoordinatesRelativeTo(aEvent, aEvent->AsGUIEvent()->mRefPoint,
1646 aFrame);
1647 }
1648
GetEventCoordinatesRelativeTo(const WidgetEvent * aEvent,const LayoutDeviceIntPoint & aPoint,RelativeTo aFrame)1649 nsPoint nsLayoutUtils::GetEventCoordinatesRelativeTo(
1650 const WidgetEvent* aEvent, const LayoutDeviceIntPoint& aPoint,
1651 RelativeTo aFrame) {
1652 if (!aFrame.mFrame) {
1653 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1654 }
1655
1656 nsIWidget* widget = aEvent->AsGUIEvent()->mWidget;
1657 if (!widget) {
1658 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1659 }
1660
1661 return GetEventCoordinatesRelativeTo(widget, aPoint, aFrame);
1662 }
1663
GetEventCoordinatesRelativeTo(nsIWidget * aWidget,const LayoutDeviceIntPoint & aPoint,RelativeTo aFrame)1664 nsPoint GetEventCoordinatesRelativeTo(nsIWidget* aWidget,
1665 const LayoutDeviceIntPoint& aPoint,
1666 RelativeTo aFrame) {
1667 const nsIFrame* frame = aFrame.mFrame;
1668 if (!frame || !aWidget) {
1669 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1670 }
1671
1672 nsView* view = frame->GetView();
1673 if (view) {
1674 nsIWidget* frameWidget = view->GetWidget();
1675 if (frameWidget && frameWidget == aWidget) {
1676 // Special case this cause it happens a lot.
1677 // This also fixes bug 664707, events in the extra-special case of select
1678 // dropdown popups that are transformed.
1679 nsPresContext* presContext = frame->PresContext();
1680 nsPoint pt(presContext->DevPixelsToAppUnits(aPoint.x),
1681 presContext->DevPixelsToAppUnits(aPoint.y));
1682 return pt - view->ViewToWidgetOffset();
1683 }
1684 }
1685
1686 /* If we walk up the frame tree and discover that any of the frames are
1687 * transformed, we need to do extra work to convert from the global
1688 * space to the local space.
1689 */
1690 const nsIFrame* rootFrame = frame;
1691 bool transformFound = false;
1692 for (const nsIFrame* f = frame; f;
1693 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
1694 if (f->IsTransformed() || ViewportUtils::IsZoomedContentRoot(f)) {
1695 transformFound = true;
1696 }
1697
1698 rootFrame = f;
1699 }
1700
1701 nsView* rootView = rootFrame->GetView();
1702 if (!rootView) {
1703 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1704 }
1705
1706 nsPoint widgetToView = nsLayoutUtils::TranslateWidgetToView(
1707 rootFrame->PresContext(), aWidget, aPoint, rootView);
1708
1709 if (widgetToView == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
1710 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
1711 }
1712
1713 // Convert from root document app units to app units of the document aFrame
1714 // is in.
1715 int32_t rootAPD = rootFrame->PresContext()->AppUnitsPerDevPixel();
1716 int32_t localAPD = frame->PresContext()->AppUnitsPerDevPixel();
1717 widgetToView = widgetToView.ScaleToOtherAppUnits(rootAPD, localAPD);
1718
1719 /* If we encountered a transform, we can't do simple arithmetic to figure
1720 * out how to convert back to aFrame's coordinates and must use the CTM.
1721 */
1722 if (transformFound || SVGUtils::IsInSVGTextSubtree(frame)) {
1723 return nsLayoutUtils::TransformRootPointToFrame(ViewportType::Visual,
1724 aFrame, widgetToView);
1725 }
1726
1727 /* Otherwise, all coordinate systems are translations of one another,
1728 * so we can just subtract out the difference.
1729 */
1730 return widgetToView - frame->GetOffsetToCrossDoc(rootFrame);
1731 }
1732
GetEventCoordinatesRelativeTo(nsIWidget * aWidget,const LayoutDeviceIntPoint & aPoint,RelativeTo aFrame)1733 nsPoint nsLayoutUtils::GetEventCoordinatesRelativeTo(
1734 nsIWidget* aWidget, const LayoutDeviceIntPoint& aPoint, RelativeTo aFrame) {
1735 nsPoint result = ::GetEventCoordinatesRelativeTo(aWidget, aPoint, aFrame);
1736 if (aFrame.mViewportType == ViewportType::Layout && aFrame.mFrame &&
1737 aFrame.mFrame->Type() == LayoutFrameType::Viewport &&
1738 aFrame.mFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
1739 result = ViewportUtils::VisualToLayout(result, aFrame.mFrame->PresShell());
1740 }
1741 return result;
1742 }
1743
GetPopupFrameForEventCoordinates(nsPresContext * aRootPresContext,const WidgetEvent * aEvent)1744 nsIFrame* nsLayoutUtils::GetPopupFrameForEventCoordinates(
1745 nsPresContext* aRootPresContext, const WidgetEvent* aEvent) {
1746 if (!IsValidCoordinateTypeEvent(aEvent)) {
1747 return nullptr;
1748 }
1749
1750 const auto* guiEvent = aEvent->AsGUIEvent();
1751 return GetPopupFrameForPoint(aRootPresContext, guiEvent->mWidget,
1752 guiEvent->mRefPoint);
1753 }
1754
GetPopupFrameForPoint(nsPresContext * aRootPresContext,nsIWidget * aWidget,const mozilla::LayoutDeviceIntPoint & aPoint)1755 nsIFrame* nsLayoutUtils::GetPopupFrameForPoint(
1756 nsPresContext* aRootPresContext, nsIWidget* aWidget,
1757 const mozilla::LayoutDeviceIntPoint& aPoint) {
1758 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
1759 if (!pm) {
1760 return nullptr;
1761 }
1762 nsTArray<nsIFrame*> popups;
1763 pm->GetVisiblePopups(popups);
1764 // Search from top to bottom
1765 for (nsIFrame* popup : popups) {
1766 if (popup->PresContext()->GetRootPresContext() == aRootPresContext &&
1767 popup->ScrollableOverflowRect().Contains(GetEventCoordinatesRelativeTo(
1768 aWidget, aPoint, RelativeTo{popup}))) {
1769 return popup;
1770 }
1771 }
1772 return nullptr;
1773 }
1774
GetContainerAndOffsetAtEvent(PresShell * aPresShell,const WidgetEvent * aEvent,nsIContent ** aContainer,int32_t * aOffset)1775 void nsLayoutUtils::GetContainerAndOffsetAtEvent(PresShell* aPresShell,
1776 const WidgetEvent* aEvent,
1777 nsIContent** aContainer,
1778 int32_t* aOffset) {
1779 MOZ_ASSERT(aContainer || aOffset);
1780
1781 if (aContainer) {
1782 *aContainer = nullptr;
1783 }
1784 if (aOffset) {
1785 *aOffset = 0;
1786 }
1787
1788 if (!aPresShell) {
1789 return;
1790 }
1791
1792 aPresShell->FlushPendingNotifications(FlushType::Layout);
1793
1794 RefPtr<nsPresContext> presContext = aPresShell->GetPresContext();
1795 if (!presContext) {
1796 return;
1797 }
1798
1799 nsIFrame* targetFrame = presContext->EventStateManager()->GetEventTarget();
1800 if (!targetFrame) {
1801 return;
1802 }
1803
1804 WidgetEvent* openingEvent = nullptr;
1805 // For popupshowing events, redirect via the original mouse event
1806 // that triggered the popup to open.
1807 if (aEvent->mMessage == eXULPopupShowing) {
1808 if (auto* pm = nsXULPopupManager::GetInstance()) {
1809 if (Event* openingPopupEvent = pm->GetOpeningPopupEvent()) {
1810 openingEvent = openingPopupEvent->WidgetEventPtr();
1811 }
1812 }
1813 }
1814
1815 nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo(
1816 openingEvent ? openingEvent : aEvent, RelativeTo{targetFrame});
1817
1818 if (aContainer) {
1819 // TODO: This result may be useful to change to Selection. However, this
1820 // may return improper node (e.g., native anonymous node) for the
1821 // Selection. Perhaps, this should take Selection optionally and
1822 // if it's specified, needs to check if it's proper for the
1823 // Selection.
1824 nsCOMPtr<nsIContent> container =
1825 targetFrame->GetContentOffsetsFromPoint(point).content;
1826 if (container && (!container->ChromeOnlyAccess() ||
1827 nsContentUtils::CanAccessNativeAnon())) {
1828 container.forget(aContainer);
1829 }
1830 }
1831 if (aOffset) {
1832 *aOffset = targetFrame->GetContentOffsetsFromPoint(point).offset;
1833 }
1834 }
1835
ConstrainToCoordValues(float & aStart,float & aSize)1836 void nsLayoutUtils::ConstrainToCoordValues(float& aStart, float& aSize) {
1837 MOZ_ASSERT(aSize >= 0);
1838
1839 // Here we try to make sure that the resulting nsRect will continue to cover
1840 // as much of the area that was covered by the original gfx Rect as possible.
1841
1842 // We clamp the bounds of the rect to {nscoord_MIN,nscoord_MAX} since
1843 // nsRect::X/Y() and nsRect::XMost/YMost() can't return values outwith this
1844 // range:
1845 float end = aStart + aSize;
1846 aStart = clamped(aStart, float(nscoord_MIN), float(nscoord_MAX));
1847 end = clamped(end, float(nscoord_MIN), float(nscoord_MAX));
1848
1849 aSize = end - aStart;
1850
1851 // We must also clamp aSize to {0,nscoord_MAX} since nsRect::Width/Height()
1852 // can't return a value greater than nscoord_MAX. If aSize is greater than
1853 // nscoord_MAX then we reduce it to nscoord_MAX while keeping the rect
1854 // centered:
1855 if (MOZ_UNLIKELY(std::isnan(aSize))) {
1856 // Can happen if aStart is -inf and aSize is +inf for example.
1857 aStart = 0.0f;
1858 aSize = float(nscoord_MAX);
1859 } else if (aSize > float(nscoord_MAX)) {
1860 float excess = aSize - float(nscoord_MAX);
1861 excess /= 2;
1862 aStart += excess;
1863 aSize = float(nscoord_MAX);
1864 }
1865 }
1866
1867 /**
1868 * Given a gfxFloat, constrains its value to be between nscoord_MIN and
1869 * nscoord_MAX.
1870 *
1871 * @param aVal The value to constrain (in/out)
1872 */
ConstrainToCoordValues(gfxFloat & aVal)1873 static void ConstrainToCoordValues(gfxFloat& aVal) {
1874 if (aVal <= nscoord_MIN)
1875 aVal = nscoord_MIN;
1876 else if (aVal >= nscoord_MAX)
1877 aVal = nscoord_MAX;
1878 }
1879
ConstrainToCoordValues(gfxFloat & aStart,gfxFloat & aSize)1880 void nsLayoutUtils::ConstrainToCoordValues(gfxFloat& aStart, gfxFloat& aSize) {
1881 gfxFloat max = aStart + aSize;
1882
1883 // Clamp the end points to within nscoord range
1884 ::ConstrainToCoordValues(aStart);
1885 ::ConstrainToCoordValues(max);
1886
1887 aSize = max - aStart;
1888 // If the width if still greater than the max nscoord, then bring both
1889 // endpoints in by the same amount until it fits.
1890 if (MOZ_UNLIKELY(std::isnan(aSize))) {
1891 // Can happen if aStart is -inf and aSize is +inf for example.
1892 aStart = 0.0f;
1893 aSize = nscoord_MAX;
1894 } else if (aSize > nscoord_MAX) {
1895 gfxFloat excess = aSize - nscoord_MAX;
1896 excess /= 2;
1897
1898 aStart += excess;
1899 aSize = nscoord_MAX;
1900 } else if (aSize < nscoord_MIN) {
1901 gfxFloat excess = aSize - nscoord_MIN;
1902 excess /= 2;
1903
1904 aStart -= excess;
1905 aSize = nscoord_MIN;
1906 }
1907 }
1908
RoundedRectIntersectRect(const nsRect & aRoundedRect,const nscoord aRadii[8],const nsRect & aContainedRect)1909 nsRegion nsLayoutUtils::RoundedRectIntersectRect(const nsRect& aRoundedRect,
1910 const nscoord aRadii[8],
1911 const nsRect& aContainedRect) {
1912 // rectFullHeight and rectFullWidth together will approximately contain
1913 // the total area of the frame minus the rounded corners.
1914 nsRect rectFullHeight = aRoundedRect;
1915 nscoord xDiff = std::max(aRadii[eCornerTopLeftX], aRadii[eCornerBottomLeftX]);
1916 rectFullHeight.x += xDiff;
1917 rectFullHeight.width -=
1918 std::max(aRadii[eCornerTopRightX], aRadii[eCornerBottomRightX]) + xDiff;
1919 nsRect r1;
1920 r1.IntersectRect(rectFullHeight, aContainedRect);
1921
1922 nsRect rectFullWidth = aRoundedRect;
1923 nscoord yDiff = std::max(aRadii[eCornerTopLeftY], aRadii[eCornerTopRightY]);
1924 rectFullWidth.y += yDiff;
1925 rectFullWidth.height -=
1926 std::max(aRadii[eCornerBottomLeftY], aRadii[eCornerBottomRightY]) + yDiff;
1927 nsRect r2;
1928 r2.IntersectRect(rectFullWidth, aContainedRect);
1929
1930 nsRegion result;
1931 result.Or(r1, r2);
1932 return result;
1933 }
1934
RoundedRectIntersectIntRect(const nsIntRect & aRoundedRect,const RectCornerRadii & aCornerRadii,const nsIntRect & aContainedRect)1935 nsIntRegion nsLayoutUtils::RoundedRectIntersectIntRect(
1936 const nsIntRect& aRoundedRect, const RectCornerRadii& aCornerRadii,
1937 const nsIntRect& aContainedRect) {
1938 // rectFullHeight and rectFullWidth together will approximately contain
1939 // the total area of the frame minus the rounded corners.
1940 nsIntRect rectFullHeight = aRoundedRect;
1941 uint32_t xDiff =
1942 std::max(aCornerRadii.TopLeft().width, aCornerRadii.BottomLeft().width);
1943 rectFullHeight.x += xDiff;
1944 rectFullHeight.width -= std::max(aCornerRadii.TopRight().width,
1945 aCornerRadii.BottomRight().width) +
1946 xDiff;
1947 nsIntRect r1;
1948 r1.IntersectRect(rectFullHeight, aContainedRect);
1949
1950 nsIntRect rectFullWidth = aRoundedRect;
1951 uint32_t yDiff =
1952 std::max(aCornerRadii.TopLeft().height, aCornerRadii.TopRight().height);
1953 rectFullWidth.y += yDiff;
1954 rectFullWidth.height -= std::max(aCornerRadii.BottomLeft().height,
1955 aCornerRadii.BottomRight().height) +
1956 yDiff;
1957 nsIntRect r2;
1958 r2.IntersectRect(rectFullWidth, aContainedRect);
1959
1960 nsIntRegion result;
1961 result.Or(r1, r2);
1962 return result;
1963 }
1964
1965 // Helper for RoundedRectIntersectsRect.
CheckCorner(nscoord aXOffset,nscoord aYOffset,nscoord aXRadius,nscoord aYRadius)1966 static bool CheckCorner(nscoord aXOffset, nscoord aYOffset, nscoord aXRadius,
1967 nscoord aYRadius) {
1968 MOZ_ASSERT(aXOffset > 0 && aYOffset > 0,
1969 "must not pass nonpositives to CheckCorner");
1970 MOZ_ASSERT(aXRadius >= 0 && aYRadius >= 0,
1971 "must not pass negatives to CheckCorner");
1972
1973 // Avoid floating point math unless we're either (1) within the
1974 // quarter-ellipse area at the rounded corner or (2) outside the
1975 // rounding.
1976 if (aXOffset >= aXRadius || aYOffset >= aYRadius) return true;
1977
1978 // Convert coordinates to a unit circle with (0,0) as the center of
1979 // curvature, and see if we're inside the circle or outside.
1980 float scaledX = float(aXRadius - aXOffset) / float(aXRadius);
1981 float scaledY = float(aYRadius - aYOffset) / float(aYRadius);
1982 return scaledX * scaledX + scaledY * scaledY < 1.0f;
1983 }
1984
RoundedRectIntersectsRect(const nsRect & aRoundedRect,const nscoord aRadii[8],const nsRect & aTestRect)1985 bool nsLayoutUtils::RoundedRectIntersectsRect(const nsRect& aRoundedRect,
1986 const nscoord aRadii[8],
1987 const nsRect& aTestRect) {
1988 if (!aTestRect.Intersects(aRoundedRect)) return false;
1989
1990 // distances from this edge of aRoundedRect to opposite edge of aTestRect,
1991 // which we know are positive due to the Intersects check above.
1992 nsMargin insets;
1993 insets.top = aTestRect.YMost() - aRoundedRect.y;
1994 insets.right = aRoundedRect.XMost() - aTestRect.x;
1995 insets.bottom = aRoundedRect.YMost() - aTestRect.y;
1996 insets.left = aTestRect.XMost() - aRoundedRect.x;
1997
1998 // Check whether the bottom-right corner of aTestRect is inside the
1999 // top left corner of aBounds when rounded by aRadii, etc. If any
2000 // corner is not, then fail; otherwise succeed.
2001 return CheckCorner(insets.left, insets.top, aRadii[eCornerTopLeftX],
2002 aRadii[eCornerTopLeftY]) &&
2003 CheckCorner(insets.right, insets.top, aRadii[eCornerTopRightX],
2004 aRadii[eCornerTopRightY]) &&
2005 CheckCorner(insets.right, insets.bottom, aRadii[eCornerBottomRightX],
2006 aRadii[eCornerBottomRightY]) &&
2007 CheckCorner(insets.left, insets.bottom, aRadii[eCornerBottomLeftX],
2008 aRadii[eCornerBottomLeftY]);
2009 }
2010
MatrixTransformRect(const nsRect & aBounds,const Matrix4x4 & aMatrix,float aFactor)2011 nsRect nsLayoutUtils::MatrixTransformRect(const nsRect& aBounds,
2012 const Matrix4x4& aMatrix,
2013 float aFactor) {
2014 RectDouble image =
2015 RectDouble(NSAppUnitsToDoublePixels(aBounds.x, aFactor),
2016 NSAppUnitsToDoublePixels(aBounds.y, aFactor),
2017 NSAppUnitsToDoublePixels(aBounds.width, aFactor),
2018 NSAppUnitsToDoublePixels(aBounds.height, aFactor));
2019
2020 RectDouble maxBounds = RectDouble(
2021 double(nscoord_MIN) / aFactor * 0.5, double(nscoord_MIN) / aFactor * 0.5,
2022 double(nscoord_MAX) / aFactor, double(nscoord_MAX) / aFactor);
2023
2024 image = aMatrix.TransformAndClipBounds(image, maxBounds);
2025
2026 return RoundGfxRectToAppRect(ThebesRect(image), aFactor);
2027 }
2028
MatrixTransformRect(const nsRect & aBounds,const Matrix4x4Flagged & aMatrix,float aFactor)2029 nsRect nsLayoutUtils::MatrixTransformRect(const nsRect& aBounds,
2030 const Matrix4x4Flagged& aMatrix,
2031 float aFactor) {
2032 RectDouble image =
2033 RectDouble(NSAppUnitsToDoublePixels(aBounds.x, aFactor),
2034 NSAppUnitsToDoublePixels(aBounds.y, aFactor),
2035 NSAppUnitsToDoublePixels(aBounds.width, aFactor),
2036 NSAppUnitsToDoublePixels(aBounds.height, aFactor));
2037
2038 RectDouble maxBounds = RectDouble(
2039 double(nscoord_MIN) / aFactor * 0.5, double(nscoord_MIN) / aFactor * 0.5,
2040 double(nscoord_MAX) / aFactor, double(nscoord_MAX) / aFactor);
2041
2042 image = aMatrix.TransformAndClipBounds(image, maxBounds);
2043
2044 return RoundGfxRectToAppRect(ThebesRect(image), aFactor);
2045 }
2046
MatrixTransformPoint(const nsPoint & aPoint,const Matrix4x4 & aMatrix,float aFactor)2047 nsPoint nsLayoutUtils::MatrixTransformPoint(const nsPoint& aPoint,
2048 const Matrix4x4& aMatrix,
2049 float aFactor) {
2050 gfxPoint image = gfxPoint(NSAppUnitsToFloatPixels(aPoint.x, aFactor),
2051 NSAppUnitsToFloatPixels(aPoint.y, aFactor));
2052 image = aMatrix.TransformPoint(image);
2053 return nsPoint(NSFloatPixelsToAppUnits(float(image.x), aFactor),
2054 NSFloatPixelsToAppUnits(float(image.y), aFactor));
2055 }
2056
PostTranslate(Matrix4x4 & aTransform,const nsPoint & aOrigin,float aAppUnitsPerPixel,bool aRounded)2057 void nsLayoutUtils::PostTranslate(Matrix4x4& aTransform, const nsPoint& aOrigin,
2058 float aAppUnitsPerPixel, bool aRounded) {
2059 Point3D gfxOrigin =
2060 Point3D(NSAppUnitsToFloatPixels(aOrigin.x, aAppUnitsPerPixel),
2061 NSAppUnitsToFloatPixels(aOrigin.y, aAppUnitsPerPixel), 0.0f);
2062 if (aRounded) {
2063 gfxOrigin.x = NS_round(gfxOrigin.x);
2064 gfxOrigin.y = NS_round(gfxOrigin.y);
2065 }
2066 aTransform.PostTranslate(gfxOrigin);
2067 }
2068
ShouldSnapToGrid(const nsIFrame * aFrame)2069 bool nsLayoutUtils::ShouldSnapToGrid(const nsIFrame* aFrame) {
2070 return !aFrame || !aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
2071 aFrame->IsSVGOuterSVGAnonChildFrame();
2072 }
2073
GetTransformToAncestor(RelativeTo aFrame,RelativeTo aAncestor,uint32_t aFlags,nsIFrame ** aOutAncestor)2074 Matrix4x4Flagged nsLayoutUtils::GetTransformToAncestor(
2075 RelativeTo aFrame, RelativeTo aAncestor, uint32_t aFlags,
2076 nsIFrame** aOutAncestor) {
2077 nsIFrame* parent;
2078 Matrix4x4Flagged ctm;
2079 // Make sure we don't get an invalid combination of source and destination
2080 // RelativeTo values.
2081 MOZ_ASSERT(!(aFrame.mViewportType == ViewportType::Visual &&
2082 aAncestor.mViewportType == ViewportType::Layout));
2083 if (aFrame == aAncestor) {
2084 return ctm;
2085 }
2086 ctm = aFrame.mFrame->GetTransformMatrix(aFrame.mViewportType, aAncestor,
2087 &parent, aFlags);
2088 if (!aFrame.mFrame->Combines3DTransformWithAncestors()) {
2089 ctm.ProjectTo2D();
2090 }
2091 while (parent && parent != aAncestor.mFrame &&
2092 (!(aFlags & nsIFrame::STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT) ||
2093 (!parent->IsStackingContext() &&
2094 !DisplayPortUtils::FrameHasDisplayPort(parent)))) {
2095 nsIFrame* cur = parent;
2096 ctm = ctm * cur->GetTransformMatrix(aFrame.mViewportType, aAncestor,
2097 &parent, aFlags);
2098 if (!cur->Combines3DTransformWithAncestors()) {
2099 ctm.ProjectTo2D();
2100 }
2101 }
2102 if (aOutAncestor) {
2103 *aOutAncestor = parent;
2104 }
2105 return ctm;
2106 }
2107
GetTransformToAncestorScale(const nsIFrame * aFrame)2108 gfxSize nsLayoutUtils::GetTransformToAncestorScale(const nsIFrame* aFrame) {
2109 Matrix4x4Flagged transform = GetTransformToAncestor(
2110 RelativeTo{aFrame},
2111 RelativeTo{nsLayoutUtils::GetDisplayRootFrame(aFrame)});
2112 Matrix transform2D;
2113 if (transform.CanDraw2D(&transform2D)) {
2114 return ThebesMatrix(transform2D).ScaleFactors();
2115 }
2116 return gfxSize(1, 1);
2117 }
2118
GetTransformToAncestorExcludingAnimated(nsIFrame * aFrame,const nsIFrame * aAncestor)2119 static Matrix4x4Flagged GetTransformToAncestorExcludingAnimated(
2120 nsIFrame* aFrame, const nsIFrame* aAncestor) {
2121 nsIFrame* parent;
2122 Matrix4x4Flagged ctm;
2123 if (aFrame == aAncestor) {
2124 return ctm;
2125 }
2126 if (ActiveLayerTracker::IsScaleSubjectToAnimation(aFrame)) {
2127 return ctm;
2128 }
2129 ctm = aFrame->GetTransformMatrix(ViewportType::Layout, RelativeTo{aAncestor},
2130 &parent);
2131 while (parent && parent != aAncestor) {
2132 if (ActiveLayerTracker::IsScaleSubjectToAnimation(parent)) {
2133 return Matrix4x4Flagged();
2134 }
2135 if (!parent->Extend3DContext()) {
2136 ctm.ProjectTo2D();
2137 }
2138 ctm = ctm * parent->GetTransformMatrix(ViewportType::Layout,
2139 RelativeTo{aAncestor}, &parent);
2140 }
2141 return ctm;
2142 }
2143
GetTransformToAncestorScaleExcludingAnimated(nsIFrame * aFrame)2144 gfxSize nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(
2145 nsIFrame* aFrame) {
2146 Matrix4x4Flagged transform = GetTransformToAncestorExcludingAnimated(
2147 aFrame, nsLayoutUtils::GetDisplayRootFrame(aFrame));
2148 Matrix transform2D;
2149 if (transform.Is2D(&transform2D)) {
2150 return ThebesMatrix(transform2D).ScaleFactors();
2151 }
2152 return gfxSize(1, 1);
2153 }
2154
FindNearestCommonAncestorFrame(const nsIFrame * aFrame1,const nsIFrame * aFrame2)2155 const nsIFrame* nsLayoutUtils::FindNearestCommonAncestorFrame(
2156 const nsIFrame* aFrame1, const nsIFrame* aFrame2) {
2157 AutoTArray<const nsIFrame*, 100> ancestors1;
2158 AutoTArray<const nsIFrame*, 100> ancestors2;
2159 const nsIFrame* commonAncestor = nullptr;
2160 if (aFrame1->PresContext() == aFrame2->PresContext()) {
2161 commonAncestor = aFrame1->PresShell()->GetRootFrame();
2162 }
2163 for (const nsIFrame* f = aFrame1; f != commonAncestor;
2164 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
2165 ancestors1.AppendElement(f);
2166 }
2167 for (const nsIFrame* f = aFrame2; f != commonAncestor;
2168 f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
2169 ancestors2.AppendElement(f);
2170 }
2171 uint32_t minLengths = std::min(ancestors1.Length(), ancestors2.Length());
2172 for (uint32_t i = 1; i <= minLengths; ++i) {
2173 if (ancestors1[ancestors1.Length() - i] ==
2174 ancestors2[ancestors2.Length() - i]) {
2175 commonAncestor = ancestors1[ancestors1.Length() - i];
2176 } else {
2177 break;
2178 }
2179 }
2180 return commonAncestor;
2181 }
2182
FindNearestCommonAncestorFrameWithinBlock(const nsTextFrame * aFrame1,const nsTextFrame * aFrame2)2183 const nsIFrame* nsLayoutUtils::FindNearestCommonAncestorFrameWithinBlock(
2184 const nsTextFrame* aFrame1, const nsTextFrame* aFrame2) {
2185 MOZ_ASSERT(aFrame1);
2186 MOZ_ASSERT(aFrame2);
2187
2188 const nsIFrame* f1 = aFrame1;
2189 const nsIFrame* f2 = aFrame2;
2190
2191 int n1 = 1;
2192 int n2 = 1;
2193
2194 for (auto f = f1->GetParent();;) {
2195 NS_ASSERTION(f, "All text frames should have a block ancestor");
2196 if (!f) {
2197 return nullptr;
2198 }
2199 if (f->IsBlockFrameOrSubclass()) {
2200 break;
2201 }
2202 ++n1;
2203 f = f->GetParent();
2204 }
2205
2206 for (auto f = f2->GetParent();;) {
2207 NS_ASSERTION(f, "All text frames should have a block ancestor");
2208 if (!f) {
2209 return nullptr;
2210 }
2211 if (f->IsBlockFrameOrSubclass()) {
2212 break;
2213 }
2214 ++n2;
2215 f = f->GetParent();
2216 }
2217
2218 if (n1 > n2) {
2219 std::swap(n1, n2);
2220 std::swap(f1, f2);
2221 }
2222
2223 while (n2 > n1) {
2224 f2 = f2->GetParent();
2225 --n2;
2226 }
2227
2228 while (n2 >= 0) {
2229 if (f1 == f2) {
2230 return f1;
2231 }
2232 f1 = f1->GetParent();
2233 f2 = f2->GetParent();
2234 --n2;
2235 }
2236
2237 return nullptr;
2238 }
2239
AuthorSpecifiedBorderBackgroundDisablesTheming(StyleAppearance aAppearance)2240 bool nsLayoutUtils::AuthorSpecifiedBorderBackgroundDisablesTheming(
2241 StyleAppearance aAppearance) {
2242 return aAppearance == StyleAppearance::NumberInput ||
2243 aAppearance == StyleAppearance::Button ||
2244 aAppearance == StyleAppearance::Textfield ||
2245 aAppearance == StyleAppearance::Textarea ||
2246 aAppearance == StyleAppearance::Listbox ||
2247 aAppearance == StyleAppearance::Menulist ||
2248 aAppearance == StyleAppearance::MenulistButton;
2249 }
2250
GetContainingSVGTextFrame(const nsIFrame * aFrame)2251 static SVGTextFrame* GetContainingSVGTextFrame(const nsIFrame* aFrame) {
2252 if (!SVGUtils::IsInSVGTextSubtree(aFrame)) {
2253 return nullptr;
2254 }
2255
2256 return static_cast<SVGTextFrame*>(nsLayoutUtils::GetClosestFrameOfType(
2257 aFrame->GetParent(), LayoutFrameType::SVGText));
2258 }
2259
TransformGfxPointFromAncestor(RelativeTo aFrame,const Point & aPoint,RelativeTo aAncestor,Maybe<Matrix4x4Flagged> & aMatrixCache,Point * aOut)2260 static bool TransformGfxPointFromAncestor(RelativeTo aFrame,
2261 const Point& aPoint,
2262 RelativeTo aAncestor,
2263 Maybe<Matrix4x4Flagged>& aMatrixCache,
2264 Point* aOut) {
2265 SVGTextFrame* text = GetContainingSVGTextFrame(aFrame.mFrame);
2266
2267 if (!aMatrixCache) {
2268 auto matrix = nsLayoutUtils::GetTransformToAncestor(
2269 RelativeTo{text ? text : aFrame.mFrame, aFrame.mViewportType},
2270 aAncestor);
2271 if (matrix.IsSingular()) {
2272 return false;
2273 }
2274 matrix.Invert();
2275 aMatrixCache.emplace(matrix);
2276 }
2277
2278 const Matrix4x4Flagged& ctm = *aMatrixCache;
2279 Point4D point = ctm.ProjectPoint(aPoint);
2280 if (!point.HasPositiveWCoord()) {
2281 return false;
2282 }
2283
2284 *aOut = point.As2DPoint();
2285
2286 if (text) {
2287 *aOut = text->TransformFramePointToTextChild(*aOut, aFrame.mFrame);
2288 }
2289
2290 return true;
2291 }
2292
TransformGfxPointToAncestor(RelativeTo aFrame,const Point & aPoint,RelativeTo aAncestor,Maybe<Matrix4x4Flagged> & aMatrixCache)2293 static Point TransformGfxPointToAncestor(
2294 RelativeTo aFrame, const Point& aPoint, RelativeTo aAncestor,
2295 Maybe<Matrix4x4Flagged>& aMatrixCache) {
2296 if (SVGTextFrame* text = GetContainingSVGTextFrame(aFrame.mFrame)) {
2297 Point result =
2298 text->TransformFramePointFromTextChild(aPoint, aFrame.mFrame);
2299 return TransformGfxPointToAncestor(RelativeTo{text}, result, aAncestor,
2300 aMatrixCache);
2301 }
2302 if (!aMatrixCache) {
2303 aMatrixCache.emplace(
2304 nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor));
2305 }
2306 return aMatrixCache->ProjectPoint(aPoint).As2DPoint();
2307 }
2308
TransformGfxRectToAncestor(RelativeTo aFrame,const Rect & aRect,RelativeTo aAncestor,bool * aPreservesAxisAlignedRectangles=nullptr,Maybe<Matrix4x4Flagged> * aMatrixCache=nullptr,bool aStopAtStackingContextAndDisplayPortAndOOFFrame=false,nsIFrame ** aOutAncestor=nullptr)2309 static Rect TransformGfxRectToAncestor(
2310 RelativeTo aFrame, const Rect& aRect, RelativeTo aAncestor,
2311 bool* aPreservesAxisAlignedRectangles = nullptr,
2312 Maybe<Matrix4x4Flagged>* aMatrixCache = nullptr,
2313 bool aStopAtStackingContextAndDisplayPortAndOOFFrame = false,
2314 nsIFrame** aOutAncestor = nullptr) {
2315 Rect result;
2316 Matrix4x4Flagged ctm;
2317 if (SVGTextFrame* text = GetContainingSVGTextFrame(aFrame.mFrame)) {
2318 result = text->TransformFrameRectFromTextChild(aRect, aFrame.mFrame);
2319
2320 result = TransformGfxRectToAncestor(
2321 RelativeTo{text}, result, aAncestor, nullptr, aMatrixCache,
2322 aStopAtStackingContextAndDisplayPortAndOOFFrame, aOutAncestor);
2323 if (aPreservesAxisAlignedRectangles) {
2324 // TransformFrameRectFromTextChild could involve any kind of transform, we
2325 // could drill down into it to get an answer out of it but we don't yet.
2326 *aPreservesAxisAlignedRectangles = false;
2327 }
2328 return result;
2329 }
2330 if (aMatrixCache && *aMatrixCache) {
2331 // We are given a matrix to use, so use it
2332 ctm = aMatrixCache->value();
2333 } else {
2334 // Else, compute it
2335 uint32_t flags = 0;
2336 if (aStopAtStackingContextAndDisplayPortAndOOFFrame) {
2337 flags |= nsIFrame::STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT;
2338 }
2339 ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor, flags,
2340 aOutAncestor);
2341 if (aMatrixCache) {
2342 // and put it in the cache, if provided
2343 *aMatrixCache = Some(ctm);
2344 }
2345 }
2346 // Fill out the axis-alignment flag
2347 if (aPreservesAxisAlignedRectangles) {
2348 // TransformFrameRectFromTextChild could involve any kind of transform, we
2349 // could drill down into it to get an answer out of it but we don't yet.
2350 Matrix matrix2d;
2351 *aPreservesAxisAlignedRectangles =
2352 ctm.Is2D(&matrix2d) && matrix2d.PreservesAxisAlignedRectangles();
2353 }
2354 const nsIFrame* ancestor = aOutAncestor ? *aOutAncestor : aAncestor.mFrame;
2355 float factor = ancestor->PresContext()->AppUnitsPerDevPixel();
2356 Rect maxBounds =
2357 Rect(float(nscoord_MIN) / factor * 0.5, float(nscoord_MIN) / factor * 0.5,
2358 float(nscoord_MAX) / factor, float(nscoord_MAX) / factor);
2359 return ctm.TransformAndClipBounds(aRect, maxBounds);
2360 }
2361
TransformPoints(RelativeTo aFromFrame,RelativeTo aToFrame,uint32_t aPointCount,CSSPoint * aPoints)2362 nsLayoutUtils::TransformResult nsLayoutUtils::TransformPoints(
2363 RelativeTo aFromFrame, RelativeTo aToFrame, uint32_t aPointCount,
2364 CSSPoint* aPoints) {
2365 // Conceptually, {ViewportFrame, Visual} is an ancestor of
2366 // {ViewportFrame, Layout}, so factor that into the nearest ancestor
2367 // computation.
2368 RelativeTo nearestCommonAncestor{
2369 FindNearestCommonAncestorFrame(aFromFrame.mFrame, aToFrame.mFrame),
2370 aFromFrame.mViewportType == ViewportType::Visual ||
2371 aToFrame.mViewportType == ViewportType::Visual
2372 ? ViewportType::Visual
2373 : ViewportType::Layout};
2374 if (!nearestCommonAncestor.mFrame) {
2375 return NO_COMMON_ANCESTOR;
2376 }
2377 CSSToLayoutDeviceScale devPixelsPerCSSPixelFromFrame =
2378 aFromFrame.mFrame->PresContext()->CSSToDevPixelScale();
2379 CSSToLayoutDeviceScale devPixelsPerCSSPixelToFrame =
2380 aToFrame.mFrame->PresContext()->CSSToDevPixelScale();
2381 Maybe<Matrix4x4Flagged> cacheTo;
2382 Maybe<Matrix4x4Flagged> cacheFrom;
2383 for (uint32_t i = 0; i < aPointCount; ++i) {
2384 LayoutDevicePoint devPixels = aPoints[i] * devPixelsPerCSSPixelFromFrame;
2385 // What should the behaviour be if some of the points aren't invertible
2386 // and others are? Just assume all points are for now.
2387 Point toDevPixels =
2388 TransformGfxPointToAncestor(aFromFrame, Point(devPixels.x, devPixels.y),
2389 nearestCommonAncestor, cacheTo);
2390 Point result;
2391 if (!TransformGfxPointFromAncestor(
2392 aToFrame, toDevPixels, nearestCommonAncestor, cacheFrom, &result)) {
2393 return NONINVERTIBLE_TRANSFORM;
2394 }
2395 // Divide here so that when the devPixelsPerCSSPixels are the same, we get
2396 // the correct answer instead of some inaccuracy multiplying a number by its
2397 // reciprocal.
2398 aPoints[i] =
2399 LayoutDevicePoint(result.x, result.y) / devPixelsPerCSSPixelToFrame;
2400 }
2401 return TRANSFORM_SUCCEEDED;
2402 }
2403
TransformPoint(RelativeTo aFromFrame,RelativeTo aToFrame,nsPoint & aPoint)2404 nsLayoutUtils::TransformResult nsLayoutUtils::TransformPoint(
2405 RelativeTo aFromFrame, RelativeTo aToFrame, nsPoint& aPoint) {
2406 CSSPoint point = CSSPoint::FromAppUnits(aPoint);
2407 auto result = TransformPoints(aFromFrame, aToFrame, 1, &point);
2408 if (result == TRANSFORM_SUCCEEDED) {
2409 aPoint = CSSPoint::ToAppUnits(point);
2410 }
2411 return result;
2412 }
2413
TransformRect(const nsIFrame * aFromFrame,const nsIFrame * aToFrame,nsRect & aRect)2414 nsLayoutUtils::TransformResult nsLayoutUtils::TransformRect(
2415 const nsIFrame* aFromFrame, const nsIFrame* aToFrame, nsRect& aRect) {
2416 const nsIFrame* nearestCommonAncestor =
2417 FindNearestCommonAncestorFrame(aFromFrame, aToFrame);
2418 if (!nearestCommonAncestor) {
2419 return NO_COMMON_ANCESTOR;
2420 }
2421 Matrix4x4Flagged downToDest = GetTransformToAncestor(
2422 RelativeTo{aToFrame}, RelativeTo{nearestCommonAncestor});
2423 if (downToDest.IsSingular()) {
2424 return NONINVERTIBLE_TRANSFORM;
2425 }
2426 downToDest.Invert();
2427 aRect = TransformFrameRectToAncestor(aFromFrame, aRect,
2428 RelativeTo{nearestCommonAncestor});
2429
2430 float devPixelsPerAppUnitFromFrame =
2431 1.0f / nearestCommonAncestor->PresContext()->AppUnitsPerDevPixel();
2432 float devPixelsPerAppUnitToFrame =
2433 1.0f / aToFrame->PresContext()->AppUnitsPerDevPixel();
2434 gfx::Rect toDevPixels = downToDest.ProjectRectBounds(
2435 gfx::Rect(aRect.x * devPixelsPerAppUnitFromFrame,
2436 aRect.y * devPixelsPerAppUnitFromFrame,
2437 aRect.width * devPixelsPerAppUnitFromFrame,
2438 aRect.height * devPixelsPerAppUnitFromFrame),
2439 Rect(-std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame *
2440 0.5f,
2441 -std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame *
2442 0.5f,
2443 std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame,
2444 std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame));
2445 aRect.x = NSToCoordRoundWithClamp(toDevPixels.x / devPixelsPerAppUnitToFrame);
2446 aRect.y = NSToCoordRoundWithClamp(toDevPixels.y / devPixelsPerAppUnitToFrame);
2447 aRect.width =
2448 NSToCoordRoundWithClamp(toDevPixels.width / devPixelsPerAppUnitToFrame);
2449 aRect.height =
2450 NSToCoordRoundWithClamp(toDevPixels.height / devPixelsPerAppUnitToFrame);
2451 return TRANSFORM_SUCCEEDED;
2452 }
2453
GetRectRelativeToFrame(Element * aElement,nsIFrame * aFrame)2454 nsRect nsLayoutUtils::GetRectRelativeToFrame(Element* aElement,
2455 nsIFrame* aFrame) {
2456 if (!aElement || !aFrame) {
2457 return nsRect();
2458 }
2459
2460 nsIFrame* frame = aElement->GetPrimaryFrame();
2461 if (!frame) {
2462 return nsRect();
2463 }
2464
2465 nsRect rect = frame->GetRectRelativeToSelf();
2466 nsLayoutUtils::TransformResult rv =
2467 nsLayoutUtils::TransformRect(frame, aFrame, rect);
2468 if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) {
2469 return nsRect();
2470 }
2471
2472 return rect;
2473 }
2474
ContainsPoint(const nsRect & aRect,const nsPoint & aPoint,nscoord aInflateSize)2475 bool nsLayoutUtils::ContainsPoint(const nsRect& aRect, const nsPoint& aPoint,
2476 nscoord aInflateSize) {
2477 nsRect rect = aRect;
2478 rect.Inflate(aInflateSize);
2479 return rect.Contains(aPoint);
2480 }
2481
ClampRectToScrollFrames(nsIFrame * aFrame,const nsRect & aRect)2482 nsRect nsLayoutUtils::ClampRectToScrollFrames(nsIFrame* aFrame,
2483 const nsRect& aRect) {
2484 nsIFrame* closestScrollFrame =
2485 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::Scroll);
2486
2487 nsRect resultRect = aRect;
2488
2489 while (closestScrollFrame) {
2490 nsIScrollableFrame* sf = do_QueryFrame(closestScrollFrame);
2491
2492 nsRect scrollPortRect = sf->GetScrollPortRect();
2493 nsLayoutUtils::TransformRect(closestScrollFrame, aFrame, scrollPortRect);
2494
2495 resultRect = resultRect.Intersect(scrollPortRect);
2496
2497 // Check whether aRect is visible in the scroll frame or not.
2498 if (resultRect.IsEmpty()) {
2499 break;
2500 }
2501
2502 // Get next ancestor scroll frame.
2503 closestScrollFrame = nsLayoutUtils::GetClosestFrameOfType(
2504 closestScrollFrame->GetParent(), LayoutFrameType::Scroll);
2505 }
2506
2507 return resultRect;
2508 }
2509
GetLayerTransformForFrame(nsIFrame * aFrame,Matrix4x4Flagged * aTransform)2510 bool nsLayoutUtils::GetLayerTransformForFrame(nsIFrame* aFrame,
2511 Matrix4x4Flagged* aTransform) {
2512 // FIXME/bug 796690: we can sometimes compute a transform in these
2513 // cases, it just increases complexity considerably. Punt for now.
2514 if (aFrame->Extend3DContext() || aFrame->GetTransformGetter()) {
2515 return false;
2516 }
2517
2518 nsIFrame* root = nsLayoutUtils::GetDisplayRootFrame(aFrame);
2519 if (root->HasAnyStateBits(NS_FRAME_UPDATE_LAYER_TREE)) {
2520 // Content may have been invalidated, so we can't reliably compute
2521 // the "layer transform" in general.
2522 return false;
2523 }
2524 // If the caller doesn't care about the value, early-return to skip
2525 // overhead below.
2526 if (!aTransform) {
2527 return true;
2528 }
2529
2530 nsDisplayListBuilder builder(root,
2531 nsDisplayListBuilderMode::TransformComputation,
2532 false /*don't build caret*/);
2533 builder.BeginFrame();
2534 nsDisplayList list(&builder);
2535 nsDisplayTransform* item =
2536 MakeDisplayItem<nsDisplayTransform>(&builder, aFrame, &list, nsRect());
2537 MOZ_ASSERT(item);
2538
2539 *aTransform = item->GetTransform();
2540 item->Destroy(&builder);
2541
2542 builder.EndFrame();
2543
2544 return true;
2545 }
2546
TransformAncestorPointToFrame(RelativeTo aFrame,const nsPoint & aPoint,RelativeTo aAncestor)2547 nsPoint nsLayoutUtils::TransformAncestorPointToFrame(RelativeTo aFrame,
2548 const nsPoint& aPoint,
2549 RelativeTo aAncestor) {
2550 float factor = aFrame.mFrame->PresContext()->AppUnitsPerDevPixel();
2551 Point result(NSAppUnitsToFloatPixels(aPoint.x, factor),
2552 NSAppUnitsToFloatPixels(aPoint.y, factor));
2553
2554 Maybe<Matrix4x4Flagged> matrixCache;
2555 if (!TransformGfxPointFromAncestor(aFrame, result, aAncestor, matrixCache,
2556 &result)) {
2557 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
2558 }
2559
2560 return nsPoint(NSFloatPixelsToAppUnits(float(result.x), factor),
2561 NSFloatPixelsToAppUnits(float(result.y), factor));
2562 }
2563
TransformFrameRectToAncestor(const nsIFrame * aFrame,const nsRect & aRect,RelativeTo aAncestor,bool * aPreservesAxisAlignedRectangles,Maybe<Matrix4x4Flagged> * aMatrixCache,bool aStopAtStackingContextAndDisplayPortAndOOFFrame,nsIFrame ** aOutAncestor)2564 nsRect nsLayoutUtils::TransformFrameRectToAncestor(
2565 const nsIFrame* aFrame, const nsRect& aRect, RelativeTo aAncestor,
2566 bool* aPreservesAxisAlignedRectangles /* = nullptr */,
2567 Maybe<Matrix4x4Flagged>* aMatrixCache /* = nullptr */,
2568 bool aStopAtStackingContextAndDisplayPortAndOOFFrame /* = false */,
2569 nsIFrame** aOutAncestor /* = nullptr */) {
2570 MOZ_ASSERT(IsAncestorFrameCrossDocInProcess(aAncestor.mFrame, aFrame),
2571 "Fix the caller");
2572 float srcAppUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
2573 Rect result(NSAppUnitsToFloatPixels(aRect.x, srcAppUnitsPerDevPixel),
2574 NSAppUnitsToFloatPixels(aRect.y, srcAppUnitsPerDevPixel),
2575 NSAppUnitsToFloatPixels(aRect.width, srcAppUnitsPerDevPixel),
2576 NSAppUnitsToFloatPixels(aRect.height, srcAppUnitsPerDevPixel));
2577 result = TransformGfxRectToAncestor(
2578 RelativeTo{aFrame}, result, aAncestor, aPreservesAxisAlignedRectangles,
2579 aMatrixCache, aStopAtStackingContextAndDisplayPortAndOOFFrame,
2580 aOutAncestor);
2581
2582 float destAppUnitsPerDevPixel =
2583 aAncestor.mFrame->PresContext()->AppUnitsPerDevPixel();
2584 return nsRect(
2585 NSFloatPixelsToAppUnits(float(result.x), destAppUnitsPerDevPixel),
2586 NSFloatPixelsToAppUnits(float(result.y), destAppUnitsPerDevPixel),
2587 NSFloatPixelsToAppUnits(float(result.width), destAppUnitsPerDevPixel),
2588 NSFloatPixelsToAppUnits(float(result.height), destAppUnitsPerDevPixel));
2589 }
2590
GetWidgetOffset(nsIWidget * aWidget,nsIWidget * & aRootWidget)2591 static LayoutDeviceIntPoint GetWidgetOffset(nsIWidget* aWidget,
2592 nsIWidget*& aRootWidget) {
2593 LayoutDeviceIntPoint offset(0, 0);
2594 while (aWidget->WindowType() == eWindowType_child) {
2595 nsIWidget* parent = aWidget->GetParent();
2596 if (!parent) {
2597 break;
2598 }
2599 LayoutDeviceIntRect bounds = aWidget->GetBounds();
2600 offset += bounds.TopLeft();
2601 aWidget = parent;
2602 }
2603 aRootWidget = aWidget;
2604 return offset;
2605 }
2606
WidgetToWidgetOffset(nsIWidget * aFrom,nsIWidget * aTo)2607 LayoutDeviceIntPoint nsLayoutUtils::WidgetToWidgetOffset(nsIWidget* aFrom,
2608 nsIWidget* aTo) {
2609 nsIWidget* fromRoot;
2610 LayoutDeviceIntPoint fromOffset = GetWidgetOffset(aFrom, fromRoot);
2611 nsIWidget* toRoot;
2612 LayoutDeviceIntPoint toOffset = GetWidgetOffset(aTo, toRoot);
2613
2614 if (fromRoot != toRoot) {
2615 fromOffset = aFrom->WidgetToScreenOffset();
2616 toOffset = aTo->WidgetToScreenOffset();
2617 }
2618 return fromOffset - toOffset;
2619 }
2620
TranslateWidgetToView(nsPresContext * aPresContext,nsIWidget * aWidget,const LayoutDeviceIntPoint & aPt,nsView * aView)2621 nsPoint nsLayoutUtils::TranslateWidgetToView(nsPresContext* aPresContext,
2622 nsIWidget* aWidget,
2623 const LayoutDeviceIntPoint& aPt,
2624 nsView* aView) {
2625 nsPoint viewOffset;
2626 nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset);
2627 if (!viewWidget) {
2628 return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
2629 }
2630
2631 LayoutDeviceIntPoint widgetPoint =
2632 aPt + WidgetToWidgetOffset(aWidget, viewWidget);
2633 nsPoint widgetAppUnits(aPresContext->DevPixelsToAppUnits(widgetPoint.x),
2634 aPresContext->DevPixelsToAppUnits(widgetPoint.y));
2635 return widgetAppUnits - viewOffset;
2636 }
2637
TranslateViewToWidget(nsPresContext * aPresContext,nsView * aView,nsPoint aPt,ViewportType aViewportType,nsIWidget * aWidget)2638 LayoutDeviceIntPoint nsLayoutUtils::TranslateViewToWidget(
2639 nsPresContext* aPresContext, nsView* aView, nsPoint aPt,
2640 ViewportType aViewportType, nsIWidget* aWidget) {
2641 nsPoint viewOffset;
2642 nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset);
2643 if (!viewWidget) {
2644 return LayoutDeviceIntPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
2645 }
2646
2647 nsPoint pt = (aPt + viewOffset);
2648 // The target coordinates are visual, so perform a layout-to-visual
2649 // conversion if the incoming coordinates are layout.
2650 if (aViewportType == ViewportType::Layout && aPresContext->GetPresShell()) {
2651 pt = ViewportUtils::LayoutToVisual(pt, aPresContext->GetPresShell());
2652 }
2653 LayoutDeviceIntPoint relativeToViewWidget(
2654 aPresContext->AppUnitsToDevPixels(pt.x),
2655 aPresContext->AppUnitsToDevPixels(pt.y));
2656 return relativeToViewWidget + WidgetToWidgetOffset(viewWidget, aWidget);
2657 }
2658
2659 // Combine aNewBreakType with aOrigBreakType, but limit the break types
2660 // to StyleClear::Left, Right, Both.
CombineBreakType(StyleClear aOrigBreakType,StyleClear aNewBreakType)2661 StyleClear nsLayoutUtils::CombineBreakType(StyleClear aOrigBreakType,
2662 StyleClear aNewBreakType) {
2663 StyleClear breakType = aOrigBreakType;
2664 switch (breakType) {
2665 case StyleClear::Left:
2666 if (StyleClear::Right == aNewBreakType ||
2667 StyleClear::Both == aNewBreakType) {
2668 breakType = StyleClear::Both;
2669 }
2670 break;
2671 case StyleClear::Right:
2672 if (StyleClear::Left == aNewBreakType ||
2673 StyleClear::Both == aNewBreakType) {
2674 breakType = StyleClear::Both;
2675 }
2676 break;
2677 case StyleClear::None:
2678 if (StyleClear::Left == aNewBreakType ||
2679 StyleClear::Right == aNewBreakType ||
2680 StyleClear::Both == aNewBreakType) {
2681 breakType = aNewBreakType;
2682 }
2683 break;
2684 default:
2685 break;
2686 }
2687 return breakType;
2688 }
2689
2690 #ifdef MOZ_DUMP_PAINTING
2691 # include <stdio.h>
2692
2693 static bool gDumpEventList = false;
2694
2695 // nsLayoutUtils::PaintFrame() can call itself recursively, so rather than
2696 // maintaining a single paint count, we need a stack.
2697 StaticAutoPtr<nsTArray<int>> gPaintCountStack;
2698
2699 struct AutoNestedPaintCount {
AutoNestedPaintCountAutoNestedPaintCount2700 AutoNestedPaintCount() { gPaintCountStack->AppendElement(0); }
~AutoNestedPaintCountAutoNestedPaintCount2701 ~AutoNestedPaintCount() { gPaintCountStack->RemoveLastElement(); }
2702 };
2703
2704 #endif
2705
GetFrameForPoint(RelativeTo aRelativeTo,nsPoint aPt,const FrameForPointOptions & aOptions)2706 nsIFrame* nsLayoutUtils::GetFrameForPoint(
2707 RelativeTo aRelativeTo, nsPoint aPt, const FrameForPointOptions& aOptions) {
2708 AUTO_PROFILER_LABEL("nsLayoutUtils::GetFrameForPoint", LAYOUT);
2709
2710 nsresult rv;
2711 AutoTArray<nsIFrame*, 8> outFrames;
2712 rv = GetFramesForArea(aRelativeTo, nsRect(aPt, nsSize(1, 1)), outFrames,
2713 aOptions);
2714 NS_ENSURE_SUCCESS(rv, nullptr);
2715 return outFrames.Length() ? outFrames.ElementAt(0) : nullptr;
2716 }
2717
GetFramesForArea(RelativeTo aRelativeTo,const nsRect & aRect,nsTArray<nsIFrame * > & aOutFrames,const FrameForPointOptions & aOptions)2718 nsresult nsLayoutUtils::GetFramesForArea(RelativeTo aRelativeTo,
2719 const nsRect& aRect,
2720 nsTArray<nsIFrame*>& aOutFrames,
2721 const FrameForPointOptions& aOptions) {
2722 AUTO_PROFILER_LABEL("nsLayoutUtils::GetFramesForArea", LAYOUT);
2723
2724 nsIFrame* frame = const_cast<nsIFrame*>(aRelativeTo.mFrame);
2725
2726 nsDisplayListBuilder builder(frame, nsDisplayListBuilderMode::EventDelivery,
2727 false);
2728 builder.BeginFrame();
2729 nsDisplayList list(&builder);
2730
2731 if (aOptions.mBits.contains(FrameForPointOption::IgnorePaintSuppression)) {
2732 builder.IgnorePaintSuppression();
2733 }
2734 if (aOptions.mBits.contains(FrameForPointOption::IgnoreRootScrollFrame)) {
2735 nsIFrame* rootScrollFrame = frame->PresShell()->GetRootScrollFrame();
2736 if (rootScrollFrame) {
2737 builder.SetIgnoreScrollFrame(rootScrollFrame);
2738 }
2739 }
2740 if (aRelativeTo.mViewportType == ViewportType::Layout) {
2741 builder.SetIsRelativeToLayoutViewport();
2742 }
2743 if (aOptions.mBits.contains(FrameForPointOption::IgnoreCrossDoc)) {
2744 builder.SetDescendIntoSubdocuments(false);
2745 }
2746
2747 if (aOptions.mBits.contains(FrameForPointOption::OnlyVisible)) {
2748 builder.SetHitTestIsForVisibility(aOptions.mVisibleThreshold);
2749 }
2750
2751 builder.EnterPresShell(frame);
2752
2753 builder.SetVisibleRect(aRect);
2754 builder.SetDirtyRect(aRect);
2755
2756 frame->BuildDisplayListForStackingContext(&builder, &list);
2757 builder.LeavePresShell(frame, nullptr);
2758
2759 #ifdef MOZ_DUMP_PAINTING
2760 if (gDumpEventList) {
2761 fprintf_stderr(stderr, "Event handling --- (%d,%d):\n", aRect.x, aRect.y);
2762
2763 std::stringstream ss;
2764 nsIFrame::PrintDisplayList(&builder, list, ss);
2765 print_stderr(ss);
2766 }
2767 #endif
2768
2769 nsDisplayItem::HitTestState hitTestState;
2770 list.HitTest(&builder, aRect, &hitTestState, &aOutFrames);
2771 list.DeleteAll(&builder);
2772 builder.EndFrame();
2773 return NS_OK;
2774 }
2775
2776 mozilla::ParentLayerToScreenScale2D
GetTransformToAncestorScaleCrossProcessForFrameMetrics(const nsIFrame * aFrame)2777 nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
2778 const nsIFrame* aFrame) {
2779 ParentLayerToScreenScale2D transformToAncestorScale(
2780 nsLayoutUtils::GetTransformToAncestorScale(aFrame));
2781
2782 if (BrowserChild* browserChild = BrowserChild::GetFrom(aFrame->PresShell())) {
2783 transformToAncestorScale =
2784 ViewTargetAs<ParentLayerPixel>(
2785 transformToAncestorScale,
2786 PixelCastJustification::PropagatingToChildProcess) *
2787 browserChild->GetEffectsInfo().mTransformToAncestorScale;
2788 }
2789
2790 return transformToAncestorScale;
2791 }
2792
2793 // aScrollFrameAsScrollable must be non-nullptr and queryable to an nsIFrame
CalculateBasicFrameMetrics(nsIScrollableFrame * aScrollFrame)2794 FrameMetrics nsLayoutUtils::CalculateBasicFrameMetrics(
2795 nsIScrollableFrame* aScrollFrame) {
2796 nsIFrame* frame = do_QueryFrame(aScrollFrame);
2797 MOZ_ASSERT(frame);
2798
2799 // Calculate the metrics necessary for calculating the displayport.
2800 // This code has a lot in common with the code in ComputeFrameMetrics();
2801 // we may want to refactor this at some point.
2802 FrameMetrics metrics;
2803 nsPresContext* presContext = frame->PresContext();
2804 PresShell* presShell = presContext->PresShell();
2805 CSSToLayoutDeviceScale deviceScale = presContext->CSSToDevPixelScale();
2806 float resolution = 1.0f;
2807 bool isRcdRsf = aScrollFrame->IsRootScrollFrameOfDocument() &&
2808 presContext->IsRootContentDocumentCrossProcess();
2809 metrics.SetIsRootContent(isRcdRsf);
2810 if (isRcdRsf) {
2811 // Only the root content document's root scrollable frame should pick up
2812 // the presShell's resolution. All the other frames are 1.0.
2813 resolution = presShell->GetResolution();
2814 }
2815 LayoutDeviceToLayerScale cumulativeResolution(
2816 LayoutDeviceToLayerScale(presShell->GetCumulativeResolution()));
2817
2818 LayerToParentLayerScale layerToParentLayerScale(1.0f);
2819 metrics.SetDevPixelsPerCSSPixel(deviceScale);
2820 metrics.SetPresShellResolution(resolution);
2821
2822 metrics.SetTransformToAncestorScale(
2823 GetTransformToAncestorScaleCrossProcessForFrameMetrics(frame));
2824 metrics.SetCumulativeResolution(cumulativeResolution);
2825 metrics.SetZoom(deviceScale * cumulativeResolution * layerToParentLayerScale);
2826
2827 // Only the size of the composition bounds is relevant to the
2828 // displayport calculation, not its origin.
2829 nsSize compositionSize =
2830 nsLayoutUtils::CalculateCompositionSizeForFrame(frame);
2831 LayoutDeviceToParentLayerScale compBoundsScale;
2832 if (frame == presShell->GetRootScrollFrame() &&
2833 presContext->IsRootContentDocumentCrossProcess()) {
2834 if (presContext->GetParentPresContext()) {
2835 float res = presContext->GetParentPresContext()
2836 ->PresShell()
2837 ->GetCumulativeResolution();
2838 compBoundsScale = LayoutDeviceToParentLayerScale(res);
2839 }
2840 } else {
2841 compBoundsScale = cumulativeResolution * layerToParentLayerScale;
2842 }
2843 metrics.SetCompositionBounds(
2844 LayoutDeviceRect::FromAppUnits(nsRect(nsPoint(0, 0), compositionSize),
2845 presContext->AppUnitsPerDevPixel()) *
2846 compBoundsScale);
2847
2848 metrics.SetBoundingCompositionSize(
2849 nsLayoutUtils::CalculateBoundingCompositionSize(frame, false, metrics));
2850
2851 metrics.SetLayoutViewport(
2852 CSSRect::FromAppUnits(nsRect(aScrollFrame->GetScrollPosition(),
2853 aScrollFrame->GetScrollPortRect().Size())));
2854 metrics.SetVisualScrollOffset(
2855 isRcdRsf ? CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset())
2856 : metrics.GetLayoutViewport().TopLeft());
2857
2858 metrics.SetScrollableRect(CSSRect::FromAppUnits(
2859 nsLayoutUtils::CalculateScrollableRectForFrame(aScrollFrame, nullptr)));
2860
2861 return metrics;
2862 }
2863
GetAsyncScrollableAncestorFrame(nsIFrame * aTarget)2864 nsIScrollableFrame* nsLayoutUtils::GetAsyncScrollableAncestorFrame(
2865 nsIFrame* aTarget) {
2866 uint32_t flags = nsLayoutUtils::SCROLLABLE_ALWAYS_MATCH_ROOT |
2867 nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE |
2868 nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT |
2869 nsLayoutUtils::SCROLLABLE_FOLLOW_OOF_TO_PLACEHOLDER;
2870 return nsLayoutUtils::GetNearestScrollableFrame(aTarget, flags);
2871 }
2872
AddExtraBackgroundItems(nsDisplayListBuilder * aBuilder,nsDisplayList * aList,nsIFrame * aFrame,const nsRect & aCanvasArea,const nsRegion & aVisibleRegion,nscolor aBackstop)2873 void nsLayoutUtils::AddExtraBackgroundItems(nsDisplayListBuilder* aBuilder,
2874 nsDisplayList* aList,
2875 nsIFrame* aFrame,
2876 const nsRect& aCanvasArea,
2877 const nsRegion& aVisibleRegion,
2878 nscolor aBackstop) {
2879 LayoutFrameType frameType = aFrame->Type();
2880 nsPresContext* presContext = aFrame->PresContext();
2881 PresShell* presShell = presContext->PresShell();
2882
2883 // For the viewport frame in print preview/page layout we want to paint
2884 // the grey background behind the page, not the canvas color.
2885 if (frameType == LayoutFrameType::Viewport &&
2886 nsLayoutUtils::NeedsPrintPreviewBackground(presContext)) {
2887 nsRect bounds =
2888 nsRect(aBuilder->ToReferenceFrame(aFrame), aFrame->GetSize());
2889 nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
2890 aBuilder, aFrame, bounds, bounds);
2891 presShell->AddPrintPreviewBackgroundItem(aBuilder, aList, aFrame, bounds);
2892 } else if (frameType != LayoutFrameType::Page) {
2893 // For printing, this function is first called on an nsPageFrame, which
2894 // creates a display list with a PageContent item. The PageContent item's
2895 // paint function calls this function on the nsPageFrame's child which is
2896 // an nsPageContentFrame. We only want to add the canvas background color
2897 // item once, for the nsPageContentFrame.
2898
2899 // Add the canvas background color to the bottom of the list. This
2900 // happens after we've built the list so that AddCanvasBackgroundColorItem
2901 // can monkey with the contents if necessary.
2902 nsRect canvasArea = aVisibleRegion.GetBounds();
2903 canvasArea.IntersectRect(aCanvasArea, canvasArea);
2904 nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
2905 aBuilder, aFrame, canvasArea, canvasArea);
2906 presShell->AddCanvasBackgroundColorItem(aBuilder, aList, aFrame, canvasArea,
2907 aBackstop);
2908 }
2909 }
2910
2911 /**
2912 * Returns a retained display list builder for frame |aFrame|. If there is no
2913 * retained display list builder property set for the frame, and if the flag
2914 * |aRetainingEnabled| is true, a new retained display list builder is created,
2915 * stored as a property for the frame, and returned.
2916 */
GetOrCreateRetainedDisplayListBuilder(nsIFrame * aFrame,bool aRetainingEnabled,bool aBuildCaret)2917 static RetainedDisplayListBuilder* GetOrCreateRetainedDisplayListBuilder(
2918 nsIFrame* aFrame, bool aRetainingEnabled, bool aBuildCaret) {
2919 RetainedDisplayListBuilder* retainedBuilder =
2920 aFrame->GetProperty(RetainedDisplayListBuilder::Cached());
2921
2922 if (retainedBuilder) {
2923 return retainedBuilder;
2924 }
2925
2926 if (aRetainingEnabled) {
2927 retainedBuilder = new RetainedDisplayListBuilder(
2928 aFrame, nsDisplayListBuilderMode::Painting, aBuildCaret);
2929 aFrame->SetProperty(RetainedDisplayListBuilder::Cached(), retainedBuilder);
2930 }
2931
2932 return retainedBuilder;
2933 }
2934
2935 // #define PRINT_HITTESTINFO_STATS
2936 #ifdef PRINT_HITTESTINFO_STATS
PrintHitTestInfoStatsInternal(nsDisplayList * aList,int & aTotal,int & aHitTest,int & aVisible,int & aSpecial)2937 void PrintHitTestInfoStatsInternal(nsDisplayList* aList, int& aTotal,
2938 int& aHitTest, int& aVisible,
2939 int& aSpecial) {
2940 for (nsDisplayItem* i : *aList) {
2941 aTotal++;
2942
2943 if (i->GetChildren()) {
2944 PrintHitTestInfoStatsInternal(i->GetChildren(), aTotal, aHitTest,
2945 aVisible, aSpecial);
2946 }
2947
2948 if (i->GetType() == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
2949 aHitTest++;
2950
2951 const auto& hitTestInfo =
2952 static_cast<nsDisplayHitTestInfoBase*>(i)->HitTestFlags();
2953
2954 if (hitTestInfo.size() > 1) {
2955 aSpecial++;
2956 continue;
2957 }
2958
2959 if (hitTestInfo == CompositorHitTestVisibleToHit) {
2960 aVisible++;
2961 continue;
2962 }
2963
2964 aSpecial++;
2965 }
2966 }
2967 }
2968
PrintHitTestInfoStats(nsDisplayList * aList)2969 void PrintHitTestInfoStats(nsDisplayList* aList) {
2970 int total = 0;
2971 int hitTest = 0;
2972 int visible = 0;
2973 int special = 0;
2974
2975 PrintHitTestInfoStatsInternal(aList, total, hitTest, visible, special);
2976
2977 double ratio = (double)hitTest / (double)total;
2978
2979 printf(
2980 "List %p: total items: %d, hit test items: %d, ratio: %f, visible: %d, "
2981 "special: %d\n",
2982 aList, total, hitTest, ratio, visible, special);
2983 }
2984 #endif
2985
2986 // Apply a batch of effects updates generated during a paint to their
2987 // respective remote browsers.
ApplyEffectsUpdates(const nsTHashMap<nsPtrHashKey<RemoteBrowser>,EffectsInfo> & aUpdates)2988 static void ApplyEffectsUpdates(
2989 const nsTHashMap<nsPtrHashKey<RemoteBrowser>, EffectsInfo>& aUpdates) {
2990 for (const auto& entry : aUpdates) {
2991 auto* browser = entry.GetKey();
2992 const auto& update = entry.GetData();
2993 browser->UpdateEffects(update);
2994 }
2995 }
2996
DumpBeforePaintDisplayList(UniquePtr<std::stringstream> & aStream,nsDisplayListBuilder * aBuilder,nsDisplayList * aList,const nsRect & aVisibleRect)2997 static void DumpBeforePaintDisplayList(UniquePtr<std::stringstream>& aStream,
2998 nsDisplayListBuilder* aBuilder,
2999 nsDisplayList* aList,
3000 const nsRect& aVisibleRect) {
3001 #ifdef MOZ_DUMP_PAINTING
3002 if (gfxEnv::DumpPaintToFile()) {
3003 nsCString string("dump-");
3004 // Include the process ID in the dump file name, to make sure that in an
3005 // e10s setup different processes don't clobber each other's dump files.
3006 string.AppendInt(getpid());
3007 for (int paintCount : *gPaintCountStack) {
3008 string.AppendLiteral("-");
3009 string.AppendInt(paintCount);
3010 }
3011 string.AppendLiteral(".html");
3012 gfxUtils::sDumpPaintFile = fopen(string.BeginReading(), "w");
3013 } else {
3014 gfxUtils::sDumpPaintFile = stderr;
3015 }
3016 if (gfxEnv::DumpPaintToFile()) {
3017 *aStream << "<html><head><script>\n"
3018 "var array = {};\n"
3019 "function ViewImage(index) { \n"
3020 " var image = document.getElementById(index);\n"
3021 " if (image.src) {\n"
3022 " image.removeAttribute('src');\n"
3023 " } else {\n"
3024 " image.src = array[index];\n"
3025 " }\n"
3026 "}</script></head><body>";
3027 }
3028 #endif
3029 *aStream << nsPrintfCString(
3030 "Painting --- before optimization (dirty %d,%d,%d,%d):\n",
3031 aVisibleRect.x, aVisibleRect.y, aVisibleRect.width,
3032 aVisibleRect.height)
3033 .get();
3034 nsIFrame::PrintDisplayList(aBuilder, *aList, *aStream,
3035 gfxEnv::DumpPaintToFile());
3036
3037 if (gfxEnv::DumpPaint() || gfxEnv::DumpPaintItems()) {
3038 // Flush stream now to avoid reordering dump output relative to
3039 // messages dumped by PaintRoot below.
3040 fprint_stderr(gfxUtils::sDumpPaintFile, *aStream);
3041 aStream = MakeUnique<std::stringstream>();
3042 }
3043 }
3044
DumpAfterPaintDisplayList(UniquePtr<std::stringstream> & aStream,nsDisplayListBuilder * aBuilder,nsDisplayList * aList)3045 static void DumpAfterPaintDisplayList(UniquePtr<std::stringstream>& aStream,
3046 nsDisplayListBuilder* aBuilder,
3047 nsDisplayList* aList) {
3048 *aStream << "Painting --- after optimization:\n";
3049 nsIFrame::PrintDisplayList(aBuilder, *aList, *aStream,
3050 gfxEnv::DumpPaintToFile());
3051
3052 fprint_stderr(gfxUtils::sDumpPaintFile, *aStream);
3053
3054 #ifdef MOZ_DUMP_PAINTING
3055 if (gfxEnv::DumpPaintToFile()) {
3056 *aStream << "</body></html>";
3057 }
3058 if (gfxEnv::DumpPaintToFile()) {
3059 fclose(gfxUtils::sDumpPaintFile);
3060 }
3061 #endif
3062
3063 std::stringstream lsStream;
3064 nsIFrame::PrintDisplayList(aBuilder, *aList, lsStream);
3065 }
3066
3067 struct TemporaryDisplayListBuilder {
TemporaryDisplayListBuilderTemporaryDisplayListBuilder3068 TemporaryDisplayListBuilder(nsIFrame* aFrame,
3069 nsDisplayListBuilderMode aBuilderMode,
3070 const bool aBuildCaret)
3071 : mBuilder(aFrame, aBuilderMode, aBuildCaret), mList(&mBuilder) {}
3072
~TemporaryDisplayListBuilderTemporaryDisplayListBuilder3073 ~TemporaryDisplayListBuilder() { mList.DeleteAll(&mBuilder); }
3074
3075 nsDisplayListBuilder mBuilder;
3076 nsDisplayList mList;
3077 RetainedDisplayListMetrics mMetrics;
3078 };
3079
PaintFrame(gfxContext * aRenderingContext,nsIFrame * aFrame,const nsRegion & aDirtyRegion,nscolor aBackstop,nsDisplayListBuilderMode aBuilderMode,PaintFrameFlags aFlags)3080 void nsLayoutUtils::PaintFrame(gfxContext* aRenderingContext, nsIFrame* aFrame,
3081 const nsRegion& aDirtyRegion, nscolor aBackstop,
3082 nsDisplayListBuilderMode aBuilderMode,
3083 PaintFrameFlags aFlags) {
3084 AUTO_PROFILER_LABEL("nsLayoutUtils::PaintFrame", GRAPHICS);
3085
3086 #ifdef MOZ_DUMP_PAINTING
3087 if (!gPaintCountStack) {
3088 gPaintCountStack = new nsTArray<int>();
3089 ClearOnShutdown(&gPaintCountStack);
3090
3091 gPaintCountStack->AppendElement(0);
3092 }
3093 ++gPaintCountStack->LastElement();
3094 AutoNestedPaintCount nestedPaintCount;
3095 #endif
3096
3097 if (aFlags & PaintFrameFlags::WidgetLayers) {
3098 nsView* view = aFrame->GetView();
3099 if (!(view && view->GetWidget() && GetDisplayRootFrame(aFrame) == aFrame)) {
3100 aFlags &= ~PaintFrameFlags::WidgetLayers;
3101 NS_ASSERTION(aRenderingContext, "need a rendering context");
3102 }
3103 }
3104
3105 nsPresContext* presContext = aFrame->PresContext();
3106 PresShell* presShell = presContext->PresShell();
3107
3108 TimeStamp startBuildDisplayList = TimeStamp::Now();
3109 auto dlTimerId = mozilla::glean::paint::build_displaylist_time.Start();
3110
3111 const bool buildCaret = !(aFlags & PaintFrameFlags::HideCaret);
3112
3113 // Note that isForPainting here does not include the PaintForPrinting builder
3114 // mode; that's OK because there is no point in using retained display lists
3115 // for a print destination.
3116 const bool isForPainting = (aFlags & PaintFrameFlags::WidgetLayers) &&
3117 aBuilderMode == nsDisplayListBuilderMode::Painting;
3118
3119 // Only allow retaining for painting when preffed on, and for root frames
3120 // (since the modified frame tracking is per-root-frame).
3121 const bool retainingEnabled =
3122 isForPainting && AreRetainedDisplayListsEnabled() && !aFrame->GetParent();
3123
3124 RetainedDisplayListBuilder* retainedBuilder =
3125 GetOrCreateRetainedDisplayListBuilder(aFrame, retainingEnabled,
3126 buildCaret);
3127
3128 // Only use the retained display list builder if the retaining is currently
3129 // enabled. This check is needed because it is possible that the pref has been
3130 // disabled after creating the retained display list builder.
3131 const bool useRetainedBuilder = retainedBuilder && retainingEnabled;
3132
3133 Maybe<TemporaryDisplayListBuilder> temporaryBuilder;
3134 nsDisplayListBuilder* builder = nullptr;
3135 nsDisplayList* list = nullptr;
3136 RetainedDisplayListMetrics* metrics = nullptr;
3137
3138 if (useRetainedBuilder) {
3139 builder = retainedBuilder->Builder();
3140 list = retainedBuilder->List();
3141 metrics = retainedBuilder->Metrics();
3142 } else {
3143 temporaryBuilder.emplace(aFrame, aBuilderMode, buildCaret);
3144 builder = &temporaryBuilder->mBuilder;
3145 list = &temporaryBuilder->mList;
3146 metrics = &temporaryBuilder->mMetrics;
3147 }
3148
3149 MOZ_ASSERT(builder && list && metrics);
3150
3151 metrics->Reset();
3152 metrics->StartBuild();
3153
3154 builder->BeginFrame();
3155
3156 if (aFlags & PaintFrameFlags::InTransform) {
3157 builder->SetInTransform(true);
3158 }
3159 if (aFlags & PaintFrameFlags::SyncDecodeImages) {
3160 builder->SetSyncDecodeImages(true);
3161 }
3162 if (aFlags & (PaintFrameFlags::WidgetLayers | PaintFrameFlags::ToWindow)) {
3163 builder->SetPaintingToWindow(true);
3164 }
3165 if (aFlags & PaintFrameFlags::UseHighQualityScaling) {
3166 builder->SetUseHighQualityScaling(true);
3167 }
3168 if (aFlags & PaintFrameFlags::ForWebRender) {
3169 builder->SetPaintingForWebRender(true);
3170 }
3171 if (aFlags & PaintFrameFlags::IgnoreSuppression) {
3172 builder->IgnorePaintSuppression();
3173 }
3174
3175 if (BrowsingContext* bc = presContext->Document()->GetBrowsingContext()) {
3176 builder->SetInActiveDocShell(bc->IsActive());
3177 }
3178
3179 nsRect rootInkOverflow = aFrame->InkOverflowRectRelativeToSelf();
3180
3181 // If we are in a remote browser, then apply clipping from ancestor browsers
3182 if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) {
3183 if (!browserChild->IsTopLevel()) {
3184 Maybe<nsRect> unscaledVisibleRect = browserChild->GetVisibleRect();
3185
3186 if (!unscaledVisibleRect) {
3187 unscaledVisibleRect = Some(nsRect());
3188 }
3189
3190 rootInkOverflow.IntersectRect(rootInkOverflow, *unscaledVisibleRect);
3191 }
3192 }
3193
3194 builder->ClearHaveScrollableDisplayPort();
3195 if (builder->IsPaintingToWindow()) {
3196 DisplayPortUtils::MaybeCreateDisplayPortInFirstScrollFrameEncountered(
3197 aFrame, builder);
3198 }
3199
3200 nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
3201 if (rootScrollFrame && !aFrame->GetParent()) {
3202 nsIScrollableFrame* rootScrollableFrame =
3203 presShell->GetRootScrollFrameAsScrollable();
3204 MOZ_ASSERT(rootScrollableFrame);
3205 nsRect displayPortBase = rootInkOverflow;
3206 nsRect temp = displayPortBase;
3207 Unused << rootScrollableFrame->DecideScrollableLayer(
3208 builder, &displayPortBase, &temp,
3209 /* aSetBase = */ true);
3210 }
3211
3212 nsRegion visibleRegion;
3213 if (aFlags & PaintFrameFlags::WidgetLayers) {
3214 // This layer tree will be reused, so we'll need to calculate it
3215 // for the whole "visible" area of the window
3216 //
3217 // |ignoreViewportScrolling| and |usingDisplayPort| are persistent
3218 // document-rendering state. We rely on PresShell to flush
3219 // retained layers as needed when that persistent state changes.
3220 visibleRegion = rootInkOverflow;
3221 } else {
3222 visibleRegion = aDirtyRegion;
3223 }
3224
3225 Maybe<nsPoint> originalScrollPosition;
3226 auto maybeResetScrollPosition = MakeScopeExit([&]() {
3227 if (originalScrollPosition && rootScrollFrame) {
3228 nsIScrollableFrame* rootScrollableFrame =
3229 presShell->GetRootScrollFrameAsScrollable();
3230 MOZ_ASSERT(rootScrollableFrame->GetScrolledFrame()->GetPosition() ==
3231 nsPoint());
3232 rootScrollableFrame->GetScrolledFrame()->SetPosition(
3233 *originalScrollPosition);
3234 }
3235 });
3236
3237 nsRect canvasArea(nsPoint(0, 0), aFrame->GetSize());
3238 bool ignoreViewportScrolling =
3239 !aFrame->GetParent() && presShell->IgnoringViewportScrolling();
3240 if (ignoreViewportScrolling && rootScrollFrame) {
3241 nsIScrollableFrame* rootScrollableFrame =
3242 presShell->GetRootScrollFrameAsScrollable();
3243 if (aFlags & PaintFrameFlags::ResetViewportScrolling) {
3244 // Temporarily scroll the root scroll frame to 0,0 so that position:fixed
3245 // elements will appear fixed to the top-left of the document. We manually
3246 // set the position of the scrolled frame instead of using ScrollTo, since
3247 // the latter fires scroll listeners, which we don't want.
3248 originalScrollPosition.emplace(
3249 rootScrollableFrame->GetScrolledFrame()->GetPosition());
3250 rootScrollableFrame->GetScrolledFrame()->SetPosition(nsPoint());
3251 }
3252 if (aFlags & PaintFrameFlags::DocumentRelative) {
3253 // Make visibleRegion and aRenderingContext relative to the
3254 // scrolled frame instead of the root frame.
3255 nsPoint pos = rootScrollableFrame->GetScrollPosition();
3256 visibleRegion.MoveBy(-pos);
3257 if (aRenderingContext) {
3258 gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
3259 pos, presContext->AppUnitsPerDevPixel());
3260 aRenderingContext->SetMatrixDouble(
3261 aRenderingContext->CurrentMatrixDouble().PreTranslate(
3262 devPixelOffset));
3263 }
3264 }
3265 builder->SetIgnoreScrollFrame(rootScrollFrame);
3266
3267 nsCanvasFrame* canvasFrame =
3268 do_QueryFrame(rootScrollableFrame->GetScrolledFrame());
3269 if (canvasFrame) {
3270 // Use UnionRect here to ensure that areas where the scrollbars
3271 // were are still filled with the background color.
3272 canvasArea.UnionRect(
3273 canvasArea,
3274 canvasFrame->CanvasArea() + builder->ToReferenceFrame(canvasFrame));
3275 }
3276 }
3277
3278 nsRect visibleRect = visibleRegion.GetBounds();
3279 PartialUpdateResult updateState = PartialUpdateResult::Failed;
3280
3281 {
3282 AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_DisplayListBuilding);
3283 AUTO_PROFILER_TRACING_MARKER("Paint", "DisplayList", GRAPHICS);
3284 PerfStats::AutoMetricRecording<PerfStats::Metric::DisplayListBuilding>
3285 autoRecording;
3286 {
3287 ViewID id = ScrollableLayerGuid::NULL_SCROLL_ID;
3288 if (presShell->GetDocument() &&
3289 presShell->GetDocument()->IsRootDisplayDocument() &&
3290 !presShell->GetRootScrollFrame()) {
3291 // In cases where the root document is a XUL document, we want to take
3292 // the ViewID from the root element, as that will be the ViewID of the
3293 // root APZC in the tree. Skip doing this in cases where we know
3294 // nsGfxScrollFrame::BuilDisplayList will do it instead.
3295 if (dom::Element* element =
3296 presShell->GetDocument()->GetDocumentElement()) {
3297 id = nsLayoutUtils::FindOrCreateIDFor(element);
3298 }
3299 // In some cases we get a root document here on an APZ-enabled window
3300 // that doesn't have the root displayport initialized yet, even though
3301 // the ChromeProcessController is supposed to do it when the widget is
3302 // created. This can happen simply because the ChromeProcessController
3303 // does it on the next spin of the event loop, and we can trigger a
3304 // paint synchronously after window creation but before that runs. In
3305 // that case we should initialize the root displayport here before we do
3306 // the paint.
3307 } else if (XRE_IsParentProcess() && presContext->IsRoot() &&
3308 presShell->GetDocument() != nullptr &&
3309 presShell->GetRootScrollFrame() != nullptr &&
3310 nsLayoutUtils::UsesAsyncScrolling(
3311 presShell->GetRootScrollFrame())) {
3312 if (dom::Element* element =
3313 presShell->GetDocument()->GetDocumentElement()) {
3314 if (!DisplayPortUtils::HasNonMinimalDisplayPort(element)) {
3315 APZCCallbackHelper::InitializeRootDisplayport(presShell);
3316 }
3317 }
3318 }
3319
3320 nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(builder,
3321 id);
3322
3323 builder->SetVisibleRect(visibleRect);
3324 builder->SetIsBuilding(true);
3325 builder->SetAncestorHasApzAwareEventHandler(
3326 gfxPlatform::AsyncPanZoomEnabled() &&
3327 nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(presShell));
3328
3329 // If a pref is toggled that adds or removes display list items,
3330 // we need to rebuild the display list. The pref may be toggled
3331 // manually by the user, or during test setup.
3332 if (useRetainedBuilder &&
3333 !builder->ShouldRebuildDisplayListDueToPrefChange()) {
3334 // Attempt to do a partial build and merge into the existing list.
3335 // This calls BuildDisplayListForStacking context on a subset of the
3336 // viewport.
3337 updateState = retainedBuilder->AttemptPartialUpdate(aBackstop);
3338 metrics->EndPartialBuild(updateState);
3339 } else {
3340 // Partial updates are disabled.
3341 DL_LOGI("Partial updates are disabled");
3342 metrics->mPartialUpdateResult = PartialUpdateResult::Failed;
3343 metrics->mPartialUpdateFailReason = PartialUpdateFailReason::Disabled;
3344 }
3345
3346 // Rebuild the full display list if the partial display list build failed.
3347 bool doFullRebuild = updateState == PartialUpdateResult::Failed;
3348
3349 if (StaticPrefs::layout_display_list_build_twice()) {
3350 // Build display list twice to compare partial and full display list
3351 // build times.
3352 metrics->StartBuild();
3353 doFullRebuild = true;
3354 }
3355
3356 if (doFullRebuild) {
3357 if (useRetainedBuilder) {
3358 retainedBuilder->ClearFramesWithProps();
3359 retainedBuilder->ClearReuseableDisplayItems();
3360 #ifdef DEBUG
3361 mozilla::RDLUtils::AssertFrameSubtreeUnmodified(
3362 builder->RootReferenceFrame());
3363 #endif
3364 }
3365
3366 list->DeleteAll(builder);
3367
3368 builder->ClearRetainedWindowRegions();
3369 builder->ClearWillChangeBudgets();
3370
3371 builder->EnterPresShell(aFrame);
3372 builder->SetDirtyRect(visibleRect);
3373
3374 DL_LOGI("Starting full display list build, root frame: %p",
3375 builder->RootReferenceFrame());
3376
3377 aFrame->BuildDisplayListForStackingContext(builder, list);
3378 AddExtraBackgroundItems(builder, list, aFrame, canvasArea,
3379 visibleRegion, aBackstop);
3380
3381 builder->LeavePresShell(aFrame, list);
3382 metrics->EndFullBuild();
3383
3384 DL_LOGI("Finished full display list build");
3385 updateState = PartialUpdateResult::Updated;
3386 }
3387 }
3388
3389 builder->SetIsBuilding(false);
3390 builder->IncrementPresShellPaintCount(presShell);
3391 }
3392
3393 MOZ_ASSERT(updateState != PartialUpdateResult::Failed);
3394 builder->Check();
3395
3396 const double geckoDLBuildTime =
3397 (TimeStamp::Now() - startBuildDisplayList).ToMilliseconds();
3398 mozilla::glean::paint::build_displaylist_time.StopAndAccumulate(
3399 std::move(dlTimerId));
3400
3401 bool consoleNeedsDisplayList =
3402 (gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint()) &&
3403 builder->IsInActiveDocShell();
3404 #ifdef MOZ_DUMP_PAINTING
3405 FILE* savedDumpFile = gfxUtils::sDumpPaintFile;
3406 #endif
3407
3408 UniquePtr<std::stringstream> ss;
3409 if (consoleNeedsDisplayList) {
3410 ss = MakeUnique<std::stringstream>();
3411 Document* doc = presContext->Document();
3412 nsAutoString uri;
3413 if (doc && doc->GetDocumentURI(uri) == NS_OK) {
3414 *ss << "Display list for " << uri << "\n";
3415 }
3416 DumpBeforePaintDisplayList(ss, builder, list, visibleRect);
3417 }
3418
3419 uint32_t flags = nsDisplayList::PAINT_DEFAULT;
3420 if (aFlags & PaintFrameFlags::WidgetLayers) {
3421 flags |= nsDisplayList::PAINT_USE_WIDGET_LAYERS;
3422 if (!(aFlags & PaintFrameFlags::DocumentRelative)) {
3423 nsIWidget* widget = aFrame->GetNearestWidget();
3424 if (widget) {
3425 // If we're finished building display list items for painting of the
3426 // outermost pres shell, notify the widget about any toolbars we've
3427 // encountered.
3428 widget->UpdateThemeGeometries(builder->GetThemeGeometries());
3429 }
3430 }
3431 }
3432 if (aFlags & PaintFrameFlags::ExistingTransaction) {
3433 flags |= nsDisplayList::PAINT_EXISTING_TRANSACTION;
3434 }
3435 if (updateState == PartialUpdateResult::NoChange && !aRenderingContext) {
3436 flags |= nsDisplayList::PAINT_IDENTICAL_DISPLAY_LIST;
3437 }
3438
3439 #ifdef PRINT_HITTESTINFO_STATS
3440 if (XRE_IsContentProcess()) {
3441 PrintHitTestInfoStats(list);
3442 }
3443 #endif
3444
3445 TimeStamp paintStart = TimeStamp::Now();
3446 list->PaintRoot(builder, aRenderingContext, flags, Some(geckoDLBuildTime));
3447 Telemetry::AccumulateTimeDelta(Telemetry::PAINT_RASTERIZE_TIME, paintStart);
3448
3449 if (builder->IsPaintingToWindow()) {
3450 presShell->EndPaint();
3451 }
3452 builder->Check();
3453
3454 if (consoleNeedsDisplayList) {
3455 DumpAfterPaintDisplayList(ss, builder, list);
3456 }
3457
3458 #ifdef MOZ_DUMP_PAINTING
3459 gfxUtils::sDumpPaintFile = savedDumpFile;
3460 #endif
3461
3462 // Update the widget's opaque region information. This sets
3463 // glass boundaries on Windows. Also set up the window dragging region.
3464 if ((aFlags & PaintFrameFlags::WidgetLayers) &&
3465 !(aFlags & PaintFrameFlags::DocumentRelative)) {
3466 nsIWidget* widget = aFrame->GetNearestWidget();
3467 if (widget) {
3468 nsRegion opaqueRegion;
3469 opaqueRegion.And(builder->GetWindowExcludeGlassRegion(),
3470 builder->GetWindowOpaqueRegion());
3471 widget->UpdateOpaqueRegion(LayoutDeviceIntRegion::FromUnknownRegion(
3472 opaqueRegion.ToNearestPixels(presContext->AppUnitsPerDevPixel())));
3473
3474 widget->UpdateWindowDraggingRegion(builder->GetWindowDraggingRegion());
3475 }
3476 }
3477
3478 // Apply effects updates if we were actually painting
3479 if (isForPainting) {
3480 ApplyEffectsUpdates(builder->GetEffectUpdates());
3481 }
3482
3483 builder->Check();
3484
3485 {
3486 AUTO_PROFILER_TRACING_MARKER("Paint", "DisplayListResources", GRAPHICS);
3487
3488 builder->EndFrame();
3489
3490 if (!useRetainedBuilder) {
3491 temporaryBuilder.reset();
3492 }
3493 }
3494
3495 #if 0
3496 if (XRE_IsParentProcess()) {
3497 if (metrics->mPartialUpdateResult == PartialUpdateResult::Failed) {
3498 printf("DL partial update failed: %s, Frame: %p\n",
3499 metrics->FailReasonString(), aFrame);
3500 } else {
3501 printf(
3502 "DL partial build success!"
3503 " new: %d, reused: %d, rebuilt: %d, removed: %d, total: %d\n",
3504 metrics->mNewItems, metrics->mReusedItems, metrics->mRebuiltItems,
3505 metrics->mRemovedItems, metrics->mTotalItems);
3506 }
3507 }
3508 #endif
3509 }
3510
3511 /**
3512 * Uses a binary search for find where the cursor falls in the line of text
3513 * It also keeps track of the part of the string that has already been measured
3514 * so it doesn't have to keep measuring the same text over and over
3515 *
3516 * @param "aBaseWidth" contains the width in twips of the portion
3517 * of the text that has already been measured, and aBaseInx contains
3518 * the index of the text that has already been measured.
3519 *
3520 * @param aTextWidth returns the (in twips) the length of the text that falls
3521 * before the cursor aIndex contains the index of the text where the cursor
3522 * falls
3523 */
BinarySearchForPosition(DrawTarget * aDrawTarget,nsFontMetrics & aFontMetrics,const char16_t * aText,int32_t aBaseWidth,int32_t aBaseInx,int32_t aStartInx,int32_t aEndInx,int32_t aCursorPos,int32_t & aIndex,int32_t & aTextWidth)3524 bool nsLayoutUtils::BinarySearchForPosition(
3525 DrawTarget* aDrawTarget, nsFontMetrics& aFontMetrics, const char16_t* aText,
3526 int32_t aBaseWidth, int32_t aBaseInx, int32_t aStartInx, int32_t aEndInx,
3527 int32_t aCursorPos, int32_t& aIndex, int32_t& aTextWidth) {
3528 int32_t range = aEndInx - aStartInx;
3529 if ((range == 1) || (range == 2 && NS_IS_HIGH_SURROGATE(aText[aStartInx]))) {
3530 aIndex = aStartInx + aBaseInx;
3531 aTextWidth = nsLayoutUtils::AppUnitWidthOfString(aText, aIndex,
3532 aFontMetrics, aDrawTarget);
3533 return true;
3534 }
3535
3536 int32_t inx = aStartInx + (range / 2);
3537
3538 // Make sure we don't leave a dangling low surrogate
3539 if (NS_IS_HIGH_SURROGATE(aText[inx - 1])) inx++;
3540
3541 int32_t textWidth = nsLayoutUtils::AppUnitWidthOfString(
3542 aText, inx, aFontMetrics, aDrawTarget);
3543
3544 int32_t fullWidth = aBaseWidth + textWidth;
3545 if (fullWidth == aCursorPos) {
3546 aTextWidth = textWidth;
3547 aIndex = inx;
3548 return true;
3549 } else if (aCursorPos < fullWidth) {
3550 aTextWidth = aBaseWidth;
3551 if (BinarySearchForPosition(aDrawTarget, aFontMetrics, aText, aBaseWidth,
3552 aBaseInx, aStartInx, inx, aCursorPos, aIndex,
3553 aTextWidth)) {
3554 return true;
3555 }
3556 } else {
3557 aTextWidth = fullWidth;
3558 if (BinarySearchForPosition(aDrawTarget, aFontMetrics, aText, aBaseWidth,
3559 aBaseInx, inx, aEndInx, aCursorPos, aIndex,
3560 aTextWidth)) {
3561 return true;
3562 }
3563 }
3564 return false;
3565 }
3566
AddBoxesForFrame(nsIFrame * aFrame,nsLayoutUtils::BoxCallback * aCallback)3567 void nsLayoutUtils::AddBoxesForFrame(nsIFrame* aFrame,
3568 nsLayoutUtils::BoxCallback* aCallback) {
3569 auto pseudoType = aFrame->Style()->GetPseudoType();
3570
3571 if (pseudoType == PseudoStyleType::tableWrapper) {
3572 AddBoxesForFrame(aFrame->PrincipalChildList().FirstChild(), aCallback);
3573 if (aCallback->mIncludeCaptionBoxForTable) {
3574 nsIFrame* kid = aFrame->GetChildList(nsIFrame::kCaptionList).FirstChild();
3575 if (kid) {
3576 AddBoxesForFrame(kid, aCallback);
3577 }
3578 }
3579 } else if (pseudoType == PseudoStyleType::mozBlockInsideInlineWrapper ||
3580 pseudoType == PseudoStyleType::mozMathMLAnonymousBlock ||
3581 pseudoType == PseudoStyleType::mozXULAnonymousBlock) {
3582 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
3583 AddBoxesForFrame(kid, aCallback);
3584 }
3585 } else {
3586 aCallback->AddBox(aFrame);
3587 }
3588 }
3589
GetAllInFlowBoxes(nsIFrame * aFrame,BoxCallback * aCallback)3590 void nsLayoutUtils::GetAllInFlowBoxes(nsIFrame* aFrame,
3591 BoxCallback* aCallback) {
3592 aCallback->mInTargetContinuation = false;
3593 while (aFrame) {
3594 AddBoxesForFrame(aFrame, aCallback);
3595 aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
3596 aCallback->mInTargetContinuation = true;
3597 }
3598 }
3599
GetFirstNonAnonymousFrame(nsIFrame * aFrame)3600 nsIFrame* nsLayoutUtils::GetFirstNonAnonymousFrame(nsIFrame* aFrame) {
3601 while (aFrame) {
3602 auto pseudoType = aFrame->Style()->GetPseudoType();
3603
3604 if (pseudoType == PseudoStyleType::tableWrapper) {
3605 nsIFrame* f =
3606 GetFirstNonAnonymousFrame(aFrame->PrincipalChildList().FirstChild());
3607 if (f) {
3608 return f;
3609 }
3610 nsIFrame* kid = aFrame->GetChildList(nsIFrame::kCaptionList).FirstChild();
3611 if (kid) {
3612 f = GetFirstNonAnonymousFrame(kid);
3613 if (f) {
3614 return f;
3615 }
3616 }
3617 } else if (pseudoType == PseudoStyleType::mozBlockInsideInlineWrapper ||
3618 pseudoType == PseudoStyleType::mozMathMLAnonymousBlock ||
3619 pseudoType == PseudoStyleType::mozXULAnonymousBlock) {
3620 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
3621 nsIFrame* f = GetFirstNonAnonymousFrame(kid);
3622 if (f) {
3623 return f;
3624 }
3625 }
3626 } else {
3627 return aFrame;
3628 }
3629
3630 aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
3631 }
3632 return nullptr;
3633 }
3634
3635 struct BoxToRect : public nsLayoutUtils::BoxCallback {
3636 const nsIFrame* mRelativeTo;
3637 RectCallback* mCallback;
3638 uint32_t mFlags;
3639 // If the frame we're measuring relative to is the root, we know all frames
3640 // are descendants of it, so we don't need to compute the common ancestor
3641 // between a frame and mRelativeTo.
3642 bool mRelativeToIsRoot;
3643 // For the same reason, if the frame we're measuring relative to is the target
3644 // (this is useful for IntersectionObserver), we know all frames are
3645 // descendants of it except if we're in a continuation or ib-split-sibling of
3646 // it.
3647 bool mRelativeToIsTarget;
3648
BoxToRectBoxToRect3649 BoxToRect(const nsIFrame* aTargetFrame, const nsIFrame* aRelativeTo,
3650 RectCallback* aCallback, uint32_t aFlags)
3651 : mRelativeTo(aRelativeTo),
3652 mCallback(aCallback),
3653 mFlags(aFlags),
3654 mRelativeToIsRoot(!aRelativeTo->GetParent()),
3655 mRelativeToIsTarget(aRelativeTo == aTargetFrame) {}
3656
AddBoxBoxToRect3657 void AddBox(nsIFrame* aFrame) override {
3658 nsRect r;
3659 nsIFrame* outer = SVGUtils::GetOuterSVGFrameAndCoveredRegion(aFrame, &r);
3660 const bool usingSVGOuterFrame = !!outer;
3661 if (!outer) {
3662 outer = aFrame;
3663 switch (mFlags & nsLayoutUtils::RECTS_WHICH_BOX_MASK) {
3664 case nsLayoutUtils::RECTS_USE_CONTENT_BOX:
3665 r = aFrame->GetContentRectRelativeToSelf();
3666 break;
3667 case nsLayoutUtils::RECTS_USE_PADDING_BOX:
3668 r = aFrame->GetPaddingRectRelativeToSelf();
3669 break;
3670 case nsLayoutUtils::RECTS_USE_MARGIN_BOX:
3671 r = aFrame->GetMarginRectRelativeToSelf();
3672 break;
3673 default: // Use the border box
3674 r = aFrame->GetRectRelativeToSelf();
3675 }
3676 }
3677 if (mFlags & nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS) {
3678 const bool isAncestorKnown = [&] {
3679 if (mRelativeToIsRoot) {
3680 return true;
3681 }
3682 if (mRelativeToIsTarget && !mInTargetContinuation) {
3683 return !usingSVGOuterFrame;
3684 }
3685 return false;
3686 }();
3687 if (isAncestorKnown) {
3688 r = nsLayoutUtils::TransformFrameRectToAncestor(outer, r, mRelativeTo);
3689 } else {
3690 nsLayoutUtils::TransformRect(outer, mRelativeTo, r);
3691 }
3692 } else {
3693 if (aFrame->PresContext() != mRelativeTo->PresContext()) {
3694 r += outer->GetOffsetToCrossDoc(mRelativeTo);
3695 } else {
3696 r += outer->GetOffsetTo(mRelativeTo);
3697 }
3698 }
3699 mCallback->AddRect(r);
3700 }
3701 };
3702
3703 struct MOZ_RAII BoxToRectAndText : public BoxToRect {
3704 Sequence<nsString>* mTextList;
3705
BoxToRectAndTextBoxToRectAndText3706 BoxToRectAndText(const nsIFrame* aTargetFrame, const nsIFrame* aRelativeTo,
3707 RectCallback* aCallback, Sequence<nsString>* aTextList,
3708 uint32_t aFlags)
3709 : BoxToRect(aTargetFrame, aRelativeTo, aCallback, aFlags),
3710 mTextList(aTextList) {}
3711
AccumulateTextBoxToRectAndText3712 static void AccumulateText(nsIFrame* aFrame, nsAString& aResult) {
3713 MOZ_ASSERT(aFrame);
3714
3715 // Get all the text in aFrame and child frames, while respecting
3716 // the content offsets in each of the nsTextFrames.
3717 if (aFrame->IsTextFrame()) {
3718 nsTextFrame* textFrame = static_cast<nsTextFrame*>(aFrame);
3719
3720 nsIFrame::RenderedText renderedText = textFrame->GetRenderedText(
3721 textFrame->GetContentOffset(),
3722 textFrame->GetContentOffset() + textFrame->GetContentLength(),
3723 nsIFrame::TextOffsetType::OffsetsInContentText,
3724 nsIFrame::TrailingWhitespace::DontTrim);
3725
3726 aResult.Append(renderedText.mString);
3727 }
3728
3729 for (nsIFrame* child = aFrame->PrincipalChildList().FirstChild(); child;
3730 child = child->GetNextSibling()) {
3731 AccumulateText(child, aResult);
3732 }
3733 }
3734
AddBoxBoxToRectAndText3735 void AddBox(nsIFrame* aFrame) override {
3736 BoxToRect::AddBox(aFrame);
3737 if (mTextList) {
3738 nsString* textForFrame = mTextList->AppendElement(fallible);
3739 if (textForFrame) {
3740 AccumulateText(aFrame, *textForFrame);
3741 }
3742 }
3743 }
3744 };
3745
GetAllInFlowRects(nsIFrame * aFrame,const nsIFrame * aRelativeTo,RectCallback * aCallback,uint32_t aFlags)3746 void nsLayoutUtils::GetAllInFlowRects(nsIFrame* aFrame,
3747 const nsIFrame* aRelativeTo,
3748 RectCallback* aCallback,
3749 uint32_t aFlags) {
3750 BoxToRect converter(aFrame, aRelativeTo, aCallback, aFlags);
3751 GetAllInFlowBoxes(aFrame, &converter);
3752 }
3753
GetAllInFlowRectsAndTexts(nsIFrame * aFrame,const nsIFrame * aRelativeTo,RectCallback * aCallback,Sequence<nsString> * aTextList,uint32_t aFlags)3754 void nsLayoutUtils::GetAllInFlowRectsAndTexts(nsIFrame* aFrame,
3755 const nsIFrame* aRelativeTo,
3756 RectCallback* aCallback,
3757 Sequence<nsString>* aTextList,
3758 uint32_t aFlags) {
3759 BoxToRectAndText converter(aFrame, aRelativeTo, aCallback, aTextList, aFlags);
3760 GetAllInFlowBoxes(aFrame, &converter);
3761 }
3762
RectAccumulator()3763 nsLayoutUtils::RectAccumulator::RectAccumulator() : mSeenFirstRect(false) {}
3764
AddRect(const nsRect & aRect)3765 void nsLayoutUtils::RectAccumulator::AddRect(const nsRect& aRect) {
3766 mResultRect.UnionRect(mResultRect, aRect);
3767 if (!mSeenFirstRect) {
3768 mSeenFirstRect = true;
3769 mFirstRect = aRect;
3770 }
3771 }
3772
RectListBuilder(DOMRectList * aList)3773 nsLayoutUtils::RectListBuilder::RectListBuilder(DOMRectList* aList)
3774 : mRectList(aList) {}
3775
AddRect(const nsRect & aRect)3776 void nsLayoutUtils::RectListBuilder::AddRect(const nsRect& aRect) {
3777 RefPtr<DOMRect> rect = new DOMRect(mRectList);
3778
3779 rect->SetLayoutRect(aRect);
3780 mRectList->Append(rect);
3781 }
3782
GetContainingBlockForClientRect(nsIFrame * aFrame)3783 nsIFrame* nsLayoutUtils::GetContainingBlockForClientRect(nsIFrame* aFrame) {
3784 return aFrame->PresShell()->GetRootFrame();
3785 }
3786
GetAllInFlowRectsUnion(nsIFrame * aFrame,const nsIFrame * aRelativeTo,uint32_t aFlags)3787 nsRect nsLayoutUtils::GetAllInFlowRectsUnion(nsIFrame* aFrame,
3788 const nsIFrame* aRelativeTo,
3789 uint32_t aFlags) {
3790 RectAccumulator accumulator;
3791 GetAllInFlowRects(aFrame, aRelativeTo, &accumulator, aFlags);
3792 return accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect
3793 : accumulator.mResultRect;
3794 }
3795
GetTextShadowRectsUnion(const nsRect & aTextAndDecorationsRect,nsIFrame * aFrame,uint32_t aFlags)3796 nsRect nsLayoutUtils::GetTextShadowRectsUnion(
3797 const nsRect& aTextAndDecorationsRect, nsIFrame* aFrame, uint32_t aFlags) {
3798 const nsStyleText* textStyle = aFrame->StyleText();
3799 auto shadows = textStyle->mTextShadow.AsSpan();
3800 if (shadows.IsEmpty()) {
3801 return aTextAndDecorationsRect;
3802 }
3803
3804 nsRect resultRect = aTextAndDecorationsRect;
3805 int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel();
3806 for (auto& shadow : shadows) {
3807 nsMargin blur =
3808 nsContextBoxBlur::GetBlurRadiusMargin(shadow.blur.ToAppUnits(), A2D);
3809 if ((aFlags & EXCLUDE_BLUR_SHADOWS) && blur != nsMargin(0, 0, 0, 0))
3810 continue;
3811
3812 nsRect tmpRect(aTextAndDecorationsRect);
3813
3814 tmpRect.MoveBy(
3815 nsPoint(shadow.horizontal.ToAppUnits(), shadow.vertical.ToAppUnits()));
3816 tmpRect.Inflate(blur);
3817
3818 resultRect.UnionRect(resultRect, tmpRect);
3819 }
3820 return resultRect;
3821 }
3822
3823 enum ObjectDimensionType { eWidth, eHeight };
ComputeMissingDimension(const nsSize & aDefaultObjectSize,const AspectRatio & aIntrinsicRatio,const Maybe<nscoord> & aSpecifiedWidth,const Maybe<nscoord> & aSpecifiedHeight,ObjectDimensionType aDimensionToCompute)3824 static nscoord ComputeMissingDimension(
3825 const nsSize& aDefaultObjectSize, const AspectRatio& aIntrinsicRatio,
3826 const Maybe<nscoord>& aSpecifiedWidth,
3827 const Maybe<nscoord>& aSpecifiedHeight,
3828 ObjectDimensionType aDimensionToCompute) {
3829 // The "default sizing algorithm" computes the missing dimension as follows:
3830 // (source: http://dev.w3.org/csswg/css-images-3/#default-sizing )
3831
3832 // 1. "If the object has an intrinsic aspect ratio, the missing dimension of
3833 // the concrete object size is calculated using the intrinsic aspect
3834 // ratio and the present dimension."
3835 if (aIntrinsicRatio) {
3836 // Fill in the missing dimension using the intrinsic aspect ratio.
3837 if (aDimensionToCompute == eWidth) {
3838 return aIntrinsicRatio.ApplyTo(*aSpecifiedHeight);
3839 }
3840 return aIntrinsicRatio.Inverted().ApplyTo(*aSpecifiedWidth);
3841 }
3842
3843 // 2. "Otherwise, if the missing dimension is present in the object's
3844 // intrinsic dimensions, [...]"
3845 // NOTE: *Skipping* this case, because we already know it's not true -- we're
3846 // in this function because the missing dimension is *not* present in
3847 // the object's intrinsic dimensions.
3848
3849 // 3. "Otherwise, the missing dimension of the concrete object size is taken
3850 // from the default object size. "
3851 return (aDimensionToCompute == eWidth) ? aDefaultObjectSize.width
3852 : aDefaultObjectSize.height;
3853 }
3854
3855 /*
3856 * This computes & returns the concrete object size of replaced content, if
3857 * that content were to be rendered with "object-fit: none". (Or, if the
3858 * element has neither an intrinsic height nor width, this method returns an
3859 * empty Maybe<> object.)
3860 *
3861 * As specced...
3862 * http://dev.w3.org/csswg/css-images-3/#valdef-object-fit-none
3863 * ..we use "the default sizing algorithm with no specified size,
3864 * and a default object size equal to the replaced element's used width and
3865 * height."
3866 *
3867 * The default sizing algorithm is described here:
3868 * http://dev.w3.org/csswg/css-images-3/#default-sizing
3869 * Quotes in the function-impl are taken from that ^ spec-text.
3870 *
3871 * Per its final bulleted section: since there's no specified size,
3872 * we run the default sizing algorithm using the object's intrinsic size in
3873 * place of the specified size. But if the object has neither an intrinsic
3874 * height nor an intrinsic width, then we instead return without populating our
3875 * outparam, and we let the caller figure out the size (using a contain
3876 * constraint).
3877 */
MaybeComputeObjectFitNoneSize(const nsSize & aDefaultObjectSize,const IntrinsicSize & aIntrinsicSize,const AspectRatio & aIntrinsicRatio)3878 static Maybe<nsSize> MaybeComputeObjectFitNoneSize(
3879 const nsSize& aDefaultObjectSize, const IntrinsicSize& aIntrinsicSize,
3880 const AspectRatio& aIntrinsicRatio) {
3881 // "If the object has an intrinsic height or width, its size is resolved as
3882 // if its intrinsic dimensions were given as the specified size."
3883 //
3884 // So, first we check if we have an intrinsic height and/or width:
3885 const Maybe<nscoord>& specifiedWidth = aIntrinsicSize.width;
3886 const Maybe<nscoord>& specifiedHeight = aIntrinsicSize.height;
3887
3888 Maybe<nsSize> noneSize; // (the value we'll return)
3889 if (specifiedWidth || specifiedHeight) {
3890 // We have at least one specified dimension; use whichever dimension is
3891 // specified, and compute the other one using our intrinsic ratio, or (if
3892 // no valid ratio) using the default object size.
3893 noneSize.emplace();
3894
3895 noneSize->width =
3896 specifiedWidth
3897 ? *specifiedWidth
3898 : ComputeMissingDimension(aDefaultObjectSize, aIntrinsicRatio,
3899 specifiedWidth, specifiedHeight, eWidth);
3900
3901 noneSize->height =
3902 specifiedHeight
3903 ? *specifiedHeight
3904 : ComputeMissingDimension(aDefaultObjectSize, aIntrinsicRatio,
3905 specifiedWidth, specifiedHeight, eHeight);
3906 }
3907 // [else:] "Otherwise [if there's neither an intrinsic height nor width], its
3908 // size is resolved as a contain constraint against the default object size."
3909 // We'll let our caller do that, to share code & avoid redundant
3910 // computations; so, we return w/out populating noneSize.
3911 return noneSize;
3912 }
3913
3914 // Computes the concrete object size to render into, as described at
3915 // http://dev.w3.org/csswg/css-images-3/#concrete-size-resolution
ComputeConcreteObjectSize(const nsSize & aConstraintSize,const IntrinsicSize & aIntrinsicSize,const AspectRatio & aIntrinsicRatio,StyleObjectFit aObjectFit)3916 static nsSize ComputeConcreteObjectSize(const nsSize& aConstraintSize,
3917 const IntrinsicSize& aIntrinsicSize,
3918 const AspectRatio& aIntrinsicRatio,
3919 StyleObjectFit aObjectFit) {
3920 // Handle default behavior (filling the container) w/ fast early return.
3921 // (Also: if there's no valid intrinsic ratio, then we have the "fill"
3922 // behavior & just use the constraint size.)
3923 if (MOZ_LIKELY(aObjectFit == StyleObjectFit::Fill) || !aIntrinsicRatio) {
3924 return aConstraintSize;
3925 }
3926
3927 // The type of constraint to compute (cover/contain), if needed:
3928 Maybe<nsImageRenderer::FitType> fitType;
3929
3930 Maybe<nsSize> noneSize;
3931 if (aObjectFit == StyleObjectFit::None ||
3932 aObjectFit == StyleObjectFit::ScaleDown) {
3933 noneSize = MaybeComputeObjectFitNoneSize(aConstraintSize, aIntrinsicSize,
3934 aIntrinsicRatio);
3935 if (!noneSize || aObjectFit == StyleObjectFit::ScaleDown) {
3936 // Need to compute a 'CONTAIN' constraint (either for the 'none' size
3937 // itself, or for comparison w/ the 'none' size to resolve 'scale-down'.)
3938 fitType.emplace(nsImageRenderer::CONTAIN);
3939 }
3940 } else if (aObjectFit == StyleObjectFit::Cover) {
3941 fitType.emplace(nsImageRenderer::COVER);
3942 } else if (aObjectFit == StyleObjectFit::Contain) {
3943 fitType.emplace(nsImageRenderer::CONTAIN);
3944 }
3945
3946 Maybe<nsSize> constrainedSize;
3947 if (fitType) {
3948 constrainedSize.emplace(nsImageRenderer::ComputeConstrainedSize(
3949 aConstraintSize, aIntrinsicRatio, *fitType));
3950 }
3951
3952 // Now, we should have all the sizing information that we need.
3953 switch (aObjectFit) {
3954 // skipping StyleObjectFit::Fill; we handled it w/ early-return.
3955 case StyleObjectFit::Contain:
3956 case StyleObjectFit::Cover:
3957 MOZ_ASSERT(constrainedSize);
3958 return *constrainedSize;
3959
3960 case StyleObjectFit::None:
3961 if (noneSize) {
3962 return *noneSize;
3963 }
3964 MOZ_ASSERT(constrainedSize);
3965 return *constrainedSize;
3966
3967 case StyleObjectFit::ScaleDown:
3968 MOZ_ASSERT(constrainedSize);
3969 if (noneSize) {
3970 constrainedSize->width =
3971 std::min(constrainedSize->width, noneSize->width);
3972 constrainedSize->height =
3973 std::min(constrainedSize->height, noneSize->height);
3974 }
3975 return *constrainedSize;
3976
3977 default:
3978 MOZ_ASSERT_UNREACHABLE("Unexpected enum value for 'object-fit'");
3979 return aConstraintSize; // fall back to (default) 'fill' behavior
3980 }
3981 }
3982
3983 // (Helper for HasInitialObjectFitAndPosition, to check
3984 // each "object-position" coord.)
IsCoord50Pct(const LengthPercentage & aCoord)3985 static bool IsCoord50Pct(const LengthPercentage& aCoord) {
3986 return aCoord.ConvertsToPercentage() && aCoord.ToPercentage() == 0.5f;
3987 }
3988
3989 // Indicates whether the given nsStylePosition has the initial values
3990 // for the "object-fit" and "object-position" properties.
HasInitialObjectFitAndPosition(const nsStylePosition * aStylePos)3991 static bool HasInitialObjectFitAndPosition(const nsStylePosition* aStylePos) {
3992 const Position& objectPos = aStylePos->mObjectPosition;
3993
3994 return aStylePos->mObjectFit == StyleObjectFit::Fill &&
3995 IsCoord50Pct(objectPos.horizontal) && IsCoord50Pct(objectPos.vertical);
3996 }
3997
3998 /* static */
ComputeObjectDestRect(const nsRect & aConstraintRect,const IntrinsicSize & aIntrinsicSize,const AspectRatio & aIntrinsicRatio,const nsStylePosition * aStylePos,nsPoint * aAnchorPoint)3999 nsRect nsLayoutUtils::ComputeObjectDestRect(const nsRect& aConstraintRect,
4000 const IntrinsicSize& aIntrinsicSize,
4001 const AspectRatio& aIntrinsicRatio,
4002 const nsStylePosition* aStylePos,
4003 nsPoint* aAnchorPoint) {
4004 // Step 1: Figure out our "concrete object size"
4005 // (the size of the region we'll actually draw our image's pixels into).
4006 nsSize concreteObjectSize =
4007 ComputeConcreteObjectSize(aConstraintRect.Size(), aIntrinsicSize,
4008 aIntrinsicRatio, aStylePos->mObjectFit);
4009
4010 // Step 2: Figure out how to align that region in the element's content-box.
4011 nsPoint imageTopLeftPt, imageAnchorPt;
4012 nsImageRenderer::ComputeObjectAnchorPoint(
4013 aStylePos->mObjectPosition, aConstraintRect.Size(), concreteObjectSize,
4014 &imageTopLeftPt, &imageAnchorPt);
4015 // Right now, we're with respect to aConstraintRect's top-left point. We add
4016 // that point here, to convert to the same broader coordinate space that
4017 // aConstraintRect is in.
4018 imageTopLeftPt += aConstraintRect.TopLeft();
4019 imageAnchorPt += aConstraintRect.TopLeft();
4020
4021 if (aAnchorPoint) {
4022 // Special-case: if our "object-fit" and "object-position" properties have
4023 // their default values ("object-fit: fill; object-position:50% 50%"), then
4024 // we'll override the calculated imageAnchorPt, and instead use the
4025 // object's top-left corner.
4026 //
4027 // This special case is partly for backwards compatibility (since
4028 // traditionally we've pixel-aligned the top-left corner of e.g. <img>
4029 // elements), and partly because ComputeSnappedDrawingParameters produces
4030 // less error if the anchor point is at the top-left corner. So, all other
4031 // things being equal, we prefer that code path with less error.
4032 if (HasInitialObjectFitAndPosition(aStylePos)) {
4033 *aAnchorPoint = imageTopLeftPt;
4034 } else {
4035 *aAnchorPoint = imageAnchorPt;
4036 }
4037 }
4038 return nsRect(imageTopLeftPt, concreteObjectSize);
4039 }
4040
GetFontMetricsForFrame(const nsIFrame * aFrame,float aInflation)4041 already_AddRefed<nsFontMetrics> nsLayoutUtils::GetFontMetricsForFrame(
4042 const nsIFrame* aFrame, float aInflation) {
4043 ComputedStyle* computedStyle = aFrame->Style();
4044 uint8_t variantWidth = NS_FONT_VARIANT_WIDTH_NORMAL;
4045 if (computedStyle->IsTextCombined()) {
4046 MOZ_ASSERT(aFrame->IsTextFrame());
4047 auto textFrame = static_cast<const nsTextFrame*>(aFrame);
4048 auto clusters = textFrame->CountGraphemeClusters();
4049 if (clusters == 2) {
4050 variantWidth = NS_FONT_VARIANT_WIDTH_HALF;
4051 } else if (clusters == 3) {
4052 variantWidth = NS_FONT_VARIANT_WIDTH_THIRD;
4053 } else if (clusters == 4) {
4054 variantWidth = NS_FONT_VARIANT_WIDTH_QUARTER;
4055 }
4056 }
4057 return GetFontMetricsForComputedStyle(computedStyle, aFrame->PresContext(),
4058 aInflation, variantWidth);
4059 }
4060
GetFontMetricsForComputedStyle(ComputedStyle * aComputedStyle,nsPresContext * aPresContext,float aInflation,uint8_t aVariantWidth)4061 already_AddRefed<nsFontMetrics> nsLayoutUtils::GetFontMetricsForComputedStyle(
4062 ComputedStyle* aComputedStyle, nsPresContext* aPresContext,
4063 float aInflation, uint8_t aVariantWidth) {
4064 WritingMode wm(aComputedStyle);
4065 const nsStyleFont* styleFont = aComputedStyle->StyleFont();
4066 nsFontMetrics::Params params;
4067 params.language = styleFont->mLanguage;
4068 params.explicitLanguage = styleFont->mExplicitLanguage;
4069 params.orientation = wm.IsVertical() && !wm.IsSideways()
4070 ? nsFontMetrics::eVertical
4071 : nsFontMetrics::eHorizontal;
4072 // pass the user font set object into the device context to
4073 // pass along to CreateFontGroup
4074 params.userFontSet = aPresContext->GetUserFontSet();
4075 params.textPerf = aPresContext->GetTextPerfMetrics();
4076 params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup();
4077
4078 // When aInflation is 1.0 and we don't require width variant, avoid
4079 // making a local copy of the nsFont.
4080 // This also avoids running font.size through floats when it is large,
4081 // which would be lossy. Fortunately, in such cases, aInflation is
4082 // guaranteed to be 1.0f.
4083 if (aInflation == 1.0f && aVariantWidth == NS_FONT_VARIANT_WIDTH_NORMAL) {
4084 return aPresContext->GetMetricsFor(styleFont->mFont, params);
4085 }
4086
4087 nsFont font = styleFont->mFont;
4088 MOZ_ASSERT(!IsNaN(float(font.size.ToCSSPixels())),
4089 "Style font should never be NaN");
4090 font.size.ScaleBy(aInflation);
4091 if (MOZ_UNLIKELY(IsNaN(float(font.size.ToCSSPixels())))) {
4092 font.size = {0};
4093 }
4094 font.variantWidth = aVariantWidth;
4095 return aPresContext->GetMetricsFor(font, params);
4096 }
4097
FindChildContainingDescendant(nsIFrame * aParent,nsIFrame * aDescendantFrame)4098 nsIFrame* nsLayoutUtils::FindChildContainingDescendant(
4099 nsIFrame* aParent, nsIFrame* aDescendantFrame) {
4100 nsIFrame* result = aDescendantFrame;
4101
4102 while (result) {
4103 nsIFrame* parent = result->GetParent();
4104 if (parent == aParent) {
4105 break;
4106 }
4107
4108 // The frame is not an immediate child of aParent so walk up another level
4109 result = parent;
4110 }
4111
4112 return result;
4113 }
4114
FindNearestBlockAncestor(nsIFrame * aFrame)4115 nsBlockFrame* nsLayoutUtils::FindNearestBlockAncestor(nsIFrame* aFrame) {
4116 nsIFrame* nextAncestor;
4117 for (nextAncestor = aFrame->GetParent(); nextAncestor;
4118 nextAncestor = nextAncestor->GetParent()) {
4119 nsBlockFrame* block = do_QueryFrame(nextAncestor);
4120 if (block) return block;
4121 }
4122 return nullptr;
4123 }
4124
GetNonGeneratedAncestor(nsIFrame * aFrame)4125 nsIFrame* nsLayoutUtils::GetNonGeneratedAncestor(nsIFrame* aFrame) {
4126 if (!aFrame->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT)) return aFrame;
4127
4128 nsIFrame* f = aFrame;
4129 do {
4130 f = GetParentOrPlaceholderFor(f);
4131 } while (f->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT));
4132 return f;
4133 }
4134
GetParentOrPlaceholderFor(const nsIFrame * aFrame)4135 nsIFrame* nsLayoutUtils::GetParentOrPlaceholderFor(const nsIFrame* aFrame) {
4136 if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
4137 !aFrame->GetPrevInFlow()) {
4138 return aFrame->GetProperty(nsIFrame::PlaceholderFrameProperty());
4139 }
4140 return aFrame->GetParent();
4141 }
4142
GetParentOrPlaceholderForCrossDoc(const nsIFrame * aFrame)4143 nsIFrame* nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(
4144 const nsIFrame* aFrame) {
4145 nsIFrame* f = GetParentOrPlaceholderFor(aFrame);
4146 if (f) return f;
4147 return GetCrossDocParentFrameInProcess(aFrame);
4148 }
4149
GetDisplayListParent(nsIFrame * aFrame)4150 nsIFrame* nsLayoutUtils::GetDisplayListParent(nsIFrame* aFrame) {
4151 if (aFrame->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
4152 return aFrame->GetParent();
4153 }
4154 return nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(aFrame);
4155 }
4156
GetPrevContinuationOrIBSplitSibling(const nsIFrame * aFrame)4157 nsIFrame* nsLayoutUtils::GetPrevContinuationOrIBSplitSibling(
4158 const nsIFrame* aFrame) {
4159 if (nsIFrame* result = aFrame->GetPrevContinuation()) {
4160 return result;
4161 }
4162
4163 if (aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
4164 // We are the first frame in the continuation chain. Get the ib-split prev
4165 // sibling property stored in us.
4166 return aFrame->GetProperty(nsIFrame::IBSplitPrevSibling());
4167 }
4168
4169 return nullptr;
4170 }
4171
GetNextContinuationOrIBSplitSibling(const nsIFrame * aFrame)4172 nsIFrame* nsLayoutUtils::GetNextContinuationOrIBSplitSibling(
4173 const nsIFrame* aFrame) {
4174 if (nsIFrame* result = aFrame->GetNextContinuation()) {
4175 return result;
4176 }
4177
4178 if (aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
4179 // We only store the ib-split sibling annotation with the first frame in the
4180 // continuation chain.
4181 return aFrame->FirstContinuation()->GetProperty(nsIFrame::IBSplitSibling());
4182 }
4183
4184 return nullptr;
4185 }
4186
FirstContinuationOrIBSplitSibling(const nsIFrame * aFrame)4187 nsIFrame* nsLayoutUtils::FirstContinuationOrIBSplitSibling(
4188 const nsIFrame* aFrame) {
4189 nsIFrame* result = aFrame->FirstContinuation();
4190
4191 if (result->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
4192 while (auto* f = result->GetProperty(nsIFrame::IBSplitPrevSibling())) {
4193 result = f;
4194 }
4195 }
4196
4197 return result;
4198 }
4199
LastContinuationOrIBSplitSibling(const nsIFrame * aFrame)4200 nsIFrame* nsLayoutUtils::LastContinuationOrIBSplitSibling(
4201 const nsIFrame* aFrame) {
4202 nsIFrame* result = aFrame->FirstContinuation();
4203
4204 if (result->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
4205 while (auto* f = result->GetProperty(nsIFrame::IBSplitSibling())) {
4206 result = f;
4207 }
4208 }
4209
4210 return result->LastContinuation();
4211 }
4212
IsFirstContinuationOrIBSplitSibling(const nsIFrame * aFrame)4213 bool nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(
4214 const nsIFrame* aFrame) {
4215 if (aFrame->GetPrevContinuation()) {
4216 return false;
4217 }
4218 if (aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT) &&
4219 aFrame->GetProperty(nsIFrame::IBSplitPrevSibling())) {
4220 return false;
4221 }
4222
4223 return true;
4224 }
4225
IsViewportScrollbarFrame(nsIFrame * aFrame)4226 bool nsLayoutUtils::IsViewportScrollbarFrame(nsIFrame* aFrame) {
4227 if (!aFrame) return false;
4228
4229 nsIFrame* rootScrollFrame = aFrame->PresShell()->GetRootScrollFrame();
4230 if (!rootScrollFrame) return false;
4231
4232 nsIScrollableFrame* rootScrollableFrame = do_QueryFrame(rootScrollFrame);
4233 NS_ASSERTION(rootScrollableFrame, "The root scorollable frame is null");
4234
4235 if (!IsProperAncestorFrame(rootScrollFrame, aFrame)) return false;
4236
4237 nsIFrame* rootScrolledFrame = rootScrollableFrame->GetScrolledFrame();
4238 return !(rootScrolledFrame == aFrame ||
4239 IsProperAncestorFrame(rootScrolledFrame, aFrame));
4240 }
4241
4242 /**
4243 * Use only for paddings / widths / heights, since it clamps negative calc() to
4244 * 0.
4245 */
4246 template <typename LengthPercentageLike>
GetAbsoluteCoord(const LengthPercentageLike & aStyle,nscoord & aResult)4247 static bool GetAbsoluteCoord(const LengthPercentageLike& aStyle,
4248 nscoord& aResult) {
4249 if (!aStyle.ConvertsToLength()) {
4250 return false;
4251 }
4252 aResult = std::max(0, aStyle.ToLength());
4253 return true;
4254 }
4255
4256 static nscoord GetBSizePercentBasisAdjustment(StyleBoxSizing aBoxSizing,
4257 nsIFrame* aFrame,
4258 bool aHorizontalAxis,
4259 bool aResolvesAgainstPaddingBox);
4260
4261 static bool GetPercentBSize(const LengthPercentage& aStyle, nsIFrame* aFrame,
4262 bool aHorizontalAxis, nscoord& aResult);
4263
4264 // Only call on style coords for which GetAbsoluteCoord returned false.
4265 template <typename SizeOrMaxSize>
GetPercentBSize(const SizeOrMaxSize & aStyle,nsIFrame * aFrame,bool aHorizontalAxis,nscoord & aResult)4266 static bool GetPercentBSize(const SizeOrMaxSize& aStyle, nsIFrame* aFrame,
4267 bool aHorizontalAxis, nscoord& aResult) {
4268 if (!aStyle.IsLengthPercentage()) {
4269 return false;
4270 }
4271 return GetPercentBSize(aStyle.AsLengthPercentage(), aFrame, aHorizontalAxis,
4272 aResult);
4273 }
4274
GetPercentBSize(const LengthPercentage & aStyle,nsIFrame * aFrame,bool aHorizontalAxis,nscoord & aResult)4275 static bool GetPercentBSize(const LengthPercentage& aStyle, nsIFrame* aFrame,
4276 bool aHorizontalAxis, nscoord& aResult) {
4277 if (!aStyle.HasPercent()) {
4278 return false;
4279 }
4280
4281 MOZ_ASSERT(!aStyle.ConvertsToLength(),
4282 "GetAbsoluteCoord should have handled this");
4283
4284 // During reflow, nsHTMLScrollFrame::ReflowScrolledFrame uses
4285 // SetComputedHeight on the reflow input for its child to propagate its
4286 // computed height to the scrolled content. So here we skip to the scroll
4287 // frame that contains this scrolled content in order to get the same
4288 // behavior as layout when computing percentage heights.
4289 nsIFrame* f = aFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME);
4290 if (!f) {
4291 MOZ_ASSERT_UNREACHABLE("top of frame tree not a containing block");
4292 return false;
4293 }
4294
4295 WritingMode wm = f->GetWritingMode();
4296
4297 const nsStylePosition* pos = f->StylePosition();
4298 const auto& bSizeCoord = pos->BSize(wm);
4299 nscoord h;
4300 if (!GetAbsoluteCoord(bSizeCoord, h) &&
4301 !GetPercentBSize(bSizeCoord, f, aHorizontalAxis, h)) {
4302 LayoutFrameType fType = f->Type();
4303 if (fType != LayoutFrameType::Viewport &&
4304 fType != LayoutFrameType::Canvas &&
4305 fType != LayoutFrameType::PageContent) {
4306 // There's no basis for the percentage height, so it acts like auto.
4307 // Should we consider a max-height < min-height pair a basis for
4308 // percentage heights? The spec is somewhat unclear, and not doing
4309 // so is simpler and avoids troubling discontinuities in behavior,
4310 // so I'll choose not to. -LDB
4311 return false;
4312 }
4313 // For the viewport, canvas, and page-content kids, the percentage
4314 // basis is just the parent block-size.
4315 h = f->BSize(wm);
4316 if (h == NS_UNCONSTRAINEDSIZE) {
4317 // We don't have a percentage basis after all
4318 return false;
4319 }
4320 }
4321
4322 const auto& maxBSizeCoord = pos->MaxBSize(wm);
4323
4324 nscoord maxh;
4325 if (GetAbsoluteCoord(maxBSizeCoord, maxh) ||
4326 GetPercentBSize(maxBSizeCoord, f, aHorizontalAxis, maxh)) {
4327 if (maxh < h) h = maxh;
4328 }
4329
4330 const auto& minBSizeCoord = pos->MinBSize(wm);
4331
4332 nscoord minh;
4333 if (GetAbsoluteCoord(minBSizeCoord, minh) ||
4334 GetPercentBSize(minBSizeCoord, f, aHorizontalAxis, minh)) {
4335 if (minh > h) {
4336 h = minh;
4337 }
4338 }
4339
4340 // If we're an abspos box, percentages in that case resolve against the
4341 // padding box.
4342 //
4343 // TODO: This could conceivably cause some problems with fieldsets (which are
4344 // the other place that wants to ignore padding), but solving that here
4345 // without hardcoding a check for f being a fieldset-content frame is a bit of
4346 // a pain.
4347 const bool resolvesAgainstPaddingBox = aFrame->IsAbsolutelyPositioned();
4348 h += GetBSizePercentBasisAdjustment(pos->mBoxSizing, f, aHorizontalAxis,
4349 resolvesAgainstPaddingBox);
4350
4351 aResult = std::max(aStyle.Resolve(std::max(h, 0)), 0);
4352 return true;
4353 }
4354
4355 // Return true if aStyle can be resolved to a definite value and if so
4356 // return that value in aResult.
GetDefiniteSize(const LengthPercentage & aStyle,nsIFrame * aFrame,bool aIsInlineAxis,const Maybe<LogicalSize> & aPercentageBasis,nscoord * aResult)4357 static bool GetDefiniteSize(const LengthPercentage& aStyle, nsIFrame* aFrame,
4358 bool aIsInlineAxis,
4359 const Maybe<LogicalSize>& aPercentageBasis,
4360 nscoord* aResult) {
4361 if (aStyle.ConvertsToLength()) {
4362 *aResult = aStyle.ToLength();
4363 return true;
4364 }
4365
4366 if (!aPercentageBasis) {
4367 return false;
4368 }
4369
4370 auto wm = aFrame->GetWritingMode();
4371 nscoord pb = aIsInlineAxis ? aPercentageBasis.value().ISize(wm)
4372 : aPercentageBasis.value().BSize(wm);
4373 if (pb == NS_UNCONSTRAINEDSIZE) {
4374 return false;
4375 }
4376 *aResult = std::max(0, aStyle.Resolve(pb));
4377 return true;
4378 }
4379
4380 // Return true if aStyle can be resolved to a definite value and if so
4381 // return that value in aResult.
4382 template <typename SizeOrMaxSize>
GetDefiniteSize(const SizeOrMaxSize & aStyle,nsIFrame * aFrame,bool aIsInlineAxis,const Maybe<LogicalSize> & aPercentageBasis,nscoord * aResult)4383 static bool GetDefiniteSize(const SizeOrMaxSize& aStyle, nsIFrame* aFrame,
4384 bool aIsInlineAxis,
4385 const Maybe<LogicalSize>& aPercentageBasis,
4386 nscoord* aResult) {
4387 if (!aStyle.IsLengthPercentage()) {
4388 return false;
4389 }
4390 return GetDefiniteSize(aStyle.AsLengthPercentage(), aFrame, aIsInlineAxis,
4391 aPercentageBasis, aResult);
4392 }
4393
4394 // NOTE: this function will be replaced by GetDefiniteSizeTakenByBoxSizing (bug
4395 // 1363918). Please do not add new uses of this function.
4396 //
4397 // Get the amount of space to add or subtract out of aFrame's 'block-size' or
4398 // property value due its borders and paddings, given the box-sizing value in
4399 // aBoxSizing.
4400 //
4401 // aHorizontalAxis is true if our inline direction is horizontal and our block
4402 // direction is vertical. aResolvesAgainstPaddingBox is true if padding should
4403 // be added or not removed.
GetBSizePercentBasisAdjustment(StyleBoxSizing aBoxSizing,nsIFrame * aFrame,bool aHorizontalAxis,bool aResolvesAgainstPaddingBox)4404 static nscoord GetBSizePercentBasisAdjustment(StyleBoxSizing aBoxSizing,
4405 nsIFrame* aFrame,
4406 bool aHorizontalAxis,
4407 bool aResolvesAgainstPaddingBox) {
4408 nscoord adjustment = 0;
4409 if (aBoxSizing == StyleBoxSizing::Border) {
4410 const auto& border = aFrame->StyleBorder()->GetComputedBorder();
4411 adjustment -= aHorizontalAxis ? border.TopBottom() : border.LeftRight();
4412 }
4413 if ((aBoxSizing == StyleBoxSizing::Border) == !aResolvesAgainstPaddingBox) {
4414 const auto& stylePadding = aFrame->StylePadding()->mPadding;
4415 const LengthPercentage& paddingStart =
4416 stylePadding.Get(aHorizontalAxis ? eSideTop : eSideLeft);
4417 const LengthPercentage& paddingEnd =
4418 stylePadding.Get(aHorizontalAxis ? eSideBottom : eSideRight);
4419 nscoord pad;
4420 // XXXbz Calling GetPercentBSize on padding values looks bogus, since
4421 // percent padding is always a percentage of the inline-size of the
4422 // containing block. We should perhaps just treat non-absolute paddings
4423 // here as 0 instead, except that in some cases the width may in fact be
4424 // known. See bug 1231059.
4425 if (GetAbsoluteCoord(paddingStart, pad) ||
4426 GetPercentBSize(paddingStart, aFrame, aHorizontalAxis, pad)) {
4427 adjustment += aResolvesAgainstPaddingBox ? pad : -pad;
4428 }
4429 if (GetAbsoluteCoord(paddingEnd, pad) ||
4430 GetPercentBSize(paddingEnd, aFrame, aHorizontalAxis, pad)) {
4431 adjustment += aResolvesAgainstPaddingBox ? pad : -pad;
4432 }
4433 }
4434 return adjustment;
4435 }
4436
4437 // Get the amount of space taken out of aFrame's content area due to its
4438 // borders and paddings given the box-sizing value in aBoxSizing. We don't
4439 // get aBoxSizing from the frame because some callers want to compute this for
4440 // specific box-sizing values.
4441 // aIsInlineAxis is true if we're computing for aFrame's inline axis.
4442 // aIgnorePadding is true if padding should be ignored.
GetDefiniteSizeTakenByBoxSizing(StyleBoxSizing aBoxSizing,nsIFrame * aFrame,bool aIsInlineAxis,bool aIgnorePadding,const Maybe<LogicalSize> & aPercentageBasis)4443 static nscoord GetDefiniteSizeTakenByBoxSizing(
4444 StyleBoxSizing aBoxSizing, nsIFrame* aFrame, bool aIsInlineAxis,
4445 bool aIgnorePadding, const Maybe<LogicalSize>& aPercentageBasis) {
4446 nscoord sizeTakenByBoxSizing = 0;
4447 if (MOZ_UNLIKELY(aBoxSizing == StyleBoxSizing::Border)) {
4448 const bool isHorizontalAxis =
4449 aIsInlineAxis == !aFrame->GetWritingMode().IsVertical();
4450 const nsStyleBorder* styleBorder = aFrame->StyleBorder();
4451 sizeTakenByBoxSizing = isHorizontalAxis
4452 ? styleBorder->GetComputedBorder().LeftRight()
4453 : styleBorder->GetComputedBorder().TopBottom();
4454 if (!aIgnorePadding) {
4455 const auto& stylePadding = aFrame->StylePadding()->mPadding;
4456 const LengthPercentage& pStart =
4457 stylePadding.Get(isHorizontalAxis ? eSideLeft : eSideTop);
4458 const LengthPercentage& pEnd =
4459 stylePadding.Get(isHorizontalAxis ? eSideRight : eSideBottom);
4460 nscoord pad;
4461 // XXXbz Calling GetPercentBSize on padding values looks bogus, since
4462 // percent padding is always a percentage of the inline-size of the
4463 // containing block. We should perhaps just treat non-absolute paddings
4464 // here as 0 instead, except that in some cases the width may in fact be
4465 // known. See bug 1231059.
4466 if (GetDefiniteSize(pStart, aFrame, aIsInlineAxis, aPercentageBasis,
4467 &pad) ||
4468 (aPercentageBasis.isNothing() &&
4469 GetPercentBSize(pStart, aFrame, isHorizontalAxis, pad))) {
4470 sizeTakenByBoxSizing += pad;
4471 }
4472 if (GetDefiniteSize(pEnd, aFrame, aIsInlineAxis, aPercentageBasis,
4473 &pad) ||
4474 (aPercentageBasis.isNothing() &&
4475 GetPercentBSize(pEnd, aFrame, isHorizontalAxis, pad))) {
4476 sizeTakenByBoxSizing += pad;
4477 }
4478 }
4479 }
4480 return sizeTakenByBoxSizing;
4481 }
4482
4483 // Handles only max-content and min-content, and
4484 // -moz-fit-content for min-width and max-width, since the others
4485 // (-moz-fit-content for width, and -moz-available) have no effect on
4486 // intrinsic widths.
GetIntrinsicCoord(nsIFrame::ExtremumLength aStyle,gfxContext * aRenderingContext,nsIFrame * aFrame,Maybe<nscoord> aInlineSizeFromAspectRatio,nsIFrame::SizeProperty aProperty,nscoord & aResult)4487 static bool GetIntrinsicCoord(nsIFrame::ExtremumLength aStyle,
4488 gfxContext* aRenderingContext, nsIFrame* aFrame,
4489 Maybe<nscoord> aInlineSizeFromAspectRatio,
4490 nsIFrame::SizeProperty aProperty,
4491 nscoord& aResult) {
4492 if (aStyle == nsIFrame::ExtremumLength::MozAvailable) {
4493 return false;
4494 }
4495
4496 if (aStyle == nsIFrame::ExtremumLength::FitContentFunction) {
4497 // fit-content() should be handled by the caller.
4498 return false;
4499 }
4500
4501 if (aStyle == nsIFrame::ExtremumLength::FitContent) {
4502 switch (aProperty) {
4503 case nsIFrame::SizeProperty::Size:
4504 // handle like 'width: auto'
4505 return false;
4506 case nsIFrame::SizeProperty::MaxSize:
4507 // constrain large 'width' values down to max-content
4508 aStyle = nsIFrame::ExtremumLength::MaxContent;
4509 break;
4510 case nsIFrame::SizeProperty::MinSize:
4511 // constrain small 'width' or 'max-width' values up to min-content
4512 aStyle = nsIFrame::ExtremumLength::MinContent;
4513 break;
4514 }
4515 }
4516
4517 NS_ASSERTION(aStyle == nsIFrame::ExtremumLength::MinContent ||
4518 aStyle == nsIFrame::ExtremumLength::MaxContent,
4519 "should have reduced everything remaining to one of these");
4520
4521 // If aFrame is a container for font size inflation, then shrink
4522 // wrapping inside of it should not apply font size inflation.
4523 AutoMaybeDisableFontInflation an(aFrame);
4524
4525 if (aInlineSizeFromAspectRatio) {
4526 aResult = *aInlineSizeFromAspectRatio;
4527 } else if (aStyle == nsIFrame::ExtremumLength::MaxContent) {
4528 aResult = aFrame->GetPrefISize(aRenderingContext);
4529 } else {
4530 aResult = aFrame->GetMinISize(aRenderingContext);
4531 }
4532 return true;
4533 }
4534
4535 template <typename SizeOrMaxSize>
GetIntrinsicCoord(const SizeOrMaxSize & aStyle,gfxContext * aRenderingContext,nsIFrame * aFrame,Maybe<nscoord> aInlineSizeFromAspectRatio,nsIFrame::SizeProperty aProperty,nscoord & aResult)4536 static bool GetIntrinsicCoord(const SizeOrMaxSize& aStyle,
4537 gfxContext* aRenderingContext, nsIFrame* aFrame,
4538 Maybe<nscoord> aInlineSizeFromAspectRatio,
4539 nsIFrame::SizeProperty aProperty,
4540 nscoord& aResult) {
4541 auto length = nsIFrame::ToExtremumLength(aStyle);
4542 if (!length) {
4543 return false;
4544 }
4545 return GetIntrinsicCoord(*length, aRenderingContext, aFrame,
4546 aInlineSizeFromAspectRatio, aProperty, aResult);
4547 }
4548
4549 #undef DEBUG_INTRINSIC_WIDTH
4550
4551 #ifdef DEBUG_INTRINSIC_WIDTH
4552 static int32_t gNoiseIndent = 0;
4553 #endif
4554
GetFitContentSizeForMaxOrPreferredSize(const IntrinsicISizeType aType,const nsIFrame::SizeProperty aProperty,const nsIFrame * aFrame,const LengthPercentage & aStyleSize,const nscoord aInitialValue,const nscoord aMinContentSize,const nscoord aMaxContentSize)4555 static nscoord GetFitContentSizeForMaxOrPreferredSize(
4556 const IntrinsicISizeType aType, const nsIFrame::SizeProperty aProperty,
4557 const nsIFrame* aFrame, const LengthPercentage& aStyleSize,
4558 const nscoord aInitialValue, const nscoord aMinContentSize,
4559 const nscoord aMaxContentSize) {
4560 MOZ_ASSERT(aProperty != nsIFrame::SizeProperty::MinSize);
4561
4562 nscoord size = NS_UNCONSTRAINEDSIZE;
4563 // 1. Treat fit-content()'s arg as a plain LengthPercentage
4564 // However, we have to handle the cyclic percentage contribution first.
4565 //
4566 // https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution
4567 if (aType == IntrinsicISizeType::MinISize &&
4568 aFrame->IsPercentageResolvedAgainstZero(aStyleSize, aProperty)) {
4569 // Case (c) in the spec.
4570 // FIXME: This doesn't follow the spec for calc(). We should fix this in
4571 // Bug 1463700.
4572 size = 0;
4573 } else if (!GetAbsoluteCoord(aStyleSize, size)) {
4574 // As initial value. Case (a) and (b) in the spec.
4575 size = aInitialValue;
4576 }
4577
4578 // 2. Clamp size by min-content and max-content.
4579 return std::max(aMinContentSize, std::min(aMaxContentSize, size));
4580 }
4581
4582 /**
4583 * Add aOffsets which describes what to add on outside of the content box
4584 * aContentSize (controlled by 'box-sizing') and apply min/max properties.
4585 * We have to account for these properties after getting all the offsets
4586 * (margin, border, padding) because percentages do not operate linearly.
4587 * Doing this is ok because although percentages aren't handled linearly,
4588 * they are handled monotonically.
4589 *
4590 * @param aContentSize the content size calculated so far
4591 (@see IntrinsicForContainer)
4592 * @param aContentMinSize ditto min content size
4593 * @param aStyleSize a 'width' or 'height' property value
4594 * @param aFixedMinSize if aStyleMinSize is a definite size then this points to
4595 * the value, otherwise nullptr
4596 * @param aStyleMinSize a 'min-width' or 'min-height' property value
4597 * @param aFixedMaxSize if aStyleMaxSize is a definite size then this points to
4598 * the value, otherwise nullptr
4599 * @param aStyleMaxSize a 'max-width' or 'max-height' property value
4600 * @param aInlineSizeFromAspectRatio the content-box inline size computed from
4601 * aspect-ratio and the definite block size.
4602 * We use this value to resolve
4603 * {min|max}-content.
4604 * @param aFlags same as for IntrinsicForContainer
4605 * @param aContainerWM the container's WM
4606 */
AddIntrinsicSizeOffset(gfxContext * aRenderingContext,nsIFrame * aFrame,const nsIFrame::IntrinsicSizeOffsetData & aOffsets,IntrinsicISizeType aType,StyleBoxSizing aBoxSizing,nscoord aContentSize,nscoord aContentMinSize,const StyleSize & aStyleSize,const nscoord * aFixedMinSize,const StyleSize & aStyleMinSize,const nscoord * aFixedMaxSize,const StyleMaxSize & aStyleMaxSize,Maybe<nscoord> aInlineSizeFromAspectRatio,uint32_t aFlags,PhysicalAxis aAxis)4607 static nscoord AddIntrinsicSizeOffset(
4608 gfxContext* aRenderingContext, nsIFrame* aFrame,
4609 const nsIFrame::IntrinsicSizeOffsetData& aOffsets, IntrinsicISizeType aType,
4610 StyleBoxSizing aBoxSizing, nscoord aContentSize, nscoord aContentMinSize,
4611 const StyleSize& aStyleSize, const nscoord* aFixedMinSize,
4612 const StyleSize& aStyleMinSize, const nscoord* aFixedMaxSize,
4613 const StyleMaxSize& aStyleMaxSize,
4614 Maybe<nscoord> aInlineSizeFromAspectRatio, uint32_t aFlags,
4615 PhysicalAxis aAxis) {
4616 nscoord result = aContentSize;
4617 nscoord min = aContentMinSize;
4618 nscoord coordOutsideSize = 0;
4619
4620 if (!(aFlags & nsLayoutUtils::IGNORE_PADDING)) {
4621 coordOutsideSize += aOffsets.padding;
4622 }
4623
4624 coordOutsideSize += aOffsets.border;
4625
4626 if (aBoxSizing == StyleBoxSizing::Border) {
4627 min += coordOutsideSize;
4628 result = NSCoordSaturatingAdd(result, coordOutsideSize);
4629
4630 coordOutsideSize = 0;
4631 }
4632
4633 coordOutsideSize += aOffsets.margin;
4634
4635 min += coordOutsideSize;
4636
4637 // Compute min-content/max-content for fit-content().
4638 nscoord minContent = 0;
4639 nscoord maxContent = NS_UNCONSTRAINEDSIZE;
4640 if (aStyleSize.IsFitContentFunction() ||
4641 aStyleMaxSize.IsFitContentFunction() ||
4642 aStyleMinSize.IsFitContentFunction()) {
4643 if (aInlineSizeFromAspectRatio) {
4644 minContent = maxContent = *aInlineSizeFromAspectRatio;
4645 } else {
4646 minContent = aFrame->GetMinISize(aRenderingContext);
4647 maxContent = aFrame->GetPrefISize(aRenderingContext);
4648 }
4649 }
4650
4651 // Compute size.
4652 nscoord size = NS_UNCONSTRAINEDSIZE;
4653 if (aType == IntrinsicISizeType::MinISize &&
4654 aFrame->IsPercentageResolvedAgainstZero(aStyleSize, aStyleMaxSize)) {
4655 // XXX bug 1463700: this doesn't handle calc() according to spec
4656 result = 0; // let |min| handle padding/border/margin
4657 } else if (GetAbsoluteCoord(aStyleSize, size) ||
4658 GetIntrinsicCoord(aStyleSize, aRenderingContext, aFrame,
4659 aInlineSizeFromAspectRatio,
4660 nsIFrame::SizeProperty::Size, size)) {
4661 result = size + coordOutsideSize;
4662 } else if (aStyleSize.IsFitContentFunction()) {
4663 // |result| here is the content size or border size, depends on
4664 // StyleBoxSizing. We use it as the initial value when handling the cyclic
4665 // percentage.
4666 nscoord initial = result;
4667 nscoord fitContentFuncSize = GetFitContentSizeForMaxOrPreferredSize(
4668 aType, nsIFrame::SizeProperty::Size, aFrame,
4669 aStyleSize.AsFitContentFunction(), initial, minContent, maxContent);
4670 // Add border and padding.
4671 result = NSCoordSaturatingAdd(fitContentFuncSize, coordOutsideSize);
4672 } else {
4673 result = NSCoordSaturatingAdd(result, coordOutsideSize);
4674 }
4675
4676 // Compute max-size.
4677 nscoord maxSize = aFixedMaxSize ? *aFixedMaxSize : 0;
4678 if (aFixedMaxSize ||
4679 GetIntrinsicCoord(aStyleMaxSize, aRenderingContext, aFrame,
4680 aInlineSizeFromAspectRatio,
4681 nsIFrame::SizeProperty::MaxSize, maxSize)) {
4682 maxSize += coordOutsideSize;
4683 if (result > maxSize) {
4684 result = maxSize;
4685 }
4686 } else if (aStyleMaxSize.IsFitContentFunction()) {
4687 nscoord fitContentFuncSize = GetFitContentSizeForMaxOrPreferredSize(
4688 aType, nsIFrame::SizeProperty::MaxSize, aFrame,
4689 aStyleMaxSize.AsFitContentFunction(), NS_UNCONSTRAINEDSIZE, minContent,
4690 maxContent);
4691 maxSize = NSCoordSaturatingAdd(fitContentFuncSize, coordOutsideSize);
4692 if (result > maxSize) {
4693 result = maxSize;
4694 }
4695 }
4696
4697 // Compute min-size.
4698 nscoord minSize = aFixedMinSize ? *aFixedMinSize : 0;
4699 if (aFixedMinSize ||
4700 GetIntrinsicCoord(aStyleMinSize, aRenderingContext, aFrame,
4701 aInlineSizeFromAspectRatio,
4702 nsIFrame::SizeProperty::MinSize, minSize)) {
4703 minSize += coordOutsideSize;
4704 if (result < minSize) {
4705 result = minSize;
4706 }
4707 } else if (aStyleMinSize.IsFitContentFunction()) {
4708 if (!GetAbsoluteCoord(aStyleMinSize.AsFitContentFunction(), minSize)) {
4709 // FIXME: Bug 1463700, we should resolve only the percentage part to 0
4710 // such as min-width: fit-content(calc(50% + 50px)).
4711 minSize = 0;
4712 }
4713 nscoord fitContentFuncSize =
4714 std::max(minContent, std::min(maxContent, minSize));
4715 minSize = NSCoordSaturatingAdd(fitContentFuncSize, coordOutsideSize);
4716 if (result < minSize) {
4717 result = minSize;
4718 }
4719 }
4720
4721 if (result < min) {
4722 result = min;
4723 }
4724
4725 const nsStyleDisplay* disp = aFrame->StyleDisplay();
4726 if (aFrame->IsThemed(disp)) {
4727 LayoutDeviceIntSize devSize;
4728 bool canOverride = true;
4729 nsPresContext* pc = aFrame->PresContext();
4730 pc->Theme()->GetMinimumWidgetSize(pc, aFrame, disp->EffectiveAppearance(),
4731 &devSize, &canOverride);
4732 nscoord themeSize = pc->DevPixelsToAppUnits(
4733 aAxis == eAxisVertical ? devSize.height : devSize.width);
4734 // GetMinimumWidgetSize() returns a border-box width.
4735 themeSize += aOffsets.margin;
4736 if (themeSize > result || !canOverride) {
4737 result = themeSize;
4738 }
4739 }
4740 return result;
4741 }
4742
AddStateBitToAncestors(nsIFrame * aFrame,nsFrameState aBit)4743 static void AddStateBitToAncestors(nsIFrame* aFrame, nsFrameState aBit) {
4744 for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
4745 if (f->HasAnyStateBits(aBit)) {
4746 break;
4747 }
4748 f->AddStateBits(aBit);
4749 }
4750 }
4751
4752 /* static */
IntrinsicForAxis(PhysicalAxis aAxis,gfxContext * aRenderingContext,nsIFrame * aFrame,IntrinsicISizeType aType,const Maybe<LogicalSize> & aPercentageBasis,uint32_t aFlags,nscoord aMarginBoxMinSizeClamp)4753 nscoord nsLayoutUtils::IntrinsicForAxis(
4754 PhysicalAxis aAxis, gfxContext* aRenderingContext, nsIFrame* aFrame,
4755 IntrinsicISizeType aType, const Maybe<LogicalSize>& aPercentageBasis,
4756 uint32_t aFlags, nscoord aMarginBoxMinSizeClamp) {
4757 MOZ_ASSERT(aFrame, "null frame");
4758 MOZ_ASSERT(aFrame->GetParent(),
4759 "IntrinsicForAxis called on frame not in tree");
4760 MOZ_ASSERT(aFrame->GetParent()->Type() != LayoutFrameType::GridContainer ||
4761 aPercentageBasis.isSome(),
4762 "grid layout should always pass a percentage basis");
4763
4764 const bool horizontalAxis = MOZ_LIKELY(aAxis == eAxisHorizontal);
4765 #ifdef DEBUG_INTRINSIC_WIDTH
4766 nsIFrame::IndentBy(stderr, gNoiseIndent);
4767 aFrame->ListTag(stderr);
4768 printf_stderr(" %s %s intrinsic size for container:\n",
4769 aType == IntrinsicISizeType::MinISize ? "min" : "pref",
4770 horizontalAxis ? "horizontal" : "vertical");
4771 #endif
4772
4773 // If aFrame is a container for font size inflation, then shrink
4774 // wrapping inside of it should not apply font size inflation.
4775 AutoMaybeDisableFontInflation an(aFrame);
4776
4777 // We want the size this frame will contribute to the parent's inline-size,
4778 // so we work in the parent's writing mode; but if aFrame is orthogonal to
4779 // its parent, we'll need to look at its BSize instead of min/pref-ISize.
4780 const nsStylePosition* stylePos = aFrame->StylePosition();
4781 StyleBoxSizing boxSizing = stylePos->mBoxSizing;
4782
4783 StyleSize styleMinISize =
4784 horizontalAxis ? stylePos->mMinWidth : stylePos->mMinHeight;
4785 StyleSize styleISize =
4786 (aFlags & MIN_INTRINSIC_ISIZE)
4787 ? styleMinISize
4788 : (horizontalAxis ? stylePos->mWidth : stylePos->mHeight);
4789 MOZ_ASSERT(!(aFlags & MIN_INTRINSIC_ISIZE) || styleISize.IsAuto() ||
4790 nsIFrame::ToExtremumLength(styleISize),
4791 "should only use MIN_INTRINSIC_ISIZE for intrinsic values");
4792 StyleMaxSize styleMaxISize =
4793 horizontalAxis ? stylePos->mMaxWidth : stylePos->mMaxHeight;
4794
4795 PhysicalAxis ourInlineAxis =
4796 aFrame->GetWritingMode().PhysicalAxis(eLogicalAxisInline);
4797 const bool isInlineAxis = aAxis == ourInlineAxis;
4798
4799 auto resetIfKeywords = [](StyleSize& aSize, StyleSize& aMinSize,
4800 StyleMaxSize& aMaxSize) {
4801 if (!aSize.IsLengthPercentage()) {
4802 aSize = StyleSize::Auto();
4803 }
4804 if (!aMinSize.IsLengthPercentage()) {
4805 aMinSize = StyleSize::Auto();
4806 }
4807 if (!aMaxSize.IsLengthPercentage()) {
4808 aMaxSize = StyleMaxSize::None();
4809 }
4810 };
4811 // According to the spec, max-content and min-content should behave as the
4812 // property's initial values in block axis.
4813 // It also make senses to use the initial values for -moz-fit-content and
4814 // -moz-available for intrinsic size in block axis. Therefore, we reset them
4815 // if needed.
4816 if (!isInlineAxis) {
4817 resetIfKeywords(styleISize, styleMinISize, styleMaxISize);
4818 }
4819
4820 // We build up two values starting with the content box, and then
4821 // adding padding, border and margin. The result is normally
4822 // |result|. Then, when we handle 'width', 'min-width', and
4823 // 'max-width', we use the results we've been building in |min| as a
4824 // minimum, overriding 'min-width'. This ensures two things:
4825 // * that we don't let a value of 'box-sizing' specifying a width
4826 // smaller than the padding/border inside the box-sizing box give
4827 // a content width less than zero
4828 // * that we prevent tables from becoming smaller than their
4829 // intrinsic minimum width
4830 nscoord result = 0, min = 0;
4831
4832 nscoord maxISize;
4833 bool haveFixedMaxISize = GetAbsoluteCoord(styleMaxISize, maxISize);
4834 nscoord minISize;
4835
4836 // Treat "min-width: auto" as 0.
4837 bool haveFixedMinISize;
4838 if (styleMinISize.IsAuto()) {
4839 // NOTE: Technically, "auto" is supposed to behave like "min-content" on
4840 // flex items. However, we don't need to worry about that here, because
4841 // flex items' min-sizes are intentionally ignored until the flex
4842 // container explicitly considers them during space distribution.
4843 minISize = 0;
4844 haveFixedMinISize = true;
4845 } else {
4846 haveFixedMinISize = GetAbsoluteCoord(styleMinISize, minISize);
4847 }
4848
4849 auto childWM = aFrame->GetWritingMode();
4850 nscoord pmPercentageBasis = NS_UNCONSTRAINEDSIZE;
4851 if (aPercentageBasis.isSome()) {
4852 // The padding/margin percentage basis is the inline-size in the parent's
4853 // writing-mode.
4854 pmPercentageBasis =
4855 aFrame->GetParent()->GetWritingMode().IsOrthogonalTo(childWM)
4856 ? aPercentageBasis->BSize(childWM)
4857 : aPercentageBasis->ISize(childWM);
4858 }
4859 nsIFrame::IntrinsicSizeOffsetData offsets =
4860 MOZ_LIKELY(isInlineAxis)
4861 ? aFrame->IntrinsicISizeOffsets(pmPercentageBasis)
4862 : aFrame->IntrinsicBSizeOffsets(pmPercentageBasis);
4863
4864 auto getContentBoxSizeToBoxSizingAdjust =
4865 [childWM, &offsets, &aFrame, isInlineAxis,
4866 pmPercentageBasis](const StyleBoxSizing aBoxSizing) {
4867 return aBoxSizing == StyleBoxSizing::Border
4868 ? LogicalSize(childWM,
4869 (isInlineAxis ? offsets
4870 : aFrame->IntrinsicISizeOffsets(
4871 pmPercentageBasis))
4872 .BorderPadding(),
4873 (!isInlineAxis ? offsets
4874 : aFrame->IntrinsicBSizeOffsets(
4875 pmPercentageBasis))
4876 .BorderPadding())
4877 : LogicalSize(childWM);
4878 };
4879
4880 Maybe<nscoord> inlineSizeFromAspectRatio;
4881 Maybe<LogicalSize> contentBoxSizeToBoxSizingAdjust;
4882
4883 const bool ignorePadding =
4884 (aFlags & IGNORE_PADDING) || aFrame->IsAbsolutelyPositioned();
4885
4886 // If we have a specified width (or a specified 'min-width' greater
4887 // than the specified 'max-width', which works out to the same thing),
4888 // don't even bother getting the frame's intrinsic width, because in
4889 // this case GetAbsoluteCoord(styleISize, w) will always succeed, so
4890 // we'll never need the intrinsic dimensions.
4891 if (styleISize.IsMaxContent() || styleISize.IsMinContent()) {
4892 MOZ_ASSERT(isInlineAxis);
4893 // -moz-fit-content and -moz-available enumerated widths compute intrinsic
4894 // widths just like auto.
4895 // For max-content and min-content, we handle them like
4896 // specified widths, but ignore box-sizing.
4897 boxSizing = StyleBoxSizing::Content;
4898 } else if (!styleISize.ConvertsToLength() &&
4899 !(styleISize.IsFitContentFunction() &&
4900 styleISize.AsFitContentFunction().ConvertsToLength()) &&
4901 !(haveFixedMinISize && haveFixedMaxISize &&
4902 maxISize <= minISize)) {
4903 #ifdef DEBUG_INTRINSIC_WIDTH
4904 ++gNoiseIndent;
4905 #endif
4906 if (MOZ_UNLIKELY(!isInlineAxis)) {
4907 IntrinsicSize intrinsicSize = aFrame->GetIntrinsicSize();
4908 const auto& intrinsicBSize =
4909 horizontalAxis ? intrinsicSize.width : intrinsicSize.height;
4910 if (intrinsicBSize) {
4911 result = *intrinsicBSize;
4912 } else {
4913 // We don't have an intrinsic bsize and we need aFrame's block-dir size.
4914 if (aFlags & BAIL_IF_REFLOW_NEEDED) {
4915 return NS_INTRINSIC_ISIZE_UNKNOWN;
4916 }
4917 // XXX Unfortunately, we probably don't know this yet, so this is
4918 // wrong... but it's not clear what we should do. If aFrame's inline
4919 // size hasn't been determined yet, we can't necessarily figure out its
4920 // block size either. For now, authors who put orthogonal elements into
4921 // things like buttons or table cells may have to explicitly provide
4922 // sizes rather than expecting intrinsic sizing to work "perfectly" in
4923 // underspecified cases.
4924 result = aFrame->BSize();
4925 }
4926 } else {
4927 result = aType == IntrinsicISizeType::MinISize
4928 ? aFrame->GetMinISize(aRenderingContext)
4929 : aFrame->GetPrefISize(aRenderingContext);
4930 }
4931 #ifdef DEBUG_INTRINSIC_WIDTH
4932 --gNoiseIndent;
4933 nsIFrame::IndentBy(stderr, gNoiseIndent);
4934 aFrame->ListTag(stderr);
4935 printf_stderr(" %s %s intrinsic size from frame is %d.\n",
4936 aType == IntrinsicISizeType::MinISize ? "min" : "pref",
4937 horizontalAxis ? "horizontal" : "vertical", result);
4938 #endif
4939
4940 // Handle elements with an intrinsic ratio (or size) and a specified
4941 // height, min-height, or max-height.
4942 // NOTE:
4943 // 1. We treat "min-height:auto" as "0" for the purpose of this code,
4944 // since that's what it means in all cases except for on flex items -- and
4945 // even there, we're supposed to ignore it (i.e. treat it as 0) until the
4946 // flex container explicitly considers it.
4947 // 2. The 'B' in |styleBSize|, |styleMinBSize|, and |styleMaxBSize|
4948 // represents the ratio-determining axis of |aFrame|. It could be the inline
4949 // axis or the block axis of |aFrame|. (So we are calculating the size
4950 // along the ratio-dependent axis in this if-branch.)
4951 StyleSize styleBSize =
4952 horizontalAxis ? stylePos->mHeight : stylePos->mWidth;
4953 StyleSize styleMinBSize =
4954 horizontalAxis ? stylePos->mMinHeight : stylePos->mMinWidth;
4955 StyleMaxSize styleMaxBSize =
4956 horizontalAxis ? stylePos->mMaxHeight : stylePos->mMaxWidth;
4957
4958 // According to the spec, max-content and min-content should behave as the
4959 // property's initial values in block axis.
4960 // It also make senses to use the initial values for -moz-fit-content and
4961 // -moz-available for intrinsic size in block axis. Therefore, we reset them
4962 // if needed.
4963 if (isInlineAxis) {
4964 resetIfKeywords(styleBSize, styleMinBSize, styleMaxBSize);
4965 }
4966
4967 // FIXME(emilio): Why the minBsize == 0 special-case? Also, shouldn't this
4968 // use BehavesLikeInitialValueOnBlockAxis instead?
4969 if (!styleBSize.IsAuto() ||
4970 !(styleMinBSize.IsAuto() || (styleMinBSize.ConvertsToLength() &&
4971 styleMinBSize.ToLength() == 0)) ||
4972 !styleMaxBSize.IsNone()) {
4973 if (AspectRatio ratio = aFrame->GetAspectRatio()) {
4974 AddStateBitToAncestors(
4975 aFrame, NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE);
4976
4977 nscoord bSizeTakenByBoxSizing = GetDefiniteSizeTakenByBoxSizing(
4978 boxSizing, aFrame, !isInlineAxis, ignorePadding, aPercentageBasis);
4979 contentBoxSizeToBoxSizingAdjust.emplace(
4980 getContentBoxSizeToBoxSizingAdjust(boxSizing));
4981 // NOTE: This is only the minContentSize if we've been passed
4982 // MIN_INTRINSIC_ISIZE (which is fine, because this should only be used
4983 // inside a check for that flag).
4984 nscoord minContentSize = result;
4985 nscoord h;
4986 if (GetDefiniteSize(styleBSize, aFrame, !isInlineAxis, aPercentageBasis,
4987 &h) ||
4988 (aPercentageBasis.isNothing() &&
4989 GetPercentBSize(styleBSize, aFrame, horizontalAxis, h))) {
4990 h = std::max(0, h - bSizeTakenByBoxSizing);
4991 // We are computing the size of |aFrame|, so we use the inline & block
4992 // dimensions of |aFrame|.
4993 result = ratio.ComputeRatioDependentSize(
4994 isInlineAxis ? eLogicalAxisInline : eLogicalAxisBlock, childWM, h,
4995 *contentBoxSizeToBoxSizingAdjust);
4996 // We have get the inlineSizeForAspectRatio value, so we don't have to
4997 // recompute this again below.
4998 inlineSizeFromAspectRatio.emplace(result);
4999 }
5000
5001 if (GetDefiniteSize(styleMaxBSize, aFrame, !isInlineAxis,
5002 aPercentageBasis, &h) ||
5003 (aPercentageBasis.isNothing() &&
5004 GetPercentBSize(styleMaxBSize, aFrame, horizontalAxis, h))) {
5005 h = std::max(0, h - bSizeTakenByBoxSizing);
5006 nscoord maxISize = ratio.ComputeRatioDependentSize(
5007 isInlineAxis ? eLogicalAxisInline : eLogicalAxisBlock, childWM, h,
5008 *contentBoxSizeToBoxSizingAdjust);
5009 if (maxISize < result) {
5010 result = maxISize;
5011 }
5012 if (maxISize < minContentSize) {
5013 minContentSize = maxISize;
5014 }
5015 }
5016
5017 if (GetDefiniteSize(styleMinBSize, aFrame, !isInlineAxis,
5018 aPercentageBasis, &h) ||
5019 (aPercentageBasis.isNothing() &&
5020 GetPercentBSize(styleMinBSize, aFrame, horizontalAxis, h))) {
5021 h = std::max(0, h - bSizeTakenByBoxSizing);
5022 nscoord minISize = ratio.ComputeRatioDependentSize(
5023 isInlineAxis ? eLogicalAxisInline : eLogicalAxisBlock, childWM, h,
5024 *contentBoxSizeToBoxSizingAdjust);
5025 if (minISize > result) {
5026 result = minISize;
5027 }
5028 if (minISize > minContentSize) {
5029 minContentSize = minISize;
5030 }
5031 }
5032
5033 if (MOZ_UNLIKELY(aFlags & nsLayoutUtils::MIN_INTRINSIC_ISIZE) &&
5034 // FIXME: Bug 1715681. Should we use eReplacedSizing instead
5035 // because eReplaced is set on some other frames which are
5036 // non-replaced elements, e.g. <select>?
5037 aFrame->IsFrameOfType(nsIFrame::eReplaced)) {
5038 // This is the 'min-width/height:auto' "transferred size" piece of:
5039 // https://drafts.csswg.org/css-flexbox-1/#min-size-auto
5040 // https://drafts.csswg.org/css-grid/#min-size-auto
5041 // Per spec, we handle it only for replaced elements.
5042 result = std::min(result, minContentSize);
5043 }
5044 }
5045 }
5046 }
5047
5048 if (aFrame->IsTableFrame()) {
5049 // Tables can't shrink smaller than their intrinsic minimum width,
5050 // no matter what.
5051 min = aFrame->GetMinISize(aRenderingContext);
5052 }
5053
5054 // If we have an aspect-ratio and a definite block size of |aFrame|, we
5055 // resolve the {min|max}-content size by the aspect-ratio and the block size.
5056 // If |aAxis| is not the inline axis of |aFrame|, {min|max}-content should
5057 // behaves as auto, so we don't need this.
5058 //
5059 // FIXME(emilio): For -moz-available it seems we shouldn't need this.
5060 //
5061 // https://github.com/w3c/csswg-drafts/issues/5032
5062 // FIXME: Bug 1670151: Use GetAspectRatio() to cover replaced elements (and
5063 // then we can drop the check of eSupportsAspectRatio).
5064 const AspectRatio ar = stylePos->mAspectRatio.ToLayoutRatio();
5065 if (isInlineAxis && ar && nsIFrame::ToExtremumLength(styleISize) &&
5066 aFrame->IsFrameOfType(nsIFrame::eSupportsAspectRatio) &&
5067 !inlineSizeFromAspectRatio) {
5068 // This 'B' in |styleBSize| means the block size of |aFrame|. We go into
5069 // this branch only if |aAxis| is the inline axis of |aFrame|.
5070 const StyleSize& styleBSize =
5071 horizontalAxis ? stylePos->mHeight : stylePos->mWidth;
5072 nscoord bSize;
5073 if (GetDefiniteSize(styleBSize, aFrame, !isInlineAxis, aPercentageBasis,
5074 &bSize) ||
5075 (aPercentageBasis.isNothing() &&
5076 GetPercentBSize(styleBSize, aFrame, horizontalAxis, bSize))) {
5077 // We cannot reuse |boxSizing| because it may be updated to content-box
5078 // in the above if-branch.
5079 const StyleBoxSizing boxSizingForAR = stylePos->mBoxSizing;
5080 if (!contentBoxSizeToBoxSizingAdjust) {
5081 contentBoxSizeToBoxSizingAdjust.emplace(
5082 getContentBoxSizeToBoxSizingAdjust(boxSizingForAR));
5083 }
5084 nscoord bSizeTakenByBoxSizing =
5085 GetDefiniteSizeTakenByBoxSizing(boxSizingForAR, aFrame, !isInlineAxis,
5086 ignorePadding, aPercentageBasis);
5087 bSize -= bSizeTakenByBoxSizing;
5088 inlineSizeFromAspectRatio.emplace(ar.ComputeRatioDependentSize(
5089 LogicalAxis::eLogicalAxisInline, childWM, bSize,
5090 *contentBoxSizeToBoxSizingAdjust));
5091 }
5092 }
5093
5094 nscoord contentBoxSize = result;
5095 result = AddIntrinsicSizeOffset(
5096 aRenderingContext, aFrame, offsets, aType, boxSizing, result, min,
5097 styleISize, haveFixedMinISize ? &minISize : nullptr, styleMinISize,
5098 haveFixedMaxISize ? &maxISize : nullptr, styleMaxISize,
5099 inlineSizeFromAspectRatio, aFlags, aAxis);
5100 nscoord overflow = result - aMarginBoxMinSizeClamp;
5101 if (MOZ_UNLIKELY(overflow > 0)) {
5102 nscoord newContentBoxSize = std::max(nscoord(0), contentBoxSize - overflow);
5103 result -= contentBoxSize - newContentBoxSize;
5104 }
5105
5106 #ifdef DEBUG_INTRINSIC_WIDTH
5107 nsIFrame::IndentBy(stderr, gNoiseIndent);
5108 aFrame->ListTag(stderr);
5109 printf_stderr(" %s %s intrinsic size for container is %d twips.\n",
5110 aType == IntrinsicISizeType::MinISize ? "min" : "pref",
5111 horizontalAxis ? "horizontal" : "vertical", result);
5112 #endif
5113
5114 return result;
5115 }
5116
5117 /* static */
IntrinsicForContainer(gfxContext * aRenderingContext,nsIFrame * aFrame,IntrinsicISizeType aType,uint32_t aFlags)5118 nscoord nsLayoutUtils::IntrinsicForContainer(gfxContext* aRenderingContext,
5119 nsIFrame* aFrame,
5120 IntrinsicISizeType aType,
5121 uint32_t aFlags) {
5122 MOZ_ASSERT(aFrame && aFrame->GetParent());
5123 // We want the size aFrame will contribute to its parent's inline-size.
5124 PhysicalAxis axis =
5125 aFrame->GetParent()->GetWritingMode().PhysicalAxis(eLogicalAxisInline);
5126 return IntrinsicForAxis(axis, aRenderingContext, aFrame, aType, Nothing(),
5127 aFlags);
5128 }
5129
5130 /* static */
MinSizeContributionForAxis(PhysicalAxis aAxis,gfxContext * aRC,nsIFrame * aFrame,IntrinsicISizeType aType,const LogicalSize & aPercentageBasis,uint32_t aFlags)5131 nscoord nsLayoutUtils::MinSizeContributionForAxis(
5132 PhysicalAxis aAxis, gfxContext* aRC, nsIFrame* aFrame,
5133 IntrinsicISizeType aType, const LogicalSize& aPercentageBasis,
5134 uint32_t aFlags) {
5135 MOZ_ASSERT(aFrame);
5136 MOZ_ASSERT(aFrame->IsFlexOrGridItem(),
5137 "only grid/flex items have this behavior currently");
5138
5139 #ifdef DEBUG_INTRINSIC_WIDTH
5140 nsIFrame::IndentBy(stderr, gNoiseIndent);
5141 aFrame->ListTag(stderr);
5142 printf_stderr(" %s min-isize for %s WM:\n",
5143 aType == IntrinsicISizeType::MinISize ? "min" : "pref",
5144 aAxis == eAxisVertical ? "vertical" : "horizontal");
5145 #endif
5146
5147 // Note: this method is only meant for grid/flex items.
5148 const nsStylePosition* const stylePos = aFrame->StylePosition();
5149 StyleSize size =
5150 aAxis == eAxisHorizontal ? stylePos->mMinWidth : stylePos->mMinHeight;
5151 StyleMaxSize maxSize =
5152 aAxis == eAxisHorizontal ? stylePos->mMaxWidth : stylePos->mMaxHeight;
5153 auto childWM = aFrame->GetWritingMode();
5154 PhysicalAxis ourInlineAxis = childWM.PhysicalAxis(eLogicalAxisInline);
5155 // According to the spec, max-content and min-content should behave as the
5156 // property's initial values in block axis.
5157 // It also make senses to use the initial values for -moz-fit-content and
5158 // -moz-available for intrinsic size in block axis. Therefore, we reset them
5159 // if needed.
5160 if (aAxis != ourInlineAxis) {
5161 if (size.BehavesLikeInitialValueOnBlockAxis()) {
5162 size = StyleSize::Auto();
5163 }
5164 if (maxSize.BehavesLikeInitialValueOnBlockAxis()) {
5165 maxSize = StyleMaxSize::None();
5166 }
5167 }
5168
5169 nscoord minSize;
5170 nscoord* fixedMinSize = nullptr;
5171 if (size.IsAuto()) {
5172 if (aFrame->StyleDisplay()->mOverflowX == StyleOverflow::Visible) {
5173 size = aAxis == eAxisHorizontal ? stylePos->mWidth : stylePos->mHeight;
5174 // This is same as above: keywords should behaves as property's initial
5175 // values in block axis.
5176 if (aAxis != ourInlineAxis && size.BehavesLikeInitialValueOnBlockAxis()) {
5177 size = StyleSize::Auto();
5178 }
5179
5180 if (GetAbsoluteCoord(size, minSize)) {
5181 // We have a definite width/height. This is the "specified size" in:
5182 // https://drafts.csswg.org/css-grid/#min-size-auto
5183 fixedMinSize = &minSize;
5184 } else if (aFrame->IsPercentageResolvedAgainstZero(size, maxSize)) {
5185 // XXX bug 1463700: this doesn't handle calc() according to spec
5186 minSize = 0;
5187 fixedMinSize = &minSize;
5188 }
5189 // fall through - the caller will have to deal with "transferred size"
5190 } else {
5191 // min-[width|height]:auto with overflow != visible computes to zero.
5192 minSize = 0;
5193 fixedMinSize = &minSize;
5194 }
5195 } else if (GetAbsoluteCoord(size, minSize)) {
5196 fixedMinSize = &minSize;
5197 } else if (size.IsLengthPercentage()) {
5198 MOZ_ASSERT(size.HasPercent());
5199 minSize = 0;
5200 fixedMinSize = &minSize;
5201 }
5202
5203 if (!fixedMinSize) {
5204 // Let the caller deal with the "content size" cases.
5205 #ifdef DEBUG_INTRINSIC_WIDTH
5206 nsIFrame::IndentBy(stderr, gNoiseIndent);
5207 aFrame->ListTag(stderr);
5208 printf_stderr(" %s min-isize is indefinite.\n",
5209 aType == IntrinsicISizeType::MinISize ? "min" : "pref");
5210 #endif
5211 return NS_UNCONSTRAINEDSIZE;
5212 }
5213
5214 // If aFrame is a container for font size inflation, then shrink
5215 // wrapping inside of it should not apply font size inflation.
5216 AutoMaybeDisableFontInflation an(aFrame);
5217
5218 // The padding/margin percentage basis is the inline-size in the parent's
5219 // writing-mode.
5220 nscoord pmPercentageBasis =
5221 aFrame->GetParent()->GetWritingMode().IsOrthogonalTo(childWM)
5222 ? aPercentageBasis.BSize(childWM)
5223 : aPercentageBasis.ISize(childWM);
5224 nsIFrame::IntrinsicSizeOffsetData offsets =
5225 ourInlineAxis == aAxis ? aFrame->IntrinsicISizeOffsets(pmPercentageBasis)
5226 : aFrame->IntrinsicBSizeOffsets(pmPercentageBasis);
5227 nscoord result = 0;
5228 nscoord min = 0;
5229 // Note: aInlineSizeFromAspectRatio is Nothing() here because we don't handle
5230 // "content size" cases here (i.e. |fixedMinSize| is false here).
5231 result = AddIntrinsicSizeOffset(
5232 aRC, aFrame, offsets, aType, stylePos->mBoxSizing, result, min, size,
5233 fixedMinSize, size, nullptr, maxSize, Nothing(), aFlags, aAxis);
5234
5235 #ifdef DEBUG_INTRINSIC_WIDTH
5236 nsIFrame::IndentBy(stderr, gNoiseIndent);
5237 aFrame->ListTag(stderr);
5238 printf_stderr(" %s min-isize is %d twips.\n",
5239 aType == IntrinsicISizeType::MinISize ? "min" : "pref", result);
5240 #endif
5241
5242 return result;
5243 }
5244
5245 /* static */
ComputeBSizeDependentValue(nscoord aContainingBlockBSize,const LengthPercentageOrAuto & aCoord)5246 nscoord nsLayoutUtils::ComputeBSizeDependentValue(
5247 nscoord aContainingBlockBSize, const LengthPercentageOrAuto& aCoord) {
5248 // XXXldb Some callers explicitly check aContainingBlockBSize
5249 // against NS_UNCONSTRAINEDSIZE *and* unit against eStyleUnit_Percent or
5250 // calc()s containing percents before calling this function.
5251 // However, it would be much more likely to catch problems without
5252 // the unit conditions.
5253 // XXXldb Many callers pass a non-'auto' containing block height when
5254 // according to CSS2.1 they should be passing 'auto'.
5255 MOZ_ASSERT(
5256 NS_UNCONSTRAINEDSIZE != aContainingBlockBSize || !aCoord.HasPercent(),
5257 "unexpected containing block block-size");
5258
5259 if (aCoord.IsAuto()) {
5260 return 0;
5261 }
5262
5263 return aCoord.AsLengthPercentage().Resolve(aContainingBlockBSize);
5264 }
5265
5266 /* static */
MarkDescendantsDirty(nsIFrame * aSubtreeRoot)5267 void nsLayoutUtils::MarkDescendantsDirty(nsIFrame* aSubtreeRoot) {
5268 AutoTArray<nsIFrame*, 4> subtrees;
5269 subtrees.AppendElement(aSubtreeRoot);
5270
5271 // dirty descendants, iterating over subtrees that may include
5272 // additional subtrees associated with placeholders
5273 do {
5274 nsIFrame* subtreeRoot = subtrees.PopLastElement();
5275
5276 // Mark all descendants dirty (using an nsTArray stack rather than
5277 // recursion).
5278 // Note that ReflowInput::InitResizeFlags has some similar
5279 // code; see comments there for how and why it differs.
5280 AutoTArray<nsIFrame*, 32> stack;
5281 stack.AppendElement(subtreeRoot);
5282
5283 do {
5284 nsIFrame* f = stack.PopLastElement();
5285
5286 f->MarkIntrinsicISizesDirty();
5287
5288 if (f->IsPlaceholderFrame()) {
5289 nsIFrame* oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
5290 if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {
5291 // We have another distinct subtree we need to mark.
5292 subtrees.AppendElement(oof);
5293 }
5294 }
5295
5296 for (const auto& childList : f->ChildLists()) {
5297 for (nsIFrame* kid : childList.mList) {
5298 stack.AppendElement(kid);
5299 }
5300 }
5301 } while (stack.Length() != 0);
5302 } while (subtrees.Length() != 0);
5303 }
5304
5305 /* static */
MarkIntrinsicISizesDirtyIfDependentOnBSize(nsIFrame * aFrame)5306 void nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(
5307 nsIFrame* aFrame) {
5308 AutoTArray<nsIFrame*, 32> stack;
5309 stack.AppendElement(aFrame);
5310
5311 do {
5312 nsIFrame* f = stack.PopLastElement();
5313
5314 if (!f->HasAnyStateBits(
5315 NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
5316 continue;
5317 }
5318 f->MarkIntrinsicISizesDirty();
5319
5320 for (const auto& childList : f->ChildLists()) {
5321 for (nsIFrame* kid : childList.mList) {
5322 stack.AppendElement(kid);
5323 }
5324 }
5325 } while (stack.Length() != 0);
5326 }
5327
ComputeAutoSizeWithIntrinsicDimensions(nscoord minWidth,nscoord minHeight,nscoord maxWidth,nscoord maxHeight,nscoord tentWidth,nscoord tentHeight)5328 nsSize nsLayoutUtils::ComputeAutoSizeWithIntrinsicDimensions(
5329 nscoord minWidth, nscoord minHeight, nscoord maxWidth, nscoord maxHeight,
5330 nscoord tentWidth, nscoord tentHeight) {
5331 // Now apply min/max-width/height - CSS 2.1 sections 10.4 and 10.7:
5332
5333 if (minWidth > maxWidth) maxWidth = minWidth;
5334 if (minHeight > maxHeight) maxHeight = minHeight;
5335
5336 nscoord heightAtMaxWidth, heightAtMinWidth, widthAtMaxHeight,
5337 widthAtMinHeight;
5338
5339 if (tentWidth > 0) {
5340 heightAtMaxWidth = NSCoordMulDiv(maxWidth, tentHeight, tentWidth);
5341 if (heightAtMaxWidth < minHeight) heightAtMaxWidth = minHeight;
5342 heightAtMinWidth = NSCoordMulDiv(minWidth, tentHeight, tentWidth);
5343 if (heightAtMinWidth > maxHeight) heightAtMinWidth = maxHeight;
5344 } else {
5345 heightAtMaxWidth = heightAtMinWidth =
5346 NS_CSS_MINMAX(tentHeight, minHeight, maxHeight);
5347 }
5348
5349 if (tentHeight > 0) {
5350 widthAtMaxHeight = NSCoordMulDiv(maxHeight, tentWidth, tentHeight);
5351 if (widthAtMaxHeight < minWidth) widthAtMaxHeight = minWidth;
5352 widthAtMinHeight = NSCoordMulDiv(minHeight, tentWidth, tentHeight);
5353 if (widthAtMinHeight > maxWidth) widthAtMinHeight = maxWidth;
5354 } else {
5355 widthAtMaxHeight = widthAtMinHeight =
5356 NS_CSS_MINMAX(tentWidth, minWidth, maxWidth);
5357 }
5358
5359 // The table at http://www.w3.org/TR/CSS21/visudet.html#min-max-widths :
5360
5361 nscoord width, height;
5362
5363 if (tentWidth > maxWidth) {
5364 if (tentHeight > maxHeight) {
5365 if (int64_t(maxWidth) * int64_t(tentHeight) <=
5366 int64_t(maxHeight) * int64_t(tentWidth)) {
5367 width = maxWidth;
5368 height = heightAtMaxWidth;
5369 } else {
5370 width = widthAtMaxHeight;
5371 height = maxHeight;
5372 }
5373 } else {
5374 // This also covers "(w > max-width) and (h < min-height)" since in
5375 // that case (max-width/w < 1), and with (h < min-height):
5376 // max(max-width * h/w, min-height) == min-height
5377 width = maxWidth;
5378 height = heightAtMaxWidth;
5379 }
5380 } else if (tentWidth < minWidth) {
5381 if (tentHeight < minHeight) {
5382 if (int64_t(minWidth) * int64_t(tentHeight) <=
5383 int64_t(minHeight) * int64_t(tentWidth)) {
5384 width = widthAtMinHeight;
5385 height = minHeight;
5386 } else {
5387 width = minWidth;
5388 height = heightAtMinWidth;
5389 }
5390 } else {
5391 // This also covers "(w < min-width) and (h > max-height)" since in
5392 // that case (min-width/w > 1), and with (h > max-height):
5393 // min(min-width * h/w, max-height) == max-height
5394 width = minWidth;
5395 height = heightAtMinWidth;
5396 }
5397 } else {
5398 if (tentHeight > maxHeight) {
5399 width = widthAtMaxHeight;
5400 height = maxHeight;
5401 } else if (tentHeight < minHeight) {
5402 width = widthAtMinHeight;
5403 height = minHeight;
5404 } else {
5405 width = tentWidth;
5406 height = tentHeight;
5407 }
5408 }
5409
5410 return nsSize(width, height);
5411 }
5412
5413 /* static */
MinISizeFromInline(nsIFrame * aFrame,gfxContext * aRenderingContext)5414 nscoord nsLayoutUtils::MinISizeFromInline(nsIFrame* aFrame,
5415 gfxContext* aRenderingContext) {
5416 NS_ASSERTION(!aFrame->IsContainerForFontSizeInflation(),
5417 "should not be container for font size inflation");
5418
5419 nsIFrame::InlineMinISizeData data;
5420 DISPLAY_MIN_INLINE_SIZE(aFrame, data.mPrevLines);
5421 aFrame->AddInlineMinISize(aRenderingContext, &data);
5422 data.ForceBreak();
5423 return data.mPrevLines;
5424 }
5425
5426 /* static */
PrefISizeFromInline(nsIFrame * aFrame,gfxContext * aRenderingContext)5427 nscoord nsLayoutUtils::PrefISizeFromInline(nsIFrame* aFrame,
5428 gfxContext* aRenderingContext) {
5429 NS_ASSERTION(!aFrame->IsContainerForFontSizeInflation(),
5430 "should not be container for font size inflation");
5431
5432 nsIFrame::InlinePrefISizeData data;
5433 DISPLAY_PREF_INLINE_SIZE(aFrame, data.mPrevLines);
5434 aFrame->AddInlinePrefISize(aRenderingContext, &data);
5435 data.ForceBreak();
5436 return data.mPrevLines;
5437 }
5438
DarkenColor(nscolor aColor)5439 static nscolor DarkenColor(nscolor aColor) {
5440 uint16_t hue, sat, value;
5441 uint8_t alpha;
5442
5443 // convert the RBG to HSV so we can get the lightness (which is the v)
5444 NS_RGB2HSV(aColor, hue, sat, value, alpha);
5445
5446 // The goal here is to send white to black while letting colored
5447 // stuff stay colored... So we adopt the following approach.
5448 // Something with sat = 0 should end up with value = 0. Something
5449 // with a high sat can end up with a high value and it's ok.... At
5450 // the same time, we don't want to make things lighter. Do
5451 // something simple, since it seems to work.
5452 if (value > sat) {
5453 value = sat;
5454 // convert this color back into the RGB color space.
5455 NS_HSV2RGB(aColor, hue, sat, value, alpha);
5456 }
5457 return aColor;
5458 }
5459
5460 // Check whether we should darken text/decoration colors. We need to do this if
5461 // background images and colors are being suppressed, because that means
5462 // light text will not be visible against the (presumed light-colored)
5463 // background.
ShouldDarkenColors(nsIFrame * aFrame)5464 static bool ShouldDarkenColors(nsIFrame* aFrame) {
5465 nsPresContext* pc = aFrame->PresContext();
5466 if (pc->GetBackgroundColorDraw() || pc->GetBackgroundImageDraw()) {
5467 return false;
5468 }
5469 return aFrame->StyleVisibility()->mPrintColorAdjust !=
5470 StylePrintColorAdjust::Exact;
5471 }
5472
DarkenColorIfNeeded(nsIFrame * aFrame,nscolor aColor)5473 nscolor nsLayoutUtils::DarkenColorIfNeeded(nsIFrame* aFrame, nscolor aColor) {
5474 return ShouldDarkenColors(aFrame) ? DarkenColor(aColor) : aColor;
5475 }
5476
GetSnappedBaselineY(nsIFrame * aFrame,gfxContext * aContext,nscoord aY,nscoord aAscent)5477 gfxFloat nsLayoutUtils::GetSnappedBaselineY(nsIFrame* aFrame,
5478 gfxContext* aContext, nscoord aY,
5479 nscoord aAscent) {
5480 gfxFloat appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
5481 gfxFloat baseline = gfxFloat(aY) + aAscent;
5482 gfxRect putativeRect(0, baseline / appUnitsPerDevUnit, 1, 1);
5483 if (!aContext->UserToDevicePixelSnapped(
5484 putativeRect, gfxContext::SnapOption::IgnoreScale)) {
5485 return baseline;
5486 }
5487 return aContext->DeviceToUser(putativeRect.TopLeft()).y * appUnitsPerDevUnit;
5488 }
5489
GetSnappedBaselineX(nsIFrame * aFrame,gfxContext * aContext,nscoord aX,nscoord aAscent)5490 gfxFloat nsLayoutUtils::GetSnappedBaselineX(nsIFrame* aFrame,
5491 gfxContext* aContext, nscoord aX,
5492 nscoord aAscent) {
5493 gfxFloat appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
5494 gfxFloat baseline = gfxFloat(aX) + aAscent;
5495 gfxRect putativeRect(baseline / appUnitsPerDevUnit, 0, 1, 1);
5496 if (!aContext->UserToDevicePixelSnapped(
5497 putativeRect, gfxContext::SnapOption::IgnoreScale)) {
5498 return baseline;
5499 }
5500 return aContext->DeviceToUser(putativeRect.TopLeft()).x * appUnitsPerDevUnit;
5501 }
5502
5503 // Hard limit substring lengths to 8000 characters ... this lets us statically
5504 // size the cluster buffer array in FindSafeLength
5505 #define MAX_GFX_TEXT_BUF_SIZE 8000
5506
FindSafeLength(const char16_t * aString,uint32_t aLength,uint32_t aMaxChunkLength)5507 static int32_t FindSafeLength(const char16_t* aString, uint32_t aLength,
5508 uint32_t aMaxChunkLength) {
5509 if (aLength <= aMaxChunkLength) return aLength;
5510
5511 int32_t len = aMaxChunkLength;
5512
5513 // Ensure that we don't break inside a surrogate pair
5514 while (len > 0 && NS_IS_LOW_SURROGATE(aString[len])) {
5515 len--;
5516 }
5517 if (len == 0) {
5518 // We don't want our caller to go into an infinite loop, so don't
5519 // return zero. It's hard to imagine how we could actually get here
5520 // unless there are languages that allow clusters of arbitrary size.
5521 // If there are and someone feeds us a 500+ character cluster, too
5522 // bad.
5523 return aMaxChunkLength;
5524 }
5525 return len;
5526 }
5527
GetMaxChunkLength(nsFontMetrics & aFontMetrics)5528 static int32_t GetMaxChunkLength(nsFontMetrics& aFontMetrics) {
5529 return std::min(aFontMetrics.GetMaxStringLength(), MAX_GFX_TEXT_BUF_SIZE);
5530 }
5531
AppUnitWidthOfString(const char16_t * aString,uint32_t aLength,nsFontMetrics & aFontMetrics,DrawTarget * aDrawTarget)5532 nscoord nsLayoutUtils::AppUnitWidthOfString(const char16_t* aString,
5533 uint32_t aLength,
5534 nsFontMetrics& aFontMetrics,
5535 DrawTarget* aDrawTarget) {
5536 uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
5537 nscoord width = 0;
5538 while (aLength > 0) {
5539 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
5540 width += aFontMetrics.GetWidth(aString, len, aDrawTarget);
5541 aLength -= len;
5542 aString += len;
5543 }
5544 return width;
5545 }
5546
AppUnitWidthOfStringBidi(const char16_t * aString,uint32_t aLength,const nsIFrame * aFrame,nsFontMetrics & aFontMetrics,gfxContext & aContext)5547 nscoord nsLayoutUtils::AppUnitWidthOfStringBidi(const char16_t* aString,
5548 uint32_t aLength,
5549 const nsIFrame* aFrame,
5550 nsFontMetrics& aFontMetrics,
5551 gfxContext& aContext) {
5552 nsPresContext* presContext = aFrame->PresContext();
5553 if (presContext->BidiEnabled()) {
5554 mozilla::intl::BidiEmbeddingLevel level =
5555 nsBidiPresUtils::BidiLevelFromStyle(aFrame->Style());
5556 return nsBidiPresUtils::MeasureTextWidth(
5557 aString, aLength, level, presContext, aContext, aFontMetrics);
5558 }
5559 aFontMetrics.SetTextRunRTL(false);
5560 aFontMetrics.SetVertical(aFrame->GetWritingMode().IsVertical());
5561 aFontMetrics.SetTextOrientation(aFrame->StyleVisibility()->mTextOrientation);
5562 return nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics,
5563 aContext.GetDrawTarget());
5564 }
5565
StringWidthIsGreaterThan(const nsString & aString,nsFontMetrics & aFontMetrics,DrawTarget * aDrawTarget,nscoord aWidth)5566 bool nsLayoutUtils::StringWidthIsGreaterThan(const nsString& aString,
5567 nsFontMetrics& aFontMetrics,
5568 DrawTarget* aDrawTarget,
5569 nscoord aWidth) {
5570 const char16_t* string = aString.get();
5571 uint32_t length = aString.Length();
5572 uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
5573 nscoord width = 0;
5574 while (length > 0) {
5575 int32_t len = FindSafeLength(string, length, maxChunkLength);
5576 width += aFontMetrics.GetWidth(string, len, aDrawTarget);
5577 if (width > aWidth) {
5578 return true;
5579 }
5580 length -= len;
5581 string += len;
5582 }
5583 return false;
5584 }
5585
AppUnitBoundsOfString(const char16_t * aString,uint32_t aLength,nsFontMetrics & aFontMetrics,DrawTarget * aDrawTarget)5586 nsBoundingMetrics nsLayoutUtils::AppUnitBoundsOfString(
5587 const char16_t* aString, uint32_t aLength, nsFontMetrics& aFontMetrics,
5588 DrawTarget* aDrawTarget) {
5589 uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
5590 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
5591 // Assign directly in the first iteration. This ensures that
5592 // negative ascent/descent can be returned and the left bearing
5593 // is properly initialized.
5594 nsBoundingMetrics totalMetrics =
5595 aFontMetrics.GetBoundingMetrics(aString, len, aDrawTarget);
5596 aLength -= len;
5597 aString += len;
5598
5599 while (aLength > 0) {
5600 len = FindSafeLength(aString, aLength, maxChunkLength);
5601 nsBoundingMetrics metrics =
5602 aFontMetrics.GetBoundingMetrics(aString, len, aDrawTarget);
5603 totalMetrics += metrics;
5604 aLength -= len;
5605 aString += len;
5606 }
5607 return totalMetrics;
5608 }
5609
DrawString(const nsIFrame * aFrame,nsFontMetrics & aFontMetrics,gfxContext * aContext,const char16_t * aString,int32_t aLength,nsPoint aPoint,ComputedStyle * aComputedStyle,DrawStringFlags aFlags)5610 void nsLayoutUtils::DrawString(const nsIFrame* aFrame,
5611 nsFontMetrics& aFontMetrics,
5612 gfxContext* aContext, const char16_t* aString,
5613 int32_t aLength, nsPoint aPoint,
5614 ComputedStyle* aComputedStyle,
5615 DrawStringFlags aFlags) {
5616 nsresult rv = NS_ERROR_FAILURE;
5617
5618 // If caller didn't pass a style, use the frame's.
5619 if (!aComputedStyle) {
5620 aComputedStyle = aFrame->Style();
5621 }
5622
5623 if (aFlags & DrawStringFlags::ForceHorizontal) {
5624 aFontMetrics.SetVertical(false);
5625 } else {
5626 aFontMetrics.SetVertical(WritingMode(aComputedStyle).IsVertical());
5627 }
5628
5629 aFontMetrics.SetTextOrientation(
5630 aComputedStyle->StyleVisibility()->mTextOrientation);
5631
5632 nsPresContext* presContext = aFrame->PresContext();
5633 if (presContext->BidiEnabled()) {
5634 mozilla::intl::BidiEmbeddingLevel level =
5635 nsBidiPresUtils::BidiLevelFromStyle(aComputedStyle);
5636 rv = nsBidiPresUtils::RenderText(aString, aLength, level, presContext,
5637 *aContext, aContext->GetDrawTarget(),
5638 aFontMetrics, aPoint.x, aPoint.y);
5639 }
5640 if (NS_FAILED(rv)) {
5641 aFontMetrics.SetTextRunRTL(false);
5642 DrawUniDirString(aString, aLength, aPoint, aFontMetrics, *aContext);
5643 }
5644 }
5645
DrawUniDirString(const char16_t * aString,uint32_t aLength,const nsPoint & aPoint,nsFontMetrics & aFontMetrics,gfxContext & aContext)5646 void nsLayoutUtils::DrawUniDirString(const char16_t* aString, uint32_t aLength,
5647 const nsPoint& aPoint,
5648 nsFontMetrics& aFontMetrics,
5649 gfxContext& aContext) {
5650 nscoord x = aPoint.x;
5651 nscoord y = aPoint.y;
5652
5653 uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
5654 if (aLength <= maxChunkLength) {
5655 aFontMetrics.DrawString(aString, aLength, x, y, &aContext,
5656 aContext.GetDrawTarget());
5657 return;
5658 }
5659
5660 bool isRTL = aFontMetrics.GetTextRunRTL();
5661
5662 // If we're drawing right to left, we must start at the end.
5663 if (isRTL) {
5664 x += nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics,
5665 aContext.GetDrawTarget());
5666 }
5667
5668 while (aLength > 0) {
5669 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
5670 nscoord width =
5671 aFontMetrics.GetWidth(aString, len, aContext.GetDrawTarget());
5672 if (isRTL) {
5673 x -= width;
5674 }
5675 aFontMetrics.DrawString(aString, len, x, y, &aContext,
5676 aContext.GetDrawTarget());
5677 if (!isRTL) {
5678 x += width;
5679 }
5680 aLength -= len;
5681 aString += len;
5682 }
5683 }
5684
5685 /* static */
PaintTextShadow(const nsIFrame * aFrame,gfxContext * aContext,const nsRect & aTextRect,const nsRect & aDirtyRect,const nscolor & aForegroundColor,TextShadowCallback aCallback,void * aCallbackData)5686 void nsLayoutUtils::PaintTextShadow(
5687 const nsIFrame* aFrame, gfxContext* aContext, const nsRect& aTextRect,
5688 const nsRect& aDirtyRect, const nscolor& aForegroundColor,
5689 TextShadowCallback aCallback, void* aCallbackData) {
5690 const nsStyleText* textStyle = aFrame->StyleText();
5691 auto shadows = textStyle->mTextShadow.AsSpan();
5692 if (shadows.IsEmpty()) {
5693 return;
5694 }
5695
5696 // Text shadow happens with the last value being painted at the back,
5697 // ie. it is painted first.
5698 gfxContext* aDestCtx = aContext;
5699 for (auto& shadow : Reversed(shadows)) {
5700 nsPoint shadowOffset(shadow.horizontal.ToAppUnits(),
5701 shadow.vertical.ToAppUnits());
5702 nscoord blurRadius = std::max(shadow.blur.ToAppUnits(), 0);
5703
5704 nsRect shadowRect(aTextRect);
5705 shadowRect.MoveBy(shadowOffset);
5706
5707 nsPresContext* presCtx = aFrame->PresContext();
5708 nsContextBoxBlur contextBoxBlur;
5709
5710 nscolor shadowColor = shadow.color.CalcColor(aForegroundColor);
5711
5712 // Webrender just needs the shadow details
5713 if (auto* textDrawer = aContext->GetTextDrawer()) {
5714 wr::Shadow wrShadow;
5715
5716 wrShadow.offset = {
5717 presCtx->AppUnitsToFloatDevPixels(shadow.horizontal.ToAppUnits()),
5718 presCtx->AppUnitsToFloatDevPixels(shadow.vertical.ToAppUnits())};
5719
5720 wrShadow.blur_radius = presCtx->AppUnitsToFloatDevPixels(blurRadius);
5721 wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
5722
5723 // Gecko already inflates the bounding rect of text shadows,
5724 // so tell WR not to inflate again.
5725 bool inflate = false;
5726 textDrawer->AppendShadow(wrShadow, inflate);
5727 continue;
5728 }
5729
5730 gfxContext* shadowContext = contextBoxBlur.Init(
5731 shadowRect, 0, blurRadius, presCtx->AppUnitsPerDevPixel(), aDestCtx,
5732 aDirtyRect, nullptr,
5733 nsContextBoxBlur::DISABLE_HARDWARE_ACCELERATION_BLUR);
5734 if (!shadowContext) continue;
5735
5736 aDestCtx->Save();
5737 aDestCtx->NewPath();
5738 aDestCtx->SetColor(sRGBColor::FromABGR(shadowColor));
5739
5740 // The callback will draw whatever we want to blur as a shadow.
5741 aCallback(shadowContext, shadowOffset, shadowColor, aCallbackData);
5742
5743 contextBoxBlur.DoPaint();
5744 aDestCtx->Restore();
5745 }
5746 }
5747
5748 /* static */
GetCenteredFontBaseline(nsFontMetrics * aFontMetrics,nscoord aLineHeight,bool aIsInverted)5749 nscoord nsLayoutUtils::GetCenteredFontBaseline(nsFontMetrics* aFontMetrics,
5750 nscoord aLineHeight,
5751 bool aIsInverted) {
5752 nscoord fontAscent =
5753 aIsInverted ? aFontMetrics->MaxDescent() : aFontMetrics->MaxAscent();
5754 nscoord fontHeight = aFontMetrics->MaxHeight();
5755
5756 nscoord leading = aLineHeight - fontHeight;
5757 return fontAscent + leading / 2;
5758 }
5759
5760 /* static */
GetFirstLineBaseline(WritingMode aWritingMode,const nsIFrame * aFrame,nscoord * aResult)5761 bool nsLayoutUtils::GetFirstLineBaseline(WritingMode aWritingMode,
5762 const nsIFrame* aFrame,
5763 nscoord* aResult) {
5764 LinePosition position;
5765 if (!GetFirstLinePosition(aWritingMode, aFrame, &position)) return false;
5766 *aResult = position.mBaseline;
5767 return true;
5768 }
5769
5770 /* static */
GetFirstLinePosition(WritingMode aWM,const nsIFrame * aFrame,LinePosition * aResult)5771 bool nsLayoutUtils::GetFirstLinePosition(WritingMode aWM,
5772 const nsIFrame* aFrame,
5773 LinePosition* aResult) {
5774 if (aFrame->StyleDisplay()->IsContainLayout()) {
5775 return false;
5776 }
5777 const nsBlockFrame* block = do_QueryFrame(aFrame);
5778 if (!block) {
5779 // For the first-line baseline we also have to check for a table, and if
5780 // so, use the baseline of its first row.
5781 LayoutFrameType fType = aFrame->Type();
5782 if (fType == LayoutFrameType::TableWrapper ||
5783 fType == LayoutFrameType::FlexContainer ||
5784 fType == LayoutFrameType::GridContainer) {
5785 if ((fType == LayoutFrameType::GridContainer &&
5786 aFrame->HasAnyStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE)) ||
5787 (fType == LayoutFrameType::FlexContainer &&
5788 aFrame->HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) ||
5789 (fType == LayoutFrameType::TableWrapper &&
5790 static_cast<const nsTableWrapperFrame*>(aFrame)->GetRowCount() ==
5791 0)) {
5792 // empty grid/flex/table container
5793 aResult->mBStart = 0;
5794 aResult->mBaseline = aFrame->SynthesizeBaselineBOffsetFromBorderBox(
5795 aWM, BaselineSharingGroup::First);
5796 aResult->mBEnd = aFrame->BSize(aWM);
5797 return true;
5798 }
5799 aResult->mBStart = 0;
5800 aResult->mBaseline = aFrame->GetLogicalBaseline(aWM);
5801 // This is what we want for the list bullet caller; not sure if
5802 // other future callers will want the same.
5803 aResult->mBEnd = aFrame->BSize(aWM);
5804 return true;
5805 }
5806
5807 // For first-line baselines, we have to consider scroll frames.
5808 if (fType == LayoutFrameType::Scroll) {
5809 nsIScrollableFrame* sFrame = do_QueryFrame(const_cast<nsIFrame*>(aFrame));
5810 if (!sFrame) {
5811 MOZ_ASSERT_UNREACHABLE("not scroll frame");
5812 }
5813 LinePosition kidPosition;
5814 if (GetFirstLinePosition(aWM, sFrame->GetScrolledFrame(), &kidPosition)) {
5815 // Consider only the border and padding that contributes to the
5816 // kid's position, not the scrolling, so we get the initial
5817 // position.
5818 *aResult = kidPosition +
5819 aFrame->GetLogicalUsedBorderAndPadding(aWM).BStart(aWM);
5820 return true;
5821 }
5822 return false;
5823 }
5824
5825 if (fType == LayoutFrameType::FieldSet ||
5826 fType == LayoutFrameType::ColumnSet) {
5827 LinePosition kidPosition;
5828 nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
5829 // If aFrame is fieldset, kid might be a legend frame here, but that's ok.
5830 if (kid && GetFirstLinePosition(aWM, kid, &kidPosition)) {
5831 *aResult = kidPosition +
5832 kid->GetLogicalNormalPosition(aWM, aFrame->GetSize()).B(aWM);
5833 return true;
5834 }
5835 return false;
5836 }
5837
5838 // No baseline.
5839 return false;
5840 }
5841
5842 for (const auto& line : block->Lines()) {
5843 if (line.IsBlock()) {
5844 const nsIFrame* kid = line.mFirstChild;
5845 LinePosition kidPosition;
5846 if (GetFirstLinePosition(aWM, kid, &kidPosition)) {
5847 // XXX Not sure if this is the correct value to use for container
5848 // width here. It will only be used in vertical-rl layout,
5849 // which we don't have full support and testing for yet.
5850 const auto& containerSize = line.mContainerSize;
5851 *aResult = kidPosition +
5852 kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
5853 return true;
5854 }
5855 } else {
5856 // XXX Is this the right test? We have some bogus empty lines
5857 // floating around, but IsEmpty is perhaps too weak.
5858 if (0 != line.BSize() || !line.IsEmpty()) {
5859 nscoord bStart = line.BStart();
5860 aResult->mBStart = bStart;
5861 aResult->mBaseline = bStart + line.GetLogicalAscent();
5862 aResult->mBEnd = bStart + line.BSize();
5863 return true;
5864 }
5865 }
5866 }
5867 return false;
5868 }
5869
5870 /* static */
GetLastLineBaseline(WritingMode aWM,const nsIFrame * aFrame,nscoord * aResult)5871 bool nsLayoutUtils::GetLastLineBaseline(WritingMode aWM, const nsIFrame* aFrame,
5872 nscoord* aResult) {
5873 if (aFrame->StyleDisplay()->IsContainLayout()) {
5874 return false;
5875 }
5876
5877 const nsBlockFrame* block = do_QueryFrame(aFrame);
5878 if (!block)
5879 // No baseline. (We intentionally don't descend into scroll frames.)
5880 return false;
5881
5882 for (nsBlockFrame::ConstReverseLineIterator line = block->LinesRBegin(),
5883 line_end = block->LinesREnd();
5884 line != line_end; ++line) {
5885 if (line->IsBlock()) {
5886 nsIFrame* kid = line->mFirstChild;
5887 nscoord kidBaseline;
5888 const nsSize& containerSize = line->mContainerSize;
5889 if (GetLastLineBaseline(aWM, kid, &kidBaseline)) {
5890 // Ignore relative positioning for baseline calculations
5891 *aResult = kidBaseline +
5892 kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
5893 return true;
5894 } else if (kid->IsScrollFrame()) {
5895 // Defer to nsIFrame::GetLogicalBaseline (which synthesizes a baseline
5896 // from the margin-box).
5897 kidBaseline = kid->GetLogicalBaseline(aWM);
5898 *aResult = kidBaseline +
5899 kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
5900 return true;
5901 }
5902 } else {
5903 // XXX Is this the right test? We have some bogus empty lines
5904 // floating around, but IsEmpty is perhaps too weak.
5905 if (line->BSize() != 0 || !line->IsEmpty()) {
5906 *aResult = line->BStart() + line->GetLogicalAscent();
5907 return true;
5908 }
5909 }
5910 }
5911 return false;
5912 }
5913
CalculateBlockContentBEnd(WritingMode aWM,nsBlockFrame * aFrame)5914 static nscoord CalculateBlockContentBEnd(WritingMode aWM,
5915 nsBlockFrame* aFrame) {
5916 MOZ_ASSERT(aFrame, "null ptr");
5917
5918 nscoord contentBEnd = 0;
5919
5920 for (const auto& line : aFrame->Lines()) {
5921 if (line.IsBlock()) {
5922 nsIFrame* child = line.mFirstChild;
5923 const auto& containerSize = line.mContainerSize;
5924 nscoord offset =
5925 child->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
5926 contentBEnd =
5927 std::max(contentBEnd,
5928 nsLayoutUtils::CalculateContentBEnd(aWM, child) + offset);
5929 } else {
5930 contentBEnd = std::max(contentBEnd, line.BEnd());
5931 }
5932 }
5933 return contentBEnd;
5934 }
5935
5936 /* static */
CalculateContentBEnd(WritingMode aWM,nsIFrame * aFrame)5937 nscoord nsLayoutUtils::CalculateContentBEnd(WritingMode aWM, nsIFrame* aFrame) {
5938 MOZ_ASSERT(aFrame, "null ptr");
5939
5940 nscoord contentBEnd = aFrame->BSize(aWM);
5941
5942 // We want scrollable overflow rather than visual because this
5943 // calculation is intended to affect layout.
5944 LogicalSize overflowSize(aWM, aFrame->ScrollableOverflowRect().Size());
5945 if (overflowSize.BSize(aWM) > contentBEnd) {
5946 nsIFrame::ChildListIDs skip = {nsIFrame::kOverflowList,
5947 nsIFrame::kExcessOverflowContainersList,
5948 nsIFrame::kOverflowOutOfFlowList};
5949 nsBlockFrame* blockFrame = do_QueryFrame(aFrame);
5950 if (blockFrame) {
5951 contentBEnd =
5952 std::max(contentBEnd, CalculateBlockContentBEnd(aWM, blockFrame));
5953 skip += nsIFrame::kPrincipalList;
5954 }
5955 for (const auto& [list, listID] : aFrame->ChildLists()) {
5956 if (!skip.contains(listID)) {
5957 for (nsIFrame* child : list) {
5958 nscoord offset =
5959 child->GetLogicalNormalPosition(aWM, aFrame->GetSize()).B(aWM);
5960 contentBEnd =
5961 std::max(contentBEnd, CalculateContentBEnd(aWM, child) + offset);
5962 }
5963 }
5964 }
5965 }
5966 return contentBEnd;
5967 }
5968
5969 /* static */
GetClosestLayer(nsIFrame * aFrame)5970 nsIFrame* nsLayoutUtils::GetClosestLayer(nsIFrame* aFrame) {
5971 nsIFrame* layer;
5972 for (layer = aFrame; layer; layer = layer->GetParent()) {
5973 if (layer->IsAbsPosContainingBlock() ||
5974 (layer->GetParent() && layer->GetParent()->IsScrollFrame()))
5975 break;
5976 }
5977 if (layer) return layer;
5978 return aFrame->PresShell()->GetRootFrame();
5979 }
5980
GetSamplingFilterForFrame(nsIFrame * aForFrame)5981 SamplingFilter nsLayoutUtils::GetSamplingFilterForFrame(nsIFrame* aForFrame) {
5982 switch (aForFrame->UsedImageRendering()) {
5983 case StyleImageRendering::Smooth:
5984 case StyleImageRendering::Optimizequality:
5985 return SamplingFilter::LINEAR;
5986 case StyleImageRendering::CrispEdges:
5987 case StyleImageRendering::Optimizespeed:
5988 case StyleImageRendering::Pixelated:
5989 return SamplingFilter::POINT;
5990 case StyleImageRendering::Auto:
5991 return SamplingFilter::GOOD;
5992 }
5993 MOZ_ASSERT_UNREACHABLE("Unknown image-rendering value");
5994 return SamplingFilter::GOOD;
5995 }
5996
5997 /**
5998 * Given an image being drawn into an appunit coordinate system, and
5999 * a point in that coordinate system, map the point back into image
6000 * pixel space.
6001 * @param aSize the size of the image, in pixels
6002 * @param aDest the rectangle that the image is being mapped into
6003 * @param aPt a point in the same coordinate system as the rectangle
6004 */
MapToFloatImagePixels(const gfxSize & aSize,const gfxRect & aDest,const gfxPoint & aPt)6005 static gfxPoint MapToFloatImagePixels(const gfxSize& aSize,
6006 const gfxRect& aDest,
6007 const gfxPoint& aPt) {
6008 return gfxPoint(((aPt.x - aDest.X()) * aSize.width) / aDest.Width(),
6009 ((aPt.y - aDest.Y()) * aSize.height) / aDest.Height());
6010 }
6011
6012 /**
6013 * Given an image being drawn into an pixel-based coordinate system, and
6014 * a point in image space, map the point into the pixel-based coordinate
6015 * system.
6016 * @param aSize the size of the image, in pixels
6017 * @param aDest the rectangle that the image is being mapped into
6018 * @param aPt a point in image space
6019 */
MapToFloatUserPixels(const gfxSize & aSize,const gfxRect & aDest,const gfxPoint & aPt)6020 static gfxPoint MapToFloatUserPixels(const gfxSize& aSize, const gfxRect& aDest,
6021 const gfxPoint& aPt) {
6022 return gfxPoint(aPt.x * aDest.Width() / aSize.width + aDest.X(),
6023 aPt.y * aDest.Height() / aSize.height + aDest.Y());
6024 }
6025
6026 /* static */
RectToGfxRect(const nsRect & aRect,int32_t aAppUnitsPerDevPixel)6027 gfxRect nsLayoutUtils::RectToGfxRect(const nsRect& aRect,
6028 int32_t aAppUnitsPerDevPixel) {
6029 return gfxRect(gfxFloat(aRect.x) / aAppUnitsPerDevPixel,
6030 gfxFloat(aRect.y) / aAppUnitsPerDevPixel,
6031 gfxFloat(aRect.width) / aAppUnitsPerDevPixel,
6032 gfxFloat(aRect.height) / aAppUnitsPerDevPixel);
6033 }
6034
6035 struct SnappedImageDrawingParameters {
6036 // A transform from image space to device space.
6037 gfxMatrix imageSpaceToDeviceSpace;
6038 // The size at which the image should be drawn (which may not be its
6039 // intrinsic size due to, for example, HQ scaling).
6040 nsIntSize size;
6041 // The region in tiled image space which will be drawn, with an associated
6042 // region to which sampling should be restricted.
6043 ImageRegion region;
6044 // The default viewport size for SVG images, which we use unless a different
6045 // one has been explicitly specified. This is the same as |size| except that
6046 // it does not take into account any transformation on the gfxContext we're
6047 // drawing to - for example, CSS transforms are not taken into account.
6048 CSSIntSize svgViewportSize;
6049 // Whether there's anything to draw at all.
6050 bool shouldDraw;
6051
SnappedImageDrawingParametersSnappedImageDrawingParameters6052 SnappedImageDrawingParameters()
6053 : region(ImageRegion::Empty()), shouldDraw(false) {}
6054
SnappedImageDrawingParametersSnappedImageDrawingParameters6055 SnappedImageDrawingParameters(const gfxMatrix& aImageSpaceToDeviceSpace,
6056 const nsIntSize& aSize,
6057 const ImageRegion& aRegion,
6058 const CSSIntSize& aSVGViewportSize)
6059 : imageSpaceToDeviceSpace(aImageSpaceToDeviceSpace),
6060 size(aSize),
6061 region(aRegion),
6062 svgViewportSize(aSVGViewportSize),
6063 shouldDraw(true) {}
6064 };
6065
6066 /**
6067 * Given two axis-aligned rectangles, returns the transformation that maps the
6068 * first onto the second.
6069 *
6070 * @param aFrom The rect to be transformed.
6071 * @param aTo The rect that aFrom should be mapped onto by the transformation.
6072 */
TransformBetweenRects(const gfxRect & aFrom,const gfxRect & aTo)6073 static gfxMatrix TransformBetweenRects(const gfxRect& aFrom,
6074 const gfxRect& aTo) {
6075 gfxSize scale(aTo.width / aFrom.width, aTo.height / aFrom.height);
6076 gfxPoint translation(aTo.x - aFrom.x * scale.width,
6077 aTo.y - aFrom.y * scale.height);
6078 return gfxMatrix(scale.width, 0, 0, scale.height, translation.x,
6079 translation.y);
6080 }
6081
TileNearRect(const nsRect & aAnyTile,const nsRect & aTargetRect)6082 static nsRect TileNearRect(const nsRect& aAnyTile, const nsRect& aTargetRect) {
6083 nsPoint distance = aTargetRect.TopLeft() - aAnyTile.TopLeft();
6084 return aAnyTile + nsPoint(distance.x / aAnyTile.width * aAnyTile.width,
6085 distance.y / aAnyTile.height * aAnyTile.height);
6086 }
6087
StableRound(gfxFloat aValue)6088 static gfxFloat StableRound(gfxFloat aValue) {
6089 // Values slightly less than 0.5 should round up like 0.5 would; we're
6090 // assuming they were meant to be 0.5.
6091 return floor(aValue + 0.5001);
6092 }
6093
StableRound(const gfxPoint & aPoint)6094 static gfxPoint StableRound(const gfxPoint& aPoint) {
6095 return gfxPoint(StableRound(aPoint.x), StableRound(aPoint.y));
6096 }
6097
6098 /**
6099 * Given a set of input parameters, compute certain output parameters
6100 * for drawing an image with the image snapping algorithm.
6101 * See https://wiki.mozilla.org/Gecko:Image_Snapping_and_Rendering
6102 *
6103 * @see nsLayoutUtils::DrawImage() for the descriptions of input parameters
6104 */
ComputeSnappedImageDrawingParameters(gfxContext * aCtx,int32_t aAppUnitsPerDevPixel,const nsRect aDest,const nsRect aFill,const nsPoint aAnchor,const nsRect aDirty,imgIContainer * aImage,const SamplingFilter aSamplingFilter,uint32_t aImageFlags,ExtendMode aExtendMode)6105 static SnappedImageDrawingParameters ComputeSnappedImageDrawingParameters(
6106 gfxContext* aCtx, int32_t aAppUnitsPerDevPixel, const nsRect aDest,
6107 const nsRect aFill, const nsPoint aAnchor, const nsRect aDirty,
6108 imgIContainer* aImage, const SamplingFilter aSamplingFilter,
6109 uint32_t aImageFlags, ExtendMode aExtendMode) {
6110 if (aDest.IsEmpty() || aFill.IsEmpty())
6111 return SnappedImageDrawingParameters();
6112
6113 // Avoid unnecessarily large offsets.
6114 bool doTile = !aDest.Contains(aFill);
6115 nsRect appUnitDest =
6116 doTile ? TileNearRect(aDest, aFill.Intersect(aDirty)) : aDest;
6117 nsPoint anchor = aAnchor + (appUnitDest.TopLeft() - aDest.TopLeft());
6118
6119 gfxRect devPixelDest =
6120 nsLayoutUtils::RectToGfxRect(appUnitDest, aAppUnitsPerDevPixel);
6121 gfxRect devPixelFill =
6122 nsLayoutUtils::RectToGfxRect(aFill, aAppUnitsPerDevPixel);
6123 gfxRect devPixelDirty =
6124 nsLayoutUtils::RectToGfxRect(aDirty, aAppUnitsPerDevPixel);
6125
6126 gfxMatrix currentMatrix = aCtx->CurrentMatrixDouble();
6127 gfxRect fill = devPixelFill;
6128 gfxRect dest = devPixelDest;
6129 bool didSnap;
6130 // Snap even if we have a scale in the context. But don't snap if
6131 // we have something that's not translation+scale, or if the scale flips in
6132 // the X or Y direction, because snapped image drawing can't handle that yet.
6133 if (!currentMatrix.HasNonAxisAlignedTransform() && currentMatrix._11 > 0.0 &&
6134 currentMatrix._22 > 0.0 &&
6135 aCtx->UserToDevicePixelSnapped(fill,
6136 gfxContext::SnapOption::IgnoreScale) &&
6137 aCtx->UserToDevicePixelSnapped(dest,
6138 gfxContext::SnapOption::IgnoreScale)) {
6139 // We snapped. On this code path, |fill| and |dest| take into account
6140 // currentMatrix's transform.
6141 didSnap = true;
6142 } else {
6143 // We didn't snap. On this code path, |fill| and |dest| do not take into
6144 // account currentMatrix's transform.
6145 didSnap = false;
6146 fill = devPixelFill;
6147 dest = devPixelDest;
6148 }
6149
6150 // If we snapped above, |dest| already takes into account |currentMatrix|'s
6151 // scale and has integer coordinates. If not, we need these properties to
6152 // compute the optimal drawn image size, so compute |snappedDestSize| here.
6153 gfxSize snappedDestSize = dest.Size();
6154 gfxSize scaleFactors = currentMatrix.ScaleFactors();
6155 if (!didSnap) {
6156 snappedDestSize.Scale(scaleFactors.width, scaleFactors.height);
6157 snappedDestSize.width = NS_round(snappedDestSize.width);
6158 snappedDestSize.height = NS_round(snappedDestSize.height);
6159 }
6160
6161 // We need to be sure that this is at least one pixel in width and height,
6162 // or we'll end up drawing nothing even if we have a nonempty fill.
6163 snappedDestSize.width = std::max(snappedDestSize.width, 1.0);
6164 snappedDestSize.height = std::max(snappedDestSize.height, 1.0);
6165
6166 // Bail if we're not going to end up drawing anything.
6167 if (fill.IsEmpty()) {
6168 return SnappedImageDrawingParameters();
6169 }
6170
6171 nsIntSize intImageSize = aImage->OptimalImageSizeForDest(
6172 snappedDestSize, imgIContainer::FRAME_CURRENT, aSamplingFilter,
6173 aImageFlags);
6174
6175 nsIntSize svgViewportSize;
6176 if (scaleFactors.width == 1.0 && scaleFactors.height == 1.0) {
6177 // intImageSize is scaled by currentMatrix. But since there are no scale
6178 // factors in currentMatrix, it is safe to assign intImageSize to
6179 // svgViewportSize directly.
6180 svgViewportSize = intImageSize;
6181 } else {
6182 // We should not take into account any transformation of currentMatrix
6183 // when computing svg viewport size. Since currentMatrix contains scale
6184 // factors, we need to recompute SVG viewport by unscaled devPixelDest.
6185 svgViewportSize = aImage->OptimalImageSizeForDest(
6186 devPixelDest.Size(), imgIContainer::FRAME_CURRENT, aSamplingFilter,
6187 aImageFlags);
6188 }
6189
6190 gfxSize imageSize(intImageSize.width, intImageSize.height);
6191
6192 // Compute the set of pixels that would be sampled by an ideal rendering
6193 gfxPoint subimageTopLeft =
6194 MapToFloatImagePixels(imageSize, devPixelDest, devPixelFill.TopLeft());
6195 gfxPoint subimageBottomRight = MapToFloatImagePixels(
6196 imageSize, devPixelDest, devPixelFill.BottomRight());
6197 gfxRect subimage;
6198 subimage.MoveTo(NSToIntFloor(subimageTopLeft.x),
6199 NSToIntFloor(subimageTopLeft.y));
6200 subimage.SizeTo(NSToIntCeil(subimageBottomRight.x) - subimage.x,
6201 NSToIntCeil(subimageBottomRight.y) - subimage.y);
6202
6203 if (subimage.IsEmpty()) {
6204 // Bail if the subimage is empty (we're not going to be drawing anything).
6205 return SnappedImageDrawingParameters();
6206 }
6207
6208 gfxMatrix transform;
6209 gfxMatrix invTransform;
6210
6211 bool anchorAtUpperLeft =
6212 anchor.x == appUnitDest.x && anchor.y == appUnitDest.y;
6213 bool exactlyOneImageCopy = aFill.IsEqualEdges(appUnitDest);
6214 if (anchorAtUpperLeft && exactlyOneImageCopy) {
6215 // The simple case: we can ignore the anchor point and compute the
6216 // transformation from the sampled region (the subimage) to the fill rect.
6217 // This approach is preferable when it works since it tends to produce
6218 // less numerical error.
6219 transform = TransformBetweenRects(subimage, fill);
6220 invTransform = TransformBetweenRects(fill, subimage);
6221 } else {
6222 // The more complicated case: we compute the transformation from the
6223 // image rect positioned at the image space anchor point to the dest rect
6224 // positioned at the device space anchor point.
6225
6226 // Compute the anchor point in both device space and image space. This
6227 // code assumes that pixel-based devices have one pixel per device unit!
6228 gfxPoint anchorPoint(gfxFloat(anchor.x) / aAppUnitsPerDevPixel,
6229 gfxFloat(anchor.y) / aAppUnitsPerDevPixel);
6230 gfxPoint imageSpaceAnchorPoint =
6231 MapToFloatImagePixels(imageSize, devPixelDest, anchorPoint);
6232
6233 if (didSnap) {
6234 imageSpaceAnchorPoint = StableRound(imageSpaceAnchorPoint);
6235 anchorPoint = imageSpaceAnchorPoint;
6236 anchorPoint = MapToFloatUserPixels(imageSize, devPixelDest, anchorPoint);
6237 anchorPoint = currentMatrix.TransformPoint(anchorPoint);
6238 anchorPoint = StableRound(anchorPoint);
6239 }
6240
6241 // Compute an unsnapped version of the dest rect's size. We continue to
6242 // follow the pattern that we take |currentMatrix| into account only if
6243 // |didSnap| is true.
6244 gfxSize unsnappedDestSize =
6245 didSnap ? devPixelDest.Size() * currentMatrix.ScaleFactors()
6246 : devPixelDest.Size();
6247
6248 gfxRect anchoredDestRect(anchorPoint, unsnappedDestSize);
6249 gfxRect anchoredImageRect(imageSpaceAnchorPoint, imageSize);
6250
6251 // Calculate anchoredDestRect with snapped fill rect when the devPixelFill
6252 // rect corresponds to just a single tile in that direction
6253 if (fill.Width() != devPixelFill.Width() &&
6254 devPixelDest.x == devPixelFill.x &&
6255 devPixelDest.XMost() == devPixelFill.XMost()) {
6256 anchoredDestRect.width = fill.width;
6257 }
6258 if (fill.Height() != devPixelFill.Height() &&
6259 devPixelDest.y == devPixelFill.y &&
6260 devPixelDest.YMost() == devPixelFill.YMost()) {
6261 anchoredDestRect.height = fill.height;
6262 }
6263
6264 transform = TransformBetweenRects(anchoredImageRect, anchoredDestRect);
6265 invTransform = TransformBetweenRects(anchoredDestRect, anchoredImageRect);
6266 }
6267
6268 // If the transform is not a straight translation by integers, then
6269 // filtering will occur, and restricting the fill rect to the dirty rect
6270 // would change the values computed for edge pixels, which we can't allow.
6271 // Also, if 'didSnap' is false then rounding out 'devPixelDirty' might not
6272 // produce pixel-aligned coordinates, which would also break the values
6273 // computed for edge pixels.
6274 if (didSnap && !invTransform.HasNonIntegerTranslation()) {
6275 // This form of Transform is safe to call since non-axis-aligned
6276 // transforms wouldn't be snapped.
6277 devPixelDirty = currentMatrix.TransformRect(devPixelDirty);
6278 devPixelDirty.RoundOut();
6279 fill = fill.Intersect(devPixelDirty);
6280 }
6281 if (fill.IsEmpty()) return SnappedImageDrawingParameters();
6282
6283 gfxRect imageSpaceFill(didSnap ? invTransform.TransformRect(fill)
6284 : invTransform.TransformBounds(fill));
6285
6286 // If we didn't snap, we need to post-multiply the matrix on the context to
6287 // get the final matrix we'll draw with, because we didn't take it into
6288 // account when computing the matrices above.
6289 if (!didSnap) {
6290 transform = transform * currentMatrix;
6291 }
6292
6293 ExtendMode extendMode = (aImageFlags & imgIContainer::FLAG_CLAMP)
6294 ? ExtendMode::CLAMP
6295 : aExtendMode;
6296 // We were passed in the default extend mode but need to tile.
6297 if (extendMode == ExtendMode::CLAMP && doTile) {
6298 MOZ_ASSERT(!(aImageFlags & imgIContainer::FLAG_CLAMP));
6299 extendMode = ExtendMode::REPEAT;
6300 }
6301
6302 ImageRegion region = ImageRegion::CreateWithSamplingRestriction(
6303 imageSpaceFill, subimage, extendMode);
6304
6305 return SnappedImageDrawingParameters(
6306 transform, intImageSize, region,
6307 CSSIntSize(svgViewportSize.width, svgViewportSize.height));
6308 }
6309
DrawImageInternal(gfxContext & aContext,nsPresContext * aPresContext,imgIContainer * aImage,const SamplingFilter aSamplingFilter,const nsRect & aDest,const nsRect & aFill,const nsPoint & aAnchor,const nsRect & aDirty,const Maybe<SVGImageContext> & aSVGContext,uint32_t aImageFlags,ExtendMode aExtendMode=ExtendMode::CLAMP,float aOpacity=1.0)6310 static ImgDrawResult DrawImageInternal(
6311 gfxContext& aContext, nsPresContext* aPresContext, imgIContainer* aImage,
6312 const SamplingFilter aSamplingFilter, const nsRect& aDest,
6313 const nsRect& aFill, const nsPoint& aAnchor, const nsRect& aDirty,
6314 const Maybe<SVGImageContext>& aSVGContext, uint32_t aImageFlags,
6315 ExtendMode aExtendMode = ExtendMode::CLAMP, float aOpacity = 1.0) {
6316 ImgDrawResult result = ImgDrawResult::SUCCESS;
6317
6318 aImageFlags |= imgIContainer::FLAG_ASYNC_NOTIFY;
6319
6320 if (aPresContext->Type() == nsPresContext::eContext_Print) {
6321 // We want vector images to be passed on as vector commands, not a raster
6322 // image.
6323 aImageFlags |= imgIContainer::FLAG_BYPASS_SURFACE_CACHE;
6324 }
6325 if (aDest.Contains(aFill)) {
6326 aImageFlags |= imgIContainer::FLAG_CLAMP;
6327 }
6328 int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
6329
6330 SnappedImageDrawingParameters params = ComputeSnappedImageDrawingParameters(
6331 &aContext, appUnitsPerDevPixel, aDest, aFill, aAnchor, aDirty, aImage,
6332 aSamplingFilter, aImageFlags, aExtendMode);
6333
6334 if (!params.shouldDraw) {
6335 return result;
6336 }
6337
6338 {
6339 gfxContextMatrixAutoSaveRestore contextMatrixRestorer(&aContext);
6340
6341 aContext.SetMatrixDouble(params.imageSpaceToDeviceSpace);
6342
6343 Maybe<SVGImageContext> fallbackContext;
6344 if (!aSVGContext) {
6345 // Use the default viewport.
6346 fallbackContext.emplace(Some(params.svgViewportSize));
6347 }
6348
6349 result = aImage->Draw(&aContext, params.size, params.region,
6350 imgIContainer::FRAME_CURRENT, aSamplingFilter,
6351 aSVGContext ? aSVGContext : fallbackContext,
6352 aImageFlags, aOpacity);
6353 }
6354
6355 return result;
6356 }
6357
6358 /* static */
DrawSingleUnscaledImage(gfxContext & aContext,nsPresContext * aPresContext,imgIContainer * aImage,const SamplingFilter aSamplingFilter,const nsPoint & aDest,const nsRect * aDirty,const Maybe<SVGImageContext> & aSVGContext,uint32_t aImageFlags,const nsRect * aSourceArea)6359 ImgDrawResult nsLayoutUtils::DrawSingleUnscaledImage(
6360 gfxContext& aContext, nsPresContext* aPresContext, imgIContainer* aImage,
6361 const SamplingFilter aSamplingFilter, const nsPoint& aDest,
6362 const nsRect* aDirty, const Maybe<SVGImageContext>& aSVGContext,
6363 uint32_t aImageFlags, const nsRect* aSourceArea) {
6364 CSSIntSize imageSize;
6365 aImage->GetWidth(&imageSize.width);
6366 aImage->GetHeight(&imageSize.height);
6367 aImage->GetResolution().ApplyTo(imageSize.width, imageSize.height);
6368
6369 if (imageSize.width < 1 || imageSize.height < 1) {
6370 NS_WARNING("Image width or height is non-positive");
6371 return ImgDrawResult::TEMPORARY_ERROR;
6372 }
6373
6374 nsSize size(CSSPixel::ToAppUnits(imageSize));
6375 nsRect source;
6376 if (aSourceArea) {
6377 source = *aSourceArea;
6378 } else {
6379 source.SizeTo(size);
6380 }
6381
6382 nsRect dest(aDest - source.TopLeft(), size);
6383 nsRect fill(aDest, source.Size());
6384 // Ensure that only a single image tile is drawn. If aSourceArea extends
6385 // outside the image bounds, we want to honor the aSourceArea-to-aDest
6386 // translation but we don't want to actually tile the image.
6387 fill.IntersectRect(fill, dest);
6388 return DrawImageInternal(aContext, aPresContext, aImage, aSamplingFilter,
6389 dest, fill, aDest, aDirty ? *aDirty : dest,
6390 aSVGContext, aImageFlags);
6391 }
6392
6393 /* static */
DrawSingleImage(gfxContext & aContext,nsPresContext * aPresContext,imgIContainer * aImage,SamplingFilter aSamplingFilter,const nsRect & aDest,const nsRect & aDirty,const Maybe<SVGImageContext> & aSVGContext,uint32_t aImageFlags,const nsPoint * aAnchorPoint,const nsRect * aSourceArea)6394 ImgDrawResult nsLayoutUtils::DrawSingleImage(
6395 gfxContext& aContext, nsPresContext* aPresContext, imgIContainer* aImage,
6396 SamplingFilter aSamplingFilter, const nsRect& aDest, const nsRect& aDirty,
6397 const Maybe<SVGImageContext>& aSVGContext, uint32_t aImageFlags,
6398 const nsPoint* aAnchorPoint, const nsRect* aSourceArea) {
6399 nscoord appUnitsPerCSSPixel = AppUnitsPerCSSPixel();
6400 // NOTE(emilio): We can hardcode resolution to 1 here, since we're interested
6401 // in the actual image pixels, for snapping purposes, not on the adjusted
6402 // size.
6403 CSSIntSize pixelImageSize(ComputeSizeForDrawingWithFallback(
6404 aImage, ImageResolution(), aDest.Size()));
6405 if (pixelImageSize.width < 1 || pixelImageSize.height < 1) {
6406 NS_ASSERTION(pixelImageSize.width >= 0 && pixelImageSize.height >= 0,
6407 "Image width or height is negative");
6408 return ImgDrawResult::SUCCESS; // no point in drawing a zero size image
6409 }
6410
6411 nsSize imageSize(CSSPixel::ToAppUnits(pixelImageSize));
6412 nsRect source;
6413 nsCOMPtr<imgIContainer> image;
6414 if (aSourceArea) {
6415 source = *aSourceArea;
6416 nsIntRect subRect(source.x, source.y, source.width, source.height);
6417 subRect.ScaleInverseRoundOut(appUnitsPerCSSPixel);
6418 image = ImageOps::Clip(aImage, subRect);
6419
6420 nsRect imageRect;
6421 imageRect.SizeTo(imageSize);
6422 nsRect clippedSource = imageRect.Intersect(source);
6423
6424 source -= clippedSource.TopLeft();
6425 imageSize = clippedSource.Size();
6426 } else {
6427 source.SizeTo(imageSize);
6428 image = aImage;
6429 }
6430
6431 nsRect dest = GetWholeImageDestination(imageSize, source, aDest);
6432
6433 // Ensure that only a single image tile is drawn. If aSourceArea extends
6434 // outside the image bounds, we want to honor the aSourceArea-to-aDest
6435 // transform but we don't want to actually tile the image.
6436 nsRect fill;
6437 fill.IntersectRect(aDest, dest);
6438 return DrawImageInternal(aContext, aPresContext, image, aSamplingFilter, dest,
6439 fill, aAnchorPoint ? *aAnchorPoint : fill.TopLeft(),
6440 aDirty, aSVGContext, aImageFlags);
6441 }
6442
6443 /* static */
ComputeSizeForDrawing(imgIContainer * aImage,const ImageResolution & aResolution,CSSIntSize & aImageSize,AspectRatio & aIntrinsicRatio,bool & aGotWidth,bool & aGotHeight)6444 void nsLayoutUtils::ComputeSizeForDrawing(
6445 imgIContainer* aImage, const ImageResolution& aResolution,
6446 /* outparam */ CSSIntSize& aImageSize,
6447 /* outparam */ AspectRatio& aIntrinsicRatio,
6448 /* outparam */ bool& aGotWidth,
6449 /* outparam */ bool& aGotHeight) {
6450 aGotWidth = NS_SUCCEEDED(aImage->GetWidth(&aImageSize.width));
6451 aGotHeight = NS_SUCCEEDED(aImage->GetHeight(&aImageSize.height));
6452 Maybe<AspectRatio> intrinsicRatio = aImage->GetIntrinsicRatio();
6453 aIntrinsicRatio = intrinsicRatio.valueOr(AspectRatio());
6454
6455 if (aGotWidth) {
6456 aResolution.ApplyXTo(aImageSize.width);
6457 }
6458 if (aGotHeight) {
6459 aResolution.ApplyYTo(aImageSize.height);
6460 }
6461
6462 if (!(aGotWidth && aGotHeight) && intrinsicRatio.isNothing()) {
6463 // We hit an error (say, because the image failed to load or couldn't be
6464 // decoded) and should return zero size.
6465 aGotWidth = aGotHeight = true;
6466 aImageSize = CSSIntSize(0, 0);
6467 }
6468 }
6469
6470 /* static */
ComputeSizeForDrawingWithFallback(imgIContainer * aImage,const ImageResolution & aResolution,const nsSize & aFallbackSize)6471 CSSIntSize nsLayoutUtils::ComputeSizeForDrawingWithFallback(
6472 imgIContainer* aImage, const ImageResolution& aResolution,
6473 const nsSize& aFallbackSize) {
6474 CSSIntSize imageSize;
6475 AspectRatio imageRatio;
6476 bool gotHeight, gotWidth;
6477 ComputeSizeForDrawing(aImage, aResolution, imageSize, imageRatio, gotWidth,
6478 gotHeight);
6479
6480 // If we didn't get both width and height, try to compute them using the
6481 // intrinsic ratio of the image.
6482 if (gotWidth != gotHeight) {
6483 if (!gotWidth) {
6484 if (imageRatio) {
6485 imageSize.width = imageRatio.ApplyTo(imageSize.height);
6486 gotWidth = true;
6487 }
6488 } else {
6489 if (imageRatio) {
6490 imageSize.height = imageRatio.Inverted().ApplyTo(imageSize.width);
6491 gotHeight = true;
6492 }
6493 }
6494 }
6495
6496 // If we still don't have a width or height, just use the fallback size the
6497 // caller provided.
6498 if (!gotWidth) {
6499 imageSize.width =
6500 nsPresContext::AppUnitsToIntCSSPixels(aFallbackSize.width);
6501 }
6502 if (!gotHeight) {
6503 imageSize.height =
6504 nsPresContext::AppUnitsToIntCSSPixels(aFallbackSize.height);
6505 }
6506
6507 return imageSize;
6508 }
6509
SnapRectForImage(const gfx::Matrix & aTransform,const gfx::Size & aScaleFactors,const LayoutDeviceRect & aRect)6510 /* static */ LayerIntRect SnapRectForImage(const gfx::Matrix& aTransform,
6511 const gfx::Size& aScaleFactors,
6512 const LayoutDeviceRect& aRect) {
6513 // Attempt to snap pixels, the same as ComputeSnappedImageDrawingParameters.
6514 // Any changes to the algorithm here will need to be reflected there.
6515 bool snapped = false;
6516 LayerIntRect snapRect;
6517 if (!aTransform.HasNonAxisAlignedTransform() && aTransform._11 > 0.0 &&
6518 aTransform._22 > 0.0) {
6519 gfxRect rect(gfxPoint(aRect.X(), aRect.Y()),
6520 gfxSize(aRect.Width(), aRect.Height()));
6521
6522 gfxPoint p1 =
6523 ThebesPoint(aTransform.TransformPoint(ToPoint(rect.TopLeft())));
6524 gfxPoint p2 =
6525 ThebesPoint(aTransform.TransformPoint(ToPoint(rect.TopRight())));
6526 gfxPoint p3 =
6527 ThebesPoint(aTransform.TransformPoint(ToPoint(rect.BottomRight())));
6528
6529 if (p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y)) {
6530 p1.Round();
6531 p3.Round();
6532
6533 IntPoint p1i(int32_t(p1.x), int32_t(p1.y));
6534 IntPoint p3i(int32_t(p3.x), int32_t(p3.y));
6535
6536 snapRect.MoveTo(std::min(p1i.x, p3i.x), std::min(p1i.y, p3i.y));
6537 snapRect.SizeTo(std::max(p1i.x, p3i.x) - snapRect.X(),
6538 std::max(p1i.y, p3i.y) - snapRect.Y());
6539 snapped = true;
6540 }
6541 }
6542
6543 if (!snapped) {
6544 // If we couldn't snap directly with the transform, we need to go best
6545 // effort in layer pixels.
6546 snapRect = RoundedToInt(LayerRect(aRect.X() * aScaleFactors.width,
6547 aRect.Y() * aScaleFactors.height,
6548 aRect.Width() * aScaleFactors.width,
6549 aRect.Height() * aScaleFactors.height));
6550 }
6551
6552 // An empty size is unacceptable so we ensure our suggested size is at least
6553 // 1 pixel wide/tall.
6554 if (snapRect.Width() < 1) {
6555 snapRect.SetWidth(1);
6556 }
6557 if (snapRect.Height() < 1) {
6558 snapRect.SetHeight(1);
6559 }
6560 return snapRect;
6561 }
6562
6563 /* static */
ComputeImageContainerDrawingParameters(imgIContainer * aImage,nsIFrame * aForFrame,const LayoutDeviceRect & aDestRect,const LayoutDeviceRect & aFillRect,const StackingContextHelper & aSc,uint32_t aFlags,Maybe<SVGImageContext> & aSVGContext,Maybe<ImageIntRegion> & aRegion)6564 IntSize nsLayoutUtils::ComputeImageContainerDrawingParameters(
6565 imgIContainer* aImage, nsIFrame* aForFrame,
6566 const LayoutDeviceRect& aDestRect, const LayoutDeviceRect& aFillRect,
6567 const StackingContextHelper& aSc, uint32_t aFlags,
6568 Maybe<SVGImageContext>& aSVGContext, Maybe<ImageIntRegion>& aRegion) {
6569 MOZ_ASSERT(aImage);
6570 MOZ_ASSERT(aForFrame);
6571
6572 gfx::Size scaleFactors = aSc.GetInheritedScale();
6573 SamplingFilter samplingFilter =
6574 nsLayoutUtils::GetSamplingFilterForFrame(aForFrame);
6575
6576 // Compute our SVG context parameters, if any. Don't replace the viewport
6577 // size if it was already set, prefer what the caller gave.
6578 SVGImageContext::MaybeStoreContextPaint(aSVGContext, aForFrame, aImage);
6579 if ((scaleFactors.width != 1.0 || scaleFactors.height != 1.0) &&
6580 aImage->GetType() == imgIContainer::TYPE_VECTOR &&
6581 (!aSVGContext || !aSVGContext->GetViewportSize())) {
6582 gfxSize gfxDestSize(aDestRect.Width(), aDestRect.Height());
6583 IntSize viewportSize = aImage->OptimalImageSizeForDest(
6584 gfxDestSize, imgIContainer::FRAME_CURRENT, samplingFilter, aFlags);
6585
6586 CSSIntSize cssViewportSize(viewportSize.width, viewportSize.height);
6587 if (!aSVGContext) {
6588 aSVGContext.emplace(Some(cssViewportSize));
6589 } else {
6590 aSVGContext->SetViewportSize(Some(cssViewportSize));
6591 }
6592 }
6593
6594 const gfx::Matrix& itm = aSc.GetInheritedTransform();
6595 LayerIntRect destRect = SnapRectForImage(itm, scaleFactors, aDestRect);
6596
6597 // Since we always decode entire raster images, we only care about the
6598 // ImageIntRegion for vector images, for which we may only draw part of in
6599 // some cases.
6600 if (aImage->GetType() != imgIContainer::TYPE_VECTOR) {
6601 return aImage->OptimalImageSizeForDest(
6602 gfxSize(destRect.Width(), destRect.Height()),
6603 imgIContainer::FRAME_CURRENT, samplingFilter, aFlags);
6604 }
6605
6606 // We only use the region rect with blob recordings. This is because when we
6607 // rasterize an SVG image in process, we always create a complete
6608 // rasterization of the whole image which can be given to any caller, while
6609 // we support partial rasterization with the blob recordings.
6610 if (aFlags & imgIContainer::FLAG_RECORD_BLOB) {
6611 // If the dest rect contains the fill rect, then we are only displaying part
6612 // of the vector image. We need to calculate the restriction region to avoid
6613 // drawing more than we need, and sampling outside the desired bounds.
6614 LayerIntRect clipRect = SnapRectForImage(itm, scaleFactors, aFillRect);
6615 if (destRect.Contains(clipRect)) {
6616 LayerIntRect restrictRect = destRect.Intersect(clipRect);
6617 restrictRect.MoveBy(-destRect.TopLeft());
6618
6619 if (restrictRect.Width() < 1) {
6620 restrictRect.SetWidth(1);
6621 }
6622 if (restrictRect.Height() < 1) {
6623 restrictRect.SetHeight(1);
6624 }
6625
6626 if (restrictRect.X() != 0 || restrictRect.Y() != 0 ||
6627 restrictRect.Size() != destRect.Size()) {
6628 IntRect sampleRect = restrictRect.ToUnknownRect();
6629 aRegion = Some(ImageIntRegion::CreateWithSamplingRestriction(
6630 sampleRect, sampleRect, ExtendMode::CLAMP));
6631 }
6632 }
6633 }
6634
6635 // VectorImage::OptimalImageSizeForDest will just round up, but we already
6636 // have an integer size.
6637 return destRect.Size().ToUnknownSize();
6638 }
6639
6640 /* static */
GetBackgroundFirstTilePos(const nsPoint & aDest,const nsPoint & aFill,const nsSize & aRepeatSize)6641 nsPoint nsLayoutUtils::GetBackgroundFirstTilePos(const nsPoint& aDest,
6642 const nsPoint& aFill,
6643 const nsSize& aRepeatSize) {
6644 return nsPoint(NSToIntFloor(float(aFill.x - aDest.x) / aRepeatSize.width) *
6645 aRepeatSize.width,
6646 NSToIntFloor(float(aFill.y - aDest.y) / aRepeatSize.height) *
6647 aRepeatSize.height) +
6648 aDest;
6649 }
6650
6651 /* static */
DrawBackgroundImage(gfxContext & aContext,nsIFrame * aForFrame,nsPresContext * aPresContext,imgIContainer * aImage,SamplingFilter aSamplingFilter,const nsRect & aDest,const nsRect & aFill,const nsSize & aRepeatSize,const nsPoint & aAnchor,const nsRect & aDirty,uint32_t aImageFlags,ExtendMode aExtendMode,float aOpacity)6652 ImgDrawResult nsLayoutUtils::DrawBackgroundImage(
6653 gfxContext& aContext, nsIFrame* aForFrame, nsPresContext* aPresContext,
6654 imgIContainer* aImage, SamplingFilter aSamplingFilter, const nsRect& aDest,
6655 const nsRect& aFill, const nsSize& aRepeatSize, const nsPoint& aAnchor,
6656 const nsRect& aDirty, uint32_t aImageFlags, ExtendMode aExtendMode,
6657 float aOpacity) {
6658 AUTO_PROFILER_LABEL("nsLayoutUtils::DrawBackgroundImage",
6659 GRAPHICS_Rasterization);
6660
6661 CSSIntSize destCSSSize{nsPresContext::AppUnitsToIntCSSPixels(aDest.width),
6662 nsPresContext::AppUnitsToIntCSSPixels(aDest.height)};
6663
6664 Maybe<SVGImageContext> svgContext(Some(SVGImageContext(Some(destCSSSize))));
6665 SVGImageContext::MaybeStoreContextPaint(svgContext, aForFrame, aImage);
6666
6667 /* Fast path when there is no need for image spacing */
6668 if (aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height) {
6669 return DrawImageInternal(aContext, aPresContext, aImage, aSamplingFilter,
6670 aDest, aFill, aAnchor, aDirty, svgContext,
6671 aImageFlags, aExtendMode, aOpacity);
6672 }
6673
6674 const nsPoint firstTilePos =
6675 GetBackgroundFirstTilePos(aDest.TopLeft(), aFill.TopLeft(), aRepeatSize);
6676 const nscoord xMost = aFill.XMost();
6677 const nscoord repeatWidth = aRepeatSize.width;
6678 const nscoord yMost = aFill.YMost();
6679 const nscoord repeatHeight = aRepeatSize.height;
6680 nsRect dest(0, 0, aDest.width, aDest.height);
6681 nsPoint anchor = aAnchor;
6682 for (nscoord x = firstTilePos.x; x < xMost; x += repeatWidth) {
6683 for (nscoord y = firstTilePos.y; y < yMost; y += repeatHeight) {
6684 dest.x = x;
6685 dest.y = y;
6686 ImgDrawResult result = DrawImageInternal(
6687 aContext, aPresContext, aImage, aSamplingFilter, dest, dest, anchor,
6688 aDirty, svgContext, aImageFlags, ExtendMode::CLAMP, aOpacity);
6689 anchor.y += repeatHeight;
6690 if (result != ImgDrawResult::SUCCESS) {
6691 return result;
6692 }
6693 }
6694 anchor.x += repeatWidth;
6695 anchor.y = aAnchor.y;
6696 }
6697
6698 return ImgDrawResult::SUCCESS;
6699 }
6700
6701 /* static */
DrawImage(gfxContext & aContext,ComputedStyle * aComputedStyle,nsPresContext * aPresContext,imgIContainer * aImage,const SamplingFilter aSamplingFilter,const nsRect & aDest,const nsRect & aFill,const nsPoint & aAnchor,const nsRect & aDirty,uint32_t aImageFlags,float aOpacity)6702 ImgDrawResult nsLayoutUtils::DrawImage(
6703 gfxContext& aContext, ComputedStyle* aComputedStyle,
6704 nsPresContext* aPresContext, imgIContainer* aImage,
6705 const SamplingFilter aSamplingFilter, const nsRect& aDest,
6706 const nsRect& aFill, const nsPoint& aAnchor, const nsRect& aDirty,
6707 uint32_t aImageFlags, float aOpacity) {
6708 Maybe<SVGImageContext> svgContext;
6709 SVGImageContext::MaybeStoreContextPaint(svgContext, aComputedStyle, aImage);
6710
6711 return DrawImageInternal(aContext, aPresContext, aImage, aSamplingFilter,
6712 aDest, aFill, aAnchor, aDirty, svgContext,
6713 aImageFlags, ExtendMode::CLAMP, aOpacity);
6714 }
6715
6716 /* static */
GetWholeImageDestination(const nsSize & aWholeImageSize,const nsRect & aImageSourceArea,const nsRect & aDestArea)6717 nsRect nsLayoutUtils::GetWholeImageDestination(const nsSize& aWholeImageSize,
6718 const nsRect& aImageSourceArea,
6719 const nsRect& aDestArea) {
6720 double scaleX = double(aDestArea.width) / aImageSourceArea.width;
6721 double scaleY = double(aDestArea.height) / aImageSourceArea.height;
6722 nscoord destOffsetX = NSToCoordRound(aImageSourceArea.x * scaleX);
6723 nscoord destOffsetY = NSToCoordRound(aImageSourceArea.y * scaleY);
6724 nscoord wholeSizeX = NSToCoordRound(aWholeImageSize.width * scaleX);
6725 nscoord wholeSizeY = NSToCoordRound(aWholeImageSize.height * scaleY);
6726 return nsRect(aDestArea.TopLeft() - nsPoint(destOffsetX, destOffsetY),
6727 nsSize(wholeSizeX, wholeSizeY));
6728 }
6729
6730 /* static */
OrientImage(imgIContainer * aContainer,const StyleImageOrientation & aOrientation)6731 already_AddRefed<imgIContainer> nsLayoutUtils::OrientImage(
6732 imgIContainer* aContainer, const StyleImageOrientation& aOrientation) {
6733 MOZ_ASSERT(aContainer, "Should have an image container");
6734 nsCOMPtr<imgIContainer> img(aContainer);
6735
6736 switch (aOrientation) {
6737 case StyleImageOrientation::FromImage:
6738 break;
6739 case StyleImageOrientation::None:
6740 img = ImageOps::Unorient(img);
6741 break;
6742 }
6743
6744 return img.forget();
6745 }
6746
NonZeroCorner(const LengthPercentage & aLength)6747 static bool NonZeroCorner(const LengthPercentage& aLength) {
6748 // Since negative results are clamped to 0, check > 0.
6749 return aLength.Resolve(nscoord_MAX) > 0 || aLength.Resolve(0) > 0;
6750 }
6751
6752 /* static */
HasNonZeroCorner(const BorderRadius & aCorners)6753 bool nsLayoutUtils::HasNonZeroCorner(const BorderRadius& aCorners) {
6754 for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
6755 if (NonZeroCorner(aCorners.Get(corner))) return true;
6756 }
6757 return false;
6758 }
6759
6760 // aCorner is a "full corner" value, i.e. eCornerTopLeft etc.
IsCornerAdjacentToSide(uint8_t aCorner,Side aSide)6761 static bool IsCornerAdjacentToSide(uint8_t aCorner, Side aSide) {
6762 static_assert((int)eSideTop == eCornerTopLeft, "Check for Full Corner");
6763 static_assert((int)eSideRight == eCornerTopRight, "Check for Full Corner");
6764 static_assert((int)eSideBottom == eCornerBottomRight,
6765 "Check for Full Corner");
6766 static_assert((int)eSideLeft == eCornerBottomLeft, "Check for Full Corner");
6767 static_assert((int)eSideTop == ((eCornerTopRight - 1) & 3),
6768 "Check for Full Corner");
6769 static_assert((int)eSideRight == ((eCornerBottomRight - 1) & 3),
6770 "Check for Full Corner");
6771 static_assert((int)eSideBottom == ((eCornerBottomLeft - 1) & 3),
6772 "Check for Full Corner");
6773 static_assert((int)eSideLeft == ((eCornerTopLeft - 1) & 3),
6774 "Check for Full Corner");
6775
6776 return aSide == aCorner || aSide == ((aCorner - 1) & 3);
6777 }
6778
6779 /* static */
HasNonZeroCornerOnSide(const BorderRadius & aCorners,Side aSide)6780 bool nsLayoutUtils::HasNonZeroCornerOnSide(const BorderRadius& aCorners,
6781 Side aSide) {
6782 static_assert(eCornerTopLeftX / 2 == eCornerTopLeft,
6783 "Check for Non Zero on side");
6784 static_assert(eCornerTopLeftY / 2 == eCornerTopLeft,
6785 "Check for Non Zero on side");
6786 static_assert(eCornerTopRightX / 2 == eCornerTopRight,
6787 "Check for Non Zero on side");
6788 static_assert(eCornerTopRightY / 2 == eCornerTopRight,
6789 "Check for Non Zero on side");
6790 static_assert(eCornerBottomRightX / 2 == eCornerBottomRight,
6791 "Check for Non Zero on side");
6792 static_assert(eCornerBottomRightY / 2 == eCornerBottomRight,
6793 "Check for Non Zero on side");
6794 static_assert(eCornerBottomLeftX / 2 == eCornerBottomLeft,
6795 "Check for Non Zero on side");
6796 static_assert(eCornerBottomLeftY / 2 == eCornerBottomLeft,
6797 "Check for Non Zero on side");
6798
6799 for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
6800 // corner is a "half corner" value, so dividing by two gives us a
6801 // "full corner" value.
6802 if (NonZeroCorner(aCorners.Get(corner)) &&
6803 IsCornerAdjacentToSide(corner / 2, aSide))
6804 return true;
6805 }
6806 return false;
6807 }
6808
6809 /* static */
GetBorderRadiusForMenuDropShadow(const nsIFrame * aFrame)6810 LayoutDeviceIntSize nsLayoutUtils::GetBorderRadiusForMenuDropShadow(
6811 const nsIFrame* aFrame) {
6812 if (aFrame->StyleUIReset()->mWindowShadow == StyleWindowShadow::Cliprounded) {
6813 const auto& corners = aFrame->StyleBorder()->mBorderRadius;
6814
6815 // Get the width and height of the top-left corner.
6816 const LengthPercentage& cornerX = corners.Get(eCornerTopLeftX);
6817 const LengthPercentage& cornerY = corners.Get(eCornerTopLeftY);
6818 nscoord lengthX = (cornerX.IsLength() ? cornerX.ToLength() : 0);
6819 nscoord lengthY = (cornerY.IsLength() ? cornerY.ToLength() : 0);
6820 if (lengthX || lengthY) {
6821 const nsPresContext* presContext = aFrame->PresContext();
6822 return LayoutDeviceIntSize(presContext->AppUnitsToDevPixels(lengthX),
6823 presContext->AppUnitsToDevPixels(lengthY));
6824 }
6825 }
6826
6827 return LayoutDeviceIntSize();
6828 }
6829
6830 /* static */
GetFrameTransparency(nsIFrame * aBackgroundFrame,nsIFrame * aCSSRootFrame)6831 nsTransparencyMode nsLayoutUtils::GetFrameTransparency(
6832 nsIFrame* aBackgroundFrame, nsIFrame* aCSSRootFrame) {
6833 if (aCSSRootFrame->StyleEffects()->mOpacity < 1.0f)
6834 return eTransparencyTransparent;
6835
6836 if (HasNonZeroCorner(aCSSRootFrame->StyleBorder()->mBorderRadius))
6837 return eTransparencyTransparent;
6838
6839 StyleAppearance appearance =
6840 aCSSRootFrame->StyleDisplay()->EffectiveAppearance();
6841
6842 if (appearance == StyleAppearance::MozWinGlass) return eTransparencyGlass;
6843
6844 if (appearance == StyleAppearance::MozWinBorderlessGlass)
6845 return eTransparencyBorderlessGlass;
6846
6847 nsITheme::Transparency transparency;
6848 if (aCSSRootFrame->IsThemed(&transparency))
6849 return transparency == nsITheme::eTransparent ? eTransparencyTransparent
6850 : eTransparencyOpaque;
6851
6852 // We need an uninitialized window to be treated as opaque because
6853 // doing otherwise breaks window display effects on some platforms,
6854 // specifically Vista. (bug 450322)
6855 if (aBackgroundFrame->IsViewportFrame() &&
6856 !aBackgroundFrame->PrincipalChildList().FirstChild()) {
6857 return eTransparencyOpaque;
6858 }
6859
6860 ComputedStyle* bgSC;
6861 if (!nsCSSRendering::FindBackground(aBackgroundFrame, &bgSC)) {
6862 return eTransparencyTransparent;
6863 }
6864 const nsStyleBackground* bg = bgSC->StyleBackground();
6865 if (NS_GET_A(bg->BackgroundColor(bgSC)) < 255 ||
6866 // bottom layer's clip is used for the color
6867 bg->BottomLayer().mClip != StyleGeometryBox::BorderBox)
6868 return eTransparencyTransparent;
6869 return eTransparencyOpaque;
6870 }
6871
6872 /* static */
IsPopup(const nsIFrame * aFrame)6873 bool nsLayoutUtils::IsPopup(const nsIFrame* aFrame) {
6874 // Optimization: the frame can't possibly be a popup if it has no view.
6875 if (!aFrame->HasView()) {
6876 NS_ASSERTION(!aFrame->IsMenuPopupFrame(), "popup frame must have a view");
6877 return false;
6878 }
6879 return aFrame->IsMenuPopupFrame();
6880 }
6881
6882 /* static */
GetDisplayRootFrame(nsIFrame * aFrame)6883 nsIFrame* nsLayoutUtils::GetDisplayRootFrame(nsIFrame* aFrame) {
6884 return const_cast<nsIFrame*>(
6885 nsLayoutUtils::GetDisplayRootFrame(const_cast<const nsIFrame*>(aFrame)));
6886 }
6887
6888 /* static */
GetDisplayRootFrame(const nsIFrame * aFrame)6889 const nsIFrame* nsLayoutUtils::GetDisplayRootFrame(const nsIFrame* aFrame) {
6890 // We could use GetRootPresContext() here if the
6891 // NS_FRAME_IN_POPUP frame bit is set.
6892 const nsIFrame* f = aFrame;
6893 for (;;) {
6894 if (!f->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
6895 f = f->PresShell()->GetRootFrame();
6896 if (!f) {
6897 return aFrame;
6898 }
6899 } else if (IsPopup(f)) {
6900 return f;
6901 }
6902 nsIFrame* parent = GetCrossDocParentFrameInProcess(f);
6903 if (!parent) return f;
6904 f = parent;
6905 }
6906 }
6907
6908 /* static */
GetReferenceFrame(nsIFrame * aFrame)6909 nsIFrame* nsLayoutUtils::GetReferenceFrame(nsIFrame* aFrame) {
6910 nsIFrame* f = aFrame;
6911 for (;;) {
6912 if (f->IsTransformed() || f->IsPreserve3DLeaf() || IsPopup(f)) {
6913 return f;
6914 }
6915 nsIFrame* parent = GetCrossDocParentFrameInProcess(f);
6916 if (!parent) {
6917 return f;
6918 }
6919 f = parent;
6920 }
6921 }
6922
GetTextRunFlagsForStyle(ComputedStyle * aComputedStyle,nsPresContext * aPresContext,const nsStyleFont * aStyleFont,const nsStyleText * aStyleText,nscoord aLetterSpacing)6923 /* static */ gfx::ShapedTextFlags nsLayoutUtils::GetTextRunFlagsForStyle(
6924 ComputedStyle* aComputedStyle, nsPresContext* aPresContext,
6925 const nsStyleFont* aStyleFont, const nsStyleText* aStyleText,
6926 nscoord aLetterSpacing) {
6927 gfx::ShapedTextFlags result = gfx::ShapedTextFlags();
6928 if (aLetterSpacing != 0 ||
6929 aStyleText->mTextJustify == StyleTextJustify::InterCharacter) {
6930 result |= gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES;
6931 }
6932 if (aStyleText->mMozControlCharacterVisibility ==
6933 StyleMozControlCharacterVisibility::Hidden) {
6934 result |= gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS;
6935 }
6936 switch (aComputedStyle->StyleText()->mTextRendering) {
6937 case StyleTextRendering::Optimizespeed:
6938 result |= gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED;
6939 break;
6940 case StyleTextRendering::Auto:
6941 if (aStyleFont->mFont.size.ToCSSPixels() <
6942 aPresContext->GetAutoQualityMinFontSize()) {
6943 result |= gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED;
6944 }
6945 break;
6946 default:
6947 break;
6948 }
6949 return result | GetTextRunOrientFlagsForStyle(aComputedStyle);
6950 }
6951
GetTextRunOrientFlagsForStyle(ComputedStyle * aComputedStyle)6952 /* static */ gfx::ShapedTextFlags nsLayoutUtils::GetTextRunOrientFlagsForStyle(
6953 ComputedStyle* aComputedStyle) {
6954 auto writingMode = aComputedStyle->StyleVisibility()->mWritingMode;
6955 switch (writingMode) {
6956 case StyleWritingModeProperty::HorizontalTb:
6957 return gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL;
6958
6959 case StyleWritingModeProperty::VerticalLr:
6960 case StyleWritingModeProperty::VerticalRl:
6961 switch (aComputedStyle->StyleVisibility()->mTextOrientation) {
6962 case StyleTextOrientation::Mixed:
6963 return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED;
6964 case StyleTextOrientation::Upright:
6965 return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
6966 case StyleTextOrientation::Sideways:
6967 return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
6968 default:
6969 MOZ_ASSERT_UNREACHABLE("unknown text-orientation");
6970 return gfx::ShapedTextFlags();
6971 }
6972
6973 case StyleWritingModeProperty::SidewaysLr:
6974 return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT;
6975
6976 case StyleWritingModeProperty::SidewaysRl:
6977 return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
6978
6979 default:
6980 MOZ_ASSERT_UNREACHABLE("unknown writing-mode");
6981 return gfx::ShapedTextFlags();
6982 }
6983 }
6984
6985 /* static */
GetRectDifferenceStrips(const nsRect & aR1,const nsRect & aR2,nsRect * aHStrip,nsRect * aVStrip)6986 void nsLayoutUtils::GetRectDifferenceStrips(const nsRect& aR1,
6987 const nsRect& aR2, nsRect* aHStrip,
6988 nsRect* aVStrip) {
6989 NS_ASSERTION(aR1.TopLeft() == aR2.TopLeft(),
6990 "expected rects at the same position");
6991 nsRect unionRect(aR1.x, aR1.y, std::max(aR1.width, aR2.width),
6992 std::max(aR1.height, aR2.height));
6993 nscoord VStripStart = std::min(aR1.width, aR2.width);
6994 nscoord HStripStart = std::min(aR1.height, aR2.height);
6995 *aVStrip = unionRect;
6996 aVStrip->x += VStripStart;
6997 aVStrip->width -= VStripStart;
6998 *aHStrip = unionRect;
6999 aHStrip->y += HStripStart;
7000 aHStrip->height -= HStripStart;
7001 }
7002
GetDeviceContextForScreenInfo(nsPIDOMWindowOuter * aWindow)7003 nsDeviceContext* nsLayoutUtils::GetDeviceContextForScreenInfo(
7004 nsPIDOMWindowOuter* aWindow) {
7005 if (!aWindow) {
7006 return nullptr;
7007 }
7008
7009 nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
7010 while (docShell) {
7011 // Now make sure our size is up to date. That will mean that the device
7012 // context does the right thing on multi-monitor systems when we return it
7013 // to the caller. It will also make sure that our prescontext has been
7014 // created, if we're supposed to have one.
7015 nsCOMPtr<nsPIDOMWindowOuter> win = docShell->GetWindow();
7016 if (!win) {
7017 // No reason to go on
7018 return nullptr;
7019 }
7020
7021 win->EnsureSizeAndPositionUpToDate();
7022
7023 RefPtr<nsPresContext> presContext = docShell->GetPresContext();
7024 if (presContext) {
7025 nsDeviceContext* context = presContext->DeviceContext();
7026 if (context) {
7027 return context;
7028 }
7029 }
7030
7031 nsCOMPtr<nsIDocShellTreeItem> parentItem;
7032 docShell->GetInProcessParent(getter_AddRefs(parentItem));
7033 docShell = do_QueryInterface(parentItem);
7034 }
7035
7036 return nullptr;
7037 }
7038
7039 /* static */
IsReallyFixedPos(const nsIFrame * aFrame)7040 bool nsLayoutUtils::IsReallyFixedPos(const nsIFrame* aFrame) {
7041 MOZ_ASSERT(aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed,
7042 "IsReallyFixedPos called on non-'position:fixed' frame");
7043 return MayBeReallyFixedPos(aFrame);
7044 }
7045
7046 /* static */
MayBeReallyFixedPos(const nsIFrame * aFrame)7047 bool nsLayoutUtils::MayBeReallyFixedPos(const nsIFrame* aFrame) {
7048 MOZ_ASSERT(aFrame->GetParent(),
7049 "MayBeReallyFixedPos called on frame not in tree");
7050 LayoutFrameType parentType = aFrame->GetParent()->Type();
7051 return parentType == LayoutFrameType::Viewport ||
7052 parentType == LayoutFrameType::PageContent;
7053 }
7054
7055 /* static */
IsInPositionFixedSubtree(const nsIFrame * aFrame)7056 bool nsLayoutUtils::IsInPositionFixedSubtree(const nsIFrame* aFrame) {
7057 for (const nsIFrame* f = aFrame; f; f = f->GetParent()) {
7058 if (f->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
7059 nsLayoutUtils::IsReallyFixedPos(f)) {
7060 return true;
7061 }
7062 }
7063 return false;
7064 }
7065
SurfaceFromOffscreenCanvas(OffscreenCanvas * aOffscreenCanvas,uint32_t aSurfaceFlags,RefPtr<DrawTarget> & aTarget)7066 SurfaceFromElementResult nsLayoutUtils::SurfaceFromOffscreenCanvas(
7067 OffscreenCanvas* aOffscreenCanvas, uint32_t aSurfaceFlags,
7068 RefPtr<DrawTarget>& aTarget) {
7069 SurfaceFromElementResult result;
7070
7071 IntSize size = aOffscreenCanvas->GetWidthHeight();
7072
7073 result.mSourceSurface =
7074 aOffscreenCanvas->GetSurfaceSnapshot(&result.mAlphaType);
7075 if (!result.mSourceSurface) {
7076 // If the element doesn't have a context then we won't get a snapshot. The
7077 // canvas spec wants us to not error and just draw nothing, so return an
7078 // empty surface.
7079 result.mAlphaType = gfxAlphaType::Opaque;
7080 RefPtr<DrawTarget> ref =
7081 aTarget ? aTarget
7082 : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
7083 if (ref->CanCreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8)) {
7084 RefPtr<DrawTarget> dt =
7085 ref->CreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8);
7086 if (dt) {
7087 result.mSourceSurface = dt->Snapshot();
7088 }
7089 }
7090 } else if (aTarget) {
7091 RefPtr<SourceSurface> opt =
7092 aTarget->OptimizeSourceSurface(result.mSourceSurface);
7093 if (opt) {
7094 result.mSourceSurface = opt;
7095 }
7096 }
7097
7098 result.mHasSize = true;
7099 result.mSize = size;
7100 result.mIntrinsicSize = size;
7101 result.mIsWriteOnly = aOffscreenCanvas->IsWriteOnly();
7102
7103 nsIGlobalObject* global = aOffscreenCanvas->GetParentObject();
7104 if (global) {
7105 result.mPrincipal = global->PrincipalOrNull();
7106 }
7107
7108 return result;
7109 }
7110
ScaleSourceSurface(SourceSurface & aSurface,const IntSize & aTargetSize)7111 static RefPtr<SourceSurface> ScaleSourceSurface(SourceSurface& aSurface,
7112 const IntSize& aTargetSize) {
7113 const IntSize surfaceSize = aSurface.GetSize();
7114
7115 MOZ_ASSERT(surfaceSize != aTargetSize);
7116 MOZ_ASSERT(!surfaceSize.IsEmpty());
7117 MOZ_ASSERT(!aTargetSize.IsEmpty());
7118
7119 RefPtr<DrawTarget> dt = Factory::CreateDrawTarget(
7120 gfxVars::ContentBackend(), aTargetSize, aSurface.GetFormat());
7121
7122 if (!dt || !dt->IsValid()) {
7123 return nullptr;
7124 }
7125
7126 RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
7127 MOZ_ASSERT(context);
7128
7129 dt->DrawSurface(&aSurface, Rect(Point(), Size(aTargetSize)),
7130 Rect(Point(), Size(surfaceSize)));
7131 return dt->GetBackingSurface();
7132 }
7133
SurfaceFromElement(nsIImageLoadingContent * aElement,uint32_t aSurfaceFlags,RefPtr<DrawTarget> & aTarget)7134 SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
7135 nsIImageLoadingContent* aElement, uint32_t aSurfaceFlags,
7136 RefPtr<DrawTarget>& aTarget) {
7137 SurfaceFromElementResult result;
7138 nsresult rv;
7139
7140 nsCOMPtr<imgIRequest> imgRequest;
7141 rv = aElement->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
7142 getter_AddRefs(imgRequest));
7143 if (NS_FAILED(rv)) {
7144 return result;
7145 }
7146
7147 if (!imgRequest) {
7148 // There's no image request. This is either because a request for
7149 // a non-empty URI failed, or the URI is the empty string.
7150 nsCOMPtr<nsIURI> currentURI;
7151 aElement->GetCurrentURI(getter_AddRefs(currentURI));
7152 if (!currentURI) {
7153 // Treat the empty URI as available instead of broken state.
7154 result.mHasSize = true;
7155 }
7156 return result;
7157 }
7158
7159 uint32_t status;
7160 imgRequest->GetImageStatus(&status);
7161 result.mHasSize = status & imgIRequest::STATUS_SIZE_AVAILABLE;
7162 if ((status & imgIRequest::STATUS_LOAD_COMPLETE) == 0) {
7163 // Spec says to use GetComplete, but that only works on
7164 // HTMLImageElement, and we support all sorts of other stuff
7165 // here. Do this for now pending spec clarification.
7166 result.mIsStillLoading = (status & imgIRequest::STATUS_ERROR) == 0;
7167 return result;
7168 }
7169
7170 nsCOMPtr<nsIPrincipal> principal;
7171 rv = imgRequest->GetImagePrincipal(getter_AddRefs(principal));
7172 if (NS_FAILED(rv)) {
7173 return result;
7174 }
7175
7176 nsCOMPtr<imgIContainer> imgContainer;
7177 rv = imgRequest->GetImage(getter_AddRefs(imgContainer));
7178 if (NS_FAILED(rv)) {
7179 return result;
7180 }
7181
7182 nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
7183
7184 // Ensure that the image is oriented the same way as it's displayed.
7185 auto orientation = StyleImageOrientation::FromImage;
7186 if (nsIFrame* f = content->GetPrimaryFrame()) {
7187 orientation = f->StyleVisibility()->mImageOrientation;
7188 }
7189 imgContainer = OrientImage(imgContainer, orientation);
7190
7191 const bool noRasterize = aSurfaceFlags & SFE_NO_RASTERIZING_VECTORS;
7192
7193 uint32_t whichFrame = aSurfaceFlags & SFE_WANT_FIRST_FRAME_IF_IMAGE
7194 ? (uint32_t)imgIContainer::FRAME_FIRST
7195 : (uint32_t)imgIContainer::FRAME_CURRENT;
7196 const bool exactSize = aSurfaceFlags & SFE_EXACT_SIZE_SURFACE;
7197
7198 uint32_t frameFlags =
7199 imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY;
7200 if (aSurfaceFlags & SFE_NO_COLORSPACE_CONVERSION)
7201 frameFlags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION;
7202 if (aSurfaceFlags & SFE_ALLOW_NON_PREMULT) {
7203 frameFlags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
7204 }
7205
7206 int32_t imgWidth, imgHeight;
7207 HTMLImageElement* element = HTMLImageElement::FromNodeOrNull(content);
7208 if (aSurfaceFlags & SFE_USE_ELEMENT_SIZE_IF_VECTOR && element &&
7209 imgContainer->GetType() == imgIContainer::TYPE_VECTOR) {
7210 // We're holding a strong ref to "element" via "content".
7211 imgWidth = MOZ_KnownLive(element)->Width();
7212 imgHeight = MOZ_KnownLive(element)->Height();
7213 } else {
7214 rv = imgContainer->GetWidth(&imgWidth);
7215 nsresult rv2 = imgContainer->GetHeight(&imgHeight);
7216 if (NS_FAILED(rv) || NS_FAILED(rv2)) return result;
7217 imgContainer->GetResolution().ApplyTo(imgWidth, imgHeight);
7218 }
7219 result.mSize = result.mIntrinsicSize = IntSize(imgWidth, imgHeight);
7220
7221 if (!noRasterize || imgContainer->GetType() == imgIContainer::TYPE_RASTER) {
7222 result.mSourceSurface =
7223 imgContainer->GetFrameAtSize(result.mSize, whichFrame, frameFlags);
7224 if (!result.mSourceSurface) {
7225 return result;
7226 }
7227 if (exactSize && result.mSourceSurface->GetSize() != result.mSize) {
7228 result.mSourceSurface =
7229 ScaleSourceSurface(*result.mSourceSurface, result.mSize);
7230 if (!result.mSourceSurface) {
7231 return result;
7232 }
7233 }
7234 // The surface we return is likely to be cached. We don't want to have to
7235 // convert to a surface that's compatible with aTarget each time it's used
7236 // (that would result in terrible performance), so we convert once here
7237 // upfront if aTarget is specified.
7238 if (aTarget) {
7239 RefPtr<SourceSurface> optSurface =
7240 aTarget->OptimizeSourceSurface(result.mSourceSurface);
7241 if (optSurface) {
7242 result.mSourceSurface = optSurface;
7243 }
7244 }
7245
7246 const auto& format = result.mSourceSurface->GetFormat();
7247 if (IsOpaque(format)) {
7248 result.mAlphaType = gfxAlphaType::Opaque;
7249 } else if (frameFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA) {
7250 result.mAlphaType = gfxAlphaType::NonPremult;
7251 } else {
7252 result.mAlphaType = gfxAlphaType::Premult;
7253 }
7254 } else {
7255 result.mDrawInfo.mImgContainer = imgContainer;
7256 result.mDrawInfo.mWhichFrame = whichFrame;
7257 result.mDrawInfo.mDrawingFlags = frameFlags;
7258 }
7259
7260 int32_t corsmode;
7261 if (NS_SUCCEEDED(imgRequest->GetCORSMode(&corsmode))) {
7262 result.mCORSUsed = corsmode != CORS_NONE;
7263 }
7264
7265 bool hadCrossOriginRedirects = true;
7266 imgRequest->GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
7267
7268 result.mPrincipal = std::move(principal);
7269 result.mHadCrossOriginRedirects = hadCrossOriginRedirects;
7270 result.mImageRequest = std::move(imgRequest);
7271 result.mIsWriteOnly = CanvasUtils::CheckWriteOnlySecurity(
7272 result.mCORSUsed, result.mPrincipal, result.mHadCrossOriginRedirects);
7273
7274 return result;
7275 }
7276
SurfaceFromElement(HTMLImageElement * aElement,uint32_t aSurfaceFlags,RefPtr<DrawTarget> & aTarget)7277 SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
7278 HTMLImageElement* aElement, uint32_t aSurfaceFlags,
7279 RefPtr<DrawTarget>& aTarget) {
7280 return SurfaceFromElement(static_cast<nsIImageLoadingContent*>(aElement),
7281 aSurfaceFlags, aTarget);
7282 }
7283
SurfaceFromElement(HTMLCanvasElement * aElement,uint32_t aSurfaceFlags,RefPtr<DrawTarget> & aTarget)7284 SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
7285 HTMLCanvasElement* aElement, uint32_t aSurfaceFlags,
7286 RefPtr<DrawTarget>& aTarget) {
7287 SurfaceFromElementResult result;
7288
7289 IntSize size = aElement->GetSize();
7290
7291 auto pAlphaType = &result.mAlphaType;
7292 if (!(aSurfaceFlags & SFE_ALLOW_NON_PREMULT)) {
7293 pAlphaType =
7294 nullptr; // Coersce GetSurfaceSnapshot to give us Opaque/Premult only.
7295 }
7296 result.mSourceSurface = aElement->GetSurfaceSnapshot(pAlphaType);
7297 if (!result.mSourceSurface) {
7298 // If the element doesn't have a context then we won't get a snapshot. The
7299 // canvas spec wants us to not error and just draw nothing, so return an
7300 // empty surface.
7301 result.mAlphaType = gfxAlphaType::Opaque;
7302 RefPtr<DrawTarget> ref =
7303 aTarget ? aTarget
7304 : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
7305 if (ref->CanCreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8)) {
7306 RefPtr<DrawTarget> dt =
7307 ref->CreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8);
7308 if (dt) {
7309 result.mSourceSurface = dt->Snapshot();
7310 }
7311 }
7312 } else if (aTarget) {
7313 RefPtr<SourceSurface> opt =
7314 aTarget->OptimizeSourceSurface(result.mSourceSurface);
7315 if (opt) {
7316 result.mSourceSurface = opt;
7317 }
7318 }
7319
7320 // Ensure that any future changes to the canvas trigger proper invalidation,
7321 // in case this is being used by -moz-element()
7322 aElement->MarkContextClean();
7323
7324 result.mHasSize = true;
7325 result.mSize = size;
7326 result.mIntrinsicSize = size;
7327 result.mPrincipal = aElement->NodePrincipal();
7328 result.mHadCrossOriginRedirects = false;
7329 result.mIsWriteOnly = aElement->IsWriteOnly();
7330
7331 return result;
7332 }
7333
SurfaceFromElement(HTMLVideoElement * aElement,uint32_t aSurfaceFlags,RefPtr<DrawTarget> & aTarget)7334 SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
7335 HTMLVideoElement* aElement, uint32_t aSurfaceFlags,
7336 RefPtr<DrawTarget>& aTarget) {
7337 SurfaceFromElementResult result;
7338 result.mAlphaType = gfxAlphaType::Opaque; // Assume opaque.
7339
7340 if (aElement->ContainsRestrictedContent()) {
7341 return result;
7342 }
7343
7344 uint16_t readyState = aElement->ReadyState();
7345 if (readyState == HAVE_NOTHING || readyState == HAVE_METADATA) {
7346 result.mIsStillLoading = true;
7347 return result;
7348 }
7349
7350 // If it doesn't have a principal, just bail
7351 nsCOMPtr<nsIPrincipal> principal = aElement->GetCurrentVideoPrincipal();
7352 if (!principal) {
7353 return result;
7354 }
7355
7356 result.mLayersImage = aElement->GetCurrentImage();
7357 if (!result.mLayersImage) {
7358 return result;
7359 }
7360
7361 result.mCORSUsed = aElement->GetCORSMode() != CORS_NONE;
7362 result.mHasSize = true;
7363 result.mSize = result.mLayersImage->GetSize();
7364 result.mIntrinsicSize =
7365 gfx::IntSize(aElement->VideoWidth(), aElement->VideoHeight());
7366 result.mPrincipal = std::move(principal);
7367 result.mHadCrossOriginRedirects = aElement->HadCrossOriginRedirects();
7368 result.mIsWriteOnly = CanvasUtils::CheckWriteOnlySecurity(
7369 result.mCORSUsed, result.mPrincipal, result.mHadCrossOriginRedirects);
7370
7371 if (aTarget) {
7372 // They gave us a DrawTarget to optimize for, so even though we have a
7373 // layers::Image, we should unconditionally try to grab a SourceSurface and
7374 // try to optimize it.
7375 if ((result.mSourceSurface = result.mLayersImage->GetAsSourceSurface())) {
7376 RefPtr<SourceSurface> opt =
7377 aTarget->OptimizeSourceSurface(result.mSourceSurface);
7378 if (opt) {
7379 result.mSourceSurface = opt;
7380 }
7381 }
7382 }
7383
7384 return result;
7385 }
7386
SurfaceFromElement(dom::Element * aElement,uint32_t aSurfaceFlags,RefPtr<DrawTarget> & aTarget)7387 SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
7388 dom::Element* aElement, uint32_t aSurfaceFlags,
7389 RefPtr<DrawTarget>& aTarget) {
7390 // If it's a <canvas>, we may be able to just grab its internal surface
7391 if (HTMLCanvasElement* canvas = HTMLCanvasElement::FromNodeOrNull(aElement)) {
7392 return SurfaceFromElement(canvas, aSurfaceFlags, aTarget);
7393 }
7394
7395 // Maybe it's <video>?
7396 if (HTMLVideoElement* video = HTMLVideoElement::FromNodeOrNull(aElement)) {
7397 return SurfaceFromElement(video, aSurfaceFlags, aTarget);
7398 }
7399
7400 // Finally, check if it's a normal image
7401 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement);
7402
7403 if (!imageLoader) {
7404 return SurfaceFromElementResult();
7405 }
7406
7407 return SurfaceFromElement(imageLoader, aSurfaceFlags, aTarget);
7408 }
7409
7410 /* static */
GetEditableRootContentByContentEditable(Document * aDocument)7411 Element* nsLayoutUtils::GetEditableRootContentByContentEditable(
7412 Document* aDocument) {
7413 // If the document is in designMode we should return nullptr.
7414 if (!aDocument || aDocument->IsInDesignMode()) {
7415 return nullptr;
7416 }
7417
7418 // contenteditable only works with HTML document.
7419 // XXXbz should this test IsHTMLOrXHTML(), or just IsHTML()?
7420 if (!aDocument->IsHTMLOrXHTML()) {
7421 return nullptr;
7422 }
7423
7424 Element* rootElement = aDocument->GetRootElement();
7425 if (rootElement && rootElement->IsEditable()) {
7426 return rootElement;
7427 }
7428
7429 // If there is no editable root element, check its <body> element.
7430 // Note that the body element could be <frameset> element.
7431 Element* bodyElement = aDocument->GetBody();
7432 if (bodyElement && bodyElement->IsEditable()) {
7433 return bodyElement;
7434 }
7435 return nullptr;
7436 }
7437
7438 #ifdef DEBUG
7439 /* static */
AssertNoDuplicateContinuations(nsIFrame * aContainer,const nsFrameList & aFrameList)7440 void nsLayoutUtils::AssertNoDuplicateContinuations(
7441 nsIFrame* aContainer, const nsFrameList& aFrameList) {
7442 for (nsIFrame* f : aFrameList) {
7443 // Check only later continuations of f; we deal with checking the
7444 // earlier continuations when we hit those earlier continuations in
7445 // the frame list.
7446 for (nsIFrame* c = f; (c = c->GetNextInFlow());) {
7447 NS_ASSERTION(c->GetParent() != aContainer || !aFrameList.ContainsFrame(c),
7448 "Two continuations of the same frame in the same "
7449 "frame list");
7450 }
7451 }
7452 }
7453
7454 // Is one of aFrame's ancestors a letter frame?
IsInLetterFrame(nsIFrame * aFrame)7455 static bool IsInLetterFrame(nsIFrame* aFrame) {
7456 for (nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) {
7457 if (f->IsLetterFrame()) {
7458 return true;
7459 }
7460 }
7461 return false;
7462 }
7463
7464 /* static */
AssertTreeOnlyEmptyNextInFlows(nsIFrame * aSubtreeRoot)7465 void nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(nsIFrame* aSubtreeRoot) {
7466 NS_ASSERTION(aSubtreeRoot->GetPrevInFlow(),
7467 "frame tree not empty, but caller reported complete status");
7468
7469 // Also assert that text frames map no text.
7470 auto [start, end] = aSubtreeRoot->GetOffsets();
7471 // In some cases involving :first-letter, we'll partially unlink a
7472 // continuation in the middle of a continuation chain from its
7473 // previous and next continuations before destroying it, presumably so
7474 // that we don't also destroy the later continuations. Once we've
7475 // done this, GetOffsets returns incorrect values.
7476 // For examples, see list of tests in
7477 // https://bugzilla.mozilla.org/show_bug.cgi?id=619021#c29
7478 NS_ASSERTION(start == end || IsInLetterFrame(aSubtreeRoot),
7479 "frame tree not empty, but caller reported complete status");
7480
7481 for (const auto& childList : aSubtreeRoot->ChildLists()) {
7482 for (nsIFrame* child : childList.mList) {
7483 nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(child);
7484 }
7485 }
7486 }
7487 #endif
7488
GetFontFacesForFramesInner(nsIFrame * aFrame,nsLayoutUtils::UsedFontFaceList & aResult,nsLayoutUtils::UsedFontFaceTable & aFontFaces,uint32_t aMaxRanges,bool aSkipCollapsedWhitespace)7489 static void GetFontFacesForFramesInner(
7490 nsIFrame* aFrame, nsLayoutUtils::UsedFontFaceList& aResult,
7491 nsLayoutUtils::UsedFontFaceTable& aFontFaces, uint32_t aMaxRanges,
7492 bool aSkipCollapsedWhitespace) {
7493 MOZ_ASSERT(aFrame, "NULL frame pointer");
7494
7495 if (aFrame->IsTextFrame()) {
7496 if (!aFrame->GetPrevContinuation()) {
7497 nsLayoutUtils::GetFontFacesForText(aFrame, 0, INT32_MAX, true, aResult,
7498 aFontFaces, aMaxRanges,
7499 aSkipCollapsedWhitespace);
7500 }
7501 return;
7502 }
7503
7504 nsIFrame::ChildListID childLists[] = {nsIFrame::kPrincipalList,
7505 nsIFrame::kPopupList};
7506 for (size_t i = 0; i < ArrayLength(childLists); ++i) {
7507 nsFrameList children(aFrame->GetChildList(childLists[i]));
7508 for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next()) {
7509 nsIFrame* child = e.get();
7510 child = nsPlaceholderFrame::GetRealFrameFor(child);
7511 GetFontFacesForFramesInner(child, aResult, aFontFaces, aMaxRanges,
7512 aSkipCollapsedWhitespace);
7513 }
7514 }
7515 }
7516
7517 /* static */
GetFontFacesForFrames(nsIFrame * aFrame,UsedFontFaceList & aResult,UsedFontFaceTable & aFontFaces,uint32_t aMaxRanges,bool aSkipCollapsedWhitespace)7518 nsresult nsLayoutUtils::GetFontFacesForFrames(nsIFrame* aFrame,
7519 UsedFontFaceList& aResult,
7520 UsedFontFaceTable& aFontFaces,
7521 uint32_t aMaxRanges,
7522 bool aSkipCollapsedWhitespace) {
7523 MOZ_ASSERT(aFrame, "NULL frame pointer");
7524
7525 while (aFrame) {
7526 GetFontFacesForFramesInner(aFrame, aResult, aFontFaces, aMaxRanges,
7527 aSkipCollapsedWhitespace);
7528 aFrame = GetNextContinuationOrIBSplitSibling(aFrame);
7529 }
7530
7531 return NS_OK;
7532 }
7533
AddFontsFromTextRun(gfxTextRun * aTextRun,nsTextFrame * aFrame,gfxSkipCharsIterator & aSkipIter,const gfxTextRun::Range & aRange,nsLayoutUtils::UsedFontFaceList & aResult,nsLayoutUtils::UsedFontFaceTable & aFontFaces,uint32_t aMaxRanges)7534 static void AddFontsFromTextRun(gfxTextRun* aTextRun, nsTextFrame* aFrame,
7535 gfxSkipCharsIterator& aSkipIter,
7536 const gfxTextRun::Range& aRange,
7537 nsLayoutUtils::UsedFontFaceList& aResult,
7538 nsLayoutUtils::UsedFontFaceTable& aFontFaces,
7539 uint32_t aMaxRanges) {
7540 gfxTextRun::GlyphRunIterator glyphRuns(aTextRun, aRange);
7541 nsIContent* content = aFrame->GetContent();
7542 int32_t contentLimit =
7543 aFrame->GetContentOffset() + aFrame->GetInFlowContentLength();
7544 while (glyphRuns.NextRun()) {
7545 gfxFontEntry* fe = glyphRuns.GetGlyphRun()->mFont->GetFontEntry();
7546 // if we have already listed this face, just make sure the match type is
7547 // recorded
7548 InspectorFontFace* fontFace = aFontFaces.Get(fe);
7549 if (fontFace) {
7550 fontFace->AddMatchType(glyphRuns.GetGlyphRun()->mMatchType);
7551 } else {
7552 // A new font entry we haven't seen before
7553 fontFace = new InspectorFontFace(fe, aTextRun->GetFontGroup(),
7554 glyphRuns.GetGlyphRun()->mMatchType);
7555 aFontFaces.InsertOrUpdate(fe, fontFace);
7556 aResult.AppendElement(fontFace);
7557 }
7558
7559 // Add this glyph run to the fontFace's list of ranges, unless we have
7560 // already collected as many as wanted.
7561 if (fontFace->RangeCount() < aMaxRanges) {
7562 int32_t start =
7563 aSkipIter.ConvertSkippedToOriginal(glyphRuns.GetStringStart());
7564 int32_t end =
7565 aSkipIter.ConvertSkippedToOriginal(glyphRuns.GetStringEnd());
7566
7567 // Mapping back from textrun offsets ("skipped" offsets that reflect the
7568 // text after whitespace collapsing, etc) to DOM content offsets in the
7569 // original text is ambiguous, because many original characters can
7570 // map to a single skipped offset. aSkipIter.ConvertSkippedToOriginal()
7571 // will return an "original" offset that corresponds to the *end* of
7572 // a collapsed run of characters in this case; but that might extend
7573 // beyond the current content node if the textrun mapped multiple nodes.
7574 // So we clamp the end offset to keep it valid for the content node
7575 // that corresponds to the current textframe.
7576 end = std::min(end, contentLimit);
7577
7578 if (end > start) {
7579 RefPtr<nsRange> range =
7580 nsRange::Create(content, start, content, end, IgnoreErrors());
7581 NS_WARNING_ASSERTION(range,
7582 "nsRange::Create() failed to create valid range");
7583 if (range) {
7584 fontFace->AddRange(range);
7585 }
7586 }
7587 }
7588 }
7589 }
7590
7591 /* static */
GetFontFacesForText(nsIFrame * aFrame,int32_t aStartOffset,int32_t aEndOffset,bool aFollowContinuations,UsedFontFaceList & aResult,UsedFontFaceTable & aFontFaces,uint32_t aMaxRanges,bool aSkipCollapsedWhitespace)7592 void nsLayoutUtils::GetFontFacesForText(nsIFrame* aFrame, int32_t aStartOffset,
7593 int32_t aEndOffset,
7594 bool aFollowContinuations,
7595 UsedFontFaceList& aResult,
7596 UsedFontFaceTable& aFontFaces,
7597 uint32_t aMaxRanges,
7598 bool aSkipCollapsedWhitespace) {
7599 MOZ_ASSERT(aFrame, "NULL frame pointer");
7600
7601 if (!aFrame->IsTextFrame()) {
7602 return;
7603 }
7604
7605 if (!aFrame->StyleVisibility()->IsVisible()) {
7606 return;
7607 }
7608
7609 nsTextFrame* curr = static_cast<nsTextFrame*>(aFrame);
7610 do {
7611 int32_t fstart = std::max(curr->GetContentOffset(), aStartOffset);
7612 int32_t fend = std::min(curr->GetContentEnd(), aEndOffset);
7613 if (fstart >= fend) {
7614 curr = static_cast<nsTextFrame*>(curr->GetNextContinuation());
7615 continue;
7616 }
7617
7618 // curr is overlapping with the offset we want
7619 gfxSkipCharsIterator iter = curr->EnsureTextRun(nsTextFrame::eInflated);
7620 gfxTextRun* textRun = curr->GetTextRun(nsTextFrame::eInflated);
7621 if (!textRun) {
7622 NS_WARNING("failed to get textRun, low memory?");
7623 return;
7624 }
7625
7626 // include continuations in the range that share the same textrun
7627 nsTextFrame* next = nullptr;
7628 if (aFollowContinuations && fend < aEndOffset) {
7629 next = static_cast<nsTextFrame*>(curr->GetNextContinuation());
7630 while (next && next->GetTextRun(nsTextFrame::eInflated) == textRun) {
7631 fend = std::min(next->GetContentEnd(), aEndOffset);
7632 next = fend < aEndOffset
7633 ? static_cast<nsTextFrame*>(next->GetNextContinuation())
7634 : nullptr;
7635 }
7636 }
7637
7638 if (!aSkipCollapsedWhitespace || (curr->HasAnyNoncollapsedCharacters() &&
7639 curr->HasNonSuppressedText())) {
7640 gfxTextRun::Range range(iter.ConvertOriginalToSkipped(fstart),
7641 iter.ConvertOriginalToSkipped(fend));
7642 AddFontsFromTextRun(textRun, curr, iter, range, aResult, aFontFaces,
7643 aMaxRanges);
7644 }
7645
7646 curr = next;
7647 } while (aFollowContinuations && curr);
7648 }
7649
7650 /* static */
SizeOfTextRunsForFrames(nsIFrame * aFrame,MallocSizeOf aMallocSizeOf,bool clear)7651 size_t nsLayoutUtils::SizeOfTextRunsForFrames(nsIFrame* aFrame,
7652 MallocSizeOf aMallocSizeOf,
7653 bool clear) {
7654 MOZ_ASSERT(aFrame, "NULL frame pointer");
7655
7656 size_t total = 0;
7657
7658 if (aFrame->IsTextFrame()) {
7659 nsTextFrame* textFrame = static_cast<nsTextFrame*>(aFrame);
7660 for (uint32_t i = 0; i < 2; ++i) {
7661 gfxTextRun* run = textFrame->GetTextRun(
7662 (i != 0) ? nsTextFrame::eInflated : nsTextFrame::eNotInflated);
7663 if (run) {
7664 if (clear) {
7665 run->ResetSizeOfAccountingFlags();
7666 } else {
7667 total += run->MaybeSizeOfIncludingThis(aMallocSizeOf);
7668 }
7669 }
7670 }
7671 return total;
7672 }
7673
7674 for (const auto& childList : aFrame->ChildLists()) {
7675 for (nsIFrame* f : childList.mList) {
7676 total += SizeOfTextRunsForFrames(f, aMallocSizeOf, clear);
7677 }
7678 }
7679 return total;
7680 }
7681
7682 /* static */
Initialize()7683 void nsLayoutUtils::Initialize() {
7684 nsComputedDOMStyle::RegisterPrefChangeCallbacks();
7685 }
7686
7687 /* static */
Shutdown()7688 void nsLayoutUtils::Shutdown() {
7689 if (sContentMap) {
7690 sContentMap = nullptr;
7691 }
7692
7693 nsComputedDOMStyle::UnregisterPrefChangeCallbacks();
7694 }
7695
7696 /* static */
RegisterImageRequest(nsPresContext * aPresContext,imgIRequest * aRequest,bool * aRequestRegistered)7697 void nsLayoutUtils::RegisterImageRequest(nsPresContext* aPresContext,
7698 imgIRequest* aRequest,
7699 bool* aRequestRegistered) {
7700 if (!aPresContext) {
7701 return;
7702 }
7703
7704 if (aRequestRegistered && *aRequestRegistered) {
7705 // Our request is already registered with the refresh driver, so
7706 // no need to register it again.
7707 return;
7708 }
7709
7710 if (aRequest) {
7711 aPresContext->RefreshDriver()->AddImageRequest(aRequest);
7712 if (aRequestRegistered) {
7713 *aRequestRegistered = true;
7714 }
7715 }
7716 }
7717
7718 /* static */
RegisterImageRequestIfAnimated(nsPresContext * aPresContext,imgIRequest * aRequest,bool * aRequestRegistered)7719 void nsLayoutUtils::RegisterImageRequestIfAnimated(nsPresContext* aPresContext,
7720 imgIRequest* aRequest,
7721 bool* aRequestRegistered) {
7722 if (!aPresContext) {
7723 return;
7724 }
7725
7726 if (aRequestRegistered && *aRequestRegistered) {
7727 // Our request is already registered with the refresh driver, so
7728 // no need to register it again.
7729 return;
7730 }
7731
7732 if (aRequest) {
7733 nsCOMPtr<imgIContainer> image;
7734 if (NS_SUCCEEDED(aRequest->GetImage(getter_AddRefs(image)))) {
7735 // Check to verify that the image is animated. If so, then add it to the
7736 // list of images tracked by the refresh driver.
7737 bool isAnimated = false;
7738 nsresult rv = image->GetAnimated(&isAnimated);
7739 if (NS_SUCCEEDED(rv) && isAnimated) {
7740 aPresContext->RefreshDriver()->AddImageRequest(aRequest);
7741 if (aRequestRegistered) {
7742 *aRequestRegistered = true;
7743 }
7744 }
7745 }
7746 }
7747 }
7748
7749 /* static */
DeregisterImageRequest(nsPresContext * aPresContext,imgIRequest * aRequest,bool * aRequestRegistered)7750 void nsLayoutUtils::DeregisterImageRequest(nsPresContext* aPresContext,
7751 imgIRequest* aRequest,
7752 bool* aRequestRegistered) {
7753 if (!aPresContext) {
7754 return;
7755 }
7756
7757 // Deregister our imgIRequest with the refresh driver to
7758 // complete tear-down, but only if it has been registered
7759 if (aRequestRegistered && !*aRequestRegistered) {
7760 return;
7761 }
7762
7763 if (aRequest) {
7764 nsCOMPtr<imgIContainer> image;
7765 if (NS_SUCCEEDED(aRequest->GetImage(getter_AddRefs(image)))) {
7766 aPresContext->RefreshDriver()->RemoveImageRequest(aRequest);
7767
7768 if (aRequestRegistered) {
7769 *aRequestRegistered = false;
7770 }
7771 }
7772 }
7773 }
7774
7775 /* static */
PostRestyleEvent(Element * aElement,RestyleHint aRestyleHint,nsChangeHint aMinChangeHint)7776 void nsLayoutUtils::PostRestyleEvent(Element* aElement,
7777 RestyleHint aRestyleHint,
7778 nsChangeHint aMinChangeHint) {
7779 if (Document* doc = aElement->GetComposedDoc()) {
7780 if (nsPresContext* presContext = doc->GetPresContext()) {
7781 presContext->RestyleManager()->PostRestyleEvent(aElement, aRestyleHint,
7782 aMinChangeHint);
7783 }
7784 }
7785 }
7786
nsSetAttrRunnable(Element * aElement,nsAtom * aAttrName,const nsAString & aValue)7787 nsSetAttrRunnable::nsSetAttrRunnable(Element* aElement, nsAtom* aAttrName,
7788 const nsAString& aValue)
7789 : mozilla::Runnable("nsSetAttrRunnable"),
7790 mElement(aElement),
7791 mAttrName(aAttrName),
7792 mValue(aValue) {
7793 NS_ASSERTION(aElement && aAttrName, "Missing stuff, prepare to crash");
7794 }
7795
nsSetAttrRunnable(Element * aElement,nsAtom * aAttrName,int32_t aValue)7796 nsSetAttrRunnable::nsSetAttrRunnable(Element* aElement, nsAtom* aAttrName,
7797 int32_t aValue)
7798 : mozilla::Runnable("nsSetAttrRunnable"),
7799 mElement(aElement),
7800 mAttrName(aAttrName) {
7801 NS_ASSERTION(aElement && aAttrName, "Missing stuff, prepare to crash");
7802 mValue.AppendInt(aValue);
7803 }
7804
7805 NS_IMETHODIMP
Run()7806 nsSetAttrRunnable::Run() {
7807 return mElement->SetAttr(kNameSpaceID_None, mAttrName, mValue, true);
7808 }
7809
nsUnsetAttrRunnable(Element * aElement,nsAtom * aAttrName)7810 nsUnsetAttrRunnable::nsUnsetAttrRunnable(Element* aElement, nsAtom* aAttrName)
7811 : mozilla::Runnable("nsUnsetAttrRunnable"),
7812 mElement(aElement),
7813 mAttrName(aAttrName) {
7814 NS_ASSERTION(aElement && aAttrName, "Missing stuff, prepare to crash");
7815 }
7816
7817 NS_IMETHODIMP
Run()7818 nsUnsetAttrRunnable::Run() {
7819 return mElement->UnsetAttr(kNameSpaceID_None, mAttrName, true);
7820 }
7821
7822 /**
7823 * Compute the minimum font size inside of a container with the given
7824 * width, such that **when the user zooms the container to fill the full
7825 * width of the device**, the fonts satisfy our minima.
7826 */
MinimumFontSizeFor(nsPresContext * aPresContext,WritingMode aWritingMode,nscoord aContainerISize)7827 static nscoord MinimumFontSizeFor(nsPresContext* aPresContext,
7828 WritingMode aWritingMode,
7829 nscoord aContainerISize) {
7830 PresShell* presShell = aPresContext->PresShell();
7831
7832 uint32_t emPerLine = presShell->FontSizeInflationEmPerLine();
7833 uint32_t minTwips = presShell->FontSizeInflationMinTwips();
7834 if (emPerLine == 0 && minTwips == 0) {
7835 return 0;
7836 }
7837
7838 nscoord byLine = 0, byInch = 0;
7839 if (emPerLine != 0) {
7840 byLine = aContainerISize / emPerLine;
7841 }
7842 if (minTwips != 0) {
7843 // REVIEW: Is this giving us app units and sizes *not* counting
7844 // viewport scaling?
7845 gfxSize screenSize = aPresContext->ScreenSizeInchesForFontInflation();
7846 float deviceISizeInches =
7847 aWritingMode.IsVertical() ? screenSize.height : screenSize.width;
7848 byInch =
7849 NSToCoordRound(aContainerISize / (deviceISizeInches * 1440 / minTwips));
7850 }
7851 return std::max(byLine, byInch);
7852 }
7853
7854 /* static */
FontSizeInflationInner(const nsIFrame * aFrame,nscoord aMinFontSize)7855 float nsLayoutUtils::FontSizeInflationInner(const nsIFrame* aFrame,
7856 nscoord aMinFontSize) {
7857 // Note that line heights should be inflated by the same ratio as the
7858 // font size of the same text; thus we operate only on the font size
7859 // even when we're scaling a line height.
7860 nscoord styleFontSize = aFrame->StyleFont()->mFont.size.ToAppUnits();
7861 if (styleFontSize <= 0) {
7862 // Never scale zero font size.
7863 return 1.0;
7864 }
7865
7866 if (aMinFontSize <= 0) {
7867 // No need to scale.
7868 return 1.0;
7869 }
7870
7871 // If between this current frame and its font inflation container there is a
7872 // non-inline element with fixed width or height, then we should not inflate
7873 // fonts for this frame.
7874 for (const nsIFrame* f = aFrame; f && !f->IsContainerForFontSizeInflation();
7875 f = f->GetParent()) {
7876 nsIContent* content = f->GetContent();
7877 LayoutFrameType fType = f->Type();
7878 nsIFrame* parent = f->GetParent();
7879 // Also, if there is more than one frame corresponding to a single
7880 // content node, we want the outermost one.
7881 if (!(parent && parent->GetContent() == content) &&
7882 // ignore width/height on inlines since they don't apply
7883 fType != LayoutFrameType::Inline &&
7884 // ignore width on radios and checkboxes since we enlarge them and
7885 // they have width/height in ua.css
7886 fType != LayoutFrameType::CheckboxRadio) {
7887 // ruby annotations should have the same inflation as its
7888 // grandparent, which is the ruby frame contains the annotation.
7889 if (fType == LayoutFrameType::RubyText) {
7890 MOZ_ASSERT(parent && parent->IsRubyTextContainerFrame());
7891 nsIFrame* grandparent = parent->GetParent();
7892 MOZ_ASSERT(grandparent && grandparent->IsRubyFrame());
7893 return FontSizeInflationFor(grandparent);
7894 }
7895 WritingMode wm = f->GetWritingMode();
7896 const auto& stylePosISize = f->StylePosition()->ISize(wm);
7897 const auto& stylePosBSize = f->StylePosition()->BSize(wm);
7898 if (!stylePosISize.IsAuto() ||
7899 !stylePosBSize.BehavesLikeInitialValueOnBlockAxis()) {
7900 return 1.0;
7901 }
7902 }
7903 }
7904
7905 int32_t interceptParam = StaticPrefs::font_size_inflation_mappingIntercept();
7906 float maxRatio = (float)StaticPrefs::font_size_inflation_maxRatio() / 100.0f;
7907
7908 float ratio = float(styleFontSize) / float(aMinFontSize);
7909 float inflationRatio;
7910
7911 // Given a minimum inflated font size m, a specified font size s, we want to
7912 // find the inflated font size i and then return the ratio of i to s (i/s).
7913 if (interceptParam >= 0) {
7914 // Since the mapping intercept parameter P is greater than zero, we use it
7915 // to determine the point where our mapping function intersects the i=s
7916 // line. This means that we have an equation of the form:
7917 //
7918 // i = m + s*(P/2)/(1 + P/2), if s <= (1 + P/2)*m
7919 // i = s, if s >= (1 + P/2)*m
7920
7921 float intercept = 1 + float(interceptParam) / 2.0f;
7922 if (ratio >= intercept) {
7923 // If we're already at 1+P/2 or more times the minimum, don't scale.
7924 return 1.0;
7925 }
7926
7927 // The point (intercept, intercept) is where the part of the i vs. s graph
7928 // that's not slope 1 meets the i=s line. (This part of the
7929 // graph is a line from (0, m), to that point). We calculate the
7930 // intersection point to be ((1+P/2)m, (1+P/2)m), where P is the
7931 // intercept parameter above. We then need to return i/s.
7932 inflationRatio = (1.0f + (ratio * (intercept - 1) / intercept)) / ratio;
7933 } else {
7934 // This is the case where P is negative. We essentially want to implement
7935 // the case for P=infinity here, so we make i = s + m, which means that
7936 // i/s = s/s + m/s = 1 + 1/ratio
7937 inflationRatio = 1 + 1.0f / ratio;
7938 }
7939
7940 if (maxRatio > 1.0 && inflationRatio > maxRatio) {
7941 return maxRatio;
7942 } else {
7943 return inflationRatio;
7944 }
7945 }
7946
ShouldInflateFontsForContainer(const nsIFrame * aFrame)7947 static bool ShouldInflateFontsForContainer(const nsIFrame* aFrame) {
7948 // We only want to inflate fonts for text that is in a place
7949 // with room to expand. The question is what the best heuristic for
7950 // that is...
7951 // For now, we're going to use NS_FRAME_IN_CONSTRAINED_BSIZE, which
7952 // indicates whether the frame is inside something with a constrained
7953 // block-size (propagating down the tree), but the propagation stops when
7954 // we hit overflow-y [or -x, for vertical mode]: scroll or auto.
7955 const nsStyleText* styleText = aFrame->StyleText();
7956
7957 return styleText->mTextSizeAdjust != StyleTextSizeAdjust::None &&
7958 !aFrame->HasAnyStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE) &&
7959 // We also want to disable font inflation for containers that have
7960 // preformatted text.
7961 // MathML cells need special treatment. See bug 1002526 comment 56.
7962 (styleText->WhiteSpaceCanWrap(aFrame) ||
7963 aFrame->IsFrameOfType(nsIFrame::eMathML));
7964 }
7965
InflationMinFontSizeFor(const nsIFrame * aFrame)7966 nscoord nsLayoutUtils::InflationMinFontSizeFor(const nsIFrame* aFrame) {
7967 nsPresContext* presContext = aFrame->PresContext();
7968 if (!FontSizeInflationEnabled(presContext) ||
7969 presContext->mInflationDisabledForShrinkWrap) {
7970 return 0;
7971 }
7972
7973 for (const nsIFrame* f = aFrame; f; f = f->GetParent()) {
7974 if (f->IsContainerForFontSizeInflation()) {
7975 if (!ShouldInflateFontsForContainer(f)) {
7976 return 0;
7977 }
7978
7979 nsFontInflationData* data =
7980 nsFontInflationData::FindFontInflationDataFor(aFrame);
7981 // FIXME: The need to null-check here is sort of a bug, and might
7982 // lead to incorrect results.
7983 if (!data || !data->InflationEnabled()) {
7984 return 0;
7985 }
7986
7987 return MinimumFontSizeFor(aFrame->PresContext(), aFrame->GetWritingMode(),
7988 data->UsableISize());
7989 }
7990 }
7991
7992 MOZ_ASSERT(false, "root should always be container");
7993
7994 return 0;
7995 }
7996
FontSizeInflationFor(const nsIFrame * aFrame)7997 float nsLayoutUtils::FontSizeInflationFor(const nsIFrame* aFrame) {
7998 if (SVGUtils::IsInSVGTextSubtree(aFrame)) {
7999 const nsIFrame* container = aFrame;
8000 while (!container->IsSVGTextFrame()) {
8001 container = container->GetParent();
8002 }
8003 NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame");
8004 return static_cast<const SVGTextFrame*>(container)
8005 ->GetFontSizeScaleFactor();
8006 }
8007
8008 if (!FontSizeInflationEnabled(aFrame->PresContext())) {
8009 return 1.0f;
8010 }
8011
8012 return FontSizeInflationInner(aFrame, InflationMinFontSizeFor(aFrame));
8013 }
8014
8015 /* static */
FontSizeInflationEnabled(nsPresContext * aPresContext)8016 bool nsLayoutUtils::FontSizeInflationEnabled(nsPresContext* aPresContext) {
8017 PresShell* presShell = aPresContext->GetPresShell();
8018 if (!presShell) {
8019 return false;
8020 }
8021 return presShell->FontSizeInflationEnabled();
8022 }
8023
8024 /* static */
GetBoxShadowRectForFrame(nsIFrame * aFrame,const nsSize & aFrameSize)8025 nsRect nsLayoutUtils::GetBoxShadowRectForFrame(nsIFrame* aFrame,
8026 const nsSize& aFrameSize) {
8027 auto boxShadows = aFrame->StyleEffects()->mBoxShadow.AsSpan();
8028 if (boxShadows.IsEmpty()) {
8029 return nsRect();
8030 }
8031
8032 nsRect inputRect(nsPoint(0, 0), aFrameSize);
8033
8034 // According to the CSS spec, box-shadow should be based on the border box.
8035 // However, that looks broken when the background extends outside the border
8036 // box, as can be the case with native theming. To fix that we expand the
8037 // area that we shadow to include the bounds of any native theme drawing.
8038 const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
8039 nsITheme::Transparency transparency;
8040 if (aFrame->IsThemed(styleDisplay, &transparency)) {
8041 // For opaque (rectangular) theme widgets we can take the generic
8042 // border-box path with border-radius disabled.
8043 if (transparency != nsITheme::eOpaque) {
8044 nsPresContext* presContext = aFrame->PresContext();
8045 presContext->Theme()->GetWidgetOverflow(
8046 presContext->DeviceContext(), aFrame,
8047 styleDisplay->EffectiveAppearance(), &inputRect);
8048 }
8049 }
8050
8051 nsRect shadows;
8052 int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel();
8053 for (auto& shadow : boxShadows) {
8054 nsRect tmpRect = inputRect;
8055
8056 // inset shadows are never painted outside the frame
8057 if (shadow.inset) {
8058 continue;
8059 }
8060
8061 tmpRect.MoveBy(nsPoint(shadow.base.horizontal.ToAppUnits(),
8062 shadow.base.vertical.ToAppUnits()));
8063 tmpRect.Inflate(shadow.spread.ToAppUnits());
8064 tmpRect.Inflate(nsContextBoxBlur::GetBlurRadiusMargin(
8065 shadow.base.blur.ToAppUnits(), A2D));
8066 shadows.UnionRect(shadows, tmpRect);
8067 }
8068 return shadows;
8069 }
8070
8071 /* static */
GetContentViewerSize(const nsPresContext * aPresContext,LayoutDeviceIntSize & aOutSize,SubtractDynamicToolbar aSubtractDynamicToolbar)8072 bool nsLayoutUtils::GetContentViewerSize(
8073 const nsPresContext* aPresContext, LayoutDeviceIntSize& aOutSize,
8074 SubtractDynamicToolbar aSubtractDynamicToolbar) {
8075 nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
8076 if (!docShell) {
8077 return false;
8078 }
8079
8080 nsCOMPtr<nsIContentViewer> cv;
8081 docShell->GetContentViewer(getter_AddRefs(cv));
8082 if (!cv) {
8083 return false;
8084 }
8085
8086 nsIntRect bounds;
8087 cv->GetBounds(bounds);
8088
8089 if (aPresContext->IsRootContentDocumentCrossProcess() &&
8090 aSubtractDynamicToolbar == SubtractDynamicToolbar::Yes &&
8091 aPresContext->HasDynamicToolbar() && !bounds.IsEmpty()) {
8092 MOZ_ASSERT(aPresContext->IsRootContentDocumentCrossProcess());
8093 bounds.height -= aPresContext->GetDynamicToolbarMaxHeight();
8094 // Collapse the size in the case the dynamic toolbar max height is greater
8095 // than the content bound height so that hopefully embedders of GeckoView
8096 // may notice they set wrong dynamic toolbar max height.
8097 if (bounds.height < 0) {
8098 bounds.height = 0;
8099 }
8100 }
8101
8102 aOutSize = LayoutDeviceIntRect::FromUnknownRect(bounds).Size();
8103 return true;
8104 }
8105
UpdateCompositionBoundsForRCDRSF(ParentLayerRect & aCompBounds,const nsPresContext * aPresContext)8106 bool nsLayoutUtils::UpdateCompositionBoundsForRCDRSF(
8107 ParentLayerRect& aCompBounds, const nsPresContext* aPresContext) {
8108 SubtractDynamicToolbar shouldSubtractDynamicToolbar =
8109 SubtractDynamicToolbar::Yes;
8110
8111 if (RefPtr<MobileViewportManager> MVM =
8112 aPresContext->PresShell()->GetMobileViewportManager()) {
8113 CSSSize intrinsicCompositionSize = MVM->GetIntrinsicCompositionSize();
8114
8115 if (nsIScrollableFrame* rootScrollableFrame =
8116 aPresContext->PresShell()->GetRootScrollFrameAsScrollable()) {
8117 // Expand the composition size to include the area initially covered by
8118 // the dynamic toolbar only if the content is taller than the intrinsic
8119 // composition size (i.e. the dynamic toolbar should be able to move only
8120 // if the content is vertically scrollable).
8121 if (intrinsicCompositionSize.height <
8122 CSSPixel::FromAppUnits(
8123 CalculateScrollableRectForFrame(rootScrollableFrame, nullptr)
8124 .Height())) {
8125 shouldSubtractDynamicToolbar = SubtractDynamicToolbar::No;
8126 }
8127 }
8128 }
8129
8130 LayoutDeviceIntSize contentSize;
8131 if (!GetContentViewerSize(aPresContext, contentSize,
8132 shouldSubtractDynamicToolbar)) {
8133 return false;
8134 }
8135 aCompBounds.SizeTo(ViewAs<ParentLayerPixel>(
8136 LayoutDeviceSize(contentSize),
8137 PixelCastJustification::LayoutDeviceIsParentLayerForRCDRSF));
8138 return true;
8139 }
8140
8141 /* static */
ScrollbarAreaToExcludeFromCompositionBoundsFor(const nsIFrame * aScrollFrame)8142 nsMargin nsLayoutUtils::ScrollbarAreaToExcludeFromCompositionBoundsFor(
8143 const nsIFrame* aScrollFrame) {
8144 if (!aScrollFrame || !aScrollFrame->GetScrollTargetFrame()) {
8145 return nsMargin();
8146 }
8147 nsPresContext* presContext = aScrollFrame->PresContext();
8148 PresShell* presShell = presContext->GetPresShell();
8149 if (!presShell) {
8150 return nsMargin();
8151 }
8152 bool isRootScrollFrame = aScrollFrame == presShell->GetRootScrollFrame();
8153 bool isRootContentDocRootScrollFrame =
8154 isRootScrollFrame && presContext->IsRootContentDocumentCrossProcess();
8155 if (!isRootContentDocRootScrollFrame) {
8156 return nsMargin();
8157 }
8158 if (presContext->UseOverlayScrollbars()) {
8159 return nsMargin();
8160 }
8161 nsIScrollableFrame* scrollableFrame = aScrollFrame->GetScrollTargetFrame();
8162 if (!scrollableFrame) {
8163 return nsMargin();
8164 }
8165 return scrollableFrame->GetActualScrollbarSizes(
8166 nsIScrollableFrame::ScrollbarSizesOptions::
8167 INCLUDE_VISUAL_VIEWPORT_SCROLLBARS);
8168 }
8169
8170 /* static */
CalculateCompositionSizeForFrame(nsIFrame * aFrame,bool aSubtractScrollbars,const nsSize * aOverrideScrollPortSize)8171 nsSize nsLayoutUtils::CalculateCompositionSizeForFrame(
8172 nsIFrame* aFrame, bool aSubtractScrollbars,
8173 const nsSize* aOverrideScrollPortSize) {
8174 // If we have a scrollable frame, restrict the composition bounds to its
8175 // scroll port. The scroll port excludes the frame borders and the scroll
8176 // bars, which we don't want to be part of the composition bounds.
8177 nsIScrollableFrame* scrollableFrame = aFrame->GetScrollTargetFrame();
8178 nsRect rect = scrollableFrame ? scrollableFrame->GetScrollPortRect()
8179 : aFrame->GetRect();
8180 nsSize size =
8181 aOverrideScrollPortSize ? *aOverrideScrollPortSize : rect.Size();
8182
8183 nsPresContext* presContext = aFrame->PresContext();
8184 PresShell* presShell = presContext->PresShell();
8185
8186 bool isRootContentDocRootScrollFrame =
8187 presContext->IsRootContentDocumentCrossProcess() &&
8188 aFrame == presShell->GetRootScrollFrame();
8189 if (isRootContentDocRootScrollFrame) {
8190 ParentLayerRect compBounds;
8191 if (UpdateCompositionBoundsForRCDRSF(compBounds, presContext)) {
8192 int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
8193 size = nsSize(compBounds.width * auPerDevPixel,
8194 compBounds.height * auPerDevPixel);
8195 }
8196 }
8197
8198 if (aSubtractScrollbars) {
8199 nsMargin margins = ScrollbarAreaToExcludeFromCompositionBoundsFor(aFrame);
8200 size.width -= margins.LeftRight();
8201 size.height -= margins.TopBottom();
8202 }
8203
8204 return size;
8205 }
8206
8207 /* static */
CalculateBoundingCompositionSize(const nsIFrame * aFrame,bool aIsRootContentDocRootScrollFrame,const FrameMetrics & aMetrics)8208 CSSSize nsLayoutUtils::CalculateBoundingCompositionSize(
8209 const nsIFrame* aFrame, bool aIsRootContentDocRootScrollFrame,
8210 const FrameMetrics& aMetrics) {
8211 if (aIsRootContentDocRootScrollFrame) {
8212 return ViewAs<LayerPixel>(
8213 aMetrics.GetCompositionBounds().Size(),
8214 PixelCastJustification::ParentLayerToLayerForRootComposition) *
8215 LayerToScreenScale(1.0f) / aMetrics.DisplayportPixelsPerCSSPixel();
8216 }
8217 nsPresContext* presContext = aFrame->PresContext();
8218 ScreenSize rootCompositionSize;
8219 nsPresContext* rootPresContext =
8220 presContext->GetInProcessRootContentDocumentPresContext();
8221 if (!rootPresContext) {
8222 rootPresContext = presContext->GetRootPresContext();
8223 }
8224 PresShell* rootPresShell = nullptr;
8225 if (rootPresContext) {
8226 rootPresShell = rootPresContext->PresShell();
8227 if (nsIFrame* rootFrame = rootPresShell->GetRootFrame()) {
8228 ParentLayerRect compBounds;
8229 if (UpdateCompositionBoundsForRCDRSF(compBounds, rootPresContext)) {
8230 rootCompositionSize = ViewAs<ScreenPixel>(
8231 compBounds.Size(),
8232 PixelCastJustification::ScreenIsParentLayerForRoot);
8233 } else {
8234 // LayoutDeviceToScreenScale2D =
8235 // LayoutDeviceToParentLayerScale *
8236 // ParentLayerToScreenScale2D
8237 LayoutDeviceToScreenScale2D cumulativeResolution =
8238 LayoutDeviceToParentLayerScale(
8239 rootPresShell->GetCumulativeResolution()) *
8240 GetTransformToAncestorScaleCrossProcessForFrameMetrics(rootFrame);
8241
8242 int32_t rootAUPerDevPixel = rootPresContext->AppUnitsPerDevPixel();
8243 rootCompositionSize = (LayoutDeviceRect::FromAppUnits(
8244 rootFrame->GetRect(), rootAUPerDevPixel) *
8245 cumulativeResolution)
8246 .Size();
8247 }
8248 }
8249 } else {
8250 nsIWidget* widget = aFrame->GetNearestWidget();
8251 LayoutDeviceIntRect widgetBounds = widget->GetBounds();
8252 rootCompositionSize = ScreenSize(ViewAs<ScreenPixel>(
8253 widgetBounds.Size(),
8254 PixelCastJustification::LayoutDeviceIsScreenForBounds));
8255 }
8256
8257 // Adjust composition size for the size of scroll bars.
8258 nsIFrame* rootRootScrollFrame =
8259 rootPresShell ? rootPresShell->GetRootScrollFrame() : nullptr;
8260 nsMargin scrollbarMargins =
8261 ScrollbarAreaToExcludeFromCompositionBoundsFor(rootRootScrollFrame);
8262 LayoutDeviceMargin margins = LayoutDeviceMargin::FromAppUnits(
8263 scrollbarMargins, rootPresContext->AppUnitsPerDevPixel());
8264 // Scrollbars are not subject to resolution scaling, so LD pixels = layer
8265 // pixels for them.
8266 rootCompositionSize.width -= margins.LeftRight();
8267 rootCompositionSize.height -= margins.TopBottom();
8268
8269 CSSSize result =
8270 rootCompositionSize / aMetrics.DisplayportPixelsPerCSSPixel();
8271
8272 // If this is a nested content process, the in-process root content document's
8273 // composition size may still be arbitrarily large, so bound it further by
8274 // how much of the in-process RCD is visible in the top-level (cross-process
8275 // RCD) viewport.
8276 if (rootPresShell) {
8277 if (BrowserChild* bc = BrowserChild::GetFrom(rootPresShell)) {
8278 if (const auto& visibleRect =
8279 bc->GetTopLevelViewportVisibleRectInSelfCoords()) {
8280 CSSSize cssVisibleRect =
8281 visibleRect->Size() / rootPresContext->CSSToDevPixelScale();
8282 result = Min(result, cssVisibleRect);
8283 }
8284 }
8285 }
8286
8287 return result;
8288 }
8289
8290 /* static */
CalculateScrollableRectForFrame(const nsIScrollableFrame * aScrollableFrame,const nsIFrame * aRootFrame)8291 nsRect nsLayoutUtils::CalculateScrollableRectForFrame(
8292 const nsIScrollableFrame* aScrollableFrame, const nsIFrame* aRootFrame) {
8293 nsRect contentBounds;
8294 if (aScrollableFrame) {
8295 contentBounds = aScrollableFrame->GetScrollRange();
8296
8297 nsPoint scrollPosition = aScrollableFrame->GetScrollPosition();
8298 if (aScrollableFrame->GetScrollStyles().mVertical ==
8299 StyleOverflow::Hidden) {
8300 contentBounds.y = scrollPosition.y;
8301 contentBounds.height = 0;
8302 }
8303 if (aScrollableFrame->GetScrollStyles().mHorizontal ==
8304 StyleOverflow::Hidden) {
8305 contentBounds.x = scrollPosition.x;
8306 contentBounds.width = 0;
8307 }
8308
8309 contentBounds.width += aScrollableFrame->GetScrollPortRect().width;
8310 contentBounds.height += aScrollableFrame->GetScrollPortRect().height;
8311 } else {
8312 contentBounds = aRootFrame->GetRect();
8313 // Clamp to (0, 0) if there is no corresponding scrollable frame for the
8314 // given |aRootFrame|.
8315 contentBounds.MoveTo(0, 0);
8316 }
8317 return contentBounds;
8318 }
8319
8320 /* static */
CalculateExpandedScrollableRect(nsIFrame * aFrame)8321 nsRect nsLayoutUtils::CalculateExpandedScrollableRect(nsIFrame* aFrame) {
8322 nsRect scrollableRect = CalculateScrollableRectForFrame(
8323 aFrame->GetScrollTargetFrame(), aFrame->PresShell()->GetRootFrame());
8324 nsSize compSize = CalculateCompositionSizeForFrame(aFrame);
8325
8326 if (aFrame == aFrame->PresShell()->GetRootScrollFrame()) {
8327 // the composition size for the root scroll frame does not include the
8328 // local resolution, so we adjust.
8329 float res = aFrame->PresShell()->GetResolution();
8330 compSize.width = NSToCoordRound(compSize.width / res);
8331 compSize.height = NSToCoordRound(compSize.height / res);
8332 }
8333
8334 if (scrollableRect.width < compSize.width) {
8335 scrollableRect.x =
8336 std::max(0, scrollableRect.x - (compSize.width - scrollableRect.width));
8337 scrollableRect.width = compSize.width;
8338 }
8339
8340 if (scrollableRect.height < compSize.height) {
8341 scrollableRect.y = std::max(
8342 0, scrollableRect.y - (compSize.height - scrollableRect.height));
8343 scrollableRect.height = compSize.height;
8344 }
8345 return scrollableRect;
8346 }
8347
8348 /* static */
DoLogTestDataForPaint(WebRenderLayerManager * aManager,ViewID aScrollId,const std::string & aKey,const std::string & aValue)8349 void nsLayoutUtils::DoLogTestDataForPaint(WebRenderLayerManager* aManager,
8350 ViewID aScrollId,
8351 const std::string& aKey,
8352 const std::string& aValue) {
8353 MOZ_ASSERT(nsLayoutUtils::IsAPZTestLoggingEnabled(), "don't call me");
8354 aManager->LogTestDataForCurrentPaint(aScrollId, aKey, aValue);
8355 }
8356
LogAdditionalTestData(nsDisplayListBuilder * aBuilder,const std::string & aKey,const std::string & aValue)8357 void nsLayoutUtils::LogAdditionalTestData(nsDisplayListBuilder* aBuilder,
8358 const std::string& aKey,
8359 const std::string& aValue) {
8360 WebRenderLayerManager* manager = aBuilder->GetWidgetLayerManager(nullptr);
8361 if (!manager) {
8362 return;
8363 }
8364 manager->LogAdditionalTestData(aKey, aValue);
8365 }
8366
8367 /* static */
IsAPZTestLoggingEnabled()8368 bool nsLayoutUtils::IsAPZTestLoggingEnabled() {
8369 return StaticPrefs::apz_test_logging_enabled();
8370 }
8371
8372 ////////////////////////////////////////
8373 // SurfaceFromElementResult
8374
SurfaceFromElementResult()8375 SurfaceFromElementResult::SurfaceFromElementResult()
8376 // Use safe default values here
8377 : mHadCrossOriginRedirects(false),
8378 mIsWriteOnly(true),
8379 mIsStillLoading(false),
8380 mHasSize(false),
8381 mCORSUsed(false),
8382 mAlphaType(gfxAlphaType::Opaque) {}
8383
8384 const RefPtr<mozilla::gfx::SourceSurface>&
GetSourceSurface()8385 SurfaceFromElementResult::GetSourceSurface() {
8386 if (!mSourceSurface && mLayersImage) {
8387 mSourceSurface = mLayersImage->GetAsSourceSurface();
8388 }
8389
8390 return mSourceSurface;
8391 }
8392
8393 ////////////////////////////////////////
8394
IsNonWrapperBlock(nsIFrame * aFrame)8395 bool nsLayoutUtils::IsNonWrapperBlock(nsIFrame* aFrame) {
8396 MOZ_ASSERT(aFrame);
8397 return aFrame->IsBlockFrameOrSubclass() && !aFrame->IsBlockWrapper();
8398 }
8399
NeedsPrintPreviewBackground(nsPresContext * aPresContext)8400 bool nsLayoutUtils::NeedsPrintPreviewBackground(nsPresContext* aPresContext) {
8401 return aPresContext->IsRootPaginatedDocument() &&
8402 (aPresContext->Type() == nsPresContext::eContext_PrintPreview ||
8403 aPresContext->Type() == nsPresContext::eContext_PageLayout);
8404 }
8405
AutoMaybeDisableFontInflation(nsIFrame * aFrame)8406 AutoMaybeDisableFontInflation::AutoMaybeDisableFontInflation(nsIFrame* aFrame) {
8407 // FIXME: Now that inflation calculations are based on the flow
8408 // root's NCA's (nearest common ancestor of its inflatable
8409 // descendants) width, we could probably disable inflation in
8410 // fewer cases than we currently do.
8411 // MathML cells need special treatment. See bug 1002526 comment 56.
8412 if (aFrame->IsContainerForFontSizeInflation() &&
8413 !aFrame->IsFrameOfType(nsIFrame::eMathML)) {
8414 mPresContext = aFrame->PresContext();
8415 mOldValue = mPresContext->mInflationDisabledForShrinkWrap;
8416 mPresContext->mInflationDisabledForShrinkWrap = true;
8417 } else {
8418 // indicate we have nothing to restore
8419 mPresContext = nullptr;
8420 mOldValue = false;
8421 }
8422 }
8423
~AutoMaybeDisableFontInflation()8424 AutoMaybeDisableFontInflation::~AutoMaybeDisableFontInflation() {
8425 if (mPresContext) {
8426 mPresContext->mInflationDisabledForShrinkWrap = mOldValue;
8427 }
8428 }
8429
8430 namespace mozilla {
8431
NSRectToRect(const nsRect & aRect,double aAppUnitsPerPixel)8432 Rect NSRectToRect(const nsRect& aRect, double aAppUnitsPerPixel) {
8433 // Note that by making aAppUnitsPerPixel a double we're doing floating-point
8434 // division using a larger type and avoiding rounding error.
8435 return Rect(Float(aRect.x / aAppUnitsPerPixel),
8436 Float(aRect.y / aAppUnitsPerPixel),
8437 Float(aRect.width / aAppUnitsPerPixel),
8438 Float(aRect.height / aAppUnitsPerPixel));
8439 }
8440
NSRectToSnappedRect(const nsRect & aRect,double aAppUnitsPerPixel,const gfx::DrawTarget & aSnapDT)8441 Rect NSRectToSnappedRect(const nsRect& aRect, double aAppUnitsPerPixel,
8442 const gfx::DrawTarget& aSnapDT) {
8443 // Note that by making aAppUnitsPerPixel a double we're doing floating-point
8444 // division using a larger type and avoiding rounding error.
8445 Rect rect(Float(aRect.x / aAppUnitsPerPixel),
8446 Float(aRect.y / aAppUnitsPerPixel),
8447 Float(aRect.width / aAppUnitsPerPixel),
8448 Float(aRect.height / aAppUnitsPerPixel));
8449 MaybeSnapToDevicePixels(rect, aSnapDT, true);
8450 return rect;
8451 }
8452 // Similar to a snapped rect, except an axis is left unsnapped if the snapping
8453 // process results in a length of 0.
NSRectToNonEmptySnappedRect(const nsRect & aRect,double aAppUnitsPerPixel,const gfx::DrawTarget & aSnapDT)8454 Rect NSRectToNonEmptySnappedRect(const nsRect& aRect, double aAppUnitsPerPixel,
8455 const gfx::DrawTarget& aSnapDT) {
8456 // Note that by making aAppUnitsPerPixel a double we're doing floating-point
8457 // division using a larger type and avoiding rounding error.
8458 Rect rect(Float(aRect.x / aAppUnitsPerPixel),
8459 Float(aRect.y / aAppUnitsPerPixel),
8460 Float(aRect.width / aAppUnitsPerPixel),
8461 Float(aRect.height / aAppUnitsPerPixel));
8462 MaybeSnapToDevicePixels(rect, aSnapDT, true, false);
8463 return rect;
8464 }
8465
StrokeLineWithSnapping(const nsPoint & aP1,const nsPoint & aP2,int32_t aAppUnitsPerDevPixel,DrawTarget & aDrawTarget,const Pattern & aPattern,const StrokeOptions & aStrokeOptions,const DrawOptions & aDrawOptions)8466 void StrokeLineWithSnapping(const nsPoint& aP1, const nsPoint& aP2,
8467 int32_t aAppUnitsPerDevPixel,
8468 DrawTarget& aDrawTarget, const Pattern& aPattern,
8469 const StrokeOptions& aStrokeOptions,
8470 const DrawOptions& aDrawOptions) {
8471 Point p1 = NSPointToPoint(aP1, aAppUnitsPerDevPixel);
8472 Point p2 = NSPointToPoint(aP2, aAppUnitsPerDevPixel);
8473 SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget,
8474 aStrokeOptions.mLineWidth);
8475 aDrawTarget.StrokeLine(p1, p2, aPattern, aStrokeOptions, aDrawOptions);
8476 }
8477
8478 } // namespace mozilla
8479
8480 /* static */
SetBSizeFromFontMetrics(const nsIFrame * aFrame,ReflowOutput & aMetrics,const LogicalMargin & aFramePadding,WritingMode aLineWM,WritingMode aFrameWM)8481 void nsLayoutUtils::SetBSizeFromFontMetrics(const nsIFrame* aFrame,
8482 ReflowOutput& aMetrics,
8483 const LogicalMargin& aFramePadding,
8484 WritingMode aLineWM,
8485 WritingMode aFrameWM) {
8486 RefPtr<nsFontMetrics> fm =
8487 nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
8488
8489 if (fm) {
8490 // Compute final height of the frame.
8491 //
8492 // Do things the standard css2 way -- though it's hard to find it
8493 // in the css2 spec! It's actually found in the css1 spec section
8494 // 4.4 (you will have to read between the lines to really see
8495 // it).
8496 //
8497 // The height of our box is the sum of our font size plus the top
8498 // and bottom border and padding. The height of children do not
8499 // affect our height.
8500 aMetrics.SetBlockStartAscent(aLineWM.IsLineInverted() ? fm->MaxDescent()
8501 : fm->MaxAscent());
8502 aMetrics.BSize(aLineWM) = fm->MaxHeight();
8503 } else {
8504 NS_WARNING("Cannot get font metrics - defaulting sizes to 0");
8505 aMetrics.SetBlockStartAscent(aMetrics.BSize(aLineWM) = 0);
8506 }
8507 aMetrics.SetBlockStartAscent(aMetrics.BlockStartAscent() +
8508 aFramePadding.BStart(aFrameWM));
8509 aMetrics.BSize(aLineWM) += aFramePadding.BStartEnd(aFrameWM);
8510 }
8511
8512 /* static */
8513 // _BOUNDARY because Dispatch() with `targets` must not handle the event.
8514 MOZ_CAN_RUN_SCRIPT_BOUNDARY bool
HasDocumentLevelListenersForApzAwareEvents(PresShell * aPresShell)8515 nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(
8516 PresShell* aPresShell) {
8517 if (Document* doc = aPresShell->GetDocument()) {
8518 WidgetEvent event(true, eVoidEvent);
8519 nsTArray<EventTarget*> targets;
8520 // TODO: Bug 1506441
8521 nsresult rv =
8522 EventDispatcher::Dispatch(MOZ_KnownLive(ToSupports(doc)), nullptr,
8523 &event, nullptr, nullptr, nullptr, &targets);
8524 NS_ENSURE_SUCCESS(rv, false);
8525 for (size_t i = 0; i < targets.Length(); i++) {
8526 if (targets[i]->IsApzAware()) {
8527 return true;
8528 }
8529 }
8530 }
8531 return false;
8532 }
8533
8534 /* static */
CanScrollOriginClobberApz(ScrollOrigin aScrollOrigin)8535 bool nsLayoutUtils::CanScrollOriginClobberApz(ScrollOrigin aScrollOrigin) {
8536 switch (aScrollOrigin) {
8537 case ScrollOrigin::None:
8538 case ScrollOrigin::NotSpecified:
8539 case ScrollOrigin::Apz:
8540 case ScrollOrigin::Restore:
8541 return false;
8542 default:
8543 return true;
8544 }
8545 }
8546
8547 /* static */
ComputeScrollMetadata(const nsIFrame * aForFrame,const nsIFrame * aScrollFrame,nsIContent * aContent,const nsIFrame * aItemFrame,const nsPoint & aOffsetToReferenceFrame,WebRenderLayerManager * aLayerManager,ViewID aScrollParentId,const nsSize & aScrollPortSize,bool aIsRootContent)8548 ScrollMetadata nsLayoutUtils::ComputeScrollMetadata(
8549 const nsIFrame* aForFrame, const nsIFrame* aScrollFrame,
8550 nsIContent* aContent, const nsIFrame* aItemFrame,
8551 const nsPoint& aOffsetToReferenceFrame,
8552 WebRenderLayerManager* aLayerManager, ViewID aScrollParentId,
8553 const nsSize& aScrollPortSize, bool aIsRootContent) {
8554 const nsPresContext* presContext = aForFrame->PresContext();
8555 int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
8556
8557 PresShell* presShell = presContext->GetPresShell();
8558 ScrollMetadata metadata;
8559 FrameMetrics& metrics = metadata.GetMetrics();
8560 metrics.SetLayoutViewport(
8561 CSSRect(CSSPoint(), CSSSize::FromAppUnits(aScrollPortSize)));
8562
8563 nsIDocShell* docShell = presContext->GetDocShell();
8564 const BrowsingContext* bc =
8565 docShell ? docShell->GetBrowsingContext() : nullptr;
8566 bool isTouchEventsEnabled =
8567 bc &&
8568 bc->TouchEventsOverride() == mozilla::dom::TouchEventsOverride::Enabled;
8569
8570 if (bc && bc->InRDMPane() && isTouchEventsEnabled) {
8571 metadata.SetIsRDMTouchSimulationActive(true);
8572 }
8573
8574 ViewID scrollId = ScrollableLayerGuid::NULL_SCROLL_ID;
8575 if (aContent) {
8576 if (void* paintRequestTime =
8577 aContent->GetProperty(nsGkAtoms::paintRequestTime)) {
8578 metrics.SetPaintRequestTime(*static_cast<TimeStamp*>(paintRequestTime));
8579 aContent->RemoveProperty(nsGkAtoms::paintRequestTime);
8580 }
8581 scrollId = nsLayoutUtils::FindOrCreateIDFor(aContent);
8582 nsRect dp;
8583 if (DisplayPortUtils::GetDisplayPort(aContent, &dp)) {
8584 metrics.SetDisplayPort(CSSRect::FromAppUnits(dp));
8585 DisplayPortUtils::MarkDisplayPortAsPainted(aContent);
8586 }
8587
8588 metrics.SetHasNonZeroDisplayPortMargins(false);
8589 if (DisplayPortMarginsPropertyData* currentData =
8590 static_cast<DisplayPortMarginsPropertyData*>(
8591 aContent->GetProperty(nsGkAtoms::DisplayPortMargins))) {
8592 if (currentData->mMargins.mMargins != ScreenMargin()) {
8593 metrics.SetHasNonZeroDisplayPortMargins(true);
8594 }
8595 }
8596
8597 // Note: GetProperty() will return nullptr both in the case where
8598 // the property hasn't been set, and in the case where the property
8599 // has been set to false (in which case the property value is
8600 // `reinterpret_cast<void*>(false)` which is nullptr.
8601 if (aContent->GetProperty(nsGkAtoms::forceMousewheelAutodir)) {
8602 metadata.SetForceMousewheelAutodir(true);
8603 }
8604
8605 if (aContent->GetProperty(nsGkAtoms::forceMousewheelAutodirHonourRoot)) {
8606 metadata.SetForceMousewheelAutodirHonourRoot(true);
8607 }
8608
8609 if (IsAPZTestLoggingEnabled()) {
8610 LogTestDataForPaint(aLayerManager, scrollId, "displayport",
8611 metrics.GetDisplayPort());
8612 }
8613
8614 metrics.SetMinimalDisplayPort(
8615 aContent->GetProperty(nsGkAtoms::MinimalDisplayPort));
8616 }
8617
8618 const nsIScrollableFrame* scrollableFrame = nullptr;
8619 if (aScrollFrame) scrollableFrame = aScrollFrame->GetScrollTargetFrame();
8620
8621 metrics.SetScrollableRect(
8622 CSSRect::FromAppUnits(nsLayoutUtils::CalculateScrollableRectForFrame(
8623 scrollableFrame, aForFrame)));
8624
8625 if (scrollableFrame) {
8626 CSSPoint layoutScrollOffset =
8627 CSSPoint::FromAppUnits(scrollableFrame->GetScrollPosition());
8628 CSSPoint visualScrollOffset =
8629 aIsRootContent
8630 ? CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset())
8631 : layoutScrollOffset;
8632 metrics.SetVisualScrollOffset(visualScrollOffset);
8633 // APZ sometimes reads this even if we haven't set a visual scroll
8634 // update type (specifically, in the isFirstPaint case), so always
8635 // set it.
8636 metrics.SetVisualDestination(visualScrollOffset);
8637
8638 if (aIsRootContent) {
8639 if (aLayerManager->GetIsFirstPaint() &&
8640 presShell->IsVisualViewportOffsetSet()) {
8641 // Restore the visual viewport offset to the copy stored on the
8642 // main thread.
8643 presShell->ScrollToVisual(presShell->GetVisualViewportOffset(),
8644 FrameMetrics::eRestore, ScrollMode::Instant);
8645 }
8646 }
8647
8648 if (scrollableFrame->IsRootScrollFrameOfDocument()) {
8649 if (const Maybe<PresShell::VisualScrollUpdate>& visualUpdate =
8650 presShell->GetPendingVisualScrollUpdate()) {
8651 metrics.SetVisualDestination(
8652 CSSPoint::FromAppUnits(visualUpdate->mVisualScrollOffset));
8653 metrics.SetVisualScrollUpdateType(visualUpdate->mUpdateType);
8654 presShell->AcknowledgePendingVisualScrollUpdate();
8655 }
8656 }
8657
8658 if (aIsRootContent) {
8659 // Expand the layout viewport to the size including the area covered by
8660 // the dynamic toolbar in the case where the dynamic toolbar is being
8661 // used, otherwise when the dynamic toolbar transitions on the compositor,
8662 // the layout viewport will be smaller than the visual viewport on the
8663 // compositor, thus the layout viewport offset will be forced to be moved
8664 // in FrameMetrics::KeepLayoutViewportEnclosingVisualViewport.
8665 if (presContext->HasDynamicToolbar()) {
8666 CSSRect viewport = metrics.GetLayoutViewport();
8667 viewport.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
8668 presContext, viewport.Size()));
8669 metrics.SetLayoutViewport(viewport);
8670
8671 // We need to set 'fixed margins' to adjust 'fixed margins' value on the
8672 // composiutor in the case where the dynamic toolbar is completely
8673 // hidden because the margin value on the compositor is offset from the
8674 // position where the dynamic toolbar is completely VISIBLE but now the
8675 // toolbar is completely HIDDEN we need to adjust the difference on the
8676 // compositor.
8677 if (presContext->GetDynamicToolbarState() ==
8678 DynamicToolbarState::Collapsed) {
8679 metrics.SetFixedLayerMargins(
8680 ScreenMargin(0, 0,
8681 presContext->GetDynamicToolbarHeight() -
8682 presContext->GetDynamicToolbarMaxHeight(),
8683 0));
8684 }
8685 }
8686 }
8687
8688 metrics.SetScrollGeneration(scrollableFrame->CurrentScrollGeneration());
8689
8690 CSSRect viewport = metrics.GetLayoutViewport();
8691 viewport.MoveTo(layoutScrollOffset);
8692 metrics.SetLayoutViewport(viewport);
8693
8694 nsSize lineScrollAmount = scrollableFrame->GetLineScrollAmount();
8695 LayoutDeviceIntSize lineScrollAmountInDevPixels =
8696 LayoutDeviceIntSize::FromAppUnitsRounded(
8697 lineScrollAmount, presContext->AppUnitsPerDevPixel());
8698 metadata.SetLineScrollAmount(lineScrollAmountInDevPixels);
8699
8700 nsSize pageScrollAmount = scrollableFrame->GetPageScrollAmount();
8701 LayoutDeviceIntSize pageScrollAmountInDevPixels =
8702 LayoutDeviceIntSize::FromAppUnitsRounded(
8703 pageScrollAmount, presContext->AppUnitsPerDevPixel());
8704 metadata.SetPageScrollAmount(pageScrollAmountInDevPixels);
8705
8706 if (aScrollFrame->GetParent()) {
8707 metadata.SetDisregardedDirection(
8708 WheelHandlingUtils::GetDisregardedWheelScrollDirection(
8709 aScrollFrame->GetParent()));
8710 }
8711
8712 metadata.SetSnapInfo(scrollableFrame->GetScrollSnapInfo());
8713 metadata.SetOverscrollBehavior(
8714 scrollableFrame->GetOverscrollBehaviorInfo());
8715 metadata.SetScrollUpdates(scrollableFrame->GetScrollUpdates());
8716 }
8717
8718 // If we have the scrollparent being the same as the scroll id, the
8719 // compositor-side code could get into an infinite loop while building the
8720 // overscroll handoff chain.
8721 MOZ_ASSERT(aScrollParentId == ScrollableLayerGuid::NULL_SCROLL_ID ||
8722 scrollId != aScrollParentId);
8723 metrics.SetScrollId(scrollId);
8724 metrics.SetIsRootContent(aIsRootContent);
8725 metadata.SetScrollParentId(aScrollParentId);
8726
8727 const nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
8728 bool isRootScrollFrame = aScrollFrame == rootScrollFrame;
8729 Document* document = presShell->GetDocument();
8730
8731 if (scrollId != ScrollableLayerGuid::NULL_SCROLL_ID &&
8732 !presContext->GetParentPresContext()) {
8733 if ((aScrollFrame && isRootScrollFrame)) {
8734 metadata.SetIsLayersIdRoot(true);
8735 } else {
8736 MOZ_ASSERT(document, "A non-root-scroll frame must be in a document");
8737 if (aContent == document->GetDocumentElement()) {
8738 metadata.SetIsLayersIdRoot(true);
8739 }
8740 }
8741 }
8742
8743 // Get whether the root content is RTL(E.g. it's true either if
8744 // "writing-mode: vertical-rl", or if
8745 // "writing-mode: horizontal-tb; direction: rtl;" in CSS).
8746 // For the concept of this and the reason why we need to get this kind of
8747 // information, see the definition of |mIsAutoDirRootContentRTL| in struct
8748 // |ScrollMetadata|.
8749 const Element* bodyElement = document ? document->GetBodyElement() : nullptr;
8750 const nsIFrame* primaryFrame =
8751 bodyElement ? bodyElement->GetPrimaryFrame() : rootScrollFrame;
8752 if (!primaryFrame) {
8753 primaryFrame = rootScrollFrame;
8754 }
8755 if (primaryFrame) {
8756 WritingMode writingModeOfRootScrollFrame = primaryFrame->GetWritingMode();
8757 WritingMode::BlockDir blockDirOfRootScrollFrame =
8758 writingModeOfRootScrollFrame.GetBlockDir();
8759 WritingMode::InlineDir inlineDirOfRootScrollFrame =
8760 writingModeOfRootScrollFrame.GetInlineDir();
8761 if (blockDirOfRootScrollFrame == WritingMode::BlockDir::eBlockRL ||
8762 (blockDirOfRootScrollFrame == WritingMode::BlockDir::eBlockTB &&
8763 inlineDirOfRootScrollFrame == WritingMode::InlineDir::eInlineRTL)) {
8764 metadata.SetIsAutoDirRootContentRTL(true);
8765 }
8766 }
8767
8768 // Only the root scrollable frame for a given presShell should pick up
8769 // the presShell's resolution. All the other frames are 1.0.
8770 if (isRootScrollFrame) {
8771 metrics.SetPresShellResolution(presShell->GetResolution());
8772 } else {
8773 metrics.SetPresShellResolution(1.0f);
8774 }
8775
8776 if (presShell->IsResolutionUpdated()) {
8777 metadata.SetResolutionUpdated(true);
8778 }
8779
8780 // The cumulative resolution is the resolution at which the scroll frame's
8781 // content is actually rendered. It includes the pres shell resolutions of
8782 // all the pres shells from here up to the root, as well as any css-driven
8783 // resolution. We don't need to compute it as it's already stored in the
8784 // container parameters... except if we're in WebRender in which case we
8785 // don't have a aContainerParameters. In that case we're also not rasterizing
8786 // in Gecko anyway, so the only resolution we care about here is the presShell
8787 // resolution which we need to propagate to WebRender.
8788 metrics.SetCumulativeResolution(
8789 LayoutDeviceToLayerScale(presShell->GetCumulativeResolution()));
8790
8791 metrics.SetTransformToAncestorScale(
8792 GetTransformToAncestorScaleCrossProcessForFrameMetrics(
8793 aScrollFrame ? aScrollFrame : aForFrame));
8794 metrics.SetDevPixelsPerCSSPixel(presContext->CSSToDevPixelScale());
8795
8796 // Initially, AsyncPanZoomController should render the content to the screen
8797 // at the painted resolution.
8798 const LayerToParentLayerScale layerToParentLayerScale(1.0f);
8799 metrics.SetZoom(metrics.GetCumulativeResolution() *
8800 metrics.GetDevPixelsPerCSSPixel() * layerToParentLayerScale);
8801
8802 // Calculate the composition bounds as the size of the scroll frame and
8803 // its origin relative to the reference frame.
8804 // If aScrollFrame is null, we are in a document without a root scroll frame,
8805 // so it's a xul document. In this case, use the size of the viewport frame.
8806 const nsIFrame* frameForCompositionBoundsCalculation =
8807 aScrollFrame ? aScrollFrame : aForFrame;
8808 nsRect compositionBounds(
8809 frameForCompositionBoundsCalculation->GetOffsetToCrossDoc(aItemFrame) +
8810 aOffsetToReferenceFrame,
8811 frameForCompositionBoundsCalculation->GetSize());
8812 if (scrollableFrame) {
8813 // If we have a scrollable frame, restrict the composition bounds to its
8814 // scroll port. The scroll port excludes the frame borders and the scroll
8815 // bars, which we don't want to be part of the composition bounds.
8816 nsRect scrollPort = scrollableFrame->GetScrollPortRect();
8817 compositionBounds = nsRect(
8818 compositionBounds.TopLeft() + scrollPort.TopLeft(), scrollPort.Size());
8819 }
8820 ParentLayerRect frameBounds =
8821 LayoutDeviceRect::FromAppUnits(compositionBounds, auPerDevPixel) *
8822 metrics.GetCumulativeResolution() * layerToParentLayerScale;
8823
8824 // For the root scroll frame of the root content document (RCD-RSF), the above
8825 // calculation will yield the size of the viewport frame as the composition
8826 // bounds, which doesn't actually correspond to what is visible when
8827 // nsIDOMWindowUtils::setCSSViewport has been called to modify the visible
8828 // area of the prescontext that the viewport frame is reflowed into. In that
8829 // case if our document has a widget then the widget's bounds will correspond
8830 // to what is visible. If we don't have a widget the root view's bounds
8831 // correspond to what would be visible because they don't get modified by
8832 // setCSSViewport.
8833 bool isRootContentDocRootScrollFrame =
8834 isRootScrollFrame && presContext->IsRootContentDocumentCrossProcess();
8835 if (isRootContentDocRootScrollFrame) {
8836 UpdateCompositionBoundsForRCDRSF(frameBounds, presContext);
8837 if (RefPtr<MobileViewportManager> MVM =
8838 presContext->PresShell()->GetMobileViewportManager()) {
8839 metrics.SetCompositionSizeWithoutDynamicToolbar(
8840 MVM->GetCompositionSizeWithoutDynamicToolbar());
8841 }
8842 }
8843
8844 metrics.SetCompositionBoundsWidthIgnoringScrollbars(frameBounds.width);
8845
8846 nsMargin sizes = ScrollbarAreaToExcludeFromCompositionBoundsFor(aScrollFrame);
8847 // Scrollbars are not subject to resolution scaling, so LD pixels = layer
8848 // pixels for them.
8849 ParentLayerMargin boundMargins =
8850 LayoutDeviceMargin::FromAppUnits(sizes, auPerDevPixel) *
8851 LayoutDeviceToParentLayerScale(1.0f);
8852 frameBounds.Deflate(boundMargins);
8853
8854 metrics.SetCompositionBounds(frameBounds);
8855
8856 metrics.SetBoundingCompositionSize(
8857 nsLayoutUtils::CalculateBoundingCompositionSize(
8858 aScrollFrame ? aScrollFrame : aForFrame,
8859 isRootContentDocRootScrollFrame, metrics));
8860
8861 if (StaticPrefs::apz_printtree() || StaticPrefs::apz_test_logging_enabled()) {
8862 if (const nsIContent* content =
8863 frameForCompositionBoundsCalculation->GetContent()) {
8864 nsAutoString contentDescription;
8865 if (content->IsElement()) {
8866 content->AsElement()->Describe(contentDescription);
8867 } else {
8868 contentDescription.AssignLiteral("(not an element)");
8869 }
8870 metadata.SetContentDescription(
8871 NS_LossyConvertUTF16toASCII(contentDescription));
8872 if (IsAPZTestLoggingEnabled()) {
8873 LogTestDataForPaint(aLayerManager, scrollId, "contentDescription",
8874 metadata.GetContentDescription().get());
8875 }
8876 }
8877 }
8878
8879 metrics.SetPresShellId(presShell->GetPresShellId());
8880
8881 // If the scroll frame's content is marked 'scrollgrab', record this
8882 // in the FrameMetrics so APZ knows to provide the scroll grabbing
8883 // behaviour.
8884 if (aScrollFrame &&
8885 nsContentUtils::HasScrollgrab(aScrollFrame->GetContent())) {
8886 metadata.SetHasScrollgrab(true);
8887 }
8888
8889 // Also compute and set the background color.
8890 // This is needed for APZ overscrolling support.
8891 if (aScrollFrame) {
8892 if (isRootScrollFrame) {
8893 metadata.SetBackgroundColor(
8894 sRGBColor::FromABGR(presShell->GetCanvasBackground()));
8895 } else {
8896 ComputedStyle* backgroundStyle;
8897 if (nsCSSRendering::FindBackground(aScrollFrame, &backgroundStyle)) {
8898 nscolor backgroundColor =
8899 backgroundStyle->StyleBackground()->BackgroundColor(
8900 backgroundStyle);
8901 metadata.SetBackgroundColor(sRGBColor::FromABGR(backgroundColor));
8902 }
8903 }
8904 }
8905
8906 if (ShouldDisableApzForElement(aContent)) {
8907 metadata.SetForceDisableApz(true);
8908 }
8909
8910 metadata.SetPrefersReducedMotion(
8911 Gecko_MediaFeatures_PrefersReducedMotion(document));
8912
8913 return metadata;
8914 }
8915
8916 /*static*/
GetRootMetadata(nsDisplayListBuilder * aBuilder,WebRenderLayerManager * aLayerManager,const std::function<bool (ViewID & aScrollId)> & aCallback)8917 Maybe<ScrollMetadata> nsLayoutUtils::GetRootMetadata(
8918 nsDisplayListBuilder* aBuilder, WebRenderLayerManager* aLayerManager,
8919 const std::function<bool(ViewID& aScrollId)>& aCallback) {
8920 nsIFrame* frame = aBuilder->RootReferenceFrame();
8921 nsPresContext* presContext = frame->PresContext();
8922 PresShell* presShell = presContext->PresShell();
8923 Document* document = presShell->GetDocument();
8924
8925 // There is one case where we want the root container layer to have metrics.
8926 // If the parent process is using XUL windows, there is no root scrollframe,
8927 // and without explicitly creating metrics there will be no guaranteed
8928 // top-level APZC.
8929 bool addMetrics = XRE_IsParentProcess() && !presShell->GetRootScrollFrame();
8930
8931 // Add metrics if there are none in the layer tree with the id (create an id
8932 // if there isn't one already) of the root scroll frame/root content.
8933 bool ensureMetricsForRootId = nsLayoutUtils::AsyncPanZoomEnabled(frame) &&
8934 aBuilder->IsPaintingToWindow() &&
8935 !presContext->GetParentPresContext();
8936
8937 nsIContent* content = nullptr;
8938 nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
8939 if (rootScrollFrame) {
8940 content = rootScrollFrame->GetContent();
8941 } else {
8942 // If there is no root scroll frame, pick the document element instead.
8943 // The only case we don't want to do this is in non-APZ fennec, where
8944 // we want the root xul document to get a null scroll id so that the root
8945 // content document gets the first non-null scroll id.
8946 content = document->GetDocumentElement();
8947 }
8948
8949 if (ensureMetricsForRootId && content) {
8950 ViewID scrollId = nsLayoutUtils::FindOrCreateIDFor(content);
8951 if (aCallback(scrollId)) {
8952 ensureMetricsForRootId = false;
8953 }
8954 }
8955
8956 if (addMetrics || ensureMetricsForRootId) {
8957 bool isRootContent = presContext->IsRootContentDocumentCrossProcess();
8958
8959 nsSize scrollPortSize = frame->GetSize();
8960 if (isRootContent && rootScrollFrame) {
8961 nsIScrollableFrame* scrollableFrame =
8962 rootScrollFrame->GetScrollTargetFrame();
8963 scrollPortSize = scrollableFrame->GetScrollPortRect().Size();
8964 }
8965 return Some(nsLayoutUtils::ComputeScrollMetadata(
8966 frame, rootScrollFrame, content, frame,
8967 aBuilder->ToReferenceFrame(frame), aLayerManager,
8968 ScrollableLayerGuid::NULL_SCROLL_ID, scrollPortSize, isRootContent));
8969 }
8970
8971 return Nothing();
8972 }
8973
8974 /* static */
TransformToAncestorAndCombineRegions(const nsRegion & aRegion,nsIFrame * aFrame,const nsIFrame * aAncestorFrame,nsRegion * aPreciseTargetDest,nsRegion * aImpreciseTargetDest,Maybe<Matrix4x4Flagged> * aMatrixCache,const DisplayItemClip * aClip)8975 void nsLayoutUtils::TransformToAncestorAndCombineRegions(
8976 const nsRegion& aRegion, nsIFrame* aFrame, const nsIFrame* aAncestorFrame,
8977 nsRegion* aPreciseTargetDest, nsRegion* aImpreciseTargetDest,
8978 Maybe<Matrix4x4Flagged>* aMatrixCache, const DisplayItemClip* aClip) {
8979 if (aRegion.IsEmpty()) {
8980 return;
8981 }
8982 bool isPrecise;
8983 RegionBuilder<nsRegion> transformedRegion;
8984 for (nsRegion::RectIterator it = aRegion.RectIter(); !it.Done(); it.Next()) {
8985 nsRect transformed = TransformFrameRectToAncestor(
8986 aFrame, it.Get(), aAncestorFrame, &isPrecise, aMatrixCache);
8987 if (aClip) {
8988 transformed = aClip->ApplyNonRoundedIntersection(transformed);
8989 if (aClip->GetRoundedRectCount() > 0) {
8990 isPrecise = false;
8991 }
8992 }
8993 transformedRegion.OrWith(transformed);
8994 }
8995 nsRegion* dest = isPrecise ? aPreciseTargetDest : aImpreciseTargetDest;
8996 dest->OrWith(transformedRegion.ToRegion());
8997 // If the region becomes too complex this has a large performance impact.
8998 // We limit its complexity here.
8999 if (dest->GetNumRects() > 12) {
9000 dest->SimplifyOutward(6);
9001 if (isPrecise) {
9002 aPreciseTargetDest->OrWith(*aImpreciseTargetDest);
9003 *aImpreciseTargetDest = std::move(*aPreciseTargetDest);
9004 aImpreciseTargetDest->SimplifyOutward(6);
9005 *aPreciseTargetDest = nsRegion();
9006 }
9007 }
9008 }
9009
9010 /* static */
ShouldUseNoScriptSheet(Document * aDocument)9011 bool nsLayoutUtils::ShouldUseNoScriptSheet(Document* aDocument) {
9012 // also handle the case where print is done from print preview
9013 // see bug #342439 for more details
9014 if (aDocument->IsStaticDocument()) {
9015 aDocument = aDocument->GetOriginalDocument();
9016 }
9017 return aDocument->IsScriptEnabled();
9018 }
9019
9020 /* static */
ShouldUseNoFramesSheet(Document * aDocument)9021 bool nsLayoutUtils::ShouldUseNoFramesSheet(Document* aDocument) {
9022 bool allowSubframes = true;
9023 nsIDocShell* docShell = aDocument->GetDocShell();
9024 if (docShell) {
9025 docShell->GetAllowSubframes(&allowSubframes);
9026 }
9027 return !allowSubframes;
9028 }
9029
9030 /* static */
GetFrameTextContent(nsIFrame * aFrame,nsAString & aResult)9031 void nsLayoutUtils::GetFrameTextContent(nsIFrame* aFrame, nsAString& aResult) {
9032 aResult.Truncate();
9033 AppendFrameTextContent(aFrame, aResult);
9034 }
9035
9036 /* static */
AppendFrameTextContent(nsIFrame * aFrame,nsAString & aResult)9037 void nsLayoutUtils::AppendFrameTextContent(nsIFrame* aFrame,
9038 nsAString& aResult) {
9039 if (aFrame->IsTextFrame()) {
9040 auto* const textFrame = static_cast<nsTextFrame*>(aFrame);
9041 const auto offset = AssertedCast<uint32_t>(textFrame->GetContentOffset());
9042 const auto length = AssertedCast<uint32_t>(textFrame->GetContentLength());
9043 textFrame->TextFragment()->AppendTo(aResult, offset, length);
9044 } else {
9045 for (nsIFrame* child : aFrame->PrincipalChildList()) {
9046 AppendFrameTextContent(child, aResult);
9047 }
9048 }
9049 }
9050
9051 /* static */
GetSelectionBoundingRect(const Selection * aSel)9052 nsRect nsLayoutUtils::GetSelectionBoundingRect(const Selection* aSel) {
9053 nsRect res;
9054 // Bounding client rect may be empty after calling GetBoundingClientRect
9055 // when range is collapsed. So we get caret's rect when range is
9056 // collapsed.
9057 if (aSel->IsCollapsed()) {
9058 nsIFrame* frame = nsCaret::GetGeometry(aSel, &res);
9059 if (frame) {
9060 nsIFrame* relativeTo = GetContainingBlockForClientRect(frame);
9061 res = TransformFrameRectToAncestor(frame, res, relativeTo);
9062 }
9063 } else {
9064 RectAccumulator accumulator;
9065 const uint32_t rangeCount = aSel->RangeCount();
9066 for (const uint32_t idx : IntegerRange(rangeCount)) {
9067 MOZ_ASSERT(aSel->RangeCount() == rangeCount);
9068 nsRange* range = aSel->GetRangeAt(idx);
9069 nsRange::CollectClientRectsAndText(
9070 &accumulator, nullptr, range, range->GetStartContainer(),
9071 range->StartOffset(), range->GetEndContainer(), range->EndOffset(),
9072 true, false);
9073 }
9074 res = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect
9075 : accumulator.mResultRect;
9076 }
9077
9078 return res;
9079 }
9080
9081 /* static */
GetFloatContainingBlock(nsIFrame * aFrame)9082 nsBlockFrame* nsLayoutUtils::GetFloatContainingBlock(nsIFrame* aFrame) {
9083 nsIFrame* ancestor = aFrame->GetParent();
9084 while (ancestor && !ancestor->IsFloatContainingBlock()) {
9085 ancestor = ancestor->GetParent();
9086 }
9087 MOZ_ASSERT(!ancestor || ancestor->IsBlockFrameOrSubclass(),
9088 "Float containing block can only be block frame");
9089 return static_cast<nsBlockFrame*>(ancestor);
9090 }
9091
9092 // The implementations of this calculation are adapted from
9093 // Element::GetBoundingClientRect().
9094 /* static */
GetBoundingContentRect(const nsIContent * aContent,const nsIScrollableFrame * aRootScrollFrame,Maybe<CSSRect> * aOutNearestScrollClip)9095 CSSRect nsLayoutUtils::GetBoundingContentRect(
9096 const nsIContent* aContent, const nsIScrollableFrame* aRootScrollFrame,
9097 Maybe<CSSRect>* aOutNearestScrollClip) {
9098 if (nsIFrame* frame = aContent->GetPrimaryFrame()) {
9099 return GetBoundingFrameRect(frame, aRootScrollFrame, aOutNearestScrollClip);
9100 }
9101 return CSSRect();
9102 }
9103
9104 /* static */
GetBoundingFrameRect(nsIFrame * aFrame,const nsIScrollableFrame * aRootScrollFrame,Maybe<CSSRect> * aOutNearestScrollClip)9105 CSSRect nsLayoutUtils::GetBoundingFrameRect(
9106 nsIFrame* aFrame, const nsIScrollableFrame* aRootScrollFrame,
9107 Maybe<CSSRect>* aOutNearestScrollClip) {
9108 CSSRect result;
9109 nsIFrame* relativeTo = aRootScrollFrame->GetScrolledFrame();
9110 result = CSSRect::FromAppUnits(nsLayoutUtils::GetAllInFlowRectsUnion(
9111 aFrame, relativeTo, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS));
9112
9113 // If the element is contained in a scrollable frame that is not
9114 // the root scroll frame, make sure to clip the result so that it is
9115 // not larger than the containing scrollable frame's bounds.
9116 nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
9117 aFrame, SCROLLABLE_INCLUDE_HIDDEN | SCROLLABLE_FIXEDPOS_FINDS_ROOT);
9118 if (scrollFrame && scrollFrame != aRootScrollFrame) {
9119 nsIFrame* subFrame = do_QueryFrame(scrollFrame);
9120 MOZ_ASSERT(subFrame);
9121 // Get the bounds of the scroll frame in the same coordinate space
9122 // as |result|.
9123 nsRect subFrameRect = subFrame->GetRectRelativeToSelf();
9124 TransformResult res =
9125 nsLayoutUtils::TransformRect(subFrame, relativeTo, subFrameRect);
9126 MOZ_ASSERT(res == TRANSFORM_SUCCEEDED || res == NONINVERTIBLE_TRANSFORM);
9127 if (res == TRANSFORM_SUCCEEDED) {
9128 CSSRect subFrameRectCSS = CSSRect::FromAppUnits(subFrameRect);
9129 if (aOutNearestScrollClip) {
9130 *aOutNearestScrollClip = Some(subFrameRectCSS);
9131 }
9132
9133 result = subFrameRectCSS.Intersect(result);
9134 }
9135 }
9136 return result;
9137 }
9138
9139 /* static */
IsTransformed(nsIFrame * aForFrame,nsIFrame * aTopFrame)9140 bool nsLayoutUtils::IsTransformed(nsIFrame* aForFrame, nsIFrame* aTopFrame) {
9141 for (nsIFrame* f = aForFrame; f != aTopFrame; f = f->GetParent()) {
9142 if (f->IsTransformed()) {
9143 return true;
9144 }
9145 }
9146 return false;
9147 }
9148
9149 /*static*/
GetCumulativeApzCallbackTransform(nsIFrame * aFrame)9150 CSSPoint nsLayoutUtils::GetCumulativeApzCallbackTransform(nsIFrame* aFrame) {
9151 CSSPoint delta;
9152 if (!aFrame) {
9153 return delta;
9154 }
9155 nsIFrame* frame = aFrame;
9156 nsCOMPtr<nsIContent> lastContent;
9157 bool seenRcdRsf = false;
9158
9159 // Helper lambda to apply the callback transform for a single frame.
9160 auto applyCallbackTransformForFrame = [&](nsIFrame* frame) {
9161 if (frame) {
9162 nsCOMPtr<nsIContent> content = frame->GetContent();
9163 if (content && (content != lastContent)) {
9164 void* property = content->GetProperty(nsGkAtoms::apzCallbackTransform);
9165 if (property) {
9166 delta += *static_cast<CSSPoint*>(property);
9167 }
9168 }
9169 lastContent = content;
9170 }
9171 };
9172
9173 while (frame) {
9174 // Apply the callback transform for the current frame.
9175 applyCallbackTransformForFrame(frame);
9176
9177 // Keep track of whether we've encountered the RCD-RSF's content element.
9178 nsPresContext* pc = frame->PresContext();
9179 if (pc->IsRootContentDocumentCrossProcess()) {
9180 if (PresShell* shell = pc->GetPresShell()) {
9181 if (nsIFrame* rsf = shell->GetRootScrollFrame()) {
9182 if (frame->GetContent() == rsf->GetContent()) {
9183 seenRcdRsf = true;
9184 }
9185 }
9186 }
9187 }
9188
9189 // If we reach the RCD's viewport frame, but have not encountered
9190 // the RCD-RSF, we were inside fixed content in the RCD.
9191 // We still want to apply the RCD-RSF's callback transform because
9192 // it contains the offset between the visual and layout viewports
9193 // which applies to fixed content as well.
9194 ViewportFrame* viewportFrame = do_QueryFrame(frame);
9195 if (viewportFrame) {
9196 if (pc->IsRootContentDocumentCrossProcess() && !seenRcdRsf) {
9197 applyCallbackTransformForFrame(pc->PresShell()->GetRootScrollFrame());
9198 }
9199 }
9200
9201 // Proceed to the parent frame.
9202 frame = GetCrossDocParentFrameInProcess(frame);
9203 }
9204 return delta;
9205 }
9206
ComputeMaxSizeForPartialPrerender(nsIFrame * aFrame,nsSize aMaxSize)9207 static nsSize ComputeMaxSizeForPartialPrerender(nsIFrame* aFrame,
9208 nsSize aMaxSize) {
9209 Matrix4x4Flagged transform = nsLayoutUtils::GetTransformToAncestor(
9210 RelativeTo{aFrame},
9211 RelativeTo{nsLayoutUtils::GetDisplayRootFrame(aFrame)});
9212
9213 Matrix transform2D;
9214 if (!transform.Is2D(&transform2D)) {
9215 return aMaxSize;
9216 }
9217
9218 gfx::Rect result(0, 0, aMaxSize.width, aMaxSize.height);
9219 gfx::Size scale = transform2D.ScaleFactors();
9220 if (scale.width != 0 && scale.height != 0) {
9221 result.width /= scale.width;
9222 result.height /= scale.height;
9223 }
9224
9225 // Don't apply translate.
9226 transform2D._31 = 0.0f;
9227 transform2D._32 = 0.0f;
9228
9229 // Don't apply scale.
9230 if (scale.width != 0 && scale.height != 0) {
9231 transform2D._11 /= scale.width;
9232 transform2D._12 /= scale.width;
9233 transform2D._21 /= scale.height;
9234 transform2D._22 /= scale.height;
9235 }
9236
9237 // Theoretically we should use transform2D.Inverse() here but in this case
9238 // |transform2D| is a pure rotation matrix, no scaling, no translate at all,
9239 // so that the result bound's width and height would be pretty much same
9240 // as the one rotated by the inverse matrix.
9241 result = transform2D.TransformBounds(result);
9242 return nsSize(
9243 result.width < (float)nscoord_MAX ? result.width : nscoord_MAX,
9244 result.height < (float)nscoord_MAX ? result.height : nscoord_MAX);
9245 }
9246
9247 /* static */
ComputePartialPrerenderArea(nsIFrame * aFrame,const nsRect & aDirtyRect,const nsRect & aOverflow,const nsSize & aPrerenderSize)9248 nsRect nsLayoutUtils::ComputePartialPrerenderArea(
9249 nsIFrame* aFrame, const nsRect& aDirtyRect, const nsRect& aOverflow,
9250 const nsSize& aPrerenderSize) {
9251 nsSize maxSizeForPartialPrerender =
9252 ComputeMaxSizeForPartialPrerender(aFrame, aPrerenderSize);
9253 // Simple calculation for now: center the pre-render area on the dirty rect,
9254 // and clamp to the overflow area. Later we can do more advanced things like
9255 // redistributing from one axis to another, or from one side to another.
9256 nscoord xExcess =
9257 std::max(maxSizeForPartialPrerender.width - aDirtyRect.width, 0);
9258 nscoord yExcess =
9259 std::max(maxSizeForPartialPrerender.height - aDirtyRect.height, 0);
9260 nsRect result = aDirtyRect;
9261 result.Inflate(xExcess / 2, yExcess / 2);
9262 return result.MoveInsideAndClamp(aOverflow);
9263 }
9264
LineHasNonEmptyContentWorker(nsIFrame * aFrame)9265 static bool LineHasNonEmptyContentWorker(nsIFrame* aFrame) {
9266 // Look for non-empty frames, but ignore inline and br frames.
9267 // For inline frames, descend into the children, if any.
9268 if (aFrame->IsInlineFrame()) {
9269 for (nsIFrame* child : aFrame->PrincipalChildList()) {
9270 if (LineHasNonEmptyContentWorker(child)) {
9271 return true;
9272 }
9273 }
9274 } else {
9275 if (!aFrame->IsBrFrame() && !aFrame->IsEmpty()) {
9276 return true;
9277 }
9278 }
9279 return false;
9280 }
9281
LineHasNonEmptyContent(nsLineBox * aLine)9282 static bool LineHasNonEmptyContent(nsLineBox* aLine) {
9283 int32_t count = aLine->GetChildCount();
9284 for (nsIFrame* frame = aLine->mFirstChild; count > 0;
9285 --count, frame = frame->GetNextSibling()) {
9286 if (LineHasNonEmptyContentWorker(frame)) {
9287 return true;
9288 }
9289 }
9290 return false;
9291 }
9292
9293 /* static */
IsInvisibleBreak(nsINode * aNode,nsIFrame ** aNextLineFrame)9294 bool nsLayoutUtils::IsInvisibleBreak(nsINode* aNode,
9295 nsIFrame** aNextLineFrame) {
9296 if (aNextLineFrame) {
9297 *aNextLineFrame = nullptr;
9298 }
9299
9300 if (!aNode->IsElement() || !aNode->IsEditable()) {
9301 return false;
9302 }
9303 nsIFrame* frame = aNode->AsElement()->GetPrimaryFrame();
9304 if (!frame || !frame->IsBrFrame()) {
9305 return false;
9306 }
9307
9308 nsContainerFrame* f = frame->GetParent();
9309 while (f && f->IsFrameOfType(nsIFrame::eLineParticipant)) {
9310 f = f->GetParent();
9311 }
9312 nsBlockFrame* blockAncestor = do_QueryFrame(f);
9313 if (!blockAncestor) {
9314 // The container frame doesn't support line breaking.
9315 return false;
9316 }
9317
9318 bool valid = false;
9319 nsBlockInFlowLineIterator iter(blockAncestor, frame, &valid);
9320 if (!valid) {
9321 return false;
9322 }
9323
9324 bool lineNonEmpty = LineHasNonEmptyContent(iter.GetLine());
9325 if (!lineNonEmpty) {
9326 return false;
9327 }
9328
9329 while (iter.Next()) {
9330 auto currentLine = iter.GetLine();
9331 // Completely skip empty lines.
9332 if (!currentLine->IsEmpty()) {
9333 // If we come across an inline line, the BR has caused a visible line
9334 // break.
9335 if (currentLine->IsInline()) {
9336 if (aNextLineFrame) {
9337 *aNextLineFrame = currentLine->mFirstChild;
9338 }
9339 return false;
9340 }
9341 break;
9342 }
9343 }
9344
9345 return lineNonEmpty;
9346 }
9347
ComputeSVGReferenceRect(nsIFrame * aFrame,StyleGeometryBox aGeometryBox)9348 static nsRect ComputeSVGReferenceRect(nsIFrame* aFrame,
9349 StyleGeometryBox aGeometryBox) {
9350 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement());
9351 nsRect r;
9352
9353 // For SVG elements without associated CSS layout box, the used value for
9354 // content-box, padding-box, border-box and margin-box is fill-box.
9355 switch (aGeometryBox) {
9356 case StyleGeometryBox::StrokeBox: {
9357 // XXX Bug 1299876
9358 // The size of stroke-box is not correct if this graphic element has
9359 // specific stroke-linejoin or stroke-linecap.
9360 gfxRect bbox =
9361 SVGUtils::GetBBox(aFrame, SVGUtils::eBBoxIncludeFillGeometry |
9362 SVGUtils::eBBoxIncludeStroke);
9363 r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, AppUnitsPerCSSPixel());
9364 break;
9365 }
9366 case StyleGeometryBox::ViewBox: {
9367 nsIContent* content = aFrame->GetContent();
9368 SVGElement* element = static_cast<SVGElement*>(content);
9369 SVGViewportElement* svgElement = element->GetCtx();
9370 MOZ_ASSERT(svgElement);
9371
9372 if (svgElement && svgElement->HasViewBox()) {
9373 // If a `viewBox` attribute is specified for the SVG viewport creating
9374 // element:
9375 // 1. The reference box is positioned at the origin of the coordinate
9376 // system established by the `viewBox` attribute.
9377 // 2. The dimension of the reference box is set to the width and height
9378 // values of the `viewBox` attribute.
9379 const SVGViewBox& value =
9380 svgElement->GetAnimatedViewBox()->GetAnimValue();
9381 r = nsRect(nsPresContext::CSSPixelsToAppUnits(value.x),
9382 nsPresContext::CSSPixelsToAppUnits(value.y),
9383 nsPresContext::CSSPixelsToAppUnits(value.width),
9384 nsPresContext::CSSPixelsToAppUnits(value.height));
9385 } else {
9386 // No viewBox is specified, uses the nearest SVG viewport as reference
9387 // box.
9388 svgFloatSize viewportSize = svgElement->GetViewportSize();
9389 r = nsRect(0, 0, nsPresContext::CSSPixelsToAppUnits(viewportSize.width),
9390 nsPresContext::CSSPixelsToAppUnits(viewportSize.height));
9391 }
9392
9393 break;
9394 }
9395 case StyleGeometryBox::NoBox:
9396 case StyleGeometryBox::BorderBox:
9397 case StyleGeometryBox::ContentBox:
9398 case StyleGeometryBox::PaddingBox:
9399 case StyleGeometryBox::MarginBox:
9400 case StyleGeometryBox::FillBox: {
9401 gfxRect bbox =
9402 SVGUtils::GetBBox(aFrame, SVGUtils::eBBoxIncludeFillGeometry);
9403 r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, AppUnitsPerCSSPixel());
9404 break;
9405 }
9406 default: {
9407 MOZ_ASSERT_UNREACHABLE("unknown StyleGeometryBox type");
9408 gfxRect bbox =
9409 SVGUtils::GetBBox(aFrame, SVGUtils::eBBoxIncludeFillGeometry);
9410 r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, AppUnitsPerCSSPixel());
9411 break;
9412 }
9413 }
9414
9415 return r;
9416 }
9417
ComputeHTMLReferenceRect(nsIFrame * aFrame,StyleGeometryBox aGeometryBox)9418 static nsRect ComputeHTMLReferenceRect(nsIFrame* aFrame,
9419 StyleGeometryBox aGeometryBox) {
9420 nsRect r;
9421
9422 // For elements with associated CSS layout box, the used value for fill-box,
9423 // stroke-box and view-box is border-box.
9424 switch (aGeometryBox) {
9425 case StyleGeometryBox::ContentBox:
9426 r = aFrame->GetContentRectRelativeToSelf();
9427 break;
9428 case StyleGeometryBox::PaddingBox:
9429 r = aFrame->GetPaddingRectRelativeToSelf();
9430 break;
9431 case StyleGeometryBox::MarginBox:
9432 r = aFrame->GetMarginRectRelativeToSelf();
9433 break;
9434 case StyleGeometryBox::NoBox:
9435 case StyleGeometryBox::BorderBox:
9436 case StyleGeometryBox::FillBox:
9437 case StyleGeometryBox::StrokeBox:
9438 case StyleGeometryBox::ViewBox:
9439 r = aFrame->GetRectRelativeToSelf();
9440 break;
9441 default:
9442 MOZ_ASSERT_UNREACHABLE("unknown StyleGeometryBox type");
9443 r = aFrame->GetRectRelativeToSelf();
9444 break;
9445 }
9446
9447 return r;
9448 }
9449
ShapeBoxToGeometryBox(const StyleShapeBox & aBox)9450 static StyleGeometryBox ShapeBoxToGeometryBox(const StyleShapeBox& aBox) {
9451 switch (aBox) {
9452 case StyleShapeBox::BorderBox:
9453 return StyleGeometryBox::BorderBox;
9454 case StyleShapeBox::ContentBox:
9455 return StyleGeometryBox::ContentBox;
9456 case StyleShapeBox::MarginBox:
9457 return StyleGeometryBox::MarginBox;
9458 case StyleShapeBox::PaddingBox:
9459 return StyleGeometryBox::PaddingBox;
9460 }
9461 MOZ_ASSERT_UNREACHABLE("Unknown shape box type");
9462 return StyleGeometryBox::MarginBox;
9463 }
9464
ClipPathBoxToGeometryBox(const StyleShapeGeometryBox & aBox)9465 static StyleGeometryBox ClipPathBoxToGeometryBox(
9466 const StyleShapeGeometryBox& aBox) {
9467 using Tag = StyleShapeGeometryBox::Tag;
9468 switch (aBox.tag) {
9469 case Tag::ShapeBox:
9470 return ShapeBoxToGeometryBox(aBox.AsShapeBox());
9471 case Tag::ElementDependent:
9472 return StyleGeometryBox::NoBox;
9473 case Tag::FillBox:
9474 return StyleGeometryBox::FillBox;
9475 case Tag::StrokeBox:
9476 return StyleGeometryBox::StrokeBox;
9477 case Tag::ViewBox:
9478 return StyleGeometryBox::ViewBox;
9479 }
9480 MOZ_ASSERT_UNREACHABLE("Unknown shape box type");
9481 return StyleGeometryBox::NoBox;
9482 }
9483
9484 /* static */
ComputeGeometryBox(nsIFrame * aFrame,StyleGeometryBox aGeometryBox)9485 nsRect nsLayoutUtils::ComputeGeometryBox(nsIFrame* aFrame,
9486 StyleGeometryBox aGeometryBox) {
9487 // We use ComputeSVGReferenceRect for all SVG elements, except <svg>
9488 // element, which does have an associated CSS layout box. In this case we
9489 // should still use ComputeHTMLReferenceRect for region computing.
9490 return aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)
9491 ? ComputeSVGReferenceRect(aFrame, aGeometryBox)
9492 : ComputeHTMLReferenceRect(aFrame, aGeometryBox);
9493 }
9494
ComputeGeometryBox(nsIFrame * aFrame,const StyleShapeBox & aBox)9495 nsRect nsLayoutUtils::ComputeGeometryBox(nsIFrame* aFrame,
9496 const StyleShapeBox& aBox) {
9497 return ComputeGeometryBox(aFrame, ShapeBoxToGeometryBox(aBox));
9498 }
9499
ComputeGeometryBox(nsIFrame * aFrame,const StyleShapeGeometryBox & aBox)9500 nsRect nsLayoutUtils::ComputeGeometryBox(nsIFrame* aFrame,
9501 const StyleShapeGeometryBox& aBox) {
9502 return ComputeGeometryBox(aFrame, ClipPathBoxToGeometryBox(aBox));
9503 }
9504
9505 /* static */
ComputeOffsetToUserSpace(nsDisplayListBuilder * aBuilder,nsIFrame * aFrame)9506 nsPoint nsLayoutUtils::ComputeOffsetToUserSpace(nsDisplayListBuilder* aBuilder,
9507 nsIFrame* aFrame) {
9508 nsPoint offsetToBoundingBox =
9509 aBuilder->ToReferenceFrame(aFrame) -
9510 SVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
9511 if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
9512 // Snap the offset if the reference frame is not a SVG frame, since other
9513 // frames will be snapped to pixel when rendering.
9514 offsetToBoundingBox =
9515 nsPoint(aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
9516 offsetToBoundingBox.x),
9517 aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
9518 offsetToBoundingBox.y));
9519 }
9520
9521 // During SVG painting, the offset computed here is applied to the gfxContext
9522 // "ctx" used to paint the mask. After applying only "offsetToBoundingBox",
9523 // "ctx" would have its origin at the top left corner of frame's bounding box
9524 // (over all continuations).
9525 // However, SVG painting needs the origin to be located at the origin of the
9526 // SVG frame's "user space", i.e. the space in which, for example, the
9527 // frame's BBox lives.
9528 // SVG geometry frames and foreignObject frames apply their own offsets, so
9529 // their position is relative to their user space. So for these frame types,
9530 // if we want "ctx" to be in user space, we first need to subtract the
9531 // frame's position so that SVG painting can later add it again and the
9532 // frame is painted in the right place.
9533 gfxPoint toUserSpaceGfx =
9534 SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
9535 nsPoint toUserSpace =
9536 nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
9537 nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
9538
9539 return (offsetToBoundingBox - toUserSpace);
9540 }
9541
9542 /* static */
GetMetricsFor(nsPresContext * aPresContext,bool aIsVertical,const nsStyleFont * aStyleFont,Length aFontSize,bool aUseUserFontSet)9543 already_AddRefed<nsFontMetrics> nsLayoutUtils::GetMetricsFor(
9544 nsPresContext* aPresContext, bool aIsVertical,
9545 const nsStyleFont* aStyleFont, Length aFontSize, bool aUseUserFontSet) {
9546 nsFont font = aStyleFont->mFont;
9547 font.size = aFontSize;
9548 gfxFont::Orientation orientation =
9549 aIsVertical ? nsFontMetrics::eVertical : nsFontMetrics::eHorizontal;
9550 nsFontMetrics::Params params;
9551 params.language = aStyleFont->mLanguage;
9552 params.explicitLanguage = aStyleFont->mExplicitLanguage;
9553 params.orientation = orientation;
9554 params.userFontSet =
9555 aUseUserFontSet ? aPresContext->GetUserFontSet() : nullptr;
9556 params.textPerf = aPresContext->GetTextPerfMetrics();
9557 params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup();
9558 return aPresContext->GetMetricsFor(font, params);
9559 }
9560
9561 /* static */
ComputeSystemFont(nsFont * aSystemFont,LookAndFeel::FontID aFontID,const nsFont & aDefaultVariableFont,const Document * aDocument)9562 void nsLayoutUtils::ComputeSystemFont(nsFont* aSystemFont,
9563 LookAndFeel::FontID aFontID,
9564 const nsFont& aDefaultVariableFont,
9565 const Document* aDocument) {
9566 gfxFontStyle fontStyle;
9567 nsAutoString systemFontName;
9568 if (!LookAndFeel::GetFont(aFontID, systemFontName, fontStyle)) {
9569 return;
9570 }
9571 systemFontName.Trim("\"'");
9572 NS_ConvertUTF16toUTF8 nameu8(systemFontName);
9573 Servo_FontFamily_ForSystemFont(&nameu8, &aSystemFont->family);
9574 aSystemFont->style = fontStyle.style;
9575 aSystemFont->family.is_system_font = fontStyle.systemFont;
9576 aSystemFont->weight = fontStyle.weight;
9577 aSystemFont->stretch = fontStyle.stretch;
9578 aSystemFont->size = Length::FromPixels(fontStyle.size);
9579
9580 // aSystemFont->langGroup = fontStyle.langGroup;
9581 switch (StyleFontSizeAdjust::Tag(fontStyle.sizeAdjustBasis)) {
9582 case StyleFontSizeAdjust::Tag::None:
9583 aSystemFont->sizeAdjust = StyleFontSizeAdjust::None();
9584 break;
9585 case StyleFontSizeAdjust::Tag::ExHeight:
9586 aSystemFont->sizeAdjust =
9587 StyleFontSizeAdjust::ExHeight(fontStyle.sizeAdjust);
9588 break;
9589 case StyleFontSizeAdjust::Tag::CapHeight:
9590 aSystemFont->sizeAdjust =
9591 StyleFontSizeAdjust::CapHeight(fontStyle.sizeAdjust);
9592 break;
9593 case StyleFontSizeAdjust::Tag::ChWidth:
9594 aSystemFont->sizeAdjust =
9595 StyleFontSizeAdjust::ChWidth(fontStyle.sizeAdjust);
9596 break;
9597 case StyleFontSizeAdjust::Tag::IcWidth:
9598 aSystemFont->sizeAdjust =
9599 StyleFontSizeAdjust::IcWidth(fontStyle.sizeAdjust);
9600 break;
9601 case StyleFontSizeAdjust::Tag::IcHeight:
9602 aSystemFont->sizeAdjust =
9603 StyleFontSizeAdjust::IcHeight(fontStyle.sizeAdjust);
9604 break;
9605 }
9606
9607 if (aFontID == LookAndFeel::FontID::MozField ||
9608 aFontID == LookAndFeel::FontID::MozButton ||
9609 aFontID == LookAndFeel::FontID::MozList) {
9610 const bool isWindowsOrNonNativeTheme =
9611 #ifdef XP_WIN
9612 true ||
9613 #endif
9614 aDocument->ShouldAvoidNativeTheme();
9615
9616 if (isWindowsOrNonNativeTheme) {
9617 // For textfields, buttons and selects, we use whatever font is defined by
9618 // the system. Which it appears (and the assumption is) it is always a
9619 // proportional font. Then we always use 2 points smaller than what the
9620 // browser has defined as the default proportional font.
9621 //
9622 // This matches historical Windows behavior and other browsers.
9623 auto newSize =
9624 aDefaultVariableFont.size.ToCSSPixels() - CSSPixel::FromPoints(2.0f);
9625 aSystemFont->size = Length::FromPixels(std::max(float(newSize), 0.0f));
9626 }
9627 }
9628 }
9629
9630 /* static */
ShouldHandleMetaViewport(const Document * aDocument)9631 bool nsLayoutUtils::ShouldHandleMetaViewport(const Document* aDocument) {
9632 auto metaViewportOverride = nsIDocShell::META_VIEWPORT_OVERRIDE_NONE;
9633 if (aDocument) {
9634 if (nsIDocShell* docShell = aDocument->GetDocShell()) {
9635 metaViewportOverride = docShell->GetMetaViewportOverride();
9636 }
9637 }
9638 switch (metaViewportOverride) {
9639 case nsIDocShell::META_VIEWPORT_OVERRIDE_ENABLED:
9640 return true;
9641 case nsIDocShell::META_VIEWPORT_OVERRIDE_DISABLED:
9642 return false;
9643 default:
9644 MOZ_ASSERT(metaViewportOverride ==
9645 nsIDocShell::META_VIEWPORT_OVERRIDE_NONE);
9646 // The META_VIEWPORT_OVERRIDE_NONE case means that there is no override
9647 // and we rely solely on the StaticPrefs.
9648 return StaticPrefs::dom_meta_viewport_enabled();
9649 }
9650 }
9651
9652 /* static */
StyleForScrollbar(nsIFrame * aScrollbarPart)9653 ComputedStyle* nsLayoutUtils::StyleForScrollbar(nsIFrame* aScrollbarPart) {
9654 // Get the closest content node which is not an anonymous scrollbar
9655 // part. It should be the originating element of the scrollbar part.
9656 nsIContent* content = aScrollbarPart->GetContent();
9657 // Note that the content may be a normal element with scrollbar part
9658 // value specified for its -moz-appearance, so don't rely on it being
9659 // a native anonymous. Also note that we have to check the node name
9660 // because anonymous element like generated content may originate a
9661 // scrollbar.
9662 MOZ_ASSERT(content, "No content for the scrollbar part?");
9663 while (content && content->IsInNativeAnonymousSubtree() &&
9664 content->IsAnyOfXULElements(
9665 nsGkAtoms::scrollbar, nsGkAtoms::scrollbarbutton,
9666 nsGkAtoms::scrollcorner, nsGkAtoms::slider, nsGkAtoms::thumb)) {
9667 content = content->GetParent();
9668 }
9669 MOZ_ASSERT(content, "Native anonymous element with no originating node?");
9670 // Use the style from the primary frame of the content.
9671 // Note: it is important to use the primary frame rather than an
9672 // ancestor frame of the scrollbar part for the correct handling of
9673 // viewport scrollbar. The content of the scroll frame of the viewport
9674 // is the root element, but its style inherits from the viewport.
9675 // Since we need to use the style of root element for the viewport
9676 // scrollbar, we have to get the style from the primary frame.
9677 if (nsIFrame* primaryFrame = content->GetPrimaryFrame()) {
9678 return primaryFrame->Style();
9679 }
9680 // If the element doesn't have primary frame, get the computed style
9681 // from the element directly. This can happen on viewport, because
9682 // the scrollbar of viewport may be shown when the root element has
9683 // > display: none; overflow: scroll;
9684 MOZ_ASSERT(
9685 content == aScrollbarPart->PresContext()->Document()->GetRootElement(),
9686 "Root element is the only case for this fallback "
9687 "path to be triggered");
9688 RefPtr<ComputedStyle> style =
9689 ServoStyleSet::ResolveServoStyle(*content->AsElement());
9690 // Dropping the strong reference is fine because the style should be
9691 // held strongly by the element.
9692 return style.get();
9693 }
9694
9695 enum class FramePosition : uint8_t {
9696 Unknown,
9697 InView,
9698 OutOfView,
9699 };
9700
9701 // NOTE: Returns a pair of Nothing() and `FramePosition::Unknown` if |aFrame|
9702 // is not in out-of-process or if we haven't received enough information from
9703 // APZ.
GetFrameVisibleRectOnScreen(const nsIFrame * aFrame)9704 static std::pair<Maybe<ScreenRect>, FramePosition> GetFrameVisibleRectOnScreen(
9705 const nsIFrame* aFrame) {
9706 // We actually want the in-process top prescontext here.
9707 nsPresContext* topContextInProcess =
9708 aFrame->PresContext()->GetInProcessRootContentDocumentPresContext();
9709 if (!topContextInProcess) {
9710 // We are in chrome process.
9711 return std::make_pair(Nothing(), FramePosition::Unknown);
9712 }
9713
9714 if (topContextInProcess->Document()->IsTopLevelContentDocument()) {
9715 // We are in the top of content document.
9716 return std::make_pair(Nothing(), FramePosition::Unknown);
9717 }
9718
9719 nsIDocShell* docShell = topContextInProcess->GetDocShell();
9720 BrowserChild* browserChild = BrowserChild::GetFrom(docShell);
9721 if (!browserChild) {
9722 // We are not in out-of-process iframe.
9723 return std::make_pair(Nothing(), FramePosition::Unknown);
9724 }
9725
9726 if (!browserChild->GetEffectsInfo().IsVisible()) {
9727 // There is no visible rect on this iframe at all.
9728 return std::make_pair(Some(ScreenRect()), FramePosition::Unknown);
9729 }
9730
9731 Maybe<ScreenRect> visibleRect =
9732 browserChild->GetTopLevelViewportVisibleRectInBrowserCoords();
9733 if (!visibleRect) {
9734 // We are unsure if we haven't received the transformed rectangle of the
9735 // iframe's visible area.
9736 return std::make_pair(Nothing(), FramePosition::Unknown);
9737 }
9738
9739 nsIFrame* rootFrame = topContextInProcess->PresShell()->GetRootFrame();
9740 nsRect transformedToIFrame = nsLayoutUtils::TransformFrameRectToAncestor(
9741 aFrame, aFrame->GetRectRelativeToSelf(), rootFrame);
9742
9743 LayoutDeviceRect rectInLayoutDevicePixel = LayoutDeviceRect::FromAppUnits(
9744 transformedToIFrame, topContextInProcess->AppUnitsPerDevPixel());
9745
9746 ScreenRect transformedToRoot = ViewAs<ScreenPixel>(
9747 browserChild->GetChildToParentConversionMatrix().TransformBounds(
9748 rectInLayoutDevicePixel),
9749 PixelCastJustification::ContentProcessIsLayerInUiProcess);
9750
9751 FramePosition position = FramePosition::Unknown;
9752 // we need to check whether the transformed rect is outside the iframe
9753 // visible rect or not because in some cases the rect size is (0x0), thus
9754 // the intersection between the transformed rect and the iframe visible rect
9755 // would also be (0x0), then we can't tell whether the given nsIFrame is
9756 // inside the iframe visible rect or not by calling BaseRect::IsEmpty for the
9757 // intersection.
9758 if (transformedToRoot.x > visibleRect->XMost() ||
9759 transformedToRoot.y > visibleRect->YMost() ||
9760 visibleRect->x > transformedToRoot.XMost() ||
9761 visibleRect->y > transformedToRoot.YMost()) {
9762 position = FramePosition::OutOfView;
9763 } else {
9764 position = FramePosition::InView;
9765 }
9766
9767 return std::make_pair(Some(visibleRect->Intersect(transformedToRoot)),
9768 position);
9769 }
9770
9771 // static
FrameIsScrolledOutOfViewInCrossProcess(const nsIFrame * aFrame)9772 bool nsLayoutUtils::FrameIsScrolledOutOfViewInCrossProcess(
9773 const nsIFrame* aFrame) {
9774 auto [visibleRect, framePosition] = GetFrameVisibleRectOnScreen(aFrame);
9775 if (visibleRect.isNothing()) {
9776 return false;
9777 }
9778
9779 return visibleRect->IsEmpty() && framePosition != FramePosition::InView;
9780 }
9781
9782 // static
FrameIsMostlyScrolledOutOfViewInCrossProcess(const nsIFrame * aFrame,nscoord aMargin)9783 bool nsLayoutUtils::FrameIsMostlyScrolledOutOfViewInCrossProcess(
9784 const nsIFrame* aFrame, nscoord aMargin) {
9785 auto [visibleRect, framePosition] = GetFrameVisibleRectOnScreen(aFrame);
9786 (void)framePosition;
9787 if (visibleRect.isNothing()) {
9788 return false;
9789 }
9790
9791 nsPresContext* topContextInProcess =
9792 aFrame->PresContext()->GetInProcessRootContentDocumentPresContext();
9793 MOZ_ASSERT(topContextInProcess);
9794
9795 nsIDocShell* docShell = topContextInProcess->GetDocShell();
9796 BrowserChild* browserChild = BrowserChild::GetFrom(docShell);
9797 MOZ_ASSERT(browserChild);
9798
9799 Size scale =
9800 browserChild->GetChildToParentConversionMatrix().As2D().ScaleFactors();
9801 ScreenSize margin(scale.width * CSSPixel::FromAppUnits(aMargin),
9802 scale.height * CSSPixel::FromAppUnits(aMargin));
9803
9804 return visibleRect->width < margin.width ||
9805 visibleRect->height < margin.height;
9806 }
9807
9808 // static
ExpandHeightForViewportUnits(nsPresContext * aPresContext,const nsSize & aSize)9809 nsSize nsLayoutUtils::ExpandHeightForViewportUnits(nsPresContext* aPresContext,
9810 const nsSize& aSize) {
9811 nsSize sizeForViewportUnits = aPresContext->GetSizeForViewportUnits();
9812
9813 // |aSize| might be the size expanded to the minimum-scale size whereas the
9814 // size for viewport units is not scaled so that we need to expand the |aSize|
9815 // height by multiplying by the ratio of the viewport units height to the
9816 // visible area height.
9817 float vhExpansionRatio = (float)sizeForViewportUnits.height /
9818 aPresContext->GetVisibleArea().height;
9819
9820 MOZ_ASSERT(aSize.height <= NSCoordSaturatingNonnegativeMultiply(
9821 aSize.height, vhExpansionRatio));
9822 return nsSize(aSize.width, NSCoordSaturatingNonnegativeMultiply(
9823 aSize.height, vhExpansionRatio));
9824 }
9825
9826 template <typename SizeType>
ExpandHeightForDynamicToolbarImpl(const nsPresContext * aPresContext,const SizeType & aSize)9827 /* static */ SizeType ExpandHeightForDynamicToolbarImpl(
9828 const nsPresContext* aPresContext, const SizeType& aSize) {
9829 MOZ_ASSERT(aPresContext);
9830
9831 LayoutDeviceIntSize displaySize;
9832 if (RefPtr<MobileViewportManager> MVM =
9833 aPresContext->PresShell()->GetMobileViewportManager()) {
9834 displaySize = MVM->DisplaySize();
9835 } else if (!nsLayoutUtils::GetContentViewerSize(aPresContext, displaySize)) {
9836 return aSize;
9837 }
9838
9839 float toolbarHeightRatio =
9840 mozilla::ScreenCoord(aPresContext->GetDynamicToolbarMaxHeight()) /
9841 mozilla::ViewAs<mozilla::ScreenPixel>(
9842 displaySize,
9843 mozilla::PixelCastJustification::LayoutDeviceIsScreenForBounds)
9844 .height;
9845
9846 return SizeType(
9847 aSize.width,
9848 NSCoordSaturatingAdd(aSize.height, aSize.height * toolbarHeightRatio));
9849 }
9850
ExpandHeightForDynamicToolbar(const nsPresContext * aPresContext,const CSSSize & aSize)9851 CSSSize nsLayoutUtils::ExpandHeightForDynamicToolbar(
9852 const nsPresContext* aPresContext, const CSSSize& aSize) {
9853 return ExpandHeightForDynamicToolbarImpl(aPresContext, aSize);
9854 }
ExpandHeightForDynamicToolbar(const nsPresContext * aPresContext,const nsSize & aSize)9855 nsSize nsLayoutUtils::ExpandHeightForDynamicToolbar(
9856 const nsPresContext* aPresContext, const nsSize& aSize) {
9857 return ExpandHeightForDynamicToolbarImpl(aPresContext, aSize);
9858 }
9859