| File: | Volumes/Data/worker/Apple-iOS-26-Safer-CPP-Checks-EWS/build/Source/WebCore/page/LargestContentfulPaintData.cpp |
| Warning: | line 243, column 65 Call argument for parameter 'image' is uncounted and unsafe |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | /* |
| 2 | * Copyright (C) 2025 Apple Inc. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 7 | * 1. Redistributions of source code must retain the above copyright |
| 8 | * notice, this list of conditions and the following disclaimer. |
| 9 | * 2. Redistributions in binary form must reproduce the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer in the |
| 11 | * documentation and/or other materials provided with the distribution. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| 14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| 15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| 17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| 23 | * THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | #include "LargestContentfulPaintData.h" |
| 28 | |
| 29 | #include "CachedImage.h" |
| 30 | #include "ContainerNodeInlines.h" |
| 31 | #include "DocumentView.h" |
| 32 | #include "ElementInlines.h" |
| 33 | #include "FloatQuad.h" |
| 34 | #include "LargestContentfulPaint.h" |
| 35 | #include "LegacyRenderSVGImage.h" |
| 36 | #include "LocalDOMWindow.h" |
| 37 | #include "LocalFrameView.h" |
| 38 | #include "Logging.h" |
| 39 | #include "Page.h" |
| 40 | #include "Performance.h" |
| 41 | #include "RenderBlockFlow.h" |
| 42 | #include "RenderBox.h" |
| 43 | #include "RenderElementInlines.h" |
| 44 | #include "RenderInline.h" |
| 45 | #include "RenderLayer.h" |
| 46 | #include "RenderLayerInlines.h" |
| 47 | #include "RenderLineBreak.h" |
| 48 | #include "RenderObjectInlines.h" |
| 49 | #include "RenderReplaced.h" |
| 50 | #include "RenderSVGImage.h" |
| 51 | #include "RenderText.h" |
| 52 | #include "RenderView.h" |
| 53 | #include "VisibleRectContext.h" |
| 54 | #include <wtf/CheckedRef.h> |
| 55 | #include <wtf/Ref.h> |
| 56 | #include <wtf/text/TextStream.h> |
| 57 | |
| 58 | namespace WebCore { |
| 59 | |
| 60 | WTF_MAKE_STRUCT_TZONE_ALLOCATED_IMPL(ElementLargestContentfulPaintData)::bmalloc::api::HeapRef ElementLargestContentfulPaintData::s_heapRef ; const TZoneSpecification ElementLargestContentfulPaintData:: s_heapSpec = { &ElementLargestContentfulPaintData::s_heapRef , TZoneSpecification::encodeSize<ElementLargestContentfulPaintData >(), TZoneSpecification::encodeAlignment<ElementLargestContentfulPaintData >(), TZoneSpecification::encodeCategory<ElementLargestContentfulPaintData >(), ::bmalloc::api::compactAllocationMode<ElementLargestContentfulPaintData >(), TZoneSpecification::encodeDescriptor<ElementLargestContentfulPaintData >(), }; void* ElementLargestContentfulPaintData::operatorNewSlow (size_t size) { if constexpr (::bmalloc::api::compactAllocationMode <ElementLargestContentfulPaintData>() == CompactAllocationMode ::Compact) return ::bmalloc::api::tzoneAllocateCompactSlow(size , s_heapSpec); return ::bmalloc::api::tzoneAllocateNonCompactSlow (size, s_heapSpec); } using __makeBtzoneMallocedInlineMacroSemicolonifier __attribute__((unused)) = int; |
| 61 | WTF_MAKE_TZONE_ALLOCATED_IMPL(LargestContentfulPaintData)::bmalloc::api::HeapRef LargestContentfulPaintData::s_heapRef ; const TZoneSpecification LargestContentfulPaintData::s_heapSpec = { &LargestContentfulPaintData::s_heapRef, TZoneSpecification ::encodeSize<LargestContentfulPaintData>(), TZoneSpecification ::encodeAlignment<LargestContentfulPaintData>(), TZoneSpecification ::encodeCategory<LargestContentfulPaintData>(), ::bmalloc ::api::compactAllocationMode<LargestContentfulPaintData> (), TZoneSpecification::encodeDescriptor<LargestContentfulPaintData >(), }; void* LargestContentfulPaintData::operatorNewSlow( size_t size) { if constexpr (::bmalloc::api::compactAllocationMode <LargestContentfulPaintData>() == CompactAllocationMode ::Compact) return ::bmalloc::api::tzoneAllocateCompactSlow(size , s_heapSpec); return ::bmalloc::api::tzoneAllocateNonCompactSlow (size, s_heapSpec); } using __makeBtzoneMallocedInlineMacroSemicolonifier __attribute__((unused)) = int; |
| 62 | |
| 63 | LargestContentfulPaintData::LargestContentfulPaintData() = default; |
| 64 | LargestContentfulPaintData::~LargestContentfulPaintData() = default; |
| 65 | |
| 66 | // https://w3c.github.io/paint-timing/#exposed-for-paint-timing |
| 67 | bool LargestContentfulPaintData::isExposedForPaintTiming(const Element& element) |
| 68 | { |
| 69 | if (!protect(element.document())->isFullyActive()) |
| 70 | return false; |
| 71 | |
| 72 | if (!element.isInDocumentTree()) // Also checks isConnected(). |
| 73 | return false; |
| 74 | |
| 75 | return true; |
| 76 | } |
| 77 | |
| 78 | // https://w3c.github.io/largest-contentful-paint/#largest-contentful-paint-candidate |
| 79 | bool LargestContentfulPaintData::isEligibleForLargestContentfulPaint(const Element& element, float effectiveVisualArea) |
| 80 | { |
| 81 | CheckedPtr renderer = element.renderer(); |
| 82 | if (!renderer) |
| 83 | return false; |
| 84 | |
| 85 | if (renderer->style().isEffectivelyTransparent()) |
| 86 | return false; |
| 87 | |
| 88 | // FIXME: Need to implement the response length vs. image size logic: webkit.org/b/299558. |
| 89 | UNUSED_PARAM(effectiveVisualArea)(void)effectiveVisualArea; |
| 90 | return true; |
| 91 | } |
| 92 | |
| 93 | bool LargestContentfulPaintData::canCompareWithLargestPaintArea(const Element& element) |
| 94 | { |
| 95 | CheckedPtr renderer = element.renderer(); |
| 96 | if (!renderer) |
| 97 | return false; |
| 98 | |
| 99 | CheckedPtr layer = renderer->enclosingLayer(); |
| 100 | if (!layer) |
| 101 | return false; |
| 102 | |
| 103 | // An ancestor transform may scale the rect. |
| 104 | if (layer->isTransformed() || layer->hasTransformedAncestor()) |
| 105 | return false; |
| 106 | |
| 107 | // Other properties like clipping on ancestors can only ever shrink the area, so it's safe to compare. |
| 108 | return true; |
| 109 | } |
| 110 | |
| 111 | // https://w3c.github.io/largest-contentful-paint/#sec-effective-visual-size |
| 112 | std::optional<float> LargestContentfulPaintData::effectiveVisualArea(const Element& element, CachedImage* image, FloatRect imageLocalRect, FloatRect intersectionRect, FloatSize viewportSize) |
| 113 | { |
| 114 | RefPtr frameView = element.document().view(); |
| 115 | if (!frameView) |
| 116 | return { }; |
| 117 | |
| 118 | auto area = intersectionRect.area(); |
| 119 | if (area >= viewportSize.area()) |
| 120 | return { }; |
| 121 | |
| 122 | if (image) { |
| 123 | CheckedPtr renderer = element.renderer(); |
| 124 | if (!renderer) |
| 125 | return { }; |
| 126 | |
| 127 | auto absoluteContentRect = renderer->localToAbsoluteQuad(FloatRect(imageLocalRect)).boundingBox(); |
| 128 | |
| 129 | auto intersectingContentRect = intersection(absoluteContentRect, intersectionRect); |
| 130 | area = intersectingContentRect.area(); |
| 131 | |
| 132 | auto naturalSize = image->imageSizeForRenderer(renderer.get(), 1); |
| 133 | if (naturalSize.isEmpty()) |
| 134 | return { }; |
| 135 | |
| 136 | auto scaleFactor = absoluteContentRect.area() / FloatSize { naturalSize }.area(); |
| 137 | if (scaleFactor > 1) |
| 138 | area /= scaleFactor; |
| 139 | |
| 140 | return area; |
| 141 | } |
| 142 | |
| 143 | return area; |
| 144 | } |
| 145 | |
| 146 | // https://w3c.github.io/largest-contentful-paint/#sec-add-lcp-entry |
| 147 | void LargestContentfulPaintData::potentiallyAddLargestContentfulPaintEntry(Element& element, CachedImage* image, FloatRect imageLocalRect, FloatRect intersectionRect, MonotonicTime loadTime, DOMHighResTimeStamp paintTimestamp, std::optional<FloatSize>& viewportSize) |
| 148 | { |
| 149 | if (!image) { |
| 150 | // For text we have to accumulate rectangles for a single element from possibly multiple text boxes, so we can only mark an element as being in the content set after all the painting is done. |
| 151 | ASSERT(!element.isInLargestContentfulPaintTextContentSet())((void)0); |
| 152 | element.setInLargestContentfulPaintTextContentSet(); |
| 153 | } |
| 154 | |
| 155 | LOG_WITH_STREAM(LargestContentfulPaint, stream << "LargestContentfulPaintData " << this << " potentiallyAddLargestContentfulPaintEntry() " << element << " image " << (image ? image->url().string() : emptyString()) << " rect " << intersectionRect)((void)0); |
| 156 | |
| 157 | if (intersectionRect.isEmpty()) |
| 158 | return; |
| 159 | |
| 160 | if (canCompareWithLargestPaintArea(element) && intersectionRect.area() <= m_largestPaintArea) |
| 161 | return; |
| 162 | |
| 163 | Ref document = element.document(); |
| 164 | RefPtr window = document->window(); |
| 165 | if (!window) |
| 166 | return; |
| 167 | |
| 168 | RefPtr view = document->view(); |
| 169 | if (!view) |
| 170 | return; |
| 171 | |
| 172 | // The spec talks about trusted scroll events, but the intent is to detect user scrolls: https://github.com/w3c/largest-contentful-paint/issues/105 |
| 173 | if ((view->wasEverScrolledExplicitlyByUser() || window->hasDispatchedInputEvent())) |
| 174 | return; |
| 175 | |
| 176 | if (!viewportSize) |
| 177 | viewportSize = FloatSize { view->visualViewportRect().size() }; |
| 178 | |
| 179 | auto elementArea = effectiveVisualArea(element, image, imageLocalRect, intersectionRect, *viewportSize); |
| 180 | if (!elementArea) |
| 181 | return; |
| 182 | |
| 183 | if (*elementArea <= m_largestPaintArea) { |
| 184 | LOG_WITH_STREAM(LargestContentfulPaint, stream << " element area " << elementArea << " less than LCP " << m_largestPaintArea)((void)0); |
| 185 | return; |
| 186 | } |
| 187 | |
| 188 | if (!isEligibleForLargestContentfulPaint(element, *elementArea)) |
| 189 | return; |
| 190 | |
| 191 | m_largestPaintArea = *elementArea; |
| 192 | |
| 193 | Ref pendingEntry = LargestContentfulPaint::create(0); |
| 194 | pendingEntry->setElement(&element); |
| 195 | pendingEntry->setSize(std::round<unsigned>(m_largestPaintArea)); |
| 196 | |
| 197 | if (image) { |
| 198 | pendingEntry->setURLString(image->url().string()); |
| 199 | auto loadTimestamp = protect(window->performance())->relativeTimeFromTimeOriginInReducedResolution(loadTime); |
| 200 | pendingEntry->setLoadTime(loadTimestamp); |
| 201 | } |
| 202 | |
| 203 | if (element.hasID()) |
| 204 | pendingEntry->setID(element.getIdAttribute().string()); |
| 205 | |
| 206 | pendingEntry->setRenderTime(paintTimestamp); |
| 207 | |
| 208 | LOG_WITH_STREAM(LargestContentfulPaint, stream << " making new entry for " << element << " image " << (image ? image->url().string() : emptyString()) << " id " << pendingEntry->id() <<((void)0) |
| 209 | ": entry size " << pendingEntry->size() << ", loadTime " << pendingEntry->loadTime() << ", renderTime " << pendingEntry->renderTime())((void)0); |
| 210 | |
| 211 | m_pendingEntry = RefPtr { WTF::move(pendingEntry) }; |
| 212 | } |
| 213 | |
| 214 | // https://w3c.github.io/largest-contentful-paint/#sec-report-largest-contentful-paint |
| 215 | RefPtr<LargestContentfulPaint> LargestContentfulPaintData::generateLargestContentfulPaintEntry(DOMHighResTimeStamp paintTimestamp) |
| 216 | { |
| 217 | std::optional<FloatSize> viewportSize; |
| 218 | |
| 219 | auto imageRecords = std::exchange(m_pendingImageRecords, { }); |
| 220 | for (auto [weakElement, imageList] : imageRecords) { |
| 221 | RefPtr element = weakElement; |
| 222 | if (!element) |
| 223 | continue; |
| 224 | |
| 225 | auto& lcpData = element->ensureLargestContentfulPaintData(); |
| 226 | |
| 227 | // FIXME: This is doing multiple localToAbsolute on the same element, but multiple images per element is rare. |
| 228 | for (auto image : imageList) { |
| 229 | if (!image) |
| 230 | continue; |
| 231 | auto findIndex = lcpData.imageData.findIf([&](auto& value) { |
| 232 | return image == value.image; |
| 233 | }); |
| 234 | if (findIndex == notFound) |
| 235 | continue; |
| 236 | |
| 237 | auto& imageData = lcpData.imageData[findIndex]; |
| 238 | if (imageData.rect.isEmpty()) |
| 239 | continue; |
| 240 | |
| 241 | auto intersectionRect = computeViewportIntersectionRect(*element, imageData.rect); |
| 242 | auto loadTimeSeconds = imageData.loadTime ? *imageData.loadTime : MonotonicTime::now(); |
| 243 | potentiallyAddLargestContentfulPaintEntry(*element, image.get(), imageData.rect, intersectionRect, loadTimeSeconds, paintTimestamp, viewportSize); |
Call argument for parameter 'image' is uncounted and unsafe | |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | auto textRecords = std::exchange(m_paintedTextRecords, { }); |
| 248 | for (RefPtr element : textRecords) { |
| 249 | if (!element) |
| 250 | continue; |
| 251 | |
| 252 | auto rect = element->ensureLargestContentfulPaintData().accumulatedTextRect; |
| 253 | if (canCompareWithLargestPaintArea(*element) && rect.area() <= m_largestPaintArea) |
| 254 | continue; |
| 255 | |
| 256 | auto intersectionRect = computeViewportIntersectionRect(*element, rect); |
| 257 | potentiallyAddLargestContentfulPaintEntry(*element, nullptr, { }, intersectionRect, { }, paintTimestamp, viewportSize); |
| 258 | } |
| 259 | |
| 260 | m_haveNewCandidate = false; |
| 261 | return std::exchange(m_pendingEntry, nullptr); |
| 262 | } |
| 263 | |
| 264 | // This is a simplified version of IntersectionObserver::computeIntersectionState(). Some code should be shared. |
| 265 | FloatRect LargestContentfulPaintData::computeViewportIntersectionRect(Element& element, FloatRect localRect) |
| 266 | { |
| 267 | RefPtr frameView = element.document().view(); |
| 268 | if (!frameView) |
| 269 | return { }; |
| 270 | |
| 271 | CheckedPtr targetRenderer = element.renderer(); |
| 272 | if (!targetRenderer) |
| 273 | return { }; |
| 274 | |
| 275 | if (targetRenderer->isSkippedContent()) |
| 276 | return { }; |
| 277 | |
| 278 | CheckedPtr rootRenderer = frameView->renderView(); |
| 279 | auto layoutViewport = frameView->layoutViewportRect(); |
| 280 | |
| 281 | auto localTargetBounds = LayoutRect { localRect }; |
| 282 | auto absoluteRects = targetRenderer->computeVisibleRectsInContainer( |
| 283 | { localTargetBounds }, |
| 284 | &protect(targetRenderer->view()).get(), |
| 285 | { |
| 286 | .hasPositionFixedDescendant = false, |
| 287 | .dirtyRectIsFlipped = false, |
| 288 | .options = { |
| 289 | VisibleRectContext::Option::UseEdgeInclusiveIntersection, |
| 290 | VisibleRectContext::Option::ApplyCompositedClips, |
| 291 | VisibleRectContext::Option::ApplyCompositedContainerScrolls |
| 292 | }, |
| 293 | } |
| 294 | ); |
| 295 | |
| 296 | if (!absoluteRects) |
| 297 | return { }; |
| 298 | |
| 299 | auto intersectionRect = layoutViewport; |
| 300 | intersectionRect.edgeInclusiveIntersect(absoluteRects->clippedOverflowRect); |
| 301 | return intersectionRect; |
| 302 | } |
| 303 | |
| 304 | FloatRect LargestContentfulPaintData::computeViewportIntersectionRectForTextContainer(Element& element, const WeakHashSet<Text, WeakPtrImplWithEventTargetData>& textNodes) |
| 305 | { |
| 306 | RefPtr frameView = element.document().view(); |
| 307 | if (!frameView) |
| 308 | return { }; |
| 309 | |
| 310 | CheckedPtr rootRenderer = frameView->renderView(); |
| 311 | auto layoutViewport = frameView->layoutViewportRect(); |
| 312 | |
| 313 | IntRect absoluteTextBounds; |
| 314 | for (RefPtr node : textNodes) { |
| 315 | if (!node) |
| 316 | continue; |
| 317 | |
| 318 | CheckedPtr renderer = node->renderer(); |
| 319 | if (!renderer) |
| 320 | continue; |
| 321 | |
| 322 | if (renderer->isSkippedContent()) |
| 323 | continue; |
| 324 | |
| 325 | static constexpr bool useTransforms = true; |
| 326 | auto absoluteBounds = renderer->absoluteBoundingBoxRect(useTransforms); |
| 327 | absoluteTextBounds.unite(absoluteBounds); |
| 328 | } |
| 329 | |
| 330 | auto intersectionRect = layoutViewport; |
| 331 | intersectionRect.edgeInclusiveIntersect(absoluteTextBounds); |
| 332 | |
| 333 | return intersectionRect; |
| 334 | } |
| 335 | |
| 336 | void LargestContentfulPaintData::didLoadImage(Element& element, CachedImage* image) |
| 337 | { |
| 338 | if (!image) |
| 339 | return; |
| 340 | |
| 341 | // `loadTime` isn't interesting for a data URI, so let's avoid the overhead of tracking it. |
| 342 | if (image->url().protocolIsData()) |
| 343 | return; |
| 344 | |
| 345 | LOG_WITH_STREAM(LargestContentfulPaint, stream << "LargestContentfulPaintData " << this << " didLoadImage() " << element << " image " << (image ? image->url().string() : emptyString()))((void)0); |
| 346 | |
| 347 | auto& lcpData = element.ensureLargestContentfulPaintData(); |
| 348 | auto findIndex = lcpData.imageData.findIf([&](auto& value) { |
| 349 | return image == value.image; |
| 350 | }); |
| 351 | |
| 352 | if (findIndex != notFound && lcpData.imageData[findIndex].inContentSet) |
| 353 | return; |
| 354 | |
| 355 | if (!isExposedForPaintTiming(element)) |
| 356 | return; |
| 357 | |
| 358 | auto now = MonotonicTime::now(); |
| 359 | if (findIndex == notFound) { |
| 360 | auto imageData = PerElementImageData { *image, { }, now }; |
| 361 | lcpData.imageData.append(WTF::move(imageData)); |
| 362 | } else |
| 363 | lcpData.imageData[findIndex].loadTime = now; |
| 364 | } |
| 365 | |
| 366 | void LargestContentfulPaintData::didPaintImage(Element& element, CachedImage* image, FloatRect localRect) |
| 367 | { |
| 368 | LOG_WITH_STREAM(LargestContentfulPaint, stream << "LargestContentfulPaintData " << this << " didPaintImage() " << element << " image " << (image ? image->url().string() : emptyString()) << " localRect " << localRect)((void)0); |
| 369 | |
| 370 | if (!image) |
| 371 | return; |
| 372 | |
| 373 | auto& lcpData = element.ensureLargestContentfulPaintData(); |
| 374 | auto findIndex = lcpData.imageData.findIf([&](auto& value) { |
| 375 | return image == value.image; |
| 376 | }); |
| 377 | |
| 378 | if (findIndex == notFound) { |
| 379 | findIndex = lcpData.imageData.size(); |
| 380 | auto imageData = PerElementImageData { *image, { }, MonotonicTime::now() }; |
| 381 | lcpData.imageData.append(WTF::move(imageData)); |
| 382 | } |
| 383 | |
| 384 | auto& imageData = lcpData.imageData[findIndex]; |
| 385 | if (imageData.inContentSet) |
| 386 | return; |
| 387 | |
| 388 | imageData.inContentSet = true; |
| 389 | |
| 390 | if (localRect.isEmpty()) |
| 391 | return; |
| 392 | |
| 393 | if (canCompareWithLargestPaintArea(element) && localRect.area() <= m_largestPaintArea) |
| 394 | return; |
| 395 | |
| 396 | if (!isExposedForPaintTiming(element)) |
| 397 | return; |
| 398 | |
| 399 | if (!imageData.loadTime) |
| 400 | imageData.loadTime = MonotonicTime::now(); |
| 401 | |
| 402 | if (localRect.area() > imageData.rect.area()) |
| 403 | imageData.rect = localRect; |
| 404 | |
| 405 | m_pendingImageRecords.ensure(element, [] { |
| 406 | return Vector<WeakPtr<CachedImage>> { }; |
| 407 | }).iterator->value.append(*image); |
| 408 | |
| 409 | scheduleRenderingUpdateIfNecessary(element); |
| 410 | } |
| 411 | |
| 412 | void LargestContentfulPaintData::didPaintText(const RenderBlockFlow& formattingContextRoot, FloatRect localRect, bool isOnlyTextBoxForElement) |
| 413 | { |
| 414 | if (localRect.isEmpty()) |
| 415 | return; |
| 416 | |
| 417 | auto& renderBlockFlow = const_cast<RenderBlockFlow&>(formattingContextRoot); |
| 418 | // https://w3c.github.io/paint-timing/#sec-modifications-dom says to get the containing block. |
| 419 | CheckedPtr<RenderBlock> containingBlock = &renderBlockFlow; |
| 420 | if (containingBlock->isAnonymous()) { |
| 421 | CheckedPtr ancestor = containingBlock->firstNonAnonymousAncestor(); |
| 422 | if (CheckedPtr ancestorBlock = dynamicDowncast<RenderBlock>(ancestor.get())) |
| 423 | containingBlock = ancestorBlock; |
| 424 | else |
| 425 | containingBlock = containingBlock->containingBlock(); |
| 426 | } |
| 427 | |
| 428 | if (!containingBlock) |
| 429 | return; |
| 430 | |
| 431 | RefPtr element = containingBlock->element(); |
| 432 | if (!element) |
| 433 | return; |
| 434 | |
| 435 | if (element->isInLargestContentfulPaintTextContentSet()) |
| 436 | return; |
| 437 | |
| 438 | if (isOnlyTextBoxForElement && canCompareWithLargestPaintArea(*element) && localRect.area() <= m_largestPaintArea) |
| 439 | return; |
| 440 | |
| 441 | if (!isExposedForPaintTiming(*element)) |
| 442 | return; |
| 443 | |
| 444 | if (containingBlock != &formattingContextRoot) |
| 445 | localRect = formattingContextRoot.localToContainerQuad({ localRect }, containingBlock.get()).boundingBox(); |
| 446 | |
| 447 | element->ensureLargestContentfulPaintData().accumulatedTextRect.unite(localRect); |
| 448 | |
| 449 | m_paintedTextRecords.add(*element); |
| 450 | |
| 451 | scheduleRenderingUpdateIfNecessary(*element); |
| 452 | } |
| 453 | |
| 454 | void LargestContentfulPaintData::scheduleRenderingUpdateIfNecessary(Element& element) |
| 455 | { |
| 456 | if (m_haveNewCandidate) |
| 457 | return; |
| 458 | |
| 459 | m_haveNewCandidate = true; |
| 460 | if (RefPtr page = element.document().page()) |
| 461 | page->scheduleRenderingUpdate(RenderingUpdateStep::PaintTiming); |
| 462 | } |
| 463 | |
| 464 | } // namespace WebCore |