| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "config.h" |
| #include "platform/graphics/paint/PaintController.h" |
| |
| #include "platform/NotImplemented.h" |
| #include "platform/TraceEvent.h" |
| #include "platform/graphics/GraphicsLayer.h" |
| #include "platform/graphics/paint/DrawingDisplayItem.h" |
| |
| #ifndef NDEBUG |
| #include "platform/graphics/LoggingCanvas.h" |
| #include "wtf/text/StringBuilder.h" |
| #include <stdio.h> |
| #endif |
| |
| namespace blink { |
| |
| const PaintArtifact& PaintController::paintArtifact() const |
| { |
| ASSERT(m_newDisplayItemList.isEmpty()); |
| ASSERT(m_newPaintChunks.isInInitialState()); |
| return m_currentPaintArtifact; |
| } |
| |
| bool PaintController::lastDisplayItemIsNoopBegin() const |
| { |
| if (m_newDisplayItemList.isEmpty()) |
| return false; |
| |
| const auto& lastDisplayItem = m_newDisplayItemList.last(); |
| return lastDisplayItem.isBegin() && !lastDisplayItem.drawsContent(); |
| } |
| |
| void PaintController::removeLastDisplayItem() |
| { |
| if (m_newDisplayItemList.isEmpty()) |
| return; |
| |
| #if ENABLE(ASSERT) |
| // Also remove the index pointing to the removed display item. |
| DisplayItemIndicesByClientMap::iterator it = m_newDisplayItemIndicesByClient.find(&m_newDisplayItemList.last().client()); |
| if (it != m_newDisplayItemIndicesByClient.end()) { |
| Vector<size_t>& indices = it->value; |
| if (!indices.isEmpty() && indices.last() == (m_newDisplayItemList.size() - 1)) |
| indices.removeLast(); |
| } |
| #endif |
| m_newDisplayItemList.removeLast(); |
| |
| if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) |
| m_newPaintChunks.decrementDisplayItemIndex(); |
| } |
| |
| void PaintController::processNewItem(DisplayItem& displayItem) |
| { |
| ASSERT(!m_constructionDisabled); |
| ASSERT(!skippingCache() || !displayItem.isCached()); |
| |
| if (displayItem.isCached()) |
| ++m_numCachedItems; |
| |
| #if ENABLE(ASSERT) |
| // Verify noop begin/end pairs have been removed. |
| if (m_newDisplayItemList.size() >= 2 && displayItem.isEnd()) { |
| const auto& beginDisplayItem = m_newDisplayItemList[m_newDisplayItemList.size() - 2]; |
| if (beginDisplayItem.isBegin() && beginDisplayItem.type() != DisplayItem::Subsequence && !beginDisplayItem.drawsContent()) |
| ASSERT(!displayItem.isEndAndPairedWith(beginDisplayItem.type())); |
| } |
| #endif |
| |
| if (!m_scopeStack.isEmpty()) |
| displayItem.setScope(m_scopeStack.last()); |
| |
| #if ENABLE(ASSERT) |
| size_t index = findMatchingItemFromIndex(displayItem.nonCachedId(), m_newDisplayItemIndicesByClient, m_newDisplayItemList); |
| if (index != kNotFound) { |
| #ifndef NDEBUG |
| showDebugData(); |
| WTFLogAlways("DisplayItem %s has duplicated id with previous %s (index=%d)\n", |
| displayItem.asDebugString().utf8().data(), m_newDisplayItemList[index].asDebugString().utf8().data(), static_cast<int>(index)); |
| #endif |
| ASSERT_NOT_REACHED(); |
| } |
| addItemToIndexIfNeeded(displayItem, m_newDisplayItemList.size() - 1, m_newDisplayItemIndicesByClient); |
| #endif // ENABLE(ASSERT) |
| |
| if (skippingCache()) |
| displayItem.setSkippedCache(); |
| |
| if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) |
| m_newPaintChunks.incrementDisplayItemIndex(); |
| } |
| |
| void PaintController::updateCurrentPaintChunkProperties(const PaintChunkProperties& newProperties) |
| { |
| m_newPaintChunks.updateCurrentPaintChunkProperties(newProperties); |
| } |
| |
| const PaintChunkProperties& PaintController::currentPaintChunkProperties() const |
| { |
| return m_newPaintChunks.currentPaintChunkProperties(); |
| } |
| |
| void PaintController::beginScope() |
| { |
| ASSERT_WITH_SECURITY_IMPLICATION(m_nextScope < UINT_MAX); |
| m_scopeStack.append(m_nextScope++); |
| beginSkippingCache(); |
| } |
| |
| void PaintController::endScope() |
| { |
| m_scopeStack.removeLast(); |
| endSkippingCache(); |
| } |
| |
| void PaintController::invalidate(const DisplayItemClient& client, PaintInvalidationReason paintInvalidationReason, const IntRect* visualRect) |
| { |
| invalidateClient(client); |
| |
| if (visualRect) { |
| // TODO(wkorman): cache visualRect for the client. |
| } |
| } |
| |
| void PaintController::invalidateClient(const DisplayItemClient& client) |
| { |
| #if ENABLE(ASSERT) |
| // Slimming paint v1 CompositedLayerMapping may invalidate client on extra layers. |
| if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() || clientCacheIsValid(client)) |
| m_invalidations.append(client.debugName()); |
| #endif |
| |
| invalidateUntracked(client); |
| if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() && m_trackedPaintInvalidationObjects) |
| m_trackedPaintInvalidationObjects->append(client.debugName()); |
| } |
| |
| void PaintController::invalidateUntracked(const DisplayItemClient& client) |
| { |
| // This can be called during painting, but we can't invalidate already painted clients. |
| ASSERT(!m_newDisplayItemIndicesByClient.contains(&client)); |
| updateValidlyCachedClientsIfNeeded(); |
| m_validlyCachedClients.remove(&client); |
| } |
| |
| void PaintController::invalidateAll() |
| { |
| // Can only be called during layout/paintInvalidation, not during painting. |
| ASSERT(m_newDisplayItemList.isEmpty()); |
| m_currentPaintArtifact.reset(); |
| m_validlyCachedClients.clear(); |
| m_validlyCachedClientsDirty = false; |
| |
| if (RuntimeEnabledFeatures::slimmingPaintV2Enabled() && m_trackedPaintInvalidationObjects) |
| m_trackedPaintInvalidationObjects->append("##ALL##"); |
| } |
| |
| bool PaintController::clientCacheIsValid(const DisplayItemClient& client) const |
| { |
| if (skippingCache()) |
| return false; |
| updateValidlyCachedClientsIfNeeded(); |
| return m_validlyCachedClients.contains(&client); |
| } |
| |
| void PaintController::invalidatePaintOffset(const DisplayItemClient& client) |
| { |
| ASSERT(RuntimeEnabledFeatures::slimmingPaintOffsetCachingEnabled()); |
| invalidateClient(client); |
| |
| #if ENABLE(ASSERT) |
| ASSERT(!paintOffsetWasInvalidated(client)); |
| m_clientsWithPaintOffsetInvalidations.add(&client); |
| #endif |
| } |
| |
| #if ENABLE(ASSERT) |
| bool PaintController::paintOffsetWasInvalidated(const DisplayItemClient& client) const |
| { |
| ASSERT(RuntimeEnabledFeatures::slimmingPaintOffsetCachingEnabled()); |
| return m_clientsWithPaintOffsetInvalidations.contains(&client); |
| } |
| #endif |
| |
| size_t PaintController::findMatchingItemFromIndex(const DisplayItem::Id& id, const DisplayItemIndicesByClientMap& displayItemIndicesByClient, const DisplayItemList& list) |
| { |
| DisplayItemIndicesByClientMap::const_iterator it = displayItemIndicesByClient.find(&id.client); |
| if (it == displayItemIndicesByClient.end()) |
| return kNotFound; |
| |
| const Vector<size_t>& indices = it->value; |
| for (size_t index : indices) { |
| const DisplayItem& existingItem = list[index]; |
| ASSERT(!existingItem.isValid() || existingItem.client() == id.client); |
| if (existingItem.isValid() && id.matches(existingItem)) |
| return index; |
| } |
| |
| return kNotFound; |
| } |
| |
| void PaintController::addItemToIndexIfNeeded(const DisplayItem& displayItem, size_t index, DisplayItemIndicesByClientMap& displayItemIndicesByClient) |
| { |
| if (!displayItem.isCacheable()) |
| return; |
| |
| DisplayItemIndicesByClientMap::iterator it = displayItemIndicesByClient.find(&displayItem.client()); |
| Vector<size_t>& indices = it == displayItemIndicesByClient.end() ? |
| displayItemIndicesByClient.add(&displayItem.client(), Vector<size_t>()).storedValue->value : it->value; |
| indices.append(index); |
| } |
| |
| struct PaintController::OutOfOrderIndexContext { |
| STACK_ALLOCATED(); |
| OutOfOrderIndexContext(DisplayItemList::iterator begin) : nextItemToIndex(begin) { } |
| |
| DisplayItemList::iterator nextItemToIndex; |
| DisplayItemIndicesByClientMap displayItemIndicesByClient; |
| }; |
| |
| DisplayItemList::iterator PaintController::findOutOfOrderCachedItem(const DisplayItem::Id& id, OutOfOrderIndexContext& context) |
| { |
| ASSERT(clientCacheIsValid(id.client)); |
| |
| size_t foundIndex = findMatchingItemFromIndex(id, context.displayItemIndicesByClient, m_currentPaintArtifact.displayItemList()); |
| if (foundIndex != kNotFound) |
| return m_currentPaintArtifact.displayItemList().begin() + foundIndex; |
| |
| return findOutOfOrderCachedItemForward(id, context); |
| } |
| |
| // Find forward for the item and index all skipped indexable items. |
| DisplayItemList::iterator PaintController::findOutOfOrderCachedItemForward(const DisplayItem::Id& id, OutOfOrderIndexContext& context) |
| { |
| DisplayItemList::iterator currentEnd = m_currentPaintArtifact.displayItemList().end(); |
| for (; context.nextItemToIndex != currentEnd; ++context.nextItemToIndex) { |
| const DisplayItem& item = *context.nextItemToIndex; |
| ASSERT(item.isValid()); |
| if (item.isCacheable() && clientCacheIsValid(item.client())) { |
| if (id.matches(item)) |
| return context.nextItemToIndex++; |
| |
| addItemToIndexIfNeeded(item, context.nextItemToIndex - m_currentPaintArtifact.displayItemList().begin(), context.displayItemIndicesByClient); |
| } |
| } |
| return currentEnd; |
| } |
| |
| void PaintController::copyCachedSubsequence(DisplayItemList::iterator& currentIt, DisplayItemList& updatedList) |
| { |
| ASSERT(currentIt->type() == DisplayItem::Subsequence); |
| ASSERT(!currentIt->scope()); |
| DisplayItem::Id endSubsequenceId(currentIt->client(), DisplayItem::EndSubsequence, 0); |
| do { |
| // We should always find the EndSubsequence display item. |
| ASSERT(currentIt != m_currentPaintArtifact.displayItemList().end()); |
| ASSERT(currentIt->isValid()); |
| updatedList.appendByMoving(*currentIt); |
| ++currentIt; |
| } while (!endSubsequenceId.matches(updatedList.last())); |
| } |
| |
| // Update the existing display items by removing invalidated entries, updating |
| // repainted ones, and appending new items. |
| // - For cached drawing display item, copy the corresponding cached DrawingDisplayItem; |
| // - For cached subsequence display item, copy the cached display items between the |
| // corresponding SubsequenceDisplayItem and EndSubsequenceDisplayItem (incl.); |
| // - Otherwise, copy the new display item. |
| // |
| // The algorithm is O(|m_currentDisplayItemList| + |m_newDisplayItemList|). |
| // Coefficients are related to the ratio of out-of-order CachedDisplayItems |
| // and the average number of (Drawing|Subsequence)DisplayItems per client. |
| // |
| void PaintController::commitNewDisplayItems() |
| { |
| TRACE_EVENT2("blink,benchmark", "PaintController::commitNewDisplayItems", |
| "current_display_list_size", (int)m_currentPaintArtifact.displayItemList().size(), |
| "num_non_cached_new_items", (int)m_newDisplayItemList.size() - m_numCachedItems); |
| |
| if (RuntimeEnabledFeatures::slimmingPaintSynchronizedPaintingEnabled() |
| && !m_newDisplayItemList.isEmpty() |
| && m_newDisplayItemList.last().type() == DisplayItem::CachedDisplayItemList) { |
| // The whole display item list is cached. |
| ASSERT(m_newDisplayItemList.size() == 1); |
| ASSERT(m_invalidations.isEmpty()); |
| ASSERT(m_clientsCheckedPaintInvalidation.isEmpty()); |
| m_newDisplayItemList.clear(); |
| m_newPaintChunks.clear(); |
| return; |
| } |
| if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) |
| m_clientsCheckedPaintInvalidation.clear(); |
| |
| // These data structures are used during painting only. |
| ASSERT(m_scopeStack.isEmpty()); |
| m_scopeStack.clear(); |
| m_nextScope = 1; |
| ASSERT(!skippingCache()); |
| #if ENABLE(ASSERT) |
| m_newDisplayItemIndicesByClient.clear(); |
| m_clientsWithPaintOffsetInvalidations.clear(); |
| m_invalidations.clear(); |
| #endif |
| |
| if (m_currentPaintArtifact.isEmpty()) { |
| #if ENABLE(ASSERT) |
| for (const auto& item : m_newDisplayItemList) |
| ASSERT(!item.isCached()); |
| #endif |
| m_currentPaintArtifact.displayItemList().swap(m_newDisplayItemList); |
| m_currentPaintArtifact.paintChunks() = m_newPaintChunks.releasePaintChunks(); |
| m_validlyCachedClientsDirty = true; |
| m_numCachedItems = 0; |
| return; |
| } |
| |
| updateValidlyCachedClientsIfNeeded(); |
| |
| // Stores indices to valid DrawingDisplayItems in m_currentDisplayItems that have not been matched |
| // by CachedDisplayItems during synchronized matching. The indexed items will be matched |
| // by later out-of-order CachedDisplayItems in m_newDisplayItemList. This ensures that when |
| // out-of-order CachedDisplayItems occur, we only traverse at most once over m_currentDisplayItems |
| // looking for potential matches. Thus we can ensure that the algorithm runs in linear time. |
| OutOfOrderIndexContext outOfOrderIndexContext(m_currentPaintArtifact.displayItemList().begin()); |
| |
| // TODO(jbroman): Consider revisiting this heuristic. |
| DisplayItemList updatedList(std::max(m_currentPaintArtifact.displayItemList().usedCapacityInBytes(), m_newDisplayItemList.usedCapacityInBytes())); |
| Vector<PaintChunk> updatedPaintChunks; |
| DisplayItemList::iterator currentIt = m_currentPaintArtifact.displayItemList().begin(); |
| DisplayItemList::iterator currentEnd = m_currentPaintArtifact.displayItemList().end(); |
| for (DisplayItemList::iterator newIt = m_newDisplayItemList.begin(); newIt != m_newDisplayItemList.end(); ++newIt) { |
| const DisplayItem& newDisplayItem = *newIt; |
| const DisplayItem::Id newDisplayItemId = newDisplayItem.nonCachedId(); |
| bool newDisplayItemHasCachedType = newDisplayItem.type() != newDisplayItemId.type; |
| |
| bool isSynchronized = currentIt != currentEnd && newDisplayItemId.matches(*currentIt); |
| |
| if (newDisplayItemHasCachedType) { |
| ASSERT(newDisplayItem.isCached()); |
| ASSERT(clientCacheIsValid(newDisplayItem.client()) || (RuntimeEnabledFeatures::slimmingPaintOffsetCachingEnabled() && !paintOffsetWasInvalidated(newDisplayItem.client()))); |
| if (!isSynchronized) { |
| currentIt = findOutOfOrderCachedItem(newDisplayItemId, outOfOrderIndexContext); |
| |
| if (currentIt == currentEnd) { |
| #ifndef NDEBUG |
| showDebugData(); |
| WTFLogAlways("%s not found in m_currentDisplayItemList\n", newDisplayItem.asDebugString().utf8().data()); |
| #endif |
| ASSERT_NOT_REACHED(); |
| // We did not find the cached display item. This should be impossible, but may occur if there is a bug |
| // in the system, such as under-invalidation, incorrect cache checking or duplicate display ids. |
| // In this case, attempt to recover rather than crashing or bailing on display of the rest of the display list. |
| continue; |
| } |
| } |
| #if ENABLE(ASSERT) |
| if (RuntimeEnabledFeatures::slimmingPaintUnderInvalidationCheckingEnabled()) { |
| DisplayItemList::iterator temp = currentIt; |
| checkUnderInvalidation(newIt, temp); |
| } |
| #endif |
| if (newDisplayItem.isCachedDrawing()) { |
| updatedList.appendByMoving(*currentIt); |
| ++currentIt; |
| } else { |
| ASSERT(newDisplayItem.type() == DisplayItem::CachedSubsequence); |
| copyCachedSubsequence(currentIt, updatedList); |
| ASSERT(updatedList.last().type() == DisplayItem::EndSubsequence); |
| } |
| } else { |
| ASSERT(!newDisplayItem.isDrawing() |
| || newDisplayItem.skippedCache() |
| || !clientCacheIsValid(newDisplayItem.client()) |
| || (RuntimeEnabledFeatures::slimmingPaintOffsetCachingEnabled() && paintOffsetWasInvalidated(newDisplayItem.client()))); |
| |
| updatedList.appendByMoving(*newIt); |
| |
| if (isSynchronized) |
| ++currentIt; |
| } |
| // Items before currentIt should have been copied so we don't need to index them. |
| if (currentIt - outOfOrderIndexContext.nextItemToIndex > 0) |
| outOfOrderIndexContext.nextItemToIndex = currentIt; |
| } |
| |
| #if ENABLE(ASSERT) |
| if (RuntimeEnabledFeatures::slimmingPaintUnderInvalidationCheckingEnabled()) |
| checkNoRemainingCachedDisplayItems(); |
| #endif // ENABLE(ASSERT) |
| |
| // TODO(jbroman): When subsequence caching applies to SPv2, we'll need to |
| // merge the paint chunks as well. |
| m_currentPaintArtifact.displayItemList().swap(updatedList); |
| m_currentPaintArtifact.paintChunks() = m_newPaintChunks.releasePaintChunks(); |
| |
| m_newDisplayItemList.clear(); |
| m_validlyCachedClientsDirty = true; |
| m_numCachedItems = 0; |
| } |
| |
| size_t PaintController::approximateUnsharedMemoryUsage() const |
| { |
| size_t memoryUsage = sizeof(*this); |
| |
| // Memory outside this class due to m_currentPaintArtifact. |
| memoryUsage += m_currentPaintArtifact.approximateUnsharedMemoryUsage() - sizeof(m_currentPaintArtifact); |
| |
| // TODO(jbroman): If display items begin to have significant external memory |
| // usage that's not shared with the embedder, we should account for it here. |
| // |
| // External objects, shared with the embedder, such as SkPicture, should be |
| // excluded to avoid double counting. It is the embedder's responsibility to |
| // count such objects. |
| // |
| // At time of writing, the only known case of unshared external memory was |
| // the rounded clips vector in ClipDisplayItem, which is not expected to |
| // contribute significantly to memory usage. |
| |
| // Memory outside this class due to m_newDisplayItemList. |
| ASSERT(m_newDisplayItemList.isEmpty()); |
| memoryUsage += m_newDisplayItemList.memoryUsageInBytes(); |
| |
| return memoryUsage; |
| } |
| |
| void PaintController::updateValidlyCachedClientsIfNeeded() const |
| { |
| if (!m_validlyCachedClientsDirty) |
| return; |
| |
| m_validlyCachedClients.clear(); |
| m_validlyCachedClientsDirty = false; |
| |
| const DisplayItemClient* lastAddedClient = nullptr; |
| for (const DisplayItem& displayItem : m_currentPaintArtifact.displayItemList()) { |
| if (&displayItem.client() == lastAddedClient) |
| continue; |
| if (displayItem.isCacheable()) { |
| lastAddedClient = &displayItem.client(); |
| m_validlyCachedClients.add(lastAddedClient); |
| } |
| } |
| } |
| |
| #if ENABLE(ASSERT) |
| |
| void PaintController::checkUnderInvalidation(DisplayItemList::iterator& newIt, DisplayItemList::iterator& currentIt) |
| { |
| ASSERT(RuntimeEnabledFeatures::slimmingPaintUnderInvalidationCheckingEnabled()); |
| ASSERT(newIt->isCached()); |
| |
| // When under-invalidation-checking is enabled, the forced painting is following the cached display item. |
| DisplayItem::Type nextItemType = DisplayItem::nonCachedType(newIt->type()); |
| ++newIt; |
| ASSERT(newIt->type() == nextItemType); |
| |
| if (newIt->isDrawing()) { |
| checkCachedDisplayItemIsUnchanged("", *newIt, *currentIt); |
| return; |
| } |
| |
| ASSERT(newIt->type() == DisplayItem::Subsequence); |
| |
| #ifndef NDEBUG |
| CString messagePrefix = String::format("(In CachedSubsequence of %s)", newIt->clientDebugString().utf8().data()).utf8(); |
| #else |
| CString messagePrefix = "(In CachedSubsequence)"; |
| #endif |
| |
| DisplayItem::Id endSubsequenceId(newIt->client(), DisplayItem::EndSubsequence, 0); |
| while (true) { |
| ASSERT(newIt != m_newDisplayItemList.end()); |
| if (newIt->isCached()) |
| checkUnderInvalidation(newIt, currentIt); |
| else |
| checkCachedDisplayItemIsUnchanged(messagePrefix.data(), *newIt, *currentIt); |
| |
| if (endSubsequenceId.matches(*newIt)) |
| break; |
| |
| ++newIt; |
| ++currentIt; |
| } |
| } |
| |
| static void showUnderInvalidationError(const char* messagePrefix, const char* reason, const DisplayItem* newItem, const DisplayItem* oldItem) |
| { |
| #ifndef NDEBUG |
| WTFLogAlways("%s %s:\nNew display item: %s\nOld display item: %s\nSee http://crbug.com/450725.", messagePrefix, reason, |
| newItem ? newItem->asDebugString().utf8().data() : "None", |
| oldItem ? oldItem->asDebugString().utf8().data() : "None"); |
| #else |
| WTFLogAlways("%s %s. Run debug build to get more details\nSee http://crbug.com/450725.", messagePrefix, reason); |
| #endif // NDEBUG |
| } |
| |
| void PaintController::checkCachedDisplayItemIsUnchanged(const char* messagePrefix, const DisplayItem& newItem, const DisplayItem& oldItem) |
| { |
| ASSERT(RuntimeEnabledFeatures::slimmingPaintUnderInvalidationCheckingEnabled()); |
| ASSERT(!newItem.isCached()); |
| ASSERT(!oldItem.isCached()); |
| |
| if (newItem.skippedCache()) { |
| showUnderInvalidationError(messagePrefix, "ERROR: under-invalidation: skipped-cache in cached subsequence", &newItem, &oldItem); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| if (newItem.isCacheable() && !m_validlyCachedClients.contains(&newItem.client())) { |
| showUnderInvalidationError(messagePrefix, "ERROR: under-invalidation: invalidated in cached subsequence", &newItem, &oldItem); |
| ASSERT_NOT_REACHED(); |
| } |
| |
| if (newItem.equals(oldItem)) |
| return; |
| |
| showUnderInvalidationError(messagePrefix, "ERROR: under-invalidation: display item changed", &newItem, &oldItem); |
| |
| #ifndef NDEBUG |
| if (newItem.isDrawing()) { |
| RefPtr<const SkPicture> newPicture = static_cast<const DrawingDisplayItem&>(newItem).picture(); |
| RefPtr<const SkPicture> oldPicture = static_cast<const DrawingDisplayItem&>(oldItem).picture(); |
| String oldPictureDebugString = oldPicture ? pictureAsDebugString(oldPicture.get()) : "None"; |
| String newPictureDebugString = newPicture ? pictureAsDebugString(newPicture.get()) : "None"; |
| WTFLogAlways("old picture:\n%s\n", oldPictureDebugString.utf8().data()); |
| WTFLogAlways("new picture:\n%s\n", newPictureDebugString.utf8().data()); |
| } |
| #endif // NDEBUG |
| |
| ASSERT_NOT_REACHED(); |
| } |
| |
| void PaintController::checkNoRemainingCachedDisplayItems() |
| { |
| ASSERT(RuntimeEnabledFeatures::slimmingPaintUnderInvalidationCheckingEnabled()); |
| |
| for (const auto& displayItem : m_currentPaintArtifact.displayItemList()) { |
| if (!displayItem.isValid() || !displayItem.isCacheable() || !clientCacheIsValid(displayItem.client())) |
| continue; |
| showUnderInvalidationError("", "May be under-invalidation: no new display item", nullptr, &displayItem); |
| } |
| } |
| |
| #endif // ENABLE(ASSERT) |
| |
| #ifndef NDEBUG |
| |
| WTF::String PaintController::displayItemListAsDebugString(const DisplayItemList& list) const |
| { |
| StringBuilder stringBuilder; |
| size_t i = 0; |
| for (auto it = list.begin(); it != list.end(); ++it, ++i) { |
| const DisplayItem& displayItem = *it; |
| if (i) |
| stringBuilder.append(",\n"); |
| stringBuilder.append(String::format("{index: %d, ", (int)i)); |
| displayItem.dumpPropertiesAsDebugString(stringBuilder); |
| if (displayItem.isValid()) { |
| stringBuilder.append(", cacheIsValid: "); |
| stringBuilder.append(clientCacheIsValid(displayItem.client()) ? "true" : "false"); |
| } |
| stringBuilder.append('}'); |
| } |
| return stringBuilder.toString(); |
| } |
| |
| void PaintController::showDebugData() const |
| { |
| WTFLogAlways("current display item list: [%s]\n", displayItemListAsDebugString(m_currentPaintArtifact.displayItemList()).utf8().data()); |
| WTFLogAlways("new display item list: [%s]\n", displayItemListAsDebugString(m_newDisplayItemList).utf8().data()); |
| } |
| |
| #endif // ifndef NDEBUG |
| |
| } // namespace blink |