ClipManager.cpp (30796B)
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 "mozilla/layers/ClipManager.h" 8 9 #include "DisplayItemClipChain.h" 10 #include "FrameMetrics.h" 11 #include "mozilla/ScrollContainerFrame.h" 12 #include "mozilla/dom/Document.h" 13 #include "mozilla/layers/StackingContextHelper.h" 14 #include "mozilla/layers/WebRenderLayerManager.h" 15 #include "mozilla/StaticPrefs_apz.h" 16 #include "mozilla/webrender/WebRenderAPI.h" 17 #include "nsDisplayList.h" 18 #include "nsLayoutUtils.h" 19 #include "nsRefreshDriver.h" 20 #include "nsStyleStructInlines.h" 21 #include "UnitTransforms.h" 22 23 static mozilla::LazyLogModule sClipLog("wr.clip"); 24 #define CLIP_LOG(...) MOZ_LOG(sClipLog, LogLevel::Debug, (__VA_ARGS__)) 25 26 namespace mozilla::layers { 27 28 ClipManager::ClipManager() : mManager(nullptr), mBuilder(nullptr) {} 29 30 void ClipManager::BeginBuild(WebRenderLayerManager* aManager, 31 wr::DisplayListBuilder& aBuilder) { 32 MOZ_ASSERT(!mManager); 33 mManager = aManager; 34 MOZ_ASSERT(!mBuilder); 35 mBuilder = &aBuilder; 36 MOZ_ASSERT(mCacheStack.empty()); 37 mCacheStack.emplace(); 38 MOZ_ASSERT(mASROverride.empty()); 39 MOZ_ASSERT(mItemClipStack.empty()); 40 } 41 42 void ClipManager::EndBuild() { 43 mBuilder = nullptr; 44 mManager = nullptr; 45 mCacheStack.pop(); 46 MOZ_ASSERT(mCacheStack.empty()); 47 MOZ_ASSERT(mASROverride.empty()); 48 MOZ_ASSERT(mItemClipStack.empty()); 49 } 50 51 void ClipManager::BeginList(const StackingContextHelper& aStackingContext) { 52 CLIP_LOG("begin list %p affects = %d, ref-frame = %d\n", &aStackingContext, 53 aStackingContext.AffectsClipPositioning(), 54 aStackingContext.ReferenceFrameId().isSome()); 55 56 ItemClips clips(nullptr, nullptr, 0, false); 57 if (!mItemClipStack.empty()) { 58 clips = mItemClipStack.top(); 59 } 60 61 if (aStackingContext.AffectsClipPositioning()) { 62 if (auto referenceFrameId = aStackingContext.ReferenceFrameId()) { 63 PushOverrideForASR(clips.mASR, *referenceFrameId); 64 clips.mScrollId = *referenceFrameId; 65 } else { 66 // Start a new cache 67 mCacheStack.emplace(); 68 } 69 if (clips.mChain) { 70 clips.mClipChainId = 71 DefineClipChain(clips.mChain, clips.mAppUnitsPerDevPixel); 72 } 73 } 74 75 CLIP_LOG(" push: clip: %p, asr: %p, scroll =%" PRIuPTR ", clip =%" PRIu64 76 "\n", 77 clips.mChain, clips.mASR, clips.mScrollId.id, 78 clips.mClipChainId.valueOr(wr::WrClipChainId{0}).id); 79 80 mItemClipStack.push(clips); 81 } 82 83 void ClipManager::EndList(const StackingContextHelper& aStackingContext) { 84 MOZ_ASSERT(!mItemClipStack.empty()); 85 86 CLIP_LOG("end list %p\n", &aStackingContext); 87 88 mBuilder->SetClipChainLeaf(Nothing()); 89 mItemClipStack.pop(); 90 91 if (aStackingContext.AffectsClipPositioning()) { 92 if (aStackingContext.ReferenceFrameId()) { 93 PopOverrideForASR(mItemClipStack.empty() ? nullptr 94 : mItemClipStack.top().mASR); 95 } else { 96 MOZ_ASSERT(!mCacheStack.empty()); 97 mCacheStack.pop(); 98 } 99 } 100 } 101 102 void ClipManager::PushOverrideForASR(const ActiveScrolledRoot* aASR, 103 const wr::WrSpatialId& aSpatialId) { 104 wr::WrSpatialId space = GetSpatialId(aASR); 105 106 CLIP_LOG("Pushing %p override %zu -> %zu\n", aASR, space.id, aSpatialId.id); 107 auto it = mASROverride.insert({space, std::stack<wr::WrSpatialId>()}); 108 it.first->second.push(aSpatialId); 109 110 // Start a new cache 111 mCacheStack.emplace(); 112 113 // Fix up our cached item clip if needed. 114 if (!mItemClipStack.empty()) { 115 auto& top = mItemClipStack.top(); 116 if (top.mASR == aASR) { 117 top.mScrollId = aSpatialId; 118 if (top.mChain) { 119 top.mClipChainId = 120 DefineClipChain(top.mChain, top.mAppUnitsPerDevPixel); 121 } 122 } 123 } 124 } 125 126 void ClipManager::PopOverrideForASR(const ActiveScrolledRoot* aASR) { 127 MOZ_ASSERT(!mCacheStack.empty()); 128 mCacheStack.pop(); 129 130 wr::WrSpatialId space = GetSpatialId(aASR); 131 auto it = mASROverride.find(space); 132 if (it == mASROverride.end()) { 133 MOZ_ASSERT_UNREACHABLE("Push/PopOverrideForASR should be balanced"); 134 } else { 135 CLIP_LOG("Popping %p override %zu -> %zu\n", aASR, space.id, 136 it->second.top().id); 137 it->second.pop(); 138 } 139 140 if (!mItemClipStack.empty()) { 141 auto& top = mItemClipStack.top(); 142 if (top.mASR == aASR) { 143 top.mScrollId = (it == mASROverride.end() || it->second.empty()) 144 ? space 145 : it->second.top(); 146 if (top.mChain) { 147 top.mClipChainId = 148 DefineClipChain(top.mChain, top.mAppUnitsPerDevPixel); 149 } 150 } 151 } 152 153 if (it != mASROverride.end() && it->second.empty()) { 154 mASROverride.erase(it); 155 } 156 } 157 158 wr::WrSpatialId ClipManager::SpatialIdAfterOverride( 159 const wr::WrSpatialId& aSpatialId) { 160 auto it = mASROverride.find(aSpatialId); 161 if (it == mASROverride.end()) { 162 return aSpatialId; 163 } 164 MOZ_ASSERT(!it->second.empty()); 165 CLIP_LOG("Overriding %zu with %zu\n", aSpatialId.id, it->second.top().id); 166 return it->second.top(); 167 } 168 169 wr::WrSpaceAndClipChain ClipManager::SwitchItem(nsDisplayListBuilder* aBuilder, 170 nsDisplayItem* aItem) { 171 const DisplayItemClipChain* clip = aItem->GetClipChain(); 172 const DisplayItemClipChain* inheritedClipChain = 173 mBuilder->GetInheritedClipChain(); 174 if (inheritedClipChain && inheritedClipChain != clip) { 175 if (!clip) { 176 clip = mBuilder->GetInheritedClipChain(); 177 } else { 178 clip = aBuilder->CreateClipChainIntersection( 179 mBuilder->GetInheritedClipChain(), clip); 180 } 181 } 182 const ActiveScrolledRoot* asr = aItem->GetActiveScrolledRoot(); 183 DisplayItemType type = aItem->GetType(); 184 const ActiveScrolledRoot* stickyAsr = nullptr; 185 if (type == DisplayItemType::TYPE_STICKY_POSITION) { 186 // For sticky position items, the ASR is computed differently depending on 187 // whether the item has a fixed descendant or not. But for WebRender 188 // purposes we always want to use the ASR that would have been used if it 189 // didn't have fixed descendants, which is stored as the "container ASR" on 190 // the sticky item. 191 auto* sticky = static_cast<nsDisplayStickyPosition*>(aItem); 192 asr = sticky->GetContainerASR(); 193 stickyAsr = ActiveScrolledRoot::GetStickyASRFromFrame(sticky->Frame()); 194 MOZ_ASSERT(stickyAsr); 195 } 196 197 CLIP_LOG("processing item %p (%s) asr %p clip %p, inherited = %p\n", aItem, 198 DisplayItemTypeName(aItem->GetType()), asr, clip, 199 inheritedClipChain); 200 201 // In most cases we can combine the leaf of the clip chain with the clip rect 202 // of the display item. This reduces the number of clip items, which avoids 203 // some overhead further down the pipeline. 204 // Container display items are not currently supported because the clip 205 // rect of a stacking context is not handled the same as normal display 206 // items. 207 const bool separateLeaf = clip && clip->mASR == asr && 208 clip->mClip.GetRoundedRectCount() == 0 && 209 !aItem->GetChildren(); 210 211 // Zoom display items report their bounds etc using the parent document's 212 // APD because zoom items act as a conversion layer between the two different 213 // APDs. 214 const int32_t auPerDevPixel = [&] { 215 if (type == DisplayItemType::TYPE_ZOOM) { 216 return static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel(); 217 } 218 return aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); 219 }(); 220 221 ItemClips clips(asr, clip, auPerDevPixel, separateLeaf); 222 MOZ_ASSERT(!mItemClipStack.empty()); 223 if (clips.HasSameInputs(mItemClipStack.top())) { 224 // Early-exit because if the clips are the same as aItem's previous sibling, 225 // then we don't need to do do the work of popping the old stuff and then 226 // pushing it right back on for the new item. Note that if aItem doesn't 227 // have a previous sibling, that means BeginList would have been called 228 // just before this, which will have pushed a ItemClips(nullptr, nullptr) 229 // onto mItemClipStack, so the HasSameInputs check should return false. 230 CLIP_LOG("\tearly-exit for %p\n", aItem); 231 return mItemClipStack.top().GetSpaceAndClipChain(); 232 } 233 234 // Pop aItem's previous sibling's stuff from mBuilder in preparation for 235 // pushing aItem's stuff. 236 mItemClipStack.pop(); 237 238 // If the leaf of the clip chain is going to be merged with the display item's 239 // clip rect, then we should create a clip chain id from the leaf's parent. 240 if (separateLeaf) { 241 CLIP_LOG("\tseparate leaf detected, ignoring the last clip\n"); 242 clip = clip->mParent; 243 } 244 245 // There are up to three ASR chains here that we need to be fully defined: 246 // 1. The ASR chain pointed to by |asr| 247 // 2. The ASR chain pointed to by clip->mASR 248 // 3. For a sticky item, the ASR chain pointed to by the item's sticky ASR. 249 // This one is needed so that when we create WebRender commands for the 250 // sticky item, we can give WebRender accurate information about the 251 // spatial node we're in. 252 // These chains will often be the same, or one will include the other, 253 // but we can't rely on that always being the case, so we make a 254 // DefineSpatialNodes call on all three. These calls will recursively 255 // define all the ASRs that we care about for this item, but will not 256 // actually push anything onto the WR stack. 257 (void)DefineSpatialNodes(aBuilder, asr, aItem); 258 if (clip && clip->mASR != asr) { 259 (void)DefineSpatialNodes(aBuilder, clip->mASR, aItem); 260 } 261 if (stickyAsr && stickyAsr != asr) { 262 (void)DefineSpatialNodes(aBuilder, stickyAsr, aItem); 263 } 264 265 // Define all the clips in the item's clip chain, and obtain a clip chain id 266 // for it. 267 clips.mClipChainId = DefineClipChain(clip, auPerDevPixel); 268 269 wr::WrSpatialId space = GetSpatialId(asr); 270 clips.mScrollId = SpatialIdAfterOverride(space); 271 CLIP_LOG("\tassigning %d -> %d\n", (int)space.id, (int)clips.mScrollId.id); 272 273 // Now that we have the scroll id and a clip id for the item, push it onto 274 // the WR stack. 275 clips.UpdateSeparateLeaf(*mBuilder, auPerDevPixel); 276 auto spaceAndClipChain = clips.GetSpaceAndClipChain(); 277 278 CLIP_LOG(" push: clip: %p, asr: %p, scroll = %" PRIuPTR ", clip = %" PRIu64 279 "\n", 280 clips.mChain, clips.mASR, clips.mScrollId.id, 281 clips.mClipChainId.valueOr(wr::WrClipChainId{0}).id); 282 283 mItemClipStack.push(clips); 284 285 CLIP_LOG("done setup for %p\n", aItem); 286 return spaceAndClipChain; 287 } 288 289 wr::WrSpatialId ClipManager::GetSpatialId(const ActiveScrolledRoot* aASR) { 290 for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) { 291 Maybe<wr::WrSpatialId> space = Nothing(); 292 if (asr->mKind == ActiveScrolledRoot::ASRKind::Sticky) { 293 space = mBuilder->GetSpatialIdForDefinedStickyLayer(asr); 294 } else { 295 space = mBuilder->GetScrollIdForDefinedScrollLayer(asr->GetViewId()); 296 } 297 if (space) { 298 return *space; 299 } 300 301 // If this ASR doesn't have a scroll ID, then we should check its ancestor. 302 // There may not be one defined because the ASR may not be scrollable or we 303 // failed to get the scroll metadata. 304 } 305 306 Maybe<wr::WrSpatialId> space = mBuilder->GetScrollIdForDefinedScrollLayer( 307 ScrollableLayerGuid::NULL_SCROLL_ID); 308 MOZ_ASSERT(space.isSome()); 309 return *space; 310 } 311 312 StickyScrollContainer* ClipManager::GetStickyScrollContainer( 313 const ActiveScrolledRoot* aASR) { 314 MOZ_ASSERT(aASR->mKind == ActiveScrolledRoot::ASRKind::Sticky); 315 StickyScrollContainer* stickyScrollContainer = 316 StickyScrollContainer::GetOrCreateForFrame(aASR->mFrame); 317 if (stickyScrollContainer) { 318 // If there's no ASR for the scrollframe that this sticky item is attached 319 // to, then don't create a WR sticky item for it either. Trying to do so 320 // will end in sadness because WR will interpret some coordinates as 321 // relative to the nearest enclosing scrollframe, which will correspond 322 // to the nearest ancestor ASR on the gecko side. That ASR will not be the 323 // same as the scrollframe this sticky item is actually supposed to be 324 // attached to, thus the sadness. 325 // Not sending WR the sticky item is ok, because the enclosing scrollframe 326 // will never be asynchronously scrolled. Instead we will always position 327 // the sticky items correctly on the gecko side and WR will never need to 328 // adjust their position itself. 329 if (!stickyScrollContainer->ScrollContainer() 330 ->IsMaybeAsynchronouslyScrolled()) { 331 stickyScrollContainer = nullptr; 332 } 333 } 334 return stickyScrollContainer; 335 } 336 337 // Returns the smallest distance from "0" to the range [min, max] where 338 // min <= max. Despite the name, the return value is actually a 1-D vector, 339 // and so may be negative if max < 0. 340 static nscoord DistanceToRange(nscoord min, nscoord max) { 341 MOZ_ASSERT(min <= max); 342 if (max < 0) { 343 return max; 344 } 345 if (min > 0) { 346 return min; 347 } 348 MOZ_ASSERT(min <= 0 && max >= 0); 349 return 0; 350 } 351 352 // Returns the magnitude of the part of the range [min, max] that is greater 353 // than zero. The return value is always non-negative. 354 static nscoord PositivePart(nscoord min, nscoord max) { 355 MOZ_ASSERT(min <= max); 356 if (min >= 0) { 357 return max - min; 358 } 359 if (max > 0) { 360 return max; 361 } 362 return 0; 363 } 364 365 // Returns the magnitude of the part of the range [min, max] that is less 366 // than zero. The return value is always non-negative. 367 static nscoord NegativePart(nscoord min, nscoord max) { 368 MOZ_ASSERT(min <= max); 369 if (max <= 0) { 370 return max - min; 371 } 372 if (min < 0) { 373 return 0 - min; 374 } 375 return 0; 376 } 377 378 Maybe<wr::WrSpatialId> ClipManager::DefineStickyNode( 379 nsDisplayListBuilder* aBuilder, Maybe<wr::WrSpatialId> aParentSpatialId, 380 const ActiveScrolledRoot* aASR, nsDisplayItem* aItem) { 381 nsIFrame* stickyFrame = aASR->mFrame; 382 383 if (Maybe<wr::WrSpatialId> space = 384 mBuilder->GetSpatialIdForDefinedStickyLayer(aASR)) { 385 return space; 386 } 387 388 StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer(aASR); 389 if (!stickyScrollContainer) { 390 // This may indicated a sticky item that does not need a webrender spatial 391 // node. See the comment in GetStickyScrollContainer for details. 392 return Nothing(); 393 } 394 395 // Do not create a spatial node for sticky items with mShouldFlatten=true. 396 // These are inside inactive scroll frames and so cannot move asynchronously. 397 if (stickyScrollContainer->ShouldFlattenAway()) { 398 return Nothing(); 399 } 400 401 float auPerDevPixel = stickyFrame->PresContext()->AppUnitsPerDevPixel(); 402 403 nsRect itemBounds; 404 405 // Make both itemBounds and scrollPort be relative to the reference frame 406 // of the sticky frame's parent. 407 nsRect scrollPort = 408 stickyScrollContainer->ScrollContainer()->GetScrollPortRect(); 409 const nsIFrame* referenceFrame = 410 aBuilder->FindReferenceFrameFor(stickyFrame->GetParent()); 411 nsRect transformedBounds = stickyFrame->GetRectRelativeToSelf(); 412 DebugOnly transformResult = nsLayoutUtils::TransformRect( 413 stickyFrame, referenceFrame, transformedBounds); 414 MOZ_ASSERT(transformResult == nsLayoutUtils::TRANSFORM_SUCCEEDED); 415 itemBounds = transformedBounds; 416 scrollPort = scrollPort + 417 stickyScrollContainer->ScrollContainer()->GetOffsetToCrossDoc( 418 referenceFrame); 419 420 Maybe<float> topMargin; 421 Maybe<float> rightMargin; 422 Maybe<float> bottomMargin; 423 Maybe<float> leftMargin; 424 wr::StickyOffsetBounds vBounds = {0.0, 0.0}; 425 wr::StickyOffsetBounds hBounds = {0.0, 0.0}; 426 nsPoint appliedOffset; 427 428 nsRectAbsolute outer; 429 nsRectAbsolute inner; 430 stickyScrollContainer->GetScrollRanges(stickyFrame, &outer, &inner); 431 432 // The following computations make more sense upon understanding the 433 // semantics of "inner" and "outer", which is explained in the comment on 434 // SetStickyPositionData in Layers.h. 435 436 if (outer.YMost() != inner.YMost()) { 437 // Question: How far will itemBounds.y be from the top of the scrollport 438 // when we have scrolled from the current scroll position of "0" to 439 // reach the range [inner.YMost(), outer.YMost()] where the item gets 440 // stuck? 441 // Answer: the current distance is "itemBounds.y - scrollPort.y". That 442 // needs to be adjusted by the distance to the range, less any other 443 // sticky ranges that fall between 0 and the range. If the distance is 444 // negative (i.e. inner.YMost() <= outer.YMost() < 0) then we would be 445 // scrolling upwards (decreasing scroll offset) to reach that range, 446 // which would increase itemBounds.y and make it farther away from the 447 // top of the scrollport. So in that case the adjustment is -distance. 448 // If the distance is positive (0 < inner.YMost() <= outer.YMost()) then 449 // we would be scrolling downwards, itemBounds.y would decrease, and we 450 // again need to adjust by -distance. If we are already in the range 451 // then no adjustment is needed and distance is 0 so again using 452 // -distance works. If the distance is positive, and the item has both 453 // top and bottom sticky ranges, then the bottom sticky range may fall 454 // (entirely[1] or partly[2]) between the current scroll position. 455 // [1]: 0 <= outer.Y() <= inner.Y() < inner.YMost() <= outer.YMost() 456 // [2]: outer.Y() < 0 <= inner.Y() < inner.YMost() <= outer.YMost() 457 // In these cases, the item doesn't actually move for that part of the 458 // distance, so we need to subtract out that bit, which can be computed 459 // as the positive portion of the range [outer.Y(), inner.Y()]. 460 nscoord distance = DistanceToRange(inner.YMost(), outer.YMost()); 461 if (distance > 0) { 462 distance -= PositivePart(outer.Y(), inner.Y()); 463 } 464 topMargin = Some(NSAppUnitsToFloatPixels( 465 itemBounds.y - scrollPort.y - distance, auPerDevPixel)); 466 // Question: What is the maximum positive ("downward") offset that WR 467 // will have to apply to this item in order to prevent the item from 468 // visually moving? 469 // Answer: Since the item is "sticky" in the range [inner.YMost(), 470 // outer.YMost()], the maximum offset will be the size of the range, which 471 // is outer.YMost() - inner.YMost(). 472 vBounds.max = 473 NSAppUnitsToFloatPixels(outer.YMost() - inner.YMost(), auPerDevPixel); 474 // Question: how much of an offset has layout already applied to the item? 475 // Answer: if we are 476 // (a) inside the sticky range (inner.YMost() < 0 <= outer.YMost()), or 477 // (b) past the sticky range (inner.YMost() < outer.YMost() < 0) 478 // then layout has already applied some offset to the position of the 479 // item. The amount of the adjustment is |0 - inner.YMost()| in case (a) 480 // and |outer.YMost() - inner.YMost()| in case (b). 481 if (inner.YMost() < 0) { 482 appliedOffset.y = std::min(0, outer.YMost()) - inner.YMost(); 483 MOZ_ASSERT(appliedOffset.y > 0); 484 } 485 } 486 if (outer.Y() != inner.Y()) { 487 // Similar logic as in the previous section, but this time we care about 488 // the distance from itemBounds.YMost() to scrollPort.YMost(). 489 nscoord distance = DistanceToRange(outer.Y(), inner.Y()); 490 if (distance < 0) { 491 distance += NegativePart(inner.YMost(), outer.YMost()); 492 } 493 bottomMargin = Some(NSAppUnitsToFloatPixels( 494 scrollPort.YMost() - itemBounds.YMost() + distance, auPerDevPixel)); 495 // And here WR will be moving the item upwards rather than downwards so 496 // again things are inverted from the previous block. 497 vBounds.min = NSAppUnitsToFloatPixels(outer.Y() - inner.Y(), auPerDevPixel); 498 // We can't have appliedOffset be both positive and negative, and the top 499 // adjustment takes priority. So here we only update appliedOffset.y if 500 // it wasn't set by the top-sticky case above. 501 if (appliedOffset.y == 0 && inner.Y() > 0) { 502 appliedOffset.y = std::max(0, outer.Y()) - inner.Y(); 503 MOZ_ASSERT(appliedOffset.y < 0); 504 } 505 } 506 // Same as above, but for the x-axis 507 if (outer.XMost() != inner.XMost()) { 508 nscoord distance = DistanceToRange(inner.XMost(), outer.XMost()); 509 if (distance > 0) { 510 distance -= PositivePart(outer.X(), inner.X()); 511 } 512 leftMargin = Some(NSAppUnitsToFloatPixels( 513 itemBounds.x - scrollPort.x - distance, auPerDevPixel)); 514 hBounds.max = 515 NSAppUnitsToFloatPixels(outer.XMost() - inner.XMost(), auPerDevPixel); 516 if (inner.XMost() < 0) { 517 appliedOffset.x = std::min(0, outer.XMost()) - inner.XMost(); 518 MOZ_ASSERT(appliedOffset.x > 0); 519 } 520 } 521 if (outer.X() != inner.X()) { 522 nscoord distance = DistanceToRange(outer.X(), inner.X()); 523 if (distance < 0) { 524 distance += NegativePart(inner.XMost(), outer.XMost()); 525 } 526 rightMargin = Some(NSAppUnitsToFloatPixels( 527 scrollPort.XMost() - itemBounds.XMost() + distance, auPerDevPixel)); 528 hBounds.min = NSAppUnitsToFloatPixels(outer.X() - inner.X(), auPerDevPixel); 529 if (appliedOffset.x == 0 && inner.X() > 0) { 530 appliedOffset.x = std::max(0, outer.X()) - inner.X(); 531 MOZ_ASSERT(appliedOffset.x < 0); 532 } 533 } 534 535 LayoutDeviceRect bounds = 536 LayoutDeviceRect::FromAppUnits(itemBounds, auPerDevPixel); 537 wr::LayoutVector2D applied = { 538 NSAppUnitsToFloatPixels(appliedOffset.x, auPerDevPixel), 539 NSAppUnitsToFloatPixels(appliedOffset.y, auPerDevPixel)}; 540 bool needsProp = 541 nsDisplayStickyPosition::ShouldGetStickyAnimationId(stickyFrame); 542 Maybe<wr::WrAnimationProperty> prop; 543 auto displayItemKey = nsDisplayItem::GetPerFrameKey( 544 0, 0, DisplayItemType::TYPE_STICKY_POSITION); 545 auto spatialKey = wr::SpatialKey(uint64_t(stickyFrame), displayItemKey, 546 wr::SpatialKeyKind::Sticky); 547 if (needsProp) { 548 RefPtr<WebRenderAPZAnimationData> animationData = 549 mManager->CommandBuilder() 550 .CreateOrRecycleWebRenderUserData<WebRenderAPZAnimationData>( 551 displayItemKey, stickyFrame); 552 uint64_t animationId = animationData->GetAnimationId(); 553 554 prop.emplace(); 555 prop->id = animationId; 556 prop->key = spatialKey; 557 prop->effect_type = wr::WrAnimationType::Transform; 558 } 559 wr::WrSpatialId spatialId = mBuilder->DefineStickyFrame( 560 aASR, aParentSpatialId, wr::ToLayoutRect(bounds), 561 topMargin.ptrOr(nullptr), rightMargin.ptrOr(nullptr), 562 bottomMargin.ptrOr(nullptr), leftMargin.ptrOr(nullptr), vBounds, hBounds, 563 applied, spatialKey, prop.ptrOr(nullptr)); 564 565 return Some(spatialId); 566 } 567 568 Maybe<wr::WrSpatialId> ClipManager::DefineSpatialNodes( 569 nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR, 570 nsDisplayItem* aItem) { 571 if (!aASR) { 572 // Recursion base case 573 return Nothing(); 574 } 575 576 ScrollableLayerGuid::ViewID viewId = ScrollableLayerGuid::NULL_SCROLL_ID; 577 if (aASR->mKind == ActiveScrolledRoot::ASRKind::Scroll) { 578 viewId = aASR->GetViewId(); 579 Maybe<wr::WrSpatialId> space = 580 mBuilder->GetScrollIdForDefinedScrollLayer(viewId); 581 if (space) { 582 // If we've already defined this scroll layer before, we can early-exit 583 return space; 584 } 585 } 586 587 // Recurse to define the ancestors 588 Maybe<wr::WrSpatialId> ancestorSpace = 589 DefineSpatialNodes(aBuilder, aASR->mParent, aItem); 590 591 if (aASR->mKind == ActiveScrolledRoot::ASRKind::Sticky) { 592 Maybe<wr::WrSpatialId> parent = ancestorSpace.map( 593 [this](wr::WrSpatialId& aId) { return SpatialIdAfterOverride(aId); }); 594 return ClipManager::DefineStickyNode(aBuilder, parent, aASR, aItem); 595 } 596 597 MOZ_ASSERT(viewId != ScrollableLayerGuid::NULL_SCROLL_ID); 598 599 ScrollContainerFrame* scrollContainerFrame = aASR->ScrollFrame(); 600 Maybe<ScrollMetadata> metadata = scrollContainerFrame->ComputeScrollMetadata( 601 mManager, aItem->Frame(), aItem->ToReferenceFrame()); 602 if (!metadata) { 603 MOZ_ASSERT_UNREACHABLE("Expected scroll metadata to be available!"); 604 return ancestorSpace; 605 } 606 607 FrameMetrics& metrics = metadata->GetMetrics(); 608 if (!metrics.IsScrollable()) { 609 // This item is a scrolling no-op, skip over it in the ASR chain. 610 return ancestorSpace; 611 } 612 613 nsPoint offset = scrollContainerFrame->GetOffsetToCrossDoc(aItem->Frame()) + 614 aItem->ToReferenceFrame(); 615 int32_t auPerDevPixel = aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); 616 nsRect scrollPort = scrollContainerFrame->GetScrollPortRect() + offset; 617 LayoutDeviceRect clipBounds = 618 LayoutDeviceRect::FromAppUnits(scrollPort, auPerDevPixel); 619 620 // The content rect that we hand to PushScrollLayer should be relative to 621 // the same origin as the clipBounds that we hand to PushScrollLayer - 622 // that is, both of them should be relative to the stacking context `aSc`. 623 // However, when we get the scrollable rect from the FrameMetrics, the 624 // origin has nothing to do with the position of the frame but instead 625 // represents the minimum allowed scroll offset of the scrollable content. 626 // While APZ uses this to clamp the scroll position, we don't need to send 627 // this to WebRender at all. Instead, we take the position from the 628 // composition bounds. 629 LayoutDeviceRect contentRect = 630 metrics.GetExpandedScrollableRect() * metrics.GetDevPixelsPerCSSPixel(); 631 contentRect.MoveTo(clipBounds.TopLeft()); 632 633 Maybe<wr::WrSpatialId> parent = ancestorSpace; 634 if (parent) { 635 *parent = SpatialIdAfterOverride(*parent); 636 } 637 // The external scroll offset is accumulated into the local space positions of 638 // display items inside WR, so that the elements hash (intern) to the same 639 // content ID for quick comparisons. To avoid invalidations when the 640 // auPerDevPixel is not a round value, round here directly from app units. 641 // This guarantees we won't introduce any inaccuracy in the external scroll 642 // offset passed to WR. 643 const bool useRoundedOffset = 644 StaticPrefs::apz_rounded_external_scroll_offset(); 645 LayoutDevicePoint scrollOffset = 646 useRoundedOffset 647 ? LayoutDevicePoint::FromAppUnitsRounded( 648 scrollContainerFrame->GetScrollPosition(), auPerDevPixel) 649 : LayoutDevicePoint::FromAppUnits( 650 scrollContainerFrame->GetScrollPosition(), auPerDevPixel); 651 652 // Currently we track scroll-linked effects at the granularity of documents, 653 // not scroll frames, so we consider a scroll frame to have a scroll-linked 654 // effect whenever its containing document does. 655 nsPresContext* presContext = aItem->Frame()->PresContext(); 656 const bool hasScrollLinkedEffect = 657 !StaticPrefs::apz_disable_for_scroll_linked_effects() && 658 presContext->Document()->HasScrollLinkedEffect(); 659 660 return Some(mBuilder->DefineScrollLayer( 661 viewId, parent, wr::ToLayoutRect(contentRect), 662 wr::ToLayoutRect(clipBounds), wr::ToLayoutVector2D(scrollOffset), 663 wr::ToWrAPZScrollGeneration( 664 scrollContainerFrame->ScrollGenerationOnApz()), 665 wr::ToWrHasScrollLinkedEffect(hasScrollLinkedEffect), 666 wr::SpatialKey(uint64_t(scrollContainerFrame), 0, 667 wr::SpatialKeyKind::Scroll))); 668 } 669 670 Maybe<wr::WrClipChainId> ClipManager::DefineClipChain( 671 const DisplayItemClipChain* aChain, int32_t aAppUnitsPerDevPixel) { 672 MOZ_ASSERT(!mCacheStack.empty()); 673 if (!aChain) { 674 return Nothing(); 675 } 676 677 ClipIdMap& cache = mCacheStack.top(); 678 MOZ_DIAGNOSTIC_ASSERT(aChain->mOnStack || !aChain->mASR || 679 aChain->mASR->mFrame); 680 681 if (auto iter = cache.find(aChain); iter != cache.end()) { 682 // Found it in the currently-active cache, so just use the id we have for 683 // it. 684 CLIP_LOG("cache[%p] => hit\n", aChain); 685 return iter->second.mWrChainID; 686 } 687 688 const auto parentChain = 689 DefineClipChain(aChain->mParent, aAppUnitsPerDevPixel); 690 if (!aChain->mClip.HasClip()) { 691 cache[aChain] = {parentChain}; 692 // This item in the chain is a no-op, skip over it 693 return parentChain; 694 } 695 696 auto clip = LayoutDeviceRect::FromAppUnits(aChain->mClip.GetClipRect(), 697 aAppUnitsPerDevPixel); 698 AutoTArray<wr::ComplexClipRegion, 6> wrRoundedRects; 699 aChain->mClip.ToComplexClipRegions(aAppUnitsPerDevPixel, wrRoundedRects); 700 wr::WrSpatialId space = GetSpatialId(aChain->mASR); 701 // Define the clip 702 space = SpatialIdAfterOverride(space); 703 // Iterate through the clips in the current item's clip chain, define them 704 // in WR, and put their IDs into |clipIds|. 705 AutoTArray<wr::WrClipId, 6> clipChainClipIds; 706 auto rectClipId = 707 mBuilder->DefineRectClip(Some(space), wr::ToLayoutRect(clip)); 708 CLIP_LOG("cache[%p] <= %zu\n", aChain, rectClipId.id); 709 clipChainClipIds.AppendElement(rectClipId); 710 711 for (const auto& complexClip : wrRoundedRects) { 712 auto complexClipId = 713 mBuilder->DefineRoundedRectClip(Some(space), complexClip); 714 CLIP_LOG("cache[%p] <= %zu\n", aChain, complexClipId.id); 715 clipChainClipIds.AppendElement(complexClipId); 716 } 717 auto id = Some(mBuilder->DefineClipChain(clipChainClipIds, parentChain)); 718 cache[aChain] = {id}; 719 return id; 720 } 721 722 ClipManager::~ClipManager() { 723 MOZ_ASSERT(!mBuilder); 724 MOZ_ASSERT(mCacheStack.empty()); 725 MOZ_ASSERT(mItemClipStack.empty()); 726 } 727 728 ClipManager::ItemClips::ItemClips(const ActiveScrolledRoot* aASR, 729 const DisplayItemClipChain* aChain, 730 int32_t aAppUnitsPerDevPixel, 731 bool aSeparateLeaf) 732 : mASR(aASR), 733 mChain(aChain), 734 mAppUnitsPerDevPixel(aAppUnitsPerDevPixel), 735 mSeparateLeaf(aSeparateLeaf) { 736 mScrollId = wr::wr_root_scroll_node_id(); 737 } 738 739 void ClipManager::ItemClips::UpdateSeparateLeaf( 740 wr::DisplayListBuilder& aBuilder, int32_t aAppUnitsPerDevPixel) { 741 Maybe<wr::LayoutRect> clipLeaf; 742 if (mSeparateLeaf) { 743 MOZ_ASSERT(mChain); 744 clipLeaf.emplace(wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits( 745 mChain->mClip.GetClipRect(), aAppUnitsPerDevPixel))); 746 } 747 748 aBuilder.SetClipChainLeaf(clipLeaf); 749 } 750 751 bool ClipManager::ItemClips::HasSameInputs(const ItemClips& aOther) { 752 if (mASR != aOther.mASR || mChain != aOther.mChain || 753 mSeparateLeaf != aOther.mSeparateLeaf) { 754 return false; 755 } 756 // AUPDP only matters if we have a clip chain, since it's only used to compute 757 // the device space clip rect. 758 if (mChain && mAppUnitsPerDevPixel != aOther.mAppUnitsPerDevPixel) { 759 return false; 760 } 761 return true; 762 } 763 764 wr::WrSpaceAndClipChain ClipManager::ItemClips::GetSpaceAndClipChain() const { 765 auto spaceAndClipChain = wr::RootScrollNodeWithChain(); 766 spaceAndClipChain.space = mScrollId; 767 if (mClipChainId) { 768 spaceAndClipChain.clip_chain = mClipChainId->id; 769 } 770 return spaceAndClipChain; 771 } 772 773 } // namespace mozilla::layers