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