Diff
Modified: trunk/Source/WebCore/ChangeLog (277522 => 277523)
--- trunk/Source/WebCore/ChangeLog 2021-05-15 00:26:58 UTC (rev 277522)
+++ trunk/Source/WebCore/ChangeLog 2021-05-15 00:29:19 UTC (rev 277523)
@@ -1,3 +1,53 @@
+2021-05-14 Devin Rousso <[email protected]>
+
+ Sampled Page Top Color: move logic out of `Document`
+ https://bugs.webkit.org/show_bug.cgi?id=225480
+ <rdar://problem/77984539>
+
+ Reviewed by Tim Horton.
+
+ Having `determineSampledPageTopColor` be called in `enqueuePaintTimingEntryIfNeeded` as what
+ is basically a side effect is not great. The only reason it was there in the first place was
+ to take advantage of the logic that decided "is this the first contentful paint", but that
+ logic can be replicated elsewhere. Since the sampled page top color deals more with the page
+ as a whole instead of an individual `Document` (which includes subframes), it makes more
+ sense to have this logic be on a utility `PageColorSampler` that's used in `Page` instead.
+
+ Test: SampledPageTopColor.MainDocumentChange
+
+ * page/PageColorSampler.h: Added.
+ * page/PageColorSampler.cpp: Added.
+ (WebCore::isValidSampleLocation):
+ (WebCore::sampleColor):
+ (WebCore::colorDifference):
+ (WebCore::averageColor):
+ (WebCore::PageColorSampler::sampleTop):
+ * page/Page.h:
+ * page/Page.cpp:
+ (WebCore::Page::doAfterUpdateRendering):
+ (WebCore::Page::sampledPageTopColor const):
+ (WebCore::Page::didChangeMainDocument):
+
+ * dom/Document.h:
+ (WebCore::Document::sampledPageTopColor const): Deleted.
+ * dom/Document.cpp:
+ (WebCore::Document::enqueuePaintTimingEntryIfNeeded):
+ (WebCore::isValidPageSampleLocation): Deleted.
+ (WebCore::samplePageColor): Deleted.
+ (WebCore::colorDifference): Deleted.
+ (WebCore::averageColor): Deleted.
+ (WebCore::Document::determineSampledPageTopColor): Deleted.
+
+ * page/FrameView.h:
+ * page/FrameView.cpp:
+ (WebCore::FrameView::hasContentfulDescendants const): Added.
+ (WebCore::FrameView::hasContenfulDescendants const): Deleted.
+ Drive-by: Fix incorrect spelling.
+
+ * Source/WebCore/Headers.cmake:
+ * Source/WebCore/Sources.txt:
+ * Source/WebCore/WebCore.xcodeproj/project.pbxproj:
+
2021-05-14 Eric Carlson <[email protected]>
[GPUP] a media element with a data url and "crossorigin='anonymous'" doesn't load
Modified: trunk/Source/WebCore/Headers.cmake (277522 => 277523)
--- trunk/Source/WebCore/Headers.cmake 2021-05-15 00:26:58 UTC (rev 277522)
+++ trunk/Source/WebCore/Headers.cmake 2021-05-15 00:29:19 UTC (rev 277523)
@@ -906,6 +906,7 @@
page/NavigatorIsLoggedIn.h
page/PDFImageCachingPolicy.h
page/Page.h
+ page/PageColorSampler.h
page/PageConfiguration.h
page/PageConsoleClient.h
page/PageGroup.h
Modified: trunk/Source/WebCore/Sources.txt (277522 => 277523)
--- trunk/Source/WebCore/Sources.txt 2021-05-15 00:26:58 UTC (rev 277522)
+++ trunk/Source/WebCore/Sources.txt 2021-05-15 00:29:19 UTC (rev 277523)
@@ -1720,6 +1720,7 @@
page/NavigatorIsLoggedIn.cpp
page/OriginAccessEntry.cpp
page/Page.cpp
+page/PageColorSampler.cpp
page/PageConfiguration.cpp
page/PageConsoleClient.cpp
page/PageDebuggable.cpp
Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (277522 => 277523)
--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj 2021-05-15 00:26:58 UTC (rev 277522)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj 2021-05-15 00:29:19 UTC (rev 277523)
@@ -2987,6 +2987,7 @@
95A1E2E925CC75A900268C8E /* MediaControlsContextMenuItem.h in Headers */ = {isa = PBXBuildFile; fileRef = 95A1E2E725CC75A800268C8E /* MediaControlsContextMenuItem.h */; settings = {ATTRIBUTES = (Private, ); }; };
95AA571025EEED32004E9283 /* InspectorCanvasCallTracer.h in Headers */ = {isa = PBXBuildFile; fileRef = 95AA570E25EEED31004E9283 /* InspectorCanvasCallTracer.h */; };
95DF9D25252BEDB2000D7F46 /* PointerCharacteristics.h in Headers */ = {isa = PBXBuildFile; fileRef = 95DF9D23252BED99000D7F46 /* PointerCharacteristics.h */; settings = {ATTRIBUTES = (Private, ); }; };
+ 95F45D27264DFBF100D0B8B8 /* PageColorSampler.h in Headers */ = {isa = PBXBuildFile; fileRef = 95F45D26264DFBEB00D0B8B8 /* PageColorSampler.h */; };
9705997A107D975200A50A7C /* PolicyChecker.h in Headers */ = {isa = PBXBuildFile; fileRef = 97059976107D975200A50A7C /* PolicyChecker.h */; settings = {ATTRIBUTES = (Private, ); }; };
970B728A144FFAC600F00A37 /* EventInterfaces.h in Headers */ = {isa = PBXBuildFile; fileRef = 970B7289144FFAC600F00A37 /* EventInterfaces.h */; settings = {ATTRIBUTES = (Private, ); }; };
970B72A6145008EB00F00A37 /* EventHeaders.h in Headers */ = {isa = PBXBuildFile; fileRef = 970B72A5145008EB00F00A37 /* EventHeaders.h */; };
@@ -11937,6 +11938,8 @@
95AA570D25EEED30004E9283 /* InspectorCanvasCallTracer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorCanvasCallTracer.cpp; sourceTree = "<group>"; };
95AA570E25EEED31004E9283 /* InspectorCanvasCallTracer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorCanvasCallTracer.h; sourceTree = "<group>"; };
95DF9D23252BED99000D7F46 /* PointerCharacteristics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PointerCharacteristics.h; sourceTree = "<group>"; };
+ 95F45D24264DFBEB00D0B8B8 /* PageColorSampler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PageColorSampler.cpp; sourceTree = "<group>"; };
+ 95F45D26264DFBEB00D0B8B8 /* PageColorSampler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PageColorSampler.h; sourceTree = "<group>"; };
97059975107D975200A50A7C /* PolicyChecker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PolicyChecker.cpp; sourceTree = "<group>"; };
97059976107D975200A50A7C /* PolicyChecker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PolicyChecker.h; sourceTree = "<group>"; };
970B7289144FFAC600F00A37 /* EventInterfaces.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EventInterfaces.h; path = DerivedSources/WebCore/EventInterfaces.h; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -22402,6 +22405,8 @@
00146289103CD1DE000B20DB /* OriginAccessEntry.h */,
65FEA86809833ADE00BED4AB /* Page.cpp */,
65A21467097A329100B9050A /* Page.h */,
+ 95F45D24264DFBEB00D0B8B8 /* PageColorSampler.cpp */,
+ 95F45D26264DFBEB00D0B8B8 /* PageColorSampler.h */,
CD5E5B601A15F156000C609E /* PageConfiguration.cpp */,
CD5E5B5E1A15CE54000C609E /* PageConfiguration.h */,
DAACB3D916F2416400666135 /* PageConsoleClient.cpp */,
@@ -34356,6 +34361,7 @@
8326BF8E24D35C33001F8A85 /* OverSampleType.h in Headers */,
65A21468097A329100B9050A /* Page.h in Headers */,
91278D5E21DEDAD600B57184 /* PageAuditAgent.h in Headers */,
+ 95F45D27264DFBF100D0B8B8 /* PageColorSampler.h in Headers */,
CD5E5B5F1A15CE54000C609E /* PageConfiguration.h in Headers */,
A5B81CC21FAA44BC0037D1E6 /* PageConsoleAgent.h in Headers */,
DAED203116F244480070EC0F /* PageConsoleClient.h in Headers */,
Modified: trunk/Source/WebCore/dom/Document.cpp (277522 => 277523)
--- trunk/Source/WebCore/dom/Document.cpp 2021-05-15 00:26:58 UTC (rev 277522)
+++ trunk/Source/WebCore/dom/Document.cpp 2021-05-15 00:29:19 UTC (rev 277523)
@@ -82,7 +82,6 @@
#include "Frame.h"
#include "FrameLoader.h"
#include "FrameLoaderClient.h"
-#include "FrameSnapshotting.h"
#include "FrameView.h"
#include "FullscreenManager.h"
#include "GCReachableRef.h"
@@ -125,7 +124,6 @@
#include "IDBOpenDBRequest.h"
#include "IdleCallbackController.h"
#include "ImageBitmapRenderingContext.h"
-#include "ImageBuffer.h"
#include "ImageLoader.h"
#include "ImageOverlayController.h"
#include "InspectorInstrumentation.h"
@@ -178,11 +176,9 @@
#include "Range.h"
#include "RealtimeMediaSourceCenter.h"
#include "RenderChildIterator.h"
-#include "RenderImage.h"
#include "RenderInline.h"
#include "RenderLayerCompositor.h"
#include "RenderLineBreak.h"
-#include "RenderStyle.h"
#include "RenderTreeUpdater.h"
#include "RenderView.h"
#include "RenderWidget.h"
@@ -3273,7 +3269,7 @@
if (!view()->isVisuallyNonEmpty() || view()->needsLayout())
return;
- if (!view()->hasContenfulDescendants())
+ if (!view()->hasContentfulDescendants())
return;
if (!ContentfulPaintChecker::qualifiesForContentfulPaint(*view()))
@@ -3281,8 +3277,6 @@
domWindow()->performance().reportFirstContentfulPaint();
m_didEnqueueFirstContentfulPaint = true;
-
- determineSampledPageTopColor();
}
ExceptionOr<void> Document::write(Document* responsibleDocument, SegmentedString&& text)
@@ -3903,197 +3897,6 @@
page->chrome().client().themeColorChanged();
}
-static bool isValidPageSampleLocation(Document& document, const IntPoint& location)
-{
- // FIXME: <https://webkit.org/b/225167> (Sampled Page Top Color: hook into painting logic instead of taking snapshots)
-
- constexpr OptionSet<HitTestRequest::Type> hitTestRequestTypes { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::IgnoreCSSPointerEventsProperty, HitTestRequest::Type::DisallowUserAgentShadowContent, HitTestRequest::Type::CollectMultipleElements, HitTestRequest::Type::IncludeAllElementsUnderPoint };
- HitTestResult hitTestResult(location);
- document.hitTest(hitTestRequestTypes, hitTestResult);
-
- for (auto& hitTestNode : hitTestResult.listBasedTestResult()) {
- auto& node = hitTestNode.get();
-
- auto* renderer = node.renderer();
- if (!renderer)
- return false;
-
- // Skip images (both `<img>` and CSS `background-image`) as they're likely not a solid color.
- if (is<RenderImage>(renderer) || renderer->style().hasBackgroundImage())
- return false;
-
- // Skip nodes with animations as the sample may get an odd color if the animation is in-progress.
- if (renderer->style().hasTransitions() || renderer->style().hasAnimations())
- return false;
-
- // Skip `<canvas>` but only if they've been drawn into. Guess this by seeing if there's already
- // a `CanvasRenderingContext`, which is only created by _javascript_.
- if (is<HTMLCanvasElement>(node) && downcast<HTMLCanvasElement>(node).renderingContext())
- return false;
-
- // Skip 3rd-party `<iframe>` as the content likely won't match the rest of the page.
- if (is<HTMLIFrameElement>(node) && !areRegistrableDomainsEqual(downcast<HTMLIFrameElement>(node).location(), document.url()))
- return false;
- }
-
- return true;
-}
-
-static Optional<Lab<float>> samplePageColor(Document& document, IntPoint&& location)
-{
- // FIXME: <https://webkit.org/b/225167> (Sampled Page Top Color: hook into painting logic instead of taking snapshots)
-
- if (!isValidPageSampleLocation(document, location))
- return WTF::nullopt;
-
- ASSERT(document.view());
- auto snapshot = snapshotFrameRect(document.view()->frame(), IntRect(location, IntSize(1, 1)), SnapshotOptionsExcludeSelectionHighlighting | SnapshotOptionsPaintEverythingExcludingSelection);
- if (!snapshot)
- return WTF::nullopt;
-
- auto snapshotData = snapshot->toBGRAData();
- return convertColor<Lab<float>>(SRGBA<uint8_t> { snapshotData[2], snapshotData[1], snapshotData[0], snapshotData[3] });
-}
-
-static double colorDifference(Lab<float>& lhs, Lab<float>& rhs)
-{
- return sqrt(pow(rhs.lightness - lhs.lightness, 2) + pow(rhs.a - lhs.a, 2) + pow(rhs.b - lhs.b, 2));
-}
-
-static Lab<float> averageColor(Lab<float> colors[], size_t count)
-{
- float totalLightness = 0;
- float totalA = 0;
- float totalB = 0;
- for (size_t i = 0; i < count; ++i) {
- totalLightness += colors[i].lightness;
- totalA += colors[i].a;
- totalB += colors[i].b;
- }
- return {
- totalLightness / count,
- totalA / count,
- totalB / count,
- 1,
- };
-}
-
-void Document::determineSampledPageTopColor()
-{
- if (m_sampledPageTopColor.isValid())
- return;
-
- auto maxDifference = settings().sampledPageTopColorMaxDifference();
- if (maxDifference <= 0)
- return;
-
- if (!isTopDocument())
- return;
-
- auto* frameView = view();
- if (!frameView)
- return;
-
- auto notifyDidSamplePageTopColorOnScopeExit = makeScopeExit([&] {
- if (auto* page = this->page())
- page->chrome().client().sampledPageTopColorChanged();
- });
-
- // Decrease the width by one pixel so that the last snapshot is within bounds and not off-by-one.
- auto frameWidth = frameView->contentsWidth() - 1;
-
- constexpr auto numSnapshots = 5;
- size_t nonMatchingColorIndex = numSnapshots;
-
- Lab<float> snapshots[numSnapshots];
- double differences[numSnapshots - 1];
-
- auto shouldStopAfterFindingNonMatchingColor = [&] (size_t i) -> bool {
- // Bail if the non-matching color is not the first or last snapshot, or there already is an non-matching color.
- if ((i && i < numSnapshots - 1) || nonMatchingColorIndex != numSnapshots)
- return true;
-
- nonMatchingColorIndex = i;
- return false;
- };
-
- for (size_t i = 0; i < numSnapshots; ++i) {
- auto snapshot = samplePageColor(*this, IntPoint(frameWidth * i / (numSnapshots - 1), 0));
- if (!snapshot) {
- if (shouldStopAfterFindingNonMatchingColor(i))
- return;
- continue;
- }
-
- snapshots[i] = *snapshot;
-
- if (i) {
- // Each `difference` item compares `i` with `i - 1` so if the first comparison (`i == 1`)
- // is too large of a difference, we should treat `i - 1` (i.e. `0`) as the problem since
- // we only allow for non-matching colors being the first or last sampled color.
- auto effectiveNonMatchingColorIndex = i == 1 ? 0 : i;
-
- differences[i - 1] = colorDifference(snapshots[i - 1], snapshots[i]);
- if (differences[i - 1] > maxDifference) {
- if (shouldStopAfterFindingNonMatchingColor(effectiveNonMatchingColorIndex))
- return;
- continue;
- }
-
- double cumuluativeDifference = 0;
- for (size_t j = 0; j < i; ++j) {
- if (j == nonMatchingColorIndex)
- continue;
- cumuluativeDifference += differences[j];
- }
- if (cumuluativeDifference > maxDifference) {
- if (shouldStopAfterFindingNonMatchingColor(effectiveNonMatchingColorIndex)) {
- // If we haven't already identified a non-matching snapshot and the difference between the first
- // and second snapshots or the second-to-last and last snapshots is less than the maximum, mark
- // the first/last snapshot as non-matching to give a chance for the rest of the snapshots to match.
- if (nonMatchingColorIndex == numSnapshots && (!i || i == numSnapshots - 1) && cumuluativeDifference - differences[i - 1] <= maxDifference) {
- nonMatchingColorIndex = effectiveNonMatchingColorIndex;
- continue;
- }
- return;
- }
- continue;
- }
- }
- }
-
- // Decrease the height by one pixel so that the last snapshot is within bounds and not off-by-one.
- auto minHeight = settings().sampledPageTopColorMinHeight() - 1;
- if (minHeight > 0) {
- if (nonMatchingColorIndex) {
- if (auto leftMiddleSnapshot = samplePageColor(*this, IntPoint(0, minHeight))) {
- if (colorDifference(*leftMiddleSnapshot, snapshots[0]) > maxDifference)
- return;
- }
- }
-
- if (nonMatchingColorIndex != numSnapshots - 1) {
- if (auto rightMiddleSnapshot = samplePageColor(*this, IntPoint(frameWidth, minHeight))) {
- if (colorDifference(*rightMiddleSnapshot, snapshots[numSnapshots - 1]) > maxDifference)
- return;
- }
- }
- }
-
- auto snapshotsToAverage = snapshots;
- auto validSnapshotCount = numSnapshots;
- if (!nonMatchingColorIndex) {
- // Skip the first snapshot by moving the pointer that indicates where the snapshot array
- // starts and decreasing the count of snapshots to average.
- ++snapshotsToAverage;
- --validSnapshotCount;
- } else if (nonMatchingColorIndex == numSnapshots - 1) {
- // Skip the last snapshot by decreasing the count of snapshots to average.
- --validSnapshotCount;
- }
- m_sampledPageTopColor = averageColor(snapshotsToAverage, validSnapshotCount);
-}
-
#if ENABLE(DARK_MODE_CSS)
static void processColorSchemeString(StringView colorScheme, const WTF::Function<void(StringView key)>& callback)
{
Modified: trunk/Source/WebCore/dom/Document.h (277522 => 277523)
--- trunk/Source/WebCore/dom/Document.h 2021-05-15 00:26:58 UTC (rev 277522)
+++ trunk/Source/WebCore/dom/Document.h 2021-05-15 00:29:19 UTC (rev 277523)
@@ -744,8 +744,6 @@
const Color& themeColor();
- const Color& sampledPageTopColor() const { return m_sampledPageTopColor; }
-
void setTextColor(const Color& color) { m_textColor = color; }
const Color& textColor() const { return m_textColor; }
@@ -1677,8 +1675,6 @@
WeakPtr<HTMLMetaElement> determineActiveThemeColorMetaElement();
void themeColorChanged();
- void determineSampledPageTopColor();
-
void invalidateAccessKeyCacheSlowCase();
void buildAccessKeyCache();
@@ -1802,8 +1798,6 @@
WeakPtr<HTMLMetaElement> m_activeThemeColorMetaElement;
Color m_applicationManifestThemeColor;
- Color m_sampledPageTopColor;
-
Color m_textColor { Color::black };
Color m_linkColor;
Color m_visitedLinkColor;
Modified: trunk/Source/WebCore/page/FrameView.cpp (277522 => 277523)
--- trunk/Source/WebCore/page/FrameView.cpp 2021-05-15 00:26:58 UTC (rev 277522)
+++ trunk/Source/WebCore/page/FrameView.cpp 2021-05-15 00:29:19 UTC (rev 277523)
@@ -4617,7 +4617,7 @@
frame().loader().didReachVisuallyNonEmptyState();
}
-bool FrameView::hasContenfulDescendants() const
+bool FrameView::hasContentfulDescendants() const
{
return m_visuallyNonEmptyCharacterCount || m_visuallyNonEmptyPixelCount;
}
Modified: trunk/Source/WebCore/page/FrameView.h (277522 => 277523)
--- trunk/Source/WebCore/page/FrameView.h 2021-05-15 00:26:58 UTC (rev 277522)
+++ trunk/Source/WebCore/page/FrameView.h 2021-05-15 00:29:19 UTC (rev 277523)
@@ -413,7 +413,7 @@
void incrementVisuallyNonEmptyCharacterCount(const String&);
void incrementVisuallyNonEmptyPixelCount(const IntSize&);
bool isVisuallyNonEmpty() const { return m_contentQualifiesAsVisuallyNonEmpty; }
- bool hasContenfulDescendants() const;
+ bool hasContentfulDescendants() const;
void checkAndDispatchDidReachVisuallyNonEmptyState();
WEBCORE_EXPORT void enableFixedWidthAutoSizeMode(bool enable, const IntSize& minSize);
Modified: trunk/Source/WebCore/page/Page.cpp (277522 => 277523)
--- trunk/Source/WebCore/page/Page.cpp 2021-05-15 00:26:58 UTC (rev 277522)
+++ trunk/Source/WebCore/page/Page.cpp 2021-05-15 00:29:19 UTC (rev 277523)
@@ -87,6 +87,7 @@
#include "MediaCanStartListener.h"
#include "MediaRecorderProvider.h"
#include "Navigator.h"
+#include "PageColorSampler.h"
#include "PageConfiguration.h"
#include "PageConsoleClient.h"
#include "PageDebuggable.h"
@@ -1672,6 +1673,12 @@
ASSERT(!frameView || !frameView->needsLayout());
}
#endif
+
+ if (!m_sampledPageTopColor) {
+ m_sampledPageTopColor = PageColorSampler::sampleTop(*this);
+ if (m_sampledPageTopColor)
+ chrome().client().sampledPageTopColorChanged();
+ }
}
void Page::finalizeRenderingUpdate(OptionSet<FinalizeRenderingUpdateFlags> flags)
@@ -2556,11 +2563,7 @@
Color Page::sampledPageTopColor() const
{
- auto* document = mainFrame().document();
- if (!document)
- return { };
-
- return document->sampledPageTopColor();
+ return m_sampledPageTopColor.valueOr(Color());
}
void Page::setUnderPageBackgroundColorOverride(Color&& underPageBackgroundColorOverride)
@@ -3285,6 +3288,11 @@
m_rtcController.reset(m_shouldEnableICECandidateFilteringByDefault);
#endif
m_pointerCaptureController->reset();
+
+ if (m_sampledPageTopColor) {
+ m_sampledPageTopColor = WTF::nullopt;
+ chrome().client().sampledPageTopColorChanged();
+ }
}
RenderingUpdateScheduler& Page::renderingUpdateScheduler()
Modified: trunk/Source/WebCore/page/Page.h (277522 => 277523)
--- trunk/Source/WebCore/page/Page.h 2021-05-15 00:26:58 UTC (rev 277522)
+++ trunk/Source/WebCore/page/Page.h 2021-05-15 00:29:19 UTC (rev 277523)
@@ -22,6 +22,7 @@
#include "ActivityState.h"
#include "AnimationFrameRate.h"
+#include "Color.h"
#include "DisabledAdaptations.h"
#include "Document.h"
#include "FindOptions.h"
@@ -94,7 +95,6 @@
class BackForwardController;
class CacheStorageProvider;
class Chrome;
-class Color;
class ContextMenuController;
class CookieJar;
class DOMRectList;
@@ -1156,6 +1156,7 @@
MonotonicTime m_lastRenderingUpdateTimestamp;
Color m_underPageBackgroundColorOverride;
+ Optional<Color> m_sampledPageTopColor;
const bool m_httpsUpgradeEnabled { true };
mutable MediaSessionGroupIdentifier m_mediaSessionGroupIdentifier;
Added: trunk/Source/WebCore/page/PageColorSampler.cpp (0 => 277523)
--- trunk/Source/WebCore/page/PageColorSampler.cpp (rev 0)
+++ trunk/Source/WebCore/page/PageColorSampler.cpp 2021-05-15 00:29:19 UTC (rev 277523)
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2021 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "PageColorSampler.h"
+
+#include "ContentfulPaintChecker.h"
+#include "Document.h"
+#include "Frame.h"
+#include "FrameSnapshotting.h"
+#include "FrameView.h"
+#include "HTMLCanvasElement.h"
+#include "HTMLIFrameElement.h"
+#include "HitTestRequest.h"
+#include "HitTestResult.h"
+#include "ImageBuffer.h"
+#include "IntPoint.h"
+#include "IntRect.h"
+#include "IntSize.h"
+#include "Node.h"
+#include "Page.h"
+#include "RegistrableDomain.h"
+#include "RenderImage.h"
+#include "RenderObject.h"
+#include "RenderStyle.h"
+#include "Settings.h"
+#include <wtf/ListHashSet.h>
+#include <wtf/OptionSet.h>
+#include <wtf/Optional.h>
+#include <wtf/Ref.h>
+#include <wtf/RefPtr.h>
+#include <wtf/URL.h>
+
+namespace WebCore {
+
+static bool isValidSampleLocation(Document& document, const IntPoint& location)
+{
+ // FIXME: <https://webkit.org/b/225167> (Sampled Page Top Color: hook into painting logic instead of taking snapshots)
+
+ constexpr OptionSet<HitTestRequest::Type> hitTestRequestTypes { HitTestRequest::Type::ReadOnly, HitTestRequest::Type::IgnoreCSSPointerEventsProperty, HitTestRequest::Type::DisallowUserAgentShadowContent, HitTestRequest::Type::CollectMultipleElements, HitTestRequest::Type::IncludeAllElementsUnderPoint };
+ HitTestResult hitTestResult(location);
+ document.hitTest(hitTestRequestTypes, hitTestResult);
+
+ for (auto& hitTestNode : hitTestResult.listBasedTestResult()) {
+ auto& node = hitTestNode.get();
+
+ auto* renderer = node.renderer();
+ if (!renderer)
+ return false;
+
+ // Skip images (both `<img>` and CSS `background-image`) as they're likely not a solid color.
+ if (is<RenderImage>(renderer) || renderer->style().hasBackgroundImage())
+ return false;
+
+ // Skip nodes with animations as the sample may get an odd color if the animation is in-progress.
+ if (renderer->style().hasTransitions() || renderer->style().hasAnimations())
+ return false;
+
+ // Skip `<canvas>` but only if they've been drawn into. Guess this by seeing if there's already
+ // a `CanvasRenderingContext`, which is only created by _javascript_.
+ if (is<HTMLCanvasElement>(node) && downcast<HTMLCanvasElement>(node).renderingContext())
+ return false;
+
+ // Skip 3rd-party `<iframe>` as the content likely won't match the rest of the page.
+ if (is<HTMLIFrameElement>(node) && !areRegistrableDomainsEqual(downcast<HTMLIFrameElement>(node).location(), document.url()))
+ return false;
+ }
+
+ return true;
+}
+
+static Optional<Lab<float>> sampleColor(Document& document, IntPoint&& location)
+{
+ // FIXME: <https://webkit.org/b/225167> (Sampled Page Top Color: hook into painting logic instead of taking snapshots)
+
+ if (!isValidSampleLocation(document, location))
+ return WTF::nullopt;
+
+ ASSERT(document.view());
+ auto snapshot = snapshotFrameRect(document.view()->frame(), IntRect(location, IntSize(1, 1)), SnapshotOptionsExcludeSelectionHighlighting | SnapshotOptionsPaintEverythingExcludingSelection);
+ if (!snapshot)
+ return WTF::nullopt;
+
+ auto snapshotData = snapshot->toBGRAData();
+ return convertColor<Lab<float>>(SRGBA<uint8_t> { snapshotData[2], snapshotData[1], snapshotData[0], snapshotData[3] });
+}
+
+static double colorDifference(Lab<float>& lhs, Lab<float>& rhs)
+{
+ return sqrt(pow(rhs.lightness - lhs.lightness, 2) + pow(rhs.a - lhs.a, 2) + pow(rhs.b - lhs.b, 2));
+}
+
+static Lab<float> averageColor(Lab<float> colors[], size_t count)
+{
+ float totalLightness = 0;
+ float totalA = 0;
+ float totalB = 0;
+ for (size_t i = 0; i < count; ++i) {
+ totalLightness += colors[i].lightness;
+ totalA += colors[i].a;
+ totalB += colors[i].b;
+ }
+ return {
+ totalLightness / count,
+ totalA / count,
+ totalB / count,
+ 1,
+ };
+}
+
+Optional<Color> PageColorSampler::sampleTop(Page& page)
+{
+ // If `WTF::nullopt` is returned then that means that no samples were taken (i.e. the `Page` is not ready yet).
+ // If an invalid `Color` is returned then that means that samples Were taken but they were too different.
+
+ auto maxDifference = page.settings().sampledPageTopColorMaxDifference();
+ if (maxDifference <= 0) {
+ // Pretend that the samples are too different so that this function is not called again.
+ return Color();
+ }
+
+ auto mainDocument = makeRefPtr(page.mainFrame().document());
+ if (!mainDocument)
+ return WTF::nullopt;
+
+ auto frameView = makeRefPtr(page.mainFrame().view());
+ if (!frameView)
+ return WTF::nullopt;
+
+ // Don't take samples if the layer tree is still frozen.
+ if (frameView->needsLayout())
+ return WTF::nullopt;
+
+ // Don't attempt to hit test or sample if we don't have any content yet.
+ if (!frameView->isVisuallyNonEmpty() || !frameView->hasContentfulDescendants() || !ContentfulPaintChecker::qualifiesForContentfulPaint(*frameView))
+ return WTF::nullopt;
+
+ // Decrease the width by one pixel so that the last sample is within bounds and not off-by-one.
+ auto frameWidth = frameView->contentsWidth() - 1;
+
+ constexpr auto numSamples = 5;
+ size_t nonMatchingColorIndex = numSamples;
+
+ Lab<float> samples[numSamples];
+ double differences[numSamples - 1];
+
+ auto shouldStopAfterFindingNonMatchingColor = [&] (size_t i) -> bool {
+ // Bail if the non-matching color is not the first or last sample, or there already is an non-matching color.
+ if ((i && i < numSamples - 1) || nonMatchingColorIndex != numSamples)
+ return true;
+
+ nonMatchingColorIndex = i;
+ return false;
+ };
+
+ for (size_t i = 0; i < numSamples; ++i) {
+ auto sample = sampleColor(*mainDocument, IntPoint(frameWidth * i / (numSamples - 1), 0));
+ if (!sample) {
+ if (shouldStopAfterFindingNonMatchingColor(i))
+ return Color();
+ continue;
+ }
+
+ samples[i] = *sample;
+
+ if (i) {
+ // Each `difference` item compares `i` with `i - 1` so if the first comparison (`i == 1`)
+ // is too large of a difference, we should treat `i - 1` (i.e. `0`) as the problem since
+ // we only allow for non-matching colors being the first or last sampled color.
+ auto effectiveNonMatchingColorIndex = i == 1 ? 0 : i;
+
+ differences[i - 1] = colorDifference(samples[i - 1], samples[i]);
+ if (differences[i - 1] > maxDifference) {
+ if (shouldStopAfterFindingNonMatchingColor(effectiveNonMatchingColorIndex))
+ return Color();
+ continue;
+ }
+
+ double cumuluativeDifference = 0;
+ for (size_t j = 0; j < i; ++j) {
+ if (j == nonMatchingColorIndex)
+ continue;
+ cumuluativeDifference += differences[j];
+ }
+ if (cumuluativeDifference > maxDifference) {
+ if (shouldStopAfterFindingNonMatchingColor(effectiveNonMatchingColorIndex)) {
+ // If we haven't already identified a non-matching sample and the difference between the first
+ // and second samples or the second-to-last and last samples is less than the maximum, mark
+ // the first/last sample as non-matching to give a chance for the rest of the samples to match.
+ if (nonMatchingColorIndex == numSamples && (!i || i == numSamples - 1) && cumuluativeDifference - differences[i - 1] <= maxDifference) {
+ nonMatchingColorIndex = effectiveNonMatchingColorIndex;
+ continue;
+ }
+ return Color();
+ }
+ continue;
+ }
+ }
+ }
+
+ // Decrease the height by one pixel so that the last sample is within bounds and not off-by-one.
+ auto minHeight = page.settings().sampledPageTopColorMinHeight() - 1;
+ if (minHeight > 0) {
+ if (nonMatchingColorIndex) {
+ if (auto leftMiddleSample = sampleColor(*mainDocument, IntPoint(0, minHeight))) {
+ if (colorDifference(*leftMiddleSample, samples[0]) > maxDifference)
+ return Color();
+ }
+ }
+
+ if (nonMatchingColorIndex != numSamples - 1) {
+ if (auto rightMiddleSample = sampleColor(*mainDocument, IntPoint(frameWidth, minHeight))) {
+ if (colorDifference(*rightMiddleSample, samples[numSamples - 1]) > maxDifference)
+ return Color();
+ }
+ }
+ }
+
+ auto samplesToAverage = samples;
+ auto validSampleCount = numSamples;
+ if (!nonMatchingColorIndex) {
+ // Skip the first sample by moving the pointer that indicates where the sample array
+ // starts and decreasing the count of samples to average.
+ ++samplesToAverage;
+ --validSampleCount;
+ } else if (nonMatchingColorIndex == numSamples - 1) {
+ // Skip the last sample by decreasing the count of samples to average.
+ --validSampleCount;
+ }
+ return Color(averageColor(samplesToAverage, validSampleCount));
+}
+
+} // namespace WebCore
Added: trunk/Source/WebCore/page/PageColorSampler.h (0 => 277523)
--- trunk/Source/WebCore/page/PageColorSampler.h (rev 0)
+++ trunk/Source/WebCore/page/PageColorSampler.h 2021-05-15 00:29:19 UTC (rev 277523)
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "Color.h"
+#include <wtf/Forward.h>
+
+namespace WebCore {
+
+class Page;
+
+class PageColorSampler {
+public:
+ static Optional<Color> sampleTop(Page&);
+};
+
+} // namespace WebCore
Modified: trunk/Tools/ChangeLog (277522 => 277523)
--- trunk/Tools/ChangeLog 2021-05-15 00:26:58 UTC (rev 277522)
+++ trunk/Tools/ChangeLog 2021-05-15 00:29:19 UTC (rev 277523)
@@ -1,3 +1,14 @@
+2021-05-14 Devin Rousso <[email protected]>
+
+ Sampled Page Top Color: move logic out of `Document`
+ https://bugs.webkit.org/show_bug.cgi?id=225480
+ <rdar://problem/77984539>
+
+ Reviewed by Tim Horton.
+
+ * TestWebKitAPI/Tests/WebKitCocoa/SampledPageTopColor.mm:
+ (TEST.SampledPageTopColor.MainDocumentChange): Added.
+
2021-05-14 Chris Dumez <[email protected]>
Port WTF::FileSystem::realPath() to std::filesystem
Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/SampledPageTopColor.mm (277522 => 277523)
--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/SampledPageTopColor.mm 2021-05-15 00:26:58 UTC (rev 277522)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/SampledPageTopColor.mm 2021-05-15 00:29:19 UTC (rev 277523)
@@ -350,3 +350,44 @@
EXPECT_IN_RANGE(components[2], 1.00, 1.01);
EXPECT_EQ(components[3], 1);
}
+
+TEST(SampledPageTopColor, MainDocumentChange)
+{
+ auto webView = createWebViewWithSampledPageTopColorMaxDifference(5);
+ EXPECT_NULL([webView _sampledPageTopColor]);
+
+ size_t notificationCount = 0;
+ auto sampledPageTopColorObserver = adoptNS([[TestKVOWrapper alloc] initWithObservable:webView.get() keyPath:@"_sampledPageTopColor" callback:[&] {
+ ++notificationCount;
+ }]);
+
+ [webView synchronouslyLoadHTMLStringAndWaitUntilAllImmediateChildFramesPaint:@"<body>Test"];
+ EXPECT_EQ(WebCore::Color([webView _sampledPageTopColor].CGColor), WebCore::Color::white);
+ EXPECT_EQ(notificationCount, 1UL);
+
+ notificationCount = 0;
+
+ [webView synchronouslyLoadHTMLStringAndWaitUntilAllImmediateChildFramesPaint:@"<body>Test"];
+ EXPECT_EQ(WebCore::Color([webView _sampledPageTopColor].CGColor), WebCore::Color::white);
+ // Depending on timing, a notification can be sent for when the main document changes and then
+ // when the new main document renders or both can be coalesced if rendering is fast enough.
+ EXPECT_TRUE(notificationCount == 0 || notificationCount == 2);
+
+ notificationCount = 0;
+
+ [webView synchronouslyLoadHTMLStringAndWaitUntilAllImmediateChildFramesPaint:@"<body>"];
+ EXPECT_NULL([webView _sampledPageTopColor]);
+ EXPECT_EQ(notificationCount, 1UL);
+
+ notificationCount = 0;
+
+ [webView synchronouslyLoadHTMLStringAndWaitUntilAllImmediateChildFramesPaint:@"<body>"];
+ EXPECT_NULL([webView _sampledPageTopColor]);
+ EXPECT_EQ(notificationCount, 0UL);
+
+ notificationCount = 0;
+
+ [webView synchronouslyLoadHTMLStringAndWaitUntilAllImmediateChildFramesPaint:@"<body>Test"];
+ EXPECT_EQ(WebCore::Color([webView _sampledPageTopColor].CGColor), WebCore::Color::white);
+ EXPECT_EQ(notificationCount, 1UL);
+}