Implement FCP (first contentful paint)
authornoam@webkit.org <noam@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 28 Apr 2020 22:34:52 +0000 (22:34 +0000)
committernoam@webkit.org <noam@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 28 Apr 2020 22:34:52 +0000 (22:34 +0000)
https://bugs.webkit.org/show_bug.cgi?id=208499

Reviewed by Simon Fraser.

LayoutTests/imported/w3c:

Imported paint timing tests that don't require first-paint.

* resources/import-expectations.json:
* web-platform-tests/paint-timing: Added.
* web-platform-tests/paint-timing/fcp-only: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-background-size-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-background-size.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-bg-image-set-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-bg-image-set.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-bg-image-two-steps-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-bg-image-two-steps.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-canvas-context-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-canvas-context.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-gradient-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-gradient.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-transition-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-transition.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-invisible-text-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-invisible-text.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-opacity-descendant-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-opacity-descendant.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-opacity-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-opacity.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-translate-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-translate.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-display-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-display.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-image-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-image.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-opacity-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-opacity.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-text-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-text.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-visibility-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-visibility.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-svg-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-svg.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-text-input-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-text-input.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-typographic-pseudo-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-typographic-pseudo.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-video-frame-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-video-frame.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-video-poster-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-video-poster.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-whitespace-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-whitespace.html: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-with-rtl-expected.txt: Added.
* web-platform-tests/paint-timing/fcp-only/fcp-with-rtl.html: Added.
* web-platform-tests/paint-timing/resources: Added.
* web-platform-tests/paint-timing/resources/circle.svg: Added.
* web-platform-tests/paint-timing/resources/circles.png: Added.
* web-platform-tests/paint-timing/resources/subframe-painting.html: Added.
* web-platform-tests/paint-timing/resources/subframe-sending-paint.html: Added.
* web-platform-tests/paint-timing/resources/utils.js: Added.
(waitForAnimationFrames):
(async assertNoFirstContentfulPaint):
(async assertFirstContentfulPaint.return.new.Promise):
(async assertFirstContentfulPaint):
(async test_fcp):

Source/WebCore:

Added the necessary interface, extensions to the performance interface and observer, new runtime flag.
Detecting contentfulness after layout and before actual paint, by running a "dummy" paint, similar to
invalidateControlTints() / invalidateImagesWithAsyncDecodes(). Save the result to the GraphicsContext and then to the document.
This would run for every paint until we detect a contentful one.

Note that it paints the entire frame contents, as FCP is not viewport-dependent.
Also, paint timing is currently disabled for LFC (layout formatting context), and will be dealt with later.

Tests: http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-num-chars.html
       http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-style.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-background-size.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-set.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-two-steps.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-canvas-context.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-gradient.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-transition.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-text.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity-descendant.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-translate.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-display.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-image.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-opacity.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-text.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-visibility.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-svg.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-text-input.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-typographic-pseudo.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-frame.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-poster.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-whitespace.html
       imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-with-rtl.html
       performance-api/paint-timing/paint-timing-apis.html
       performance-api/paint-timing/paint-timing-frames.html
       performance-api/paint-timing/paint-timing-with-worker.html
       performance-api/paint-timing/performance-observer-first-contentful-paint.html

* CMakeLists.txt:
* DerivedSources.make:
* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:
* bindings/js/JSPerformanceEntryCustom.cpp:
(WebCore::toJSNewlyCreated):
* bindings/js/WebCoreBuiltinNames.h:
        Add PerformancePaintTiming interface. https://w3c.github.io/paint-timing/#sec-PerformancePaintTiming

* dom/Document.cpp:
* dom/Document.h:
(WebCore::Document::supportsPaintTiming const):
        We only report paint timing for document that can access the top level security origin, to avoid leakage of information to sandboxed iframes.

(WebCore::Document::enqueuePaintTimingEntryIfNeeded):
        Enqueue a paint timing entry, according to https://w3c.github.io/paint-timing/#sec-reporting-paint-timing

* page/FrameView.cpp:
(WebCore::FrameView::paintContents):
        Disable FCP fake-paint in LFC mode.

* page/Page.cpp:
(WebCore::Page::doAfterUpdateRendering):
* page/Performance.cpp:
(WebCore::Performance::getEntries const):
(WebCore::Performance::getEntriesByType const):
(WebCore::Performance::getEntriesByName const):
(WebCore::Performance::reportFirstContentfulPaint):
* page/Performance.h:
        Support first-contentful-paint reporting.

* page/PerformanceEntry.cpp:
(WebCore::PerformanceEntry::parseEntryTypeString):
* page/PerformanceEntry.h:
(WebCore::PerformanceEntry::isPaint const):
* page/PerformanceObserver.cpp:
(WebCore::PerformanceObserver::supportedEntryTypes):
* page/PerformanceObserver.h:
* page/PerformanceObserver.idl:
* page/PerformancePaintTiming.h: Added.
(isType):
* page/PerformancePaintTiming.idl: Added.
        Add paint performance entry type.

* page/RuntimeEnabledFeatures.h:
(WebCore::RuntimeEnabledFeatures::setPaintTimingEnabled):
(WebCore::RuntimeEnabledFeatures::paintTimingEnabled const):
        New runtime flag for paint timing.

* platform/graphics/GraphicsContext.h:
(WebCore::GraphicsContext::detectingContentfulPaint const):
(WebCore::GraphicsContext::setContentfulPaintDetected):
(WebCore::GraphicsContext::contenfulPaintDetected const):
        Add a flag in GraphicsContext where different render operations can report if they're contentful.

* rendering/ContentfulPaintChecker.cpp: Added.
(WebCore::ContentfulPaintChecker::qualifiesForContentfulPaint):
* rendering/ContentfulPaintChecker.h: Added.
        Run a "dummy" paint of the FrameView, to detect contentful elements.

* rendering/RenderBoxModelObject.cpp:
(WebCore::RenderBoxModelObject::paintFillLayerExtended):
* rendering/RenderHTMLCanvas.cpp:
(WebCore::RenderHTMLCanvas::paintReplaced):
* rendering/RenderImage.cpp:
(WebCore::RenderImage::paintReplaced):
* rendering/RenderLayer.cpp:
(WebCore::RenderLayer::paintLayerContents):
* rendering/RenderVideo.cpp:
(WebCore::RenderVideo::paintReplaced):
* rendering/TextPainter.cpp:
(WebCore::TextPainter::paintTextOrEmphasisMarks):
* rendering/svg/RenderSVGRoot.cpp:
(WebCore::RenderSVGRoot::paintReplaced):
        Report contentfulness when we reach anything that qualifies as contentful,
        based on https://w3c.github.io/paint-timing/#contentful.

Source/WebKit:

Add an experimental runtime flag for paint-timing (paintTimingEnabled).

* Shared/WebPreferences.yaml:
* UIProcess/API/C/WKPreferences.cpp:
(WKPreferencesSetPaintTimingEnabled):
(WKPreferencesGetPaintTimingEnabled):
* UIProcess/API/C/WKPreferencesRefPrivate.h:
* WebProcess/Storage/WebSWContextManagerConnection.cpp:
(WebKit::WebSWContextManagerConnection::updatePreferencesStore):

LayoutTests:

Added tests for some first-contentful-paint (FCP) use-cases.
Ensure that FCP works well with VNE (first paint delay until contents are visually non-empty).

* platform/win/TestExpectations:
* platform/mac-wk1/TestExpectations:
        Disable paint timing for WebKit1.

* TestExpectations:
        Disable of the WPT tests.

* http/tests/performance/paint-timing: Added.
* http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-num-chars-expected.txt: Added.
* http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-num-chars.html: Added.
        If VNE due to number of characters blocks painting, make sure FCP is reported only after painting is unblocked.

* http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-style-expected.txt: Added.
* http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-style.html: Added.
        If a pending stylesheet blocks painting, make sure FCP is reported only after stylesheet has loaded.

* http/tests/performance/paint-timing/resources: Added.
* http/tests/performance/paint-timing/resources/slowscript.php: Added.
* http/tests/performance/paint-timing/resources/slowstyle.php: Added.
* http/tests/performance/paint-timing/resources/style.css: Added.
(body):
* performance-api/paint-timing: Added.
* performance-api/paint-timing/paint-timing-apis-expected.txt: Added.
* performance-api/paint-timing/paint-timing-apis.html: Added.
* performance-api/paint-timing/paint-timing-frames-expected.txt: Added.
* performance-api/paint-timing/paint-timing-frames.html: Added.
* performance-api/paint-timing/paint-timing-with-worker-expected.txt: Added.
* performance-api/paint-timing/paint-timing-with-worker.html: Added.
        Test that FCP is not available for cross-origin frames and workers.

* performance-api/paint-timing/performance-observer-first-contentful-paint-expected.txt: Added.
* performance-api/paint-timing/performance-observer-first-contentful-paint.html: Added.
        Test that performance observers are called for FCP.

* performance-api/paint-timing/resources: Added.
* performance-api/paint-timing/resources/fcp-subframe.html: Added.
* performance-api/paint-timing/resources/paint-api-utils.js: Added.
(async waitForFCP):
* performance-api/paint-timing/resources/paint-timing-api.js: Added.
* performance-api/paint-timing/resources/worker.html: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@260851 268f45cc-cd09-0410-ab3c-d52691b4dbfc

118 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-num-chars-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-num-chars.html [new file with mode: 0644]
LayoutTests/http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-style-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-style.html [new file with mode: 0644]
LayoutTests/http/tests/performance/paint-timing/resources/slowscript.php [new file with mode: 0644]
LayoutTests/http/tests/performance/paint-timing/resources/slowstyle.php [new file with mode: 0644]
LayoutTests/http/tests/performance/paint-timing/resources/style.css [new file with mode: 0644]
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/resources/import-expectations.json
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-background-size-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-background-size.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-set-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-set.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-two-steps-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-two-steps.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-canvas-context-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-canvas-context.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-gradient-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-gradient.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-transition-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-transition.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-text-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-text.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity-descendant-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity-descendant.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-translate-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-translate.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-display-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-display.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-image-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-image.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-opacity-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-opacity.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-text-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-text.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-visibility-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-visibility.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-svg-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-svg.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-text-input-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-text-input.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-typographic-pseudo-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-typographic-pseudo.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-frame-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-frame.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-poster-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-poster.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-whitespace-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-whitespace.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-with-rtl-expected.txt [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-with-rtl.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/circle.svg [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/circles.png [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/subframe-painting.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/subframe-sending-paint.html [new file with mode: 0644]
LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/utils.js [new file with mode: 0644]
LayoutTests/performance-api/paint-timing/paint-timing-apis-expected.txt [new file with mode: 0644]
LayoutTests/performance-api/paint-timing/paint-timing-apis.html [new file with mode: 0644]
LayoutTests/performance-api/paint-timing/paint-timing-frames-expected.txt [new file with mode: 0644]
LayoutTests/performance-api/paint-timing/paint-timing-frames.html [new file with mode: 0644]
LayoutTests/performance-api/paint-timing/paint-timing-with-worker-expected.txt [new file with mode: 0644]
LayoutTests/performance-api/paint-timing/paint-timing-with-worker.html [new file with mode: 0644]
LayoutTests/performance-api/paint-timing/performance-observer-first-contentful-paint-expected.txt [new file with mode: 0644]
LayoutTests/performance-api/paint-timing/performance-observer-first-contentful-paint.html [new file with mode: 0644]
LayoutTests/performance-api/paint-timing/resources/fcp-subframe.html [new file with mode: 0644]
LayoutTests/performance-api/paint-timing/resources/paint-api-utils.js [new file with mode: 0644]
LayoutTests/performance-api/paint-timing/resources/paint-timing-api.js [new file with mode: 0644]
LayoutTests/performance-api/paint-timing/resources/worker.html [new file with mode: 0644]
LayoutTests/platform/mac-wk1/TestExpectations
LayoutTests/platform/win/TestExpectations
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/DerivedSources.make
Source/WebCore/Sources.txt
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/bindings/js/JSPerformanceEntryCustom.cpp
Source/WebCore/bindings/js/WebCoreBuiltinNames.h
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/Document.h
Source/WebCore/page/FrameView.cpp
Source/WebCore/page/Page.cpp
Source/WebCore/page/Performance.cpp
Source/WebCore/page/Performance.h
Source/WebCore/page/PerformanceEntry.cpp
Source/WebCore/page/PerformanceEntry.h
Source/WebCore/page/PerformanceObserver.cpp
Source/WebCore/page/PerformanceObserver.h
Source/WebCore/page/PerformanceObserver.idl
Source/WebCore/page/PerformancePaintTiming.h [new file with mode: 0644]
Source/WebCore/page/PerformancePaintTiming.idl [new file with mode: 0644]
Source/WebCore/page/RuntimeEnabledFeatures.h
Source/WebCore/platform/graphics/GraphicsContext.h
Source/WebCore/rendering/ContentfulPaintChecker.cpp [new file with mode: 0644]
Source/WebCore/rendering/ContentfulPaintChecker.h [new file with mode: 0644]
Source/WebCore/rendering/RenderBoxModelObject.cpp
Source/WebCore/rendering/RenderHTMLCanvas.cpp
Source/WebCore/rendering/RenderImage.cpp
Source/WebCore/rendering/RenderLayer.cpp
Source/WebCore/rendering/RenderVideo.cpp
Source/WebCore/rendering/TextPainter.cpp
Source/WebCore/rendering/svg/RenderSVGRoot.cpp
Source/WebKit/ChangeLog
Source/WebKit/Shared/WebPreferences.yaml
Source/WebKit/UIProcess/API/C/WKPreferences.cpp
Source/WebKit/UIProcess/API/C/WKPreferencesRefPrivate.h
Source/WebKit/WebProcess/Storage/WebSWContextManagerConnection.cpp

index 478d21d..af0614d 100644 (file)
@@ -1,3 +1,54 @@
+2020-04-28  Noam Rosenthal  <noam@webkit.org>
+
+        Implement FCP (first contentful paint)
+        https://bugs.webkit.org/show_bug.cgi?id=208499
+
+        Reviewed by Simon Fraser.
+
+        Added tests for some first-contentful-paint (FCP) use-cases.
+        Ensure that FCP works well with VNE (first paint delay until contents are visually non-empty).
+
+        * platform/win/TestExpectations:
+        * platform/mac-wk1/TestExpectations:
+                Disable paint timing for WebKit1.
+
+        * TestExpectations:
+                Disable of the WPT tests.
+        
+        * http/tests/performance/paint-timing: Added.
+        * http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-num-chars-expected.txt: Added.
+        * http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-num-chars.html: Added.
+                If VNE due to number of characters blocks painting, make sure FCP is reported only after painting is unblocked.
+
+        * http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-style-expected.txt: Added.
+        * http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-style.html: Added.
+                If a pending stylesheet blocks painting, make sure FCP is reported only after stylesheet has loaded.
+
+        * http/tests/performance/paint-timing/resources: Added.
+        * http/tests/performance/paint-timing/resources/slowscript.php: Added.
+        * http/tests/performance/paint-timing/resources/slowstyle.php: Added.
+        * http/tests/performance/paint-timing/resources/style.css: Added.
+        (body):
+        * performance-api/paint-timing: Added.
+        * performance-api/paint-timing/paint-timing-apis-expected.txt: Added.
+        * performance-api/paint-timing/paint-timing-apis.html: Added.
+        * performance-api/paint-timing/paint-timing-frames-expected.txt: Added.
+        * performance-api/paint-timing/paint-timing-frames.html: Added.
+        * performance-api/paint-timing/paint-timing-with-worker-expected.txt: Added.
+        * performance-api/paint-timing/paint-timing-with-worker.html: Added.
+                Test that FCP is not available for cross-origin frames and workers.
+
+        * performance-api/paint-timing/performance-observer-first-contentful-paint-expected.txt: Added.
+        * performance-api/paint-timing/performance-observer-first-contentful-paint.html: Added.
+                Test that performance observers are called for FCP.
+
+        * performance-api/paint-timing/resources: Added.
+        * performance-api/paint-timing/resources/fcp-subframe.html: Added.
+        * performance-api/paint-timing/resources/paint-api-utils.js: Added.
+        (async waitForFCP):
+        * performance-api/paint-timing/resources/paint-timing-api.js: Added.
+        * performance-api/paint-timing/resources/worker.html: Added.
+
 2020-04-28  Philippe Normand  <pnormand@igalia.com>
 
         REGRESSION(r260822): Broke media/track/track-in-band-metadata-display-order.html on Mac
index 8d34489..b1e7fb4 100644 (file)
@@ -208,6 +208,9 @@ fast/visual-viewport/rubberbanding-viewport-rects-header-footer.html  [ Skip ]
 # DataDetectors tests only make sense on Mac
 fast/events/do-not-drag-and-drop-data-detectors-link.html [ Skip ]
 
+# A rounding error may cause 90deg 3d-rotated elements to count as painted
+imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant.html
+
 webkit.org/b/202617 imported/w3c/web-platform-tests/picture-in-picture [ Skip ]
 
 # PiP API tests are only relevant on mac and iPad
diff --git a/LayoutTests/http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-num-chars-expected.txt b/LayoutTests/http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-num-chars-expected.txt
new file mode 100644 (file)
index 0000000..9071ebe
--- /dev/null
@@ -0,0 +1,3 @@
+PASS paintEntry.startTime > timestamps[expectedIterations] is true
+PASS paintEntry.startTime < timestamps[expectedIterations + 2] is true
+
diff --git a/LayoutTests/http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-num-chars.html b/LayoutTests/http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-num-chars.html
new file mode 100644 (file)
index 0000000..0d6c61d
--- /dev/null
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+    if (window.testRunner) {
+        testRunner.dumpAsText()
+        testRunner.waitUntilDone();
+    }
+</script>
+
+<script src="../../resources/js-test-pre.js"></script>
+<script>
+    var styleEntry = null
+    var paintEntry = null
+    var timestamps = []
+    var charsPerIteration = 80
+    var charThreshold = 200
+    var expectedIterations = Math.ceil(charThreshold / charsPerIteration)
+
+    function slowString(str, delay) {
+        const script = document.createElement('script')
+        const source = `document.write('${str}'); timestamps.push(${performance.now()}); check();`
+        script.src = `./resources/slowscript.php?cacheBuster=${performance.now()}&delay=${delay}&script=${source}`
+        document.write(script.outerHTML)
+    }
+
+    function check() {
+        if (timestamps.length < expectedIterations + 3 || !paintEntry)
+            return;
+
+        document.body.innerText = ''
+
+        shouldBeTrue("paintEntry.startTime > timestamps[expectedIterations]")
+        shouldBeTrue("paintEntry.startTime < timestamps[expectedIterations + 2]")
+        
+        if (window.testRunner)
+            testRunner.notifyDone()
+    }
+
+    const observer = new PerformanceObserver(list => {
+        paintEntry = performance.getEntriesByType('paint')[0]
+        if (paintEntry)
+            check()
+    });
+    observer.observe({entryTypes: ["paint"]})
+
+    function next() {
+        slowString('\t ' + Array(charsPerIteration / 2).fill(' 0 \t A').join('') + "   \n\t", 0.3, charsPerIteration)
+    }
+</script>
+</head>
+<body>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+    <script>next()</script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-style-expected.txt b/LayoutTests/http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-style-expected.txt
new file mode 100644 (file)
index 0000000..42cf88a
--- /dev/null
@@ -0,0 +1,2 @@
+PASS paintEntry.startTime >= styleEntry.startTime + styleEntry.duration is true
+TEXT
diff --git a/LayoutTests/http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-style.html b/LayoutTests/http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-style.html
new file mode 100644 (file)
index 0000000..3b65f02
--- /dev/null
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+    if (window.testRunner) {
+        testRunner.dumpAsText()
+        testRunner.waitUntilDone();
+    }
+</script>
+
+<script src="../../../resources/js-test-pre.js"></script>
+<script>
+    var styleEntry = null;
+    var paintEntry = null;
+    const observer = new PerformanceObserver(list => {
+        const paintEntries = performance.getEntriesByType('paint');
+        if (paintEntries.length)
+            paintEntry = paintEntries[0];
+
+        const resourceEntries = performance.getEntriesByType('resource').filter(r => r.name.includes('slowstyle.php'));
+        if (resourceEntries.length)
+            styleEntry = resourceEntries[0];
+
+        if (!styleEntry || !paintEntry)
+            return;
+
+        shouldBeTrue("paintEntry.startTime >= styleEntry.startTime + styleEntry.duration");
+                
+        if (window.testRunner)
+            testRunner.notifyDone();
+    });
+    observer.observe({entryTypes: ["paint", "resource"]});
+</script>
+<link rel="stylesheet" href="./resources/slowstyle.php?delay=0.5" />
+</head>
+<body>
+TEXT
+</body>
+</html>
diff --git a/LayoutTests/http/tests/performance/paint-timing/resources/slowscript.php b/LayoutTests/http/tests/performance/paint-timing/resources/slowscript.php
new file mode 100644 (file)
index 0000000..3b2ee7a
--- /dev/null
@@ -0,0 +1,9 @@
+<?php
+if (isset($_GET["delay"]))
+    sleep($_GET["delay"]);
+header('Cache-Control: no-cache, must-revalidate');
+header('Content-Type: text/javascript');
+
+echo($_GET["script"])
+?>
+
diff --git a/LayoutTests/http/tests/performance/paint-timing/resources/slowstyle.php b/LayoutTests/http/tests/performance/paint-timing/resources/slowstyle.php
new file mode 100644 (file)
index 0000000..722ae14
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+if (isset($_GET["delay"]))
+    sleep($_GET["delay"]);
+header('Cache-Control: no-cache, must-revalidate');
+header('Location: style.css');
+?>
diff --git a/LayoutTests/http/tests/performance/paint-timing/resources/style.css b/LayoutTests/http/tests/performance/paint-timing/resources/style.css
new file mode 100644 (file)
index 0000000..fc5252c
--- /dev/null
@@ -0,0 +1,3 @@
+body {
+    background-color: green
+};
\ No newline at end of file
index 1ce56cd..ea281fe 100644 (file)
@@ -1,3 +1,79 @@
+2020-04-28  Noam Rosenthal  <noam@webkit.org>
+
+        Implement FCP (first contentful paint)
+        https://bugs.webkit.org/show_bug.cgi?id=208499
+
+        Reviewed by Simon Fraser.
+
+        Imported paint timing tests that don't require first-paint.
+
+        * resources/import-expectations.json:
+        * web-platform-tests/paint-timing: Added.
+        * web-platform-tests/paint-timing/fcp-only: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-background-size-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-background-size.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-bg-image-set-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-bg-image-set.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-bg-image-two-steps-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-bg-image-two-steps.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-canvas-context-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-canvas-context.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-gradient-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-gradient.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-transition-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-transition.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-invisible-text-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-invisible-text.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-opacity-descendant-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-opacity-descendant.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-opacity-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-opacity.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-translate-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-translate.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-display-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-display.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-image-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-image.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-opacity-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-opacity.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-text-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-text.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-visibility-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-visibility.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-svg-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-svg.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-text-input-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-text-input.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-typographic-pseudo-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-typographic-pseudo.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-video-frame-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-video-frame.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-video-poster-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-video-poster.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-whitespace-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-whitespace.html: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-with-rtl-expected.txt: Added.
+        * web-platform-tests/paint-timing/fcp-only/fcp-with-rtl.html: Added.
+        * web-platform-tests/paint-timing/resources: Added.
+        * web-platform-tests/paint-timing/resources/circle.svg: Added.
+        * web-platform-tests/paint-timing/resources/circles.png: Added.
+        * web-platform-tests/paint-timing/resources/subframe-painting.html: Added.
+        * web-platform-tests/paint-timing/resources/subframe-sending-paint.html: Added.
+        * web-platform-tests/paint-timing/resources/utils.js: Added.
+        (waitForAnimationFrames):
+        (async assertNoFirstContentfulPaint):
+        (async assertFirstContentfulPaint.return.new.Promise):
+        (async assertFirstContentfulPaint):
+        (async test_fcp):
+
 2020-04-26  Alexey Shvayka  <shvaikalesh@gmail.com>
 
         InternalFunction::createSubclassStructure should use newTarget's globalObject
index 03ac203..3668fe9 100644 (file)
     "css/geometry": "import", 
     "css/support": "import", 
     "custom-elements": "import", 
+    "paint-timing/": "import", 
+    "paint-timing/fcp-only": "import", 
+    "paint-timing/resources": "import", 
+    "paint-timing/resources/": "import", 
+    "resources/testharness.js": "import", 
     "shadow-dom": "import", 
     "tools": "import", 
     "web-animations": "import", 
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-background-size-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-background-size-expected.txt
new file mode 100644 (file)
index 0000000..e77352f
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS First contentful paint fires due to background size. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-background-size.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-background-size.html
new file mode 100644 (file)
index 0000000..8cc8370
--- /dev/null
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to background size</title>
+<style>
+    #main {
+        position: relative;
+        width: 100px;
+        height: 100px;
+        background-image: url(../resources/circles.png);
+        background-size: 0 0;
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful {
+        background-size: 100% 100%;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main"></div>
+<script>
+    test_fcp("First contentful paint fires due to background size.");
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-set-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-set-expected.txt
new file mode 100644 (file)
index 0000000..c59a6d0
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS First contentful paint fires due to background image in image-set. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-set.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-set.html
new file mode 100644 (file)
index 0000000..0cc52d0
--- /dev/null
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to background image in image-set</title>
+<style>
+    #main {
+        width: 100px;
+        height: 100px;
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful {
+        background-image: -webkit-image-set(url(../resources/circles.png) 1x);
+        background-image: image-set(url(../resources/circles.png) 1x);
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main"></div>
+<script>
+    test_fcp("First contentful paint fires due to background image in image-set.");
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-two-steps-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-two-steps-expected.txt
new file mode 100644 (file)
index 0000000..22b7e3a
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS First contentful paint fires for background image only when visible. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-two-steps.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-two-steps.html
new file mode 100644 (file)
index 0000000..4dc9af9
--- /dev/null
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP should fire for background image only when visible</title>
+<style>
+    #main {
+        width: 100px;
+        height: 100px;
+    }
+
+    /* contentful and preFCP classes are defined in test_fcp script. */
+    #main.preFCP {
+        visibility: hidden;
+    }
+
+    #main.contentful,  #main.preFCP {
+        background-image: url(../resources/circles.png);
+    }
+
+    #main.contentful {
+        visibility: visible;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main"></div>
+<script>
+    test_fcp("First contentful paint fires for background image only when visible.");
+</script>
+</body>
+
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-canvas-context-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-canvas-context-expected.txt
new file mode 100644 (file)
index 0000000..48fbe51
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Canvas should count as contentful when context is created 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-canvas-context.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-canvas-context.html
new file mode 100644 (file)
index 0000000..5f3c9d9
--- /dev/null
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP when canvas context is created</title>
+</head>
+<body>
+<script src="../resources/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<canvas id="canvas" width="50" height="50"></canvas>
+<script>
+  promise_test(async t => {
+    assert_precondition(window.PerformancePaintTiming, "Paint Timing isn't supported.");
+    await new Promise(r => window.addEventListener('load', r));
+    await assertNoFirstContentfulPaint(t);
+    const canvas = document.getElementById('canvas');
+    const context = canvas.getContext('2d');
+    context.fillRect(0, 0, 100, 100);
+    await assertFirstContentfulPaint(t);
+  }, 'Canvas should count as contentful when context is created');
+</script>
+</body>
+
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-gradient-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-gradient-expected.txt
new file mode 100644 (file)
index 0000000..a7a68be
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS Gradients should not count as contentful 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-gradient.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-gradient.html
new file mode 100644 (file)
index 0000000..1d15812
--- /dev/null
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP should not fire with gradient-only backgrounds</title>
+<style>
+    #main {
+        width: 100px;
+        height: 100px;
+        background-image: linear-gradient(to bottom, orange, blue);
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main"></div>
+<script>
+  promise_test(async t => {
+      assert_precondition(window.PerformancePaintTiming, "Paint Timing isn't supported.");
+      await new Promise(r => window.addEventListener('load', r));
+      await assertNoFirstContentfulPaint(t);
+  },  'Gradients should not count as contentful');
+</script>
+</body>
+
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant-expected.txt
new file mode 100644 (file)
index 0000000..25c3252
--- /dev/null
@@ -0,0 +1,4 @@
+Text
+
+PASS First contentful paint fires due to its ancestor getting rotating into view. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant.html
new file mode 100644 (file)
index 0000000..76d459d
--- /dev/null
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to 3d revealing of descendants</title>
+<style>
+    #main {
+        width: 100px;
+        height: 100px;
+        left: 0px;
+        position: relative;
+        top: 0;
+        transform: rotateX(45deg);
+        transform-style: preserve-3d;
+    }
+
+    /*
+       This tests that given multiplication effect of 3d transforms on bounding rect,
+       An element counts as contentful/paintable only when its bounding rect is truly non-empty */
+    #child {
+        transform: rotateX(45deg);
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful {
+        transform: none;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main">
+    <div id="child">Text</div>
+</div>
+<script>
+    test_fcp("First contentful paint fires due to its ancestor getting rotating into view.");
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-expected.txt
new file mode 100644 (file)
index 0000000..d413d38
--- /dev/null
@@ -0,0 +1,4 @@
+Text
+
+PASS First contentful paint fires due to 3d rotation into view. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate.html
new file mode 100644 (file)
index 0000000..0b7fc32
--- /dev/null
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to 3d rotation into view</title>
+<style>
+    #main {
+        width: 100px;
+        height: 100px;
+        left: 0px;
+        transform: rotateY(90deg);
+        position: relative;
+        top: 0;
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful {
+        transform: none;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main">Text</div>
+<script>
+    test_fcp("First contentful paint fires due to 3d rotation into view.")
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-expected.txt
new file mode 100644 (file)
index 0000000..7c5a68e
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS First contentful paint fires due to scale becoming positive. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-transition-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-transition-expected.txt
new file mode 100644 (file)
index 0000000..7633ecd
--- /dev/null
@@ -0,0 +1,4 @@
+TEXT
+
+PASS First contentful paint fires when revealed during transition. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-transition.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-transition.html
new file mode 100644 (file)
index 0000000..c0c0af0
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP in transition</title>
+<style>
+    #main {
+        width: 100px;
+        height: 100px;
+        left: 0px;
+        transform: scale(0);
+        transition: transform 1s;
+        position: relative;
+        top: 0;
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful {
+        transform: scale(1);
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main">TEXT</div>
+<script>
+    test_fcp("First contentful paint fires when revealed during transition.");
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale.html
new file mode 100644 (file)
index 0000000..5389e8a
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to scale change</title>
+<style>
+    #main {
+        width: 100px;
+        height: 100px;
+        left: 0px;
+        transform: scale(0);
+        position: relative;
+        top: 0;
+        background-image: url(../resources/circles.png);
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful {
+        transform: none;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main"></div>
+<script>
+    test_fcp("First contentful paint fires due to scale becoming positive.")
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-text-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-text-expected.txt
new file mode 100644 (file)
index 0000000..f6a827b
--- /dev/null
@@ -0,0 +1,4 @@
+INVISIBLE
+
+PASS First contentful paint fires due to pseudo-element becoming visible. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-text.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-text.html
new file mode 100644 (file)
index 0000000..e1b3871
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP should still fire for invisible text</title>
+<style>
+    #main {
+        position: relative;
+        width: 100px;
+        height: 100px;
+        visibility: hidden;
+        color: rgba(0, 0, 0, 0);
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful {
+        visibility: visible;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main">
+    INVISIBLE
+</div>
+<script>
+    test_fcp("First contentful paint fires due to pseudo-element becoming visible.")
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity-descendant-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity-descendant-expected.txt
new file mode 100644 (file)
index 0000000..5cd0769
--- /dev/null
@@ -0,0 +1,4 @@
+Text
+
+PASS First contentful paint fires due to its ancestor getting positive opacity. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity-descendant.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity-descendant.html
new file mode 100644 (file)
index 0000000..8ada49b
--- /dev/null
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to ancestor opacity</title>
+<style>
+    #main {
+        width: 100px;
+        height: 100px;
+        left: 0px;
+        position: relative;
+        top: 0;
+    }
+
+    #child {
+        opacity: 0;
+    }
+
+    /* contentful and intermediate classes are defined in test_fcp script. */
+    #main.contentful #child {
+        opacity: 1;
+    }
+
+    #main.intermediate #child {
+        opacity: 1;
+    }
+
+    #main.intermediate {
+        opacity: 0;
+    }
+
+    #main.contentful {
+        opacity: 0.5;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main">
+    <div id="child">Text</div>
+</div>
+<script>
+    test_fcp("First contentful paint fires due to its ancestor getting positive opacity.");
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity-expected.txt
new file mode 100644 (file)
index 0000000..1c7caee
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS First contentful paint fires due to opacity-revealed element. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity.html
new file mode 100644 (file)
index 0000000..83afdde
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to opacity</title>
+<style>
+    #main {
+        width: 100px;
+        height: 100px;
+        left: 0px;
+        position: relative;
+        top: 0;
+        background-image: url(../resources/circles.png);
+        opacity: 0;
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful {
+        opacity: 0.1;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main"></div>
+<script>
+    test_fcp("First contentful paint fires due to opacity-revealed element.");
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-expected.txt
new file mode 100644 (file)
index 0000000..158c0e6
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS First contentful paint fires due to intersection with document. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-translate-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-translate-expected.txt
new file mode 100644 (file)
index 0000000..6649d90
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS First contentful paint fires due to transform-based intersection with document. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-translate.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-translate.html
new file mode 100644 (file)
index 0000000..ee7975e
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to transform-based intersection with document</title>
+<style>
+    #main {
+        width: 100px;
+        height: 100px;
+        left: 0px;
+        transform: translate(-1000px);
+        position: relative;
+        top: 0;
+        background-image: url(../resources/circles.png);
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful {
+        transform: none;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main"></div>
+<script>
+    test_fcp("First contentful paint fires due to transform-based intersection with document.")
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds.html
new file mode 100644 (file)
index 0000000..3553772
--- /dev/null
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to intersection with document</title>
+<style>
+    #main {
+        width: 100px;
+        height: 100px;
+        left: -1000px;
+        position: relative;
+        top: 0;
+        background-image: url(../resources/circles.png);
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful {
+        left: 0px;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main"></div>
+<script>
+    test_fcp("First contentful paint fires due to intersection with document.")
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-display-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-display-expected.txt
new file mode 100644 (file)
index 0000000..255b2b8
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS First contentful paint fires due to pseudo-element text. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-display.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-display.html
new file mode 100644 (file)
index 0000000..50fd626
--- /dev/null
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to pseudo-element text</title>
+<style>
+    #main {
+        position: relative;
+        width: 100px;
+        height: 100px;
+    }
+
+    #main:after {
+        content: "TEXT";
+        display: none;
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful:after {
+        display: block;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main">
+</div>
+<script>
+    test_fcp("First contentful paint fires due to pseudo-element text.")
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-image-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-image-expected.txt
new file mode 100644 (file)
index 0000000..2ed72cb
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS First contentful paint fires due to pseudo-element image. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-image.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-image.html
new file mode 100644 (file)
index 0000000..ba38edb
--- /dev/null
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to pseudo-element image</title>
+<style>
+    #main {
+        position: relative;
+        width: 100px;
+        height: 100px;
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful:after {
+        content: url(../resources/circles.png);
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main">
+</div>
+<script>
+    test_fcp("First contentful paint fires due to pseudo-element image.")
+</script>
+</body>
+
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-opacity-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-opacity-expected.txt
new file mode 100644 (file)
index 0000000..735790f
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS First contentful paint fires due to pseudo-element getting positive opacity. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-opacity.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-opacity.html
new file mode 100644 (file)
index 0000000..b209864
--- /dev/null
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to pseudo-element getting positive opacity</title>
+<style>
+    #main {
+        position: relative;
+        width: 100px;
+        height: 100px;
+    }
+
+    #main:after {
+        content: "TEXT";
+        opacity: 0;
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful:after {
+        opacity: 0.5;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main">
+</div>
+<script>
+    test_fcp("First contentful paint fires due to pseudo-element getting positive opacity.")
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-text-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-text-expected.txt
new file mode 100644 (file)
index 0000000..255b2b8
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS First contentful paint fires due to pseudo-element text. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-text.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-text.html
new file mode 100644 (file)
index 0000000..ea2105e
--- /dev/null
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to pseudo-element text</title>
+<style>
+    #main {
+        position: relative;
+        width: 100px;
+        height: 100px;
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful:after {
+        content: "TEXT"
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main">
+</div>
+<script>
+    test_fcp("First contentful paint fires due to pseudo-element text.")
+</script>
+</body>
+
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-visibility-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-visibility-expected.txt
new file mode 100644 (file)
index 0000000..85eceee
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS First contentful paint fires due to pseudo-element becoming visible. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-visibility.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-visibility.html
new file mode 100644 (file)
index 0000000..c903c47
--- /dev/null
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to pseudo-element becoming visible</title>
+<style>
+    #main {
+        position: relative;
+        width: 100px;
+        height: 100px;
+    }
+
+    #main:after {
+        content: "TEXT";
+        visibility: hidden;
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful:after {
+        visibility: visible;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main">
+</div>
+<script>
+    test_fcp("First contentful paint fires due to pseudo-element becoming visible.")
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-svg-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-svg-expected.txt
new file mode 100644 (file)
index 0000000..592659b
--- /dev/null
@@ -0,0 +1,3 @@
+
+PASS First contentful paint fires when SVG becomes contentful. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-svg.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-svg.html
new file mode 100644 (file)
index 0000000..bcd2372
--- /dev/null
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP with SVG</title>
+<style>
+    #main {
+        width: 100px;
+        height: 100px;
+        left: 0px;
+        position: relative;
+    }
+
+    #circle {
+      display: none;
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful #circle {
+        display: block;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main">
+  <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+    <defs>
+      <circle id="myCircle" cx="5" cy="5" r="4" stroke="blue"/>
+    </defs>
+    <use id="circle" href="#myCircle" fill="green" />
+  </svg>
+</div>
+<script>
+    test_fcp("First contentful paint fires when SVG becomes contentful.")
+</script>
+</body>
+
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-text-input-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-text-input-expected.txt
new file mode 100644 (file)
index 0000000..0200019
--- /dev/null
@@ -0,0 +1,4 @@
+
+
+PASS Text input should become contentful when its value is non-empty 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-text-input.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-text-input.html
new file mode 100644 (file)
index 0000000..b22c618
--- /dev/null
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due for non-empty text input</title>
+</head>
+<body>
+<script src="../resources/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<input id="input" type="text" />
+<script>
+  promise_test(async t => {
+      assert_precondition(window.PerformancePaintTiming, "Paint Timing isn't supported.");
+      await new Promise(r => window.addEventListener('load', r));
+      await assertNoFirstContentfulPaint(t);
+      const input = document.getElementById('input');
+      input.setAttribute('value', '  ');
+      await assertNoFirstContentfulPaint(t);
+      input.setAttribute('value', 'value');
+      await assertFirstContentfulPaint(t);
+  }, 'Text input should become contentful when its value is non-empty');
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-typographic-pseudo-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-typographic-pseudo-expected.txt
new file mode 100644 (file)
index 0000000..ea8c1db
--- /dev/null
@@ -0,0 +1,4 @@
+A
+
+PASS First contentful paint fires only when some of the text is visible, considering ::first-letter. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-typographic-pseudo.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-typographic-pseudo.html
new file mode 100644 (file)
index 0000000..5ec62b7
--- /dev/null
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP should not fire when all text is hidden due to typographic pseudo-elements</title>
+<style>
+    #main {
+        position: relative;
+        width: 100px;
+        height: 100px;
+    }
+
+    #main *::first-letter {
+        opacity: 0;
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful *::first-letter {
+        opacity: 1;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main">
+    <div>A</div>
+</div>
+<script>
+    test_fcp("First contentful paint fires only when some of the text is visible, considering ::first-letter.")
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-frame-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-frame-expected.txt
new file mode 100644 (file)
index 0000000..09f7cba
--- /dev/null
@@ -0,0 +1,4 @@
+
+
+PASS Video should become contentful when first frame is loaded 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-frame.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-frame.html
new file mode 100644 (file)
index 0000000..0f61b7e
--- /dev/null
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to loaded video frame</title>
+</head>
+<body>
+<script src="../resources/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video id="video" autoplay></video>
+<script>
+  promise_test(async t => {
+      assert_precondition(window.PerformancePaintTiming, "Paint Timing isn't supported.");
+      await new Promise(r => window.addEventListener('load', r));
+      await assertNoFirstContentfulPaint(t);
+      // Set actual video content to trigger FCP.
+      const video = document.getElementById('video');
+      video.setAttribute('src', '/media/test.mp4');
+      await new Promise(resolve => {
+        video.oncanplay = resolve;
+      });
+      await assertFirstContentfulPaint(t);
+  }, 'Video should become contentful when first frame is loaded');
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-poster-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-poster-expected.txt
new file mode 100644 (file)
index 0000000..883aec5
--- /dev/null
@@ -0,0 +1,4 @@
+
+
+PASS Video should become contentful when poster is loaded 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-poster.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-poster.html
new file mode 100644 (file)
index 0000000..5c1048a
--- /dev/null
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to loaded video poster</title>
+</head>
+<body>
+<script src="../resources/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<video id="video" width="50" height="50"></video>
+<script>
+  promise_test(async t => {
+    assert_precondition(window.PerformancePaintTiming, "Paint Timing isn't supported.");
+    await new Promise(r => window.addEventListener('load', r));
+    await assertNoFirstContentfulPaint(t);
+    const video = document.getElementById('video');
+    const src = '../resources/circles.png';
+    const image = new Image();
+    image.src = src;
+    video.setAttribute('poster', src);
+    await new Promise(resolve => {
+        image.onload = async () => resolve();
+    })
+    await assertFirstContentfulPaint(t);
+  }, 'Video should become contentful when poster is loaded');
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-whitespace-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-whitespace-expected.txt
new file mode 100644 (file)
index 0000000..059c2fe
--- /dev/null
@@ -0,0 +1,4 @@
+TEXT
+
+PASS Whitespace should not count as contentful. 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-whitespace.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-whitespace.html
new file mode 100644 (file)
index 0000000..6e1f425
--- /dev/null
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: Whitespace should not count as contentful</title>
+<style>
+    #main {
+        position: relative;
+        width: 100px;
+        height: 100px;
+        background-image: url(../resources/circles.png);
+        background-size: 0 0;
+    }
+
+    #text {
+        display: none;
+    }
+
+    /* contentful class is defined in test_fcp script. */
+    #main.contentful #text{
+        display: block;
+    }
+</style>
+</head>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
+<div id="main">
+    <div id="whitespace">   </div>
+    <div id="text">TEXT</div>
+</div>
+<script>
+    test_fcp("Whitespace should not count as contentful.")
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-with-rtl-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-with-rtl-expected.txt
new file mode 100644 (file)
index 0000000..ac0300f
--- /dev/null
@@ -0,0 +1,4 @@
+TEXT
+
+PASS FCP should fire when coordinates are negative, if within document scrollable area 
+
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-with-rtl.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-with-rtl.html
new file mode 100644 (file)
index 0000000..44449d5
--- /dev/null
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<head>
+<title>Performance Paint Timing Test: FCP due to direction change</title>
+<style>
+  #text {
+    right: -100px;
+    position: absolute;
+  }
+
+  body {
+    direction: rtl;
+  }
+
+  #dummy {
+    background: yellow;
+    width: 10px;
+    height: 10px;
+    position: absolute;
+    left: 0;
+    top: 0;
+  }
+</style>
+</head>
+<body>
+<script src="../resources/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="dummy"></div>
+<div id="text">TEXT</div>
+<script>
+  promise_test(async t => {
+      assert_precondition(window.PerformancePaintTiming, "Paint Timing isn't supported.");
+      await new Promise(r => window.addEventListener('load', r));
+      await assertNoFirstContentfulPaint(t);
+      document.body.style.direction = 'ltr'
+      document.getElementById('dummy').style.background = 'green'
+      await assertFirstContentfulPaint(t);
+  }, 'FCP should fire when coordinates are negative, if within document scrollable area');
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/circle.svg b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/circle.svg
new file mode 100644 (file)
index 0000000..6b5fdbe
--- /dev/null
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
+   <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="none" />
+</svg>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/circles.png b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/circles.png
new file mode 100644 (file)
index 0000000..708682a
Binary files /dev/null and b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/circles.png differ
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/subframe-painting.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/subframe-painting.html
new file mode 100644 (file)
index 0000000..5fb5e28
--- /dev/null
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<body>
+<div id="image"></div>
+<script>
+  const img = document.createElement('IMG');
+  img.src = 'circles.png';
+  img.onload = function() {
+    function sendPaintEntries() {
+      const paintEntries = performance.getEntriesByType('paint');
+      if (paintEntries.length < 2) {
+        setTimeout(sendPaintEntries, 20);
+        return;
+      }
+      let entryContents = paintEntries.length + '';
+      for (let i = 0; i < paintEntries.length; i++) {
+        const entry = paintEntries[i];
+        entryContents += ' ' + entry.entryType + ' '  + entry.name;
+      }
+      parent.postMessage(entryContents, '*');
+    };
+    sendPaintEntries();
+  };
+  document.getElementById('image').appendChild(img);
+</script>
+</body>
+</html>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/subframe-sending-paint.html b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/subframe-sending-paint.html
new file mode 100644 (file)
index 0000000..f372bd6
--- /dev/null
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script>
+  self.addEventListener('message', function(e) {
+    // Send paint-timing entries upon receiving a message.
+    const paintEntries = performance.getEntriesByType('paint');
+    let entryContents = paintEntries.length + '';
+    for (let i = 0; i < paintEntries.length; i++) {
+      const entry = paintEntries[i];
+      entryContents += ' ' + entry.entryType + ' '  + entry.name;
+    }
+    parent.postMessage(entryContents, '*');
+  });
+</script>
diff --git a/LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/utils.js b/LayoutTests/imported/w3c/web-platform-tests/paint-timing/resources/utils.js
new file mode 100644 (file)
index 0000000..94673b7
--- /dev/null
@@ -0,0 +1,48 @@
+function waitForAnimationFrames(count) {
+  return new Promise(resolve => {
+    if (count-- <= 0) {
+      resolve();
+    } else {
+      requestAnimationFrame(() => {
+        waitForAnimationFrames(count).then(resolve);
+      });
+    }
+  });
+}
+
+// Asserts that there is currently no FCP reported. Pass t to add some wait, in case CSS is loaded
+// and FCP is incorrectly fired afterwards.
+async function assertNoFirstContentfulPaint(t) {
+  await waitForAnimationFrames(3);
+  assert_equals(performance.getEntriesByName('first-contentful-paint').length, 0, 'First contentful paint marked too early. ');
+}
+
+// Function that is resolved once FCP is reported, using PerformanceObserver. It rejects after a long
+// wait time so that failing tests don't timeout.
+async function assertFirstContentfulPaint(t) {
+  return new Promise(resolve  => {
+    function checkFCP() {
+      if (performance.getEntriesByName('first-contentful-paint').length === 1) {
+        resolve();
+      } else {
+        requestAnimationFrame(checkFCP)
+      }
+    }
+    t.step(checkFCP);
+  });
+}
+
+async function test_fcp(label) {
+  const style = document.createElement('style');
+  document.head.appendChild(style);
+  await promise_test(async t => {
+    assert_precondition(window.PerformancePaintTiming, "Paint Timing isn't supported.");
+    const main = document.getElementById('main');
+    await new Promise(r => window.addEventListener('load', r));
+    await assertNoFirstContentfulPaint(t);
+    main.className = 'preFCP';
+    await assertNoFirstContentfulPaint(t);
+    main.className = 'contentful';
+    await assertFirstContentfulPaint(t);
+  }, label);
+}
diff --git a/LayoutTests/performance-api/paint-timing/paint-timing-apis-expected.txt b/LayoutTests/performance-api/paint-timing/paint-timing-apis-expected.txt
new file mode 100644 (file)
index 0000000..f97a829
--- /dev/null
@@ -0,0 +1,13 @@
+Basic Interface test for paint-timing APIs.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PerformancePaintTiming
+PASS PerformancePaintTiming is defined.
+PASS PerformancePaintTiming.prototype.toJSON is defined.
+PASS new PerformancePaintTiming() threw exception TypeError: function is not a constructor (evaluating 'new PerformancePaintTiming()').
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/performance-api/paint-timing/paint-timing-apis.html b/LayoutTests/performance-api/paint-timing/paint-timing-apis.html
new file mode 100644 (file)
index 0000000..4a82ecd
--- /dev/null
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<script src="resources/paint-timing-api.js"></script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/performance-api/paint-timing/paint-timing-frames-expected.txt b/LayoutTests/performance-api/paint-timing/paint-timing-frames-expected.txt
new file mode 100644 (file)
index 0000000..beb8c24
--- /dev/null
@@ -0,0 +1,20 @@
+Paint API should be available for same-origin frames but not to cross-origin/sandboxed frames.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS receivedEvent.source is document.getElementById('nonSandboxed').contentWindow
+PASS receivedEntry.name is 'first-contentful-paint'
+PASS didReceiveMessageFromSandboxedIframe is false
+PASS Array.isArray(nonSandboxSupportedTypes) is true
+PASS Array.isArray(sandboxSupportedTypes) is true
+PASS didReceiveMessage is true
+PASS nonSandboxSupportedTypes.includes("paint") is true
+PASS sandboxSupportedTypes.includes("paint") is false
+PASS PerformanceObserver first-contentful-paint callback was fired for same-origin frame but not for cross-origin frame
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+
+TEXT
diff --git a/LayoutTests/performance-api/paint-timing/paint-timing-frames.html b/LayoutTests/performance-api/paint-timing/paint-timing-frames.html
new file mode 100644 (file)
index 0000000..645740c
--- /dev/null
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="./resources/paint-api-utils.js"></script>
+</head>
+<body>
+<div class="spacer"></div>
+    <iframe width="100" height="100" id="nonSandboxed" src="./resources/fcp-subframe.html"></iframe>
+<div class="spacer"></div>
+<iframe id="sandboxed" src="./resources/fcp-subframe.html" sandbox="allow-scripts"></iframe>
+<div class="spacer"></div>
+
+    TEXT
+
+<script>
+description("Paint API should be available for same-origin frames but not to cross-origin/sandboxed frames.");
+window.jsTestIsAsync = true;
+window.didReceiveMessage = false;
+
+window.onload = async () => {
+    const sandboxed = document.getElementById('sandboxed');
+    const nonSandboxed = document.getElementById('nonSandboxed');
+    const fcpMessagePromise = new Promise(resolve => {
+        window.onmessage = event => {
+            window.receivedEvent = event;
+            const isFromSandboxed = event.source === sandboxed.contentWindow;
+            const message = JSON.parse(event.data);
+            switch (message.type) {
+                case 'supported':
+                    if (isFromSandboxed)
+                        window.sandboxSupportedTypes = message.supported;
+                    else
+                        window.nonSandboxSupportedTypes = message.supported;
+                    break;
+                case 'received':
+                    window.didReceiveMessageFromSandboxedIframe = isFromSandboxed;
+                    window.receivedEntry = message.entry;
+                    didReceiveMessage = true;
+                    shouldBe('receivedEvent.source', "document.getElementById('nonSandboxed').contentWindow");
+                    shouldBe('receivedEntry.name', "'first-contentful-paint'");
+                    resolve();
+                    break;
+            }
+        };
+    });
+
+    sandboxed.contentWindow.postMessage("paint", "*");
+    await waitForFCP();
+    nonSandboxed.contentWindow.postMessage("paint", "*");
+    await waitForFCP();
+    await fcpMessagePromise;
+    shouldBeFalse('didReceiveMessageFromSandboxedIframe');
+    shouldBeTrue('Array.isArray(nonSandboxSupportedTypes)');
+    shouldBeTrue('Array.isArray(sandboxSupportedTypes)');
+    shouldBeTrue('didReceiveMessage');
+    shouldBeTrue('nonSandboxSupportedTypes.includes("paint")');
+    shouldBeFalse('sandboxSupportedTypes.includes("paint")');
+    testPassed("PerformanceObserver first-contentful-paint callback was fired for same-origin frame but not for cross-origin frame");
+    finishJSTest();
+};
+
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/performance-api/paint-timing/paint-timing-with-worker-expected.txt b/LayoutTests/performance-api/paint-timing/paint-timing-with-worker-expected.txt
new file mode 100644 (file)
index 0000000..07813cf
--- /dev/null
@@ -0,0 +1,13 @@
+Paint API should not be available in worker contexts.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS PerformanceObserver.supportedEntryTypes.includes("paint") is true
+PASS workerSupportedEntryTypes.includes("paint") is false
+PASS workerSupportedEntryTypes.includes("mark") is true
+PASS PerformanceObserver.supportedEntryTypes did not include 'paint' for workers
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/performance-api/paint-timing/paint-timing-with-worker.html b/LayoutTests/performance-api/paint-timing/paint-timing-with-worker.html
new file mode 100644 (file)
index 0000000..55edf5f
--- /dev/null
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="./resources/paint-api-utils.js"></script>
+</head>
+<body>
+<script>
+description("Paint API should not be available in worker contexts.");
+window.jsTestIsAsync = true;
+const worker = new Worker("./resources/worker.html");
+worker.onmessage = event => {
+    window.workerSupportedEntryTypes = JSON.parse(event.data);
+    shouldBeTrue('PerformanceObserver.supportedEntryTypes.includes("paint")');
+    shouldBeFalse('workerSupportedEntryTypes.includes("paint")');
+    shouldBeTrue('workerSupportedEntryTypes.includes("mark")');
+    testPassed("PerformanceObserver.supportedEntryTypes did not include 'paint' for workers");
+    finishJSTest();
+};
+
+worker.postMessage("start");
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/performance-api/paint-timing/performance-observer-first-contentful-paint-expected.txt b/LayoutTests/performance-api/paint-timing/performance-observer-first-contentful-paint-expected.txt
new file mode 100644 (file)
index 0000000..eac409d
--- /dev/null
@@ -0,0 +1,10 @@
+PASS didReceiveFirstContentfulPaint is true
+PASS performance.getEntries().length is 1
+PASS performance.getEntriesByName("first-contentful-paint").length is 1
+PASS performance.getEntriesByName("first-contentful-paint", "paint").length is 1
+PASS performance.getEntriesByType("paint").length is 1
+PASS PerformanceObserver first-contentful-paint callback wasn't fired again 
+PASS successfullyParsed is true
+
+TEST COMPLETE
+TEXT2
diff --git a/LayoutTests/performance-api/paint-timing/performance-observer-first-contentful-paint.html b/LayoutTests/performance-api/paint-timing/performance-observer-first-contentful-paint.html
new file mode 100644 (file)
index 0000000..0078a43
--- /dev/null
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+<script src="./resources/paint-api-utils.js"></script>
+</head>
+<body>
+<script>
+description("Ensure PerformanceObservers are notified once for first-contentful-paint.");
+window.jsTestIsAsync = true;
+(async () => {
+    window.didReceiveFirstContentfulPaint = false;
+    window.startTime = performance.now();
+    await waitForFCP();
+    await new Promise(resolve => {
+        const observer = new PerformanceObserver(entryList => {
+            window.entryList = entryList;
+            shouldBe('entryList.getEntries().length', "1");
+            shouldBe('entryList.getEntriesByName("first-contentful-paint").length', 1);
+            shouldBe('entryList.getEntriesByType("paint").length', "1");
+            window.fcpEntry = entryList.getEntries()[0];
+            shouldBeFalse('didReceiveFirstContentfulPaint');
+            shouldBe('fcpEntry.name', '"first-contentful-paint"');
+            shouldBe('fcpEntry.entryType', '"paint"');
+            shouldBeTrue('fcpEntry.startTime > window.startTime');
+            shouldBeTrue('fcpEntry.startTime < performance.now()');
+            shouldBe('fcpEntry.duration', "0");
+            didReceiveFirstContentfulPaint = true;
+            resolve();
+        });
+
+        observer.observe({entryTypes: ['paint']});
+        document.body.innerText = 'TEXT';
+    })
+    document.body.innerText = 'TEXT2';
+    await waitForFCP();
+    shouldBeTrue('didReceiveFirstContentfulPaint');
+    shouldBe('performance.getEntries().length', "1");
+    shouldBe('performance.getEntriesByName("first-contentful-paint").length', "1");
+    shouldBe('performance.getEntriesByName("first-contentful-paint", "paint").length', "1");
+    shouldBe('performance.getEntriesByType("paint").length', "1");
+    testPassed("PerformanceObserver first-contentful-paint callback wasn't fired again ");
+    finishJSTest();
+})();
+
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/performance-api/paint-timing/resources/fcp-subframe.html b/LayoutTests/performance-api/paint-timing/resources/fcp-subframe.html
new file mode 100644 (file)
index 0000000..65e0f49
--- /dev/null
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script>
+    const observer = new PerformanceObserver(list => {
+        list.getEntries().forEach(entry => {
+            window.parent.postMessage(JSON.stringify({type: 'received', entry}), '*');
+        });
+    });
+
+    observer.observe({entryTypes: ["paint"]});
+    window.onmessage = e => {
+        window.parent.postMessage(JSON.stringify({supported: PerformanceObserver.supportedEntryTypes, type: 'supported'}), '*');
+        document.body.innerText = "TEXT";
+    }
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/LayoutTests/performance-api/paint-timing/resources/paint-api-utils.js b/LayoutTests/performance-api/paint-timing/resources/paint-api-utils.js
new file mode 100644 (file)
index 0000000..5a7ceef
--- /dev/null
@@ -0,0 +1,5 @@
+async function waitForFCP()
+{
+    await new Promise(resolve => requestAnimationFrame(() => resolve()));
+    return window.performance.getEntriesByName('first-contentful-paint').length == 1;
+}
\ No newline at end of file
diff --git a/LayoutTests/performance-api/paint-timing/resources/paint-timing-api.js b/LayoutTests/performance-api/paint-timing/resources/paint-timing-api.js
new file mode 100644 (file)
index 0000000..d51cf8d
--- /dev/null
@@ -0,0 +1,13 @@
+if (self.importScripts)
+    importScripts("../../../resources/js-test-pre.js");
+
+if (self.window)
+    description("Basic Interface test for paint-timing APIs.");
+
+debug("PerformancePaintTiming");
+shouldBeDefined("PerformancePaintTiming");
+shouldBeDefined(`PerformancePaintTiming.prototype.toJSON`);
+shouldThrow(`new PerformancePaintTiming()`);
+
+if (self.importScripts)
+    finishJSTest();
diff --git a/LayoutTests/performance-api/paint-timing/resources/worker.html b/LayoutTests/performance-api/paint-timing/resources/worker.html
new file mode 100644 (file)
index 0000000..5893273
--- /dev/null
@@ -0,0 +1,3 @@
+self.onmessage = () => {
+    self.postMessage(JSON.stringify(PerformanceObserver.supportedEntryTypes));
+}
\ No newline at end of file
index 98779c1..937b03d 100644 (file)
@@ -557,6 +557,11 @@ http/tests/inspector/network/har/har-page.html [ Failure ]
 [ Release ] http/tests/inspector/network/har/har-page-aggressive-gc.html [ Failure ]
 [ Debug ] http/tests/inspector/network/har/har-page-aggressive-gc.html [ Slow Failure ]
 
+# Paint Timing is not available in WebKit1
+imported/w3c/web-platform-tests/paint-timing [ Skip ]
+performance-api/paint-timing [ Skip ]
+http/tests/performance/paint-timing [ Skip ]
+
 # Local Overrides not available in WebKit1
 http/tests/inspector/network/local-resource-override-basic.html [ Failure ]
 http/tests/inspector/network/local-resource-override-main-resource.html [ Failure ]
index a62ad24..2bb6045 100644 (file)
@@ -3703,6 +3703,11 @@ http/wpt/webrtc [ Skip ]
 http/wpt/resource-timing/rt-nextHopProtocol.html [ Skip ]
 http/wpt/resource-timing/rt-nextHopProtocol.worker.html [ Skip ]
 
+# TODO Paint Timing is not enabled for WK1.
+http/tests/performance/paint-timing [ Skip ]
+imported/w3c/web-platform-tests/paint-timing [ Skip ]
+performance-api/paint-timing [ Skip ]
+
 # WebCrypto tests are failing.
 webkit.org/b/165090 imported/w3c/web-platform-tests/WebCryptoAPI [ Skip ]
 webkit.org/b/165090 http/wpt/crypto [ Skip ]
index 35d0cce..71a6f0c 100644 (file)
@@ -936,6 +936,7 @@ set(WebCore_NON_SVG_IDL_FILES
     page/PerformanceObserverCallback.idl
     page/PerformanceObserverEntryList.idl
     page/PerformanceNavigation.idl
+    page/PerformancePaintTiming.idl
     page/PerformanceResourceTiming.idl
     page/PerformanceServerTiming.idl
     page/PerformanceTiming.idl
index 178b577..1cc757e 100644 (file)
@@ -1,3 +1,128 @@
+2020-04-28  Noam Rosenthal  <noam@webkit.org>
+
+        Implement FCP (first contentful paint)
+        https://bugs.webkit.org/show_bug.cgi?id=208499
+
+        Reviewed by Simon Fraser.
+
+        Added the necessary interface, extensions to the performance interface and observer, new runtime flag.
+        Detecting contentfulness after layout and before actual paint, by running a "dummy" paint, similar to 
+        invalidateControlTints() / invalidateImagesWithAsyncDecodes(). Save the result to the GraphicsContext and then to the document.
+        This would run for every paint until we detect a contentful one.
+
+        Note that it paints the entire frame contents, as FCP is not viewport-dependent.
+        Also, paint timing is currently disabled for LFC (layout formatting context), and will be dealt with later.
+
+        Tests: http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-num-chars.html
+               http/tests/performance/paint-timing/performance-paint-timing-fcp-after-visually-non-empty-for-style.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-background-size.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-set.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-bg-image-two-steps.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-canvas-context.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-gradient.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate-descendant.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-3d-rotate.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale-transition.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-scale.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-invisible-text.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity-descendant.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-opacity.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds-translate.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-out-of-bounds.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-display.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-image.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-opacity.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-text.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-pseudo-element-visibility.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-svg.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-text-input.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-typographic-pseudo.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-frame.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-video-poster.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-whitespace.html
+               imported/w3c/web-platform-tests/paint-timing/fcp-only/fcp-with-rtl.html
+               performance-api/paint-timing/paint-timing-apis.html
+               performance-api/paint-timing/paint-timing-frames.html
+               performance-api/paint-timing/paint-timing-with-worker.html
+               performance-api/paint-timing/performance-observer-first-contentful-paint.html
+
+        * CMakeLists.txt:
+        * DerivedSources.make:
+        * Sources.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * bindings/js/JSPerformanceEntryCustom.cpp:
+        (WebCore::toJSNewlyCreated):
+        * bindings/js/WebCoreBuiltinNames.h:
+                Add PerformancePaintTiming interface. https://w3c.github.io/paint-timing/#sec-PerformancePaintTiming
+
+        * dom/Document.cpp:
+        * dom/Document.h:
+        (WebCore::Document::supportsPaintTiming const):
+                We only report paint timing for document that can access the top level security origin, to avoid leakage of information to sandboxed iframes.
+
+        (WebCore::Document::enqueuePaintTimingEntryIfNeeded):
+                Enqueue a paint timing entry, according to https://w3c.github.io/paint-timing/#sec-reporting-paint-timing
+
+        * page/FrameView.cpp:
+        (WebCore::FrameView::paintContents):
+                Disable FCP fake-paint in LFC mode.
+
+        * page/Page.cpp:
+        (WebCore::Page::doAfterUpdateRendering):
+        * page/Performance.cpp:
+        (WebCore::Performance::getEntries const):
+        (WebCore::Performance::getEntriesByType const):
+        (WebCore::Performance::getEntriesByName const):
+        (WebCore::Performance::reportFirstContentfulPaint):
+        * page/Performance.h:
+                Support first-contentful-paint reporting.
+
+        * page/PerformanceEntry.cpp:
+        (WebCore::PerformanceEntry::parseEntryTypeString):
+        * page/PerformanceEntry.h:
+        (WebCore::PerformanceEntry::isPaint const):
+        * page/PerformanceObserver.cpp:
+        (WebCore::PerformanceObserver::supportedEntryTypes):
+        * page/PerformanceObserver.h:
+        * page/PerformanceObserver.idl:
+        * page/PerformancePaintTiming.h: Added.
+        (isType):
+        * page/PerformancePaintTiming.idl: Added.
+                Add paint performance entry type.
+
+        * page/RuntimeEnabledFeatures.h:
+        (WebCore::RuntimeEnabledFeatures::setPaintTimingEnabled):
+        (WebCore::RuntimeEnabledFeatures::paintTimingEnabled const):
+                New runtime flag for paint timing.
+
+        * platform/graphics/GraphicsContext.h:
+        (WebCore::GraphicsContext::detectingContentfulPaint const):
+        (WebCore::GraphicsContext::setContentfulPaintDetected):
+        (WebCore::GraphicsContext::contenfulPaintDetected const):
+                Add a flag in GraphicsContext where different render operations can report if they're contentful.
+
+        * rendering/ContentfulPaintChecker.cpp: Added.
+        (WebCore::ContentfulPaintChecker::qualifiesForContentfulPaint):
+        * rendering/ContentfulPaintChecker.h: Added.
+                Run a "dummy" paint of the FrameView, to detect contentful elements. 
+
+        * rendering/RenderBoxModelObject.cpp:
+        (WebCore::RenderBoxModelObject::paintFillLayerExtended):
+        * rendering/RenderHTMLCanvas.cpp:
+        (WebCore::RenderHTMLCanvas::paintReplaced):
+        * rendering/RenderImage.cpp:
+        (WebCore::RenderImage::paintReplaced):
+        * rendering/RenderLayer.cpp:
+        (WebCore::RenderLayer::paintLayerContents):
+        * rendering/RenderVideo.cpp:
+        (WebCore::RenderVideo::paintReplaced):
+        * rendering/TextPainter.cpp:
+        (WebCore::TextPainter::paintTextOrEmphasisMarks):
+        * rendering/svg/RenderSVGRoot.cpp:
+        (WebCore::RenderSVGRoot::paintReplaced):
+                Report contentfulness when we reach anything that qualifies as contentful,
+                based on https://w3c.github.io/paint-timing/#contentful.
+
 2020-04-28  Simon Fraser  <simon.fraser@apple.com>
 
         MRMediaRemoteSetNowPlayingApplicationPlaybackStateForOrigin log with no error
index 850dd07..7f72b5d 100644 (file)
@@ -930,6 +930,7 @@ JS_BINDING_IDLS = \
     $(WebCore)/page/PerformanceObserver.idl \
     $(WebCore)/page/PerformanceObserverCallback.idl \
     $(WebCore)/page/PerformanceObserverEntryList.idl \
+    $(WebCore)/page/PerformancePaintTiming.idl \
     $(WebCore)/page/PerformanceResourceTiming.idl \
     $(WebCore)/page/PerformanceServerTiming.idl \
     $(WebCore)/page/PerformanceTiming.idl \
index 9d2531b..987ebe5 100644 (file)
@@ -2125,6 +2125,7 @@ rendering/BreakLines.cpp
 rendering/CSSFilter.cpp
 rendering/ClipRect.cpp
 rendering/ComplexLineLayout.cpp
+rendering/ContentfulPaintChecker.cpp
 rendering/CounterNode.cpp
 rendering/EllipsisBox.cpp
 rendering/EventRegion.cpp
@@ -3203,6 +3204,7 @@ JSPerformanceNavigation.cpp
 JSPerformanceObserver.cpp
 JSPerformanceObserverCallback.cpp
 JSPerformanceObserverEntryList.cpp
+JSPerformancePaintTiming.cpp
 JSPerformanceResourceTiming.cpp
 JSPerformanceServerTiming.cpp
 JSPerformanceTiming.cpp
index 97f6301..760c85e 100644 (file)
                C9F87CFE1B28F40E00979B83 /* MediaSessionEvents.h in Headers */ = {isa = PBXBuildFile; fileRef = C9F87CFD1B28E5F600979B83 /* MediaSessionEvents.h */; settings = {ATTRIBUTES = (Private, ); }; };
                CA3BF67E10D99BAE00E6CE53 /* ScrollAnimator.h in Headers */ = {isa = PBXBuildFile; fileRef = CA3BF67D10D99BAE00E6CE53 /* ScrollAnimator.h */; settings = {ATTRIBUTES = (Private, ); }; };
                CA6C1530220B98CC0055CBFC /* JSValueInWrappedObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 931AE3B81FB80EAE00F5EFB2 /* JSValueInWrappedObject.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               CA7E9B4924361DC5004E89F6 /* ContentfulPaintChecker.h in Headers */ = {isa = PBXBuildFile; fileRef = CA7E9B4724361DC5004E89F6 /* ContentfulPaintChecker.h */; };
                CAE9F910146441F000C245B0 /* CSSAspectRatioValue.h in Headers */ = {isa = PBXBuildFile; fileRef = CAE9F90E146441F000C245B0 /* CSSAspectRatioValue.h */; };
                CB38FD521CCF939400592A3F /* JSPerformanceEntry.h in Headers */ = {isa = PBXBuildFile; fileRef = CB38FD4E1CCF937E00592A3F /* JSPerformanceEntry.h */; };
                CB38FD5B1CD2325B00592A3F /* JSPerformanceResourceTiming.h in Headers */ = {isa = PBXBuildFile; fileRef = CB38FD591CD2314500592A3F /* JSPerformanceResourceTiming.h */; };
                CA091D8623CF907800AD4346 /* StyleImageSet.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = StyleImageSet.cpp; sourceTree = "<group>"; };
                CA091D8923CF908800AD4346 /* StyleImageSet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StyleImageSet.h; sourceTree = "<group>"; };
                CA1635DC2072E76900E7D2CE /* ReferrerPolicy.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ReferrerPolicy.cpp; sourceTree = "<group>"; };
+               CA1933D52407D6400071241A /* PerformancePaintTiming.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PerformancePaintTiming.h; sourceTree = "<group>"; };
+               CA1933D82407EDCF0071241A /* PerformancePaintTiming.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = PerformancePaintTiming.idl; sourceTree = "<group>"; };
                CA3BF67B10D99BAE00E6CE53 /* ScrollAnimator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScrollAnimator.cpp; sourceTree = "<group>"; };
                CA3BF67D10D99BAE00E6CE53 /* ScrollAnimator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScrollAnimator.h; sourceTree = "<group>"; };
                CA52BAE523D0AF1600DE0B84 /* StyleCursorImage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = StyleCursorImage.cpp; sourceTree = "<group>"; };
                CA6C152F220B4A550055CBFC /* JSIDBRequestCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSIDBRequestCustom.cpp; sourceTree = "<group>"; };
                CA6C1537220D1EB30055CBFC /* JSIDBCursorCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSIDBCursorCustom.cpp; sourceTree = "<group>"; };
                CA6C1538220D1EB30055CBFC /* JSIDBCursorWithValueCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSIDBCursorWithValueCustom.cpp; sourceTree = "<group>"; };
+               CA7E9B4724361DC5004E89F6 /* ContentfulPaintChecker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContentfulPaintChecker.h; sourceTree = "<group>"; };
+               CA7E9B4824361DC5004E89F6 /* ContentfulPaintChecker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ContentfulPaintChecker.cpp; sourceTree = "<group>"; };
                CAE9F90D146441F000C245B0 /* CSSAspectRatioValue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CSSAspectRatioValue.cpp; sourceTree = "<group>"; };
                CAE9F90E146441F000C245B0 /* CSSAspectRatioValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSSAspectRatioValue.h; sourceTree = "<group>"; };
                CB38FD4A1CCCF2DD00592A3F /* PerformanceEntry.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PerformanceEntry.cpp; sourceTree = "<group>"; };
                                A5A9933A1E37FAFD005B5E4D /* PerformanceObserverEntryList.cpp */,
                                A5A9933B1E37FAFD005B5E4D /* PerformanceObserverEntryList.h */,
                                A5A9933C1E37FAFD005B5E4D /* PerformanceObserverEntryList.idl */,
+                               CA1933D52407D6400071241A /* PerformancePaintTiming.h */,
+                               CA1933D82407EDCF0071241A /* PerformancePaintTiming.idl */,
                                86512EDB154A2AEE00A90426 /* PerformanceResourceTiming.cpp */,
                                86512EDC154A2AEF00A90426 /* PerformanceResourceTiming.h */,
                                86512EDD154A2AEF00A90426 /* PerformanceResourceTiming.idl */,
                                BCDD454D1236C95C009A7985 /* ColumnInfo.h */,
                                BCEA4813097D93020094C9E4 /* ComplexLineLayout.cpp */,
                                E4A1AC7822FAFD500017B75B /* ComplexLineLayout.h */,
+                               CA7E9B4824361DC5004E89F6 /* ContentfulPaintChecker.cpp */,
+                               CA7E9B4724361DC5004E89F6 /* ContentfulPaintChecker.h */,
                                9392F14F0AD1862300691BD4 /* CounterNode.cpp */,
                                9392F14B0AD1861B00691BD4 /* CounterNode.h */,
                                0F53FB81213B1BB800C40D34 /* CSSFilter.cpp */,
                                A149786F1ABAF33800CEF7E4 /* ContentFilter.h in Headers */,
                                5CDCDDC623D80A1C00BA34A1 /* ContentFilterClient.h in Headers */,
                                A14090FD1AA51E480091191A /* ContentFilterUnblockHandler.h in Headers */,
+                               CA7E9B4924361DC5004E89F6 /* ContentfulPaintChecker.h in Headers */,
                                5C9C2DB52241A67B00996B0B /* ContentRuleListResults.h in Headers */,
                                97C471DC12F925BD0086354B /* ContentSecurityPolicy.h in Headers */,
                                CE5FA255209E48C50051D700 /* ContentSecurityPolicyClient.h in Headers */,
index 3178a1f..bbf8111 100644 (file)
 #include "JSDOMBinding.h"
 #include "JSPerformanceMark.h"
 #include "JSPerformanceMeasure.h"
+#include "JSPerformancePaintTiming.h"
 #include "JSPerformanceResourceTiming.h"
 #include "PerformanceMark.h"
 #include "PerformanceMeasure.h"
+#include "PerformancePaintTiming.h"
 #include "PerformanceResourceTiming.h"
 
 
@@ -46,6 +48,9 @@ using namespace JSC;
 
 JSValue toJSNewlyCreated(JSGlobalObject*, JSDOMGlobalObject* globalObject, Ref<PerformanceEntry>&& entry)
 {
+    if (is<PerformancePaintTiming>(entry))
+        return createWrapper<PerformancePaintTiming>(globalObject, WTFMove(entry));
+
     if (is<PerformanceResourceTiming>(entry))
         return createWrapper<PerformanceResourceTiming>(globalObject, WTFMove(entry));
 
index ee11b36..470ab3a 100644 (file)
@@ -182,6 +182,7 @@ namespace WebCore {
     macro(PerformanceMeasure) \
     macro(PerformanceObserver) \
     macro(PerformanceObserverEntryList) \
+    macro(PerformancePaintTiming) \
     macro(PerformanceResourceTiming) \
     macro(PerformanceServerTiming) \
     macro(PointerEvent) \
index 6907862..c6d958b 100644 (file)
@@ -48,6 +48,7 @@
 #include "CompositionEvent.h"
 #include "ConstantPropertyMap.h"
 #include "ContentSecurityPolicy.h"
+#include "ContentfulPaintChecker.h"
 #include "CookieJar.h"
 #include "CustomElementReactionQueue.h"
 #include "CustomElementRegistry.h"
 #include "PageGroup.h"
 #include "PageTransitionEvent.h"
 #include "PaintWorkletGlobalScope.h"
+#include "Performance.h"
 #include "PlatformLocale.h"
 #include "PlatformMediaSessionManager.h"
 #include "PlatformScreen.h"
@@ -3147,6 +3149,34 @@ bool Document::isLayoutTimerActive() const
     return view() && view()->layoutContext().isLayoutPending();
 }
 
+bool Document::supportsPaintTiming() const
+{
+    return RuntimeEnabledFeatures::sharedFeatures().paintTimingEnabled() && securityOrigin().canAccess(topOrigin());
+}
+
+// https://w3c.github.io/paint-timing/#ref-for-mark-paint-timing
+void Document::enqueuePaintTimingEntryIfNeeded()
+{
+    if (m_didEnqueueFirstContentfulPaint)
+        return;
+
+    if (!supportsPaintTiming())
+        return;
+
+    if (!domWindow() || !view())
+        return;
+
+    // To make sure we don't report paint while the layer tree is still frozen.
+    if (!view()->isVisuallyNonEmpty() || view()->needsLayout())
+        return;
+
+    if (!ContentfulPaintChecker::qualifiesForContentfulPaint(*view()))
+        return;
+
+    domWindow()->performance().reportFirstContentfulPaint();
+    m_didEnqueueFirstContentfulPaint = true;
+}
+
 ExceptionOr<void> Document::write(Document* responsibleDocument, SegmentedString&& text)
 {
     if (m_activeParserWasAborted)
index 1869e28..d8280a7 100644 (file)
@@ -1040,6 +1040,8 @@ public:
 
     bool shouldDeferAsynchronousScriptsUntilParsingFinishes() const;
 
+    bool supportsPaintTiming() const;
+
 #if ENABLE(XSLT)
     void scheduleToApplyXSLTransforms();
     void applyPendingXSLTransformsNowIfScheduled();
@@ -1579,6 +1581,7 @@ public:
 
     void setHasVisuallyNonEmptyCustomContent() { m_hasVisuallyNonEmptyCustomContent = true; }
     bool hasVisuallyNonEmptyCustomContent() const { return m_hasVisuallyNonEmptyCustomContent; }
+    void enqueuePaintTimingEntryIfNeeded();
 
     Editor& editor() { return m_editor; }
     const Editor& editor() const { return m_editor; }
@@ -2045,6 +2048,9 @@ private:
 
     bool m_areDeviceMotionAndOrientationUpdatesSuspended { false };
     bool m_userDidInteractWithPage { false };
+
+    bool m_didEnqueueFirstContentfulPaint { false };
+
 #if ASSERT_ENABLED
     bool m_inHitTesting { false };
 #endif
index 712dc79..b3fe140 100644 (file)
@@ -4252,6 +4252,9 @@ void FrameView::paintContents(GraphicsContext& context, const IntRect& dirtyRect
 
 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
     if (RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextEnabled()) {
+        // Integrate paint timing with LFC later on.
+        if (context.detectingContentfulPaint())
+            return;
         if (auto* layoutState = layoutContext().layoutFormattingState())
             Layout::LayoutContext::paint(*layoutState, context, dirtyRect);
         return;
index 7b4a522..383059b 100644 (file)
@@ -1408,6 +1408,10 @@ void Page::doAfterUpdateRendering()
     // layout to be up-to-date. It should not run script, trigger layout, or dirty layout.
 
     forEachDocument([] (Document& document) {
+        document.enqueuePaintTimingEntryIfNeeded();
+    });
+
+    forEachDocument([] (Document& document) {
         document.updateHighlightPositions();
     });
 
index 7eb7d3d..a1443d1 100644 (file)
@@ -42,6 +42,7 @@
 #include "PerformanceEntry.h"
 #include "PerformanceNavigation.h"
 #include "PerformanceObserver.h"
+#include "PerformancePaintTiming.h"
 #include "PerformanceResourceTiming.h"
 #include "PerformanceTiming.h"
 #include "PerformanceUserTiming.h"
@@ -129,6 +130,9 @@ Vector<RefPtr<PerformanceEntry>> Performance::getEntries() const
         entries.appendVector(m_userTiming->getMeasures());
     }
 
+    if (m_firstContentfulPaint)
+        entries.append(m_firstContentfulPaint);
+
     std::sort(entries.begin(), entries.end(), PerformanceEntry::startTimeCompareLessThan);
     return entries;
 }
@@ -140,6 +144,9 @@ Vector<RefPtr<PerformanceEntry>> Performance::getEntriesByType(const String& ent
     if (equalLettersIgnoringASCIICase(entryType, "resource"))
         entries.appendVector(m_resourceTimingBuffer);
 
+    if (m_firstContentfulPaint && equalLettersIgnoringASCIICase(entryType, "paint"))
+        entries.append(m_firstContentfulPaint);
+
     if (m_userTiming) {
         if (equalLettersIgnoringASCIICase(entryType, "mark"))
             entries.appendVector(m_userTiming->getMarks());
@@ -162,6 +169,9 @@ Vector<RefPtr<PerformanceEntry>> Performance::getEntriesByName(const String& nam
         }
     }
 
+    if (m_firstContentfulPaint && (entryType.isNull() || equalLettersIgnoringASCIICase(entryType, "paint")) && name == "first-contentful-paint")
+        entries.append(m_firstContentfulPaint);
+
     if (m_userTiming) {
         if (entryType.isNull() || equalLettersIgnoringASCIICase(entryType, "mark"))
             entries.appendVector(m_userTiming->getMarks(name));
@@ -185,6 +195,13 @@ void Performance::setResourceTimingBufferSize(unsigned size)
     m_resourceTimingBufferFullFlag = false;
 }
 
+void Performance::reportFirstContentfulPaint()
+{
+    ASSERT(!m_firstContentfulPaint);
+    m_firstContentfulPaint = PerformancePaintTiming::createFirstContentfulPaint(now());
+    queueEntry(*m_firstContentfulPaint);
+}
+
 void Performance::addResourceTiming(ResourceTiming&& resourceTiming)
 {
     ASSERT(scriptExecutionContext());
index 53d6f12..f631c6d 100644 (file)
@@ -46,6 +46,7 @@ class LoadTiming;
 class PerformanceEntry;
 class PerformanceNavigation;
 class PerformanceObserver;
+class PerformancePaintTiming;
 class PerformanceTiming;
 class ResourceResponse;
 class ResourceTiming;
@@ -79,6 +80,8 @@ public:
 
     void addResourceTiming(ResourceTiming&&);
 
+    void reportFirstContentfulPaint();
+
     void removeAllObservers();
     void registerPerformanceObserver(PerformanceObserver&);
     void unregisterPerformanceObserver(PerformanceObserver&);
@@ -123,6 +126,7 @@ private:
 
     MonotonicTime m_timeOrigin;
 
+    RefPtr<PerformancePaintTiming> m_firstContentfulPaint;
     std::unique_ptr<UserTiming> m_userTiming;
 
     GenericTaskQueue<ScriptExecutionContext> m_performanceTimelineTaskQueue;
index 94c3eca..42c051e 100644 (file)
@@ -64,6 +64,11 @@ Optional<PerformanceEntry::Type> PerformanceEntry::parseEntryTypeString(const St
             return Optional<Type>(Type::Resource);
     }
 
+    if (RuntimeEnabledFeatures::sharedFeatures().paintTimingEnabled()) {
+        if (entryType == "paint")
+            return Optional<Type>(Type::Paint);
+    }
+
     return WTF::nullopt;
 }
 
index 9b4f1e5..afacfb0 100644 (file)
@@ -48,10 +48,11 @@ public:
     double duration() const { return m_duration; }
 
     enum class Type {
-        Navigation = 1 << 0,
-        Mark = 1 << 1,
-        Measure = 1 << 2,
-        Resource = 1 << 3,
+        Navigation  = 1 << 0,
+        Mark        = 1 << 1,
+        Measure     = 1 << 2,
+        Resource    = 1 << 3,
+        Paint       = 1 << 4
     };
 
     virtual Type type() const = 0;
@@ -62,6 +63,7 @@ public:
     bool isResource() const { return type() == Type::Resource; }
     bool isMark() const { return type() == Type::Mark; }
     bool isMeasure() const { return type() == Type::Measure; }
+    bool isPaint() const { return type() == Type::Paint; }
 
     static bool startTimeCompareLessThan(const RefPtr<PerformanceEntry>& a, const RefPtr<PerformanceEntry>& b)
     {
index 9243504..124f4ee 100644 (file)
@@ -113,15 +113,21 @@ void PerformanceObserver::deliver()
     InspectorInstrumentation::didFireObserverCallback(*context);
 }
 
-Vector<String> PerformanceObserver::supportedEntryTypes()
+Vector<String> PerformanceObserver::supportedEntryTypes(ScriptExecutionContext& context)
 {
-    return {
+    Vector<String> entryTypes = {
         // FIXME: <https://webkit.org/b/184363> Add support for Navigation Timing Level 2
         // "navigation"_s,
         "mark"_s,
-        "measure"_s,
-        "resource"_s
+        "measure"_s
     };
+
+    if (is<Document>(context) && downcast<Document>(context).supportsPaintTiming())
+        entryTypes.append("paint"_s);
+
+    entryTypes.append("resource"_s);
+
+    return entryTypes;
 }
 
 } // namespace WebCore
index 6ab21f0..ec11189 100644 (file)
@@ -49,7 +49,7 @@ public:
         return adoptRef(*new PerformanceObserver(context, WTFMove(callback)));
     }
 
-    static Vector<String> supportedEntryTypes();
+    static Vector<String> supportedEntryTypes(ScriptExecutionContext&);
 
     void disassociate();
 
index 87ede75..f0f24c5 100644 (file)
@@ -36,7 +36,7 @@
 ] interface PerformanceObserver {
     [MayThrowException] void observe(PerformanceObserverInit options);
     void disconnect();
-    static readonly attribute FrozenArray<DOMString> supportedEntryTypes;
+    [CallWith=ScriptExecutionContext] static readonly attribute FrozenArray<DOMString> supportedEntryTypes;
 };
 
 dictionary PerformanceObserverInit {
diff --git a/Source/WebCore/page/PerformancePaintTiming.h b/Source/WebCore/page/PerformancePaintTiming.h
new file mode 100644 (file)
index 0000000..a281ba3
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 WikiMedia Foundation. All Rights Reserve.
+ *
+ * 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 "PerformanceEntry.h"
+#include <wtf/text/WTFString.h>
+
+namespace WebCore {
+
+class PerformancePaintTiming final : public PerformanceEntry {
+public:
+    static Ref<PerformancePaintTiming> createFirstContentfulPaint(DOMHighResTimeStamp timeStamp)
+    {
+        return adoptRef(*new PerformancePaintTiming("first-contentful-paint"_s, timeStamp));
+    }
+
+private:
+    PerformancePaintTiming(const String& name, DOMHighResTimeStamp timeStamp)
+        : PerformanceEntry(name, timeStamp, timeStamp)
+    {
+    }
+
+    ASCIILiteral entryType() const final { return "paint"_s; }
+    Type type() const final { return Type::Paint; }
+
+    ~PerformancePaintTiming() = default;
+};
+
+} // namespace WebCore
+
+SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::PerformancePaintTiming)
+    static bool isType(const WebCore::PerformanceEntry& entry) { return entry.isPaint(); }
+SPECIALIZE_TYPE_TRAITS_END()
diff --git a/Source/WebCore/page/PerformancePaintTiming.idl b/Source/WebCore/page/PerformancePaintTiming.idl
new file mode 100644 (file)
index 0000000..ed34641
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 WikiMedia Foundation. All Rights Reserve.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * 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.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "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 THE COPYRIGHT
+ * OWNER 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.
+ */
+
+// https://w3c.github.io/paint-timing/
+
+typedef double DOMHighResTimeStamp;
+
+[
+    EnabledAtRuntime=PaintTiming,
+    Exposed=(Window),
+] interface PerformancePaintTiming : PerformanceEntry {
+};
index 1ee0d27..962ca0a 100644 (file)
@@ -62,6 +62,9 @@ public:
     void setUserTimingEnabled(bool isEnabled) { m_isUserTimingEnabled = isEnabled; }
     bool userTimingEnabled() const { return m_isUserTimingEnabled; }
 
+    void setPaintTimingEnabled(bool isEnabled) { m_isPaintTimingEnabled = isEnabled; }
+    bool paintTimingEnabled() const { return m_isPaintTimingEnabled; }
+
     bool performanceTimelineEnabled() const { return resourceTimingEnabled() || userTimingEnabled(); }
 
     void setShadowDOMEnabled(bool isEnabled) { m_isShadowDOMEnabled = isEnabled; }
@@ -424,6 +427,7 @@ private:
     bool m_isLinkPreloadEnabled { true };
     bool m_isLinkPrefetchEnabled { false };
     bool m_isMediaPreloadingEnabled { false };
+    bool m_isPaintTimingEnabled { false };
     bool m_isResourceTimingEnabled { false };
     bool m_isUserTimingEnabled { false };
     bool m_isInteractiveFormValidationEnabled { false };
index a1baafa..d454738 100644 (file)
@@ -273,7 +273,8 @@ public:
     enum class PaintInvalidationReasons : uint8_t {
         None,
         InvalidatingControlTints,
-        InvalidatingImagesWithAsyncDecodes
+        InvalidatingImagesWithAsyncDecodes,
+        DetectingContentfulPaint
     };
     GraphicsContext(PaintInvalidationReasons);
 
@@ -292,6 +293,7 @@ public:
     bool performingPaintInvalidation() const { return m_paintInvalidationReasons != PaintInvalidationReasons::None; }
     bool invalidatingControlTints() const { return m_paintInvalidationReasons == PaintInvalidationReasons::InvalidatingControlTints; }
     bool invalidatingImagesWithAsyncDecodes() const { return m_paintInvalidationReasons == PaintInvalidationReasons::InvalidatingImagesWithAsyncDecodes; }
+    bool detectingContentfulPaint() const { return m_paintInvalidationReasons == PaintInvalidationReasons::DetectingContentfulPaint; }
 
     WEBCORE_EXPORT void setStrokeThickness(float);
     float strokeThickness() const { return m_state.strokeThickness; }
@@ -513,6 +515,9 @@ public:
     FloatSize scaleFactor() const;
     FloatSize scaleFactorForDrawing(const FloatRect& destRect, const FloatRect& srcRect) const;
 
+    void setContentfulPaintDetected() { m_contenfulPaintDetected = true; }
+    bool contenfulPaintDetected() const { return m_contenfulPaintDetected; }
+
 #if OS(WINDOWS)
     HDC getWindowsContext(const IntRect&, bool supportAlphaBlend); // The passed in rect is used to create a bitmap for compositing inside transparency layers.
     void releaseWindowsContext(HDC, const IntRect&, bool supportAlphaBlend); // The passed in HDC should be the one handed back by getWindowsContext.
@@ -661,6 +666,7 @@ private:
 
     const PaintInvalidationReasons m_paintInvalidationReasons { PaintInvalidationReasons::None };
     unsigned m_transparencyCount { 0 };
+    bool m_contenfulPaintDetected { false };
 };
 
 class GraphicsContextStateSaver {
diff --git a/Source/WebCore/rendering/ContentfulPaintChecker.cpp b/Source/WebCore/rendering/ContentfulPaintChecker.cpp
new file mode 100644 (file)
index 0000000..eff5b73
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 WikiMedia Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+*/
+
+#include "config.h"
+#include "ContentfulPaintChecker.h"
+
+#include "FrameView.h"
+#include "GraphicsContext.h"
+
+namespace WebCore {
+
+bool ContentfulPaintChecker::qualifiesForContentfulPaint(FrameView& frameView)
+{
+    ASSERT(!frameView.needsLayout());
+    ASSERT(frameView.renderView());
+
+    auto oldPaintBehavior = frameView.paintBehavior();
+    auto oldEntireContents = frameView.paintsEntireContents();
+
+    frameView.setPaintBehavior(PaintBehavior::FlattenCompositingLayers);
+    frameView.setPaintsEntireContents(true);
+
+    GraphicsContext checkerContext(GraphicsContext::PaintInvalidationReasons::DetectingContentfulPaint);
+    frameView.paint(checkerContext, frameView.renderView()->documentRect());
+
+    frameView.setPaintsEntireContents(oldEntireContents);
+    frameView.setPaintBehavior(oldPaintBehavior);
+
+    return checkerContext.contenfulPaintDetected();
+}
+
+}
diff --git a/Source/WebCore/rendering/ContentfulPaintChecker.h b/Source/WebCore/rendering/ContentfulPaintChecker.h
new file mode 100644 (file)
index 0000000..a9e895e
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 WikiMedia Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public License
+ * along with this library; see the file COPYING.LIB.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+*/
+
+#pragma once
+
+namespace WebCore {
+
+class FrameView;
+
+class ContentfulPaintChecker {
+public:
+    static bool qualifiesForContentfulPaint(FrameView&);
+};
+
+}
index ac65979..40078ba 100644 (file)
@@ -716,7 +716,8 @@ void RenderBoxModelObject::paintFillLayerExtended(const PaintInfo& paintInfo, co
     BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* box, const LayoutSize& boxSize, CompositeOperator op, RenderElement* backgroundObject, BaseBackgroundColorUsage baseBgColorUsage)
 {
     GraphicsContext& context = paintInfo.context();
-    if (context.paintingDisabled() || rect.isEmpty())
+
+    if ((context.paintingDisabled() && !context.detectingContentfulPaint()) || rect.isEmpty())
         return;
 
     bool includeLeftEdge = box ? box->includeLogicalLeftEdge() : true;
@@ -731,6 +732,14 @@ void RenderBoxModelObject::paintFillLayerExtended(const PaintInfo& paintInfo, co
     StyleImage* bgImage = bgLayer.image();
     bool shouldPaintBackgroundImage = bgImage && bgImage->canRender(this, style().effectiveZoom());
     
+    if (context.detectingContentfulPaint()) {
+        if (!context.contenfulPaintDetected() && shouldPaintBackgroundImage && bgImage->cachedImage()) {
+            if (style().backgroundSizeType() != FillSizeType::Size || !style().backgroundSizeLength().isEmpty())
+                context.setContentfulPaintDetected();
+            return;
+        }
+    }
+
     if (context.invalidatingImagesWithAsyncDecodes()) {
         if (shouldPaintBackgroundImage && bgImage->cachedImage()->isClientWaitingForAsyncDecoding(*this))
             bgImage->cachedImage()->removeAllClientsWaitingForAsyncDecoding();
index d192c73..6799bc1 100644 (file)
@@ -71,6 +71,13 @@ void RenderHTMLCanvas::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& pa
     GraphicsContext& context = paintInfo.context();
 
     LayoutRect contentBoxRect = this->contentBoxRect();
+
+    if (context.detectingContentfulPaint()) {
+        if (!context.contenfulPaintDetected() && canvasElement().renderingContext())
+            context.setContentfulPaintDetected();
+        return;
+    }
+
     contentBoxRect.moveBy(paintOffset);
     LayoutRect replacedContentRect = this->replacedContentRect();
     replacedContentRect.moveBy(paintOffset);
index 355f327..f10b1db 100644 (file)
@@ -492,6 +492,13 @@ void RenderImage::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOf
     float deviceScaleFactor = document().deviceScaleFactor();
     LayoutUnit missingImageBorderWidth(1 / deviceScaleFactor);
 
+    if (context.detectingContentfulPaint()) {
+        if (!context.contenfulPaintDetected() && !isDeferredImage(element()) && cachedImage() && cachedImage()->canRender(this, deviceScaleFactor) && !contentSize.isEmpty())
+            context.setContentfulPaintDetected();
+
+        return;
+    }
+
     if (!imageResource().cachedImage() || isDeferredImage(element()) || shouldDisplayBrokenImageIcon()) {
         if (paintInfo.phase == PaintPhase::Selection)
             return;
index fd0bed2..27ac824 100644 (file)
@@ -4540,6 +4540,9 @@ void RenderLayer::paintLayerContents(GraphicsContext& context, const LayerPainti
 {
     ASSERT(isSelfPaintingLayer() || hasSelfPaintingLayerDescendant());
 
+    if (context.detectingContentfulPaint() && context.contenfulPaintDetected())
+        return;
+
     auto localPaintFlags = paintFlags - PaintLayerAppliedTransform;
     bool haveTransparency = localPaintFlags.contains(PaintLayerHaveTransparency);
     bool isSelfPaintingLayer = this->isSelfPaintingLayer();
index f80c203..e4c5e74 100644 (file)
@@ -204,6 +204,12 @@ void RenderVideo::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paintOf
     LayoutRect contentRect = contentBoxRect();
     contentRect.moveBy(paintOffset);
     GraphicsContext& context = paintInfo.context();
+
+    if (context.detectingContentfulPaint()) {
+        context.setContentfulPaintDetected();
+        return;
+    }
+
     bool clip = !contentRect.contains(rect);
     GraphicsContextStateSaver stateSaver(context, clip);
     if (clip)
index ed71bb6..bfcb548 100644 (file)
@@ -27,6 +27,7 @@
 #include "DisplayRun.h"
 #include "FilterOperations.h"
 #include "GraphicsContext.h"
+#include "HTMLParserIdioms.h"
 #include "InlineTextBox.h"
 #include "RenderCombineText.h"
 #include "RenderLayer.h"
@@ -105,6 +106,13 @@ void TextPainter::paintTextOrEmphasisMarks(const FontCascade& font, const TextRu
     float emphasisMarkOffset, const FloatPoint& textOrigin, unsigned startOffset, unsigned endOffset)
 {
     ASSERT(startOffset < endOffset);
+
+    if (m_context.detectingContentfulPaint()) {
+        if (!textRun.text().toStringWithoutCopying().isAllSpecialCharacters<isHTMLSpace>())
+            m_context.setContentfulPaintDetected();
+        return;
+    }
+
     if (!emphasisMark.isEmpty())
         m_context.drawEmphasisMarks(font, textRun, emphasisMark, textOrigin + FloatSize(0, emphasisMarkOffset), startOffset, endOffset);
     else if (startOffset || endOffset < textRun.length() || !m_glyphDisplayList)
index 19d3022..4df1c71 100644 (file)
@@ -29,6 +29,7 @@
 #include "HitTestResult.h"
 #include "LayoutRepainter.h"
 #include "Page.h"
+#include "RenderChildIterator.h"
 #include "RenderIterator.h"
 #include "RenderLayer.h"
 #include "RenderLayoutState.h"
@@ -211,7 +212,7 @@ void RenderSVGRoot::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paint
         return;
 
     // Don't paint, if the context explicitly disabled it.
-    if (paintInfo.context().paintingDisabled())
+    if (paintInfo.context().paintingDisabled() && !paintInfo.context().detectingContentfulPaint())
         return;
 
     // SVG outlines are painted during PaintPhase::Foreground.
@@ -223,6 +224,17 @@ void RenderSVGRoot::paintReplaced(PaintInfo& paintInfo, const LayoutPoint& paint
     if (svgSVGElement().hasEmptyViewBox())
         return;
 
+    GraphicsContext& context = paintInfo.context();
+    if (context.detectingContentfulPaint()) {
+        for (auto& current : childrenOfType<RenderObject>(*this)) {
+            if (!current.isSVGHiddenContainer()) {
+                context.setContentfulPaintDetected();
+                return;
+            }
+        }
+        return;
+    }
+
     // Don't paint if we don't have kids, except if we have filters we should paint those.
     if (!firstChild()) {
         auto* resources = SVGResourcesCache::cachedResourcesForRenderer(*this);
index 8d79824..1cc9e57 100644 (file)
@@ -1,3 +1,20 @@
+2020-04-28  Noam Rosenthal  <noam@webkit.org>
+
+        Implement FCP (first contentful paint)
+        https://bugs.webkit.org/show_bug.cgi?id=208499
+
+        Reviewed by Simon Fraser.
+
+        Add an experimental runtime flag for paint-timing (paintTimingEnabled).
+
+        * Shared/WebPreferences.yaml:
+        * UIProcess/API/C/WKPreferences.cpp:
+        (WKPreferencesSetPaintTimingEnabled):
+        (WKPreferencesGetPaintTimingEnabled):
+        * UIProcess/API/C/WKPreferencesRefPrivate.h:
+        * WebProcess/Storage/WebSWContextManagerConnection.cpp:
+        (WebKit::WebSWContextManagerConnection::updatePreferencesStore):
+
 2020-04-28  Ross Kirsling  <ross.kirsling@sony.com>
 
         [JSC] Align upon the name isCallable instead of isFunction
index 72a2a0f..a68139c 100644 (file)
@@ -825,6 +825,14 @@ ModernMediaControlsEnabled:
   humanReadableDescription: "Use modern media controls look"
   webcoreBinding: RuntimeEnabledFeatures
 
+PaintTimingEnabled:
+  type: bool
+  defaultValue: false
+  humanReadableName: "Paint Timing"
+  humanReadableDescription: "Enable PaintTiming API"
+  webcoreBinding: RuntimeEnabledFeatures
+  category: experimental
+
 ResourceTimingEnabled:
   type: bool
   defaultValue: true
index 009384c..851fd79 100644 (file)
@@ -1820,6 +1820,16 @@ bool WKPreferencesGetUserTimingEnabled(WKPreferencesRef preferencesRef)
     return toImpl(preferencesRef)->userTimingEnabled();
 }
 
+void WKPreferencesSetPaintTimingEnabled(WKPreferencesRef preferencesRef, bool flag)
+{
+    toImpl(preferencesRef)->setPaintTimingEnabled(flag);
+}
+
+bool WKPreferencesGetPaintTimingEnabled(WKPreferencesRef preferencesRef)
+{
+    return toImpl(preferencesRef)->paintTimingEnabled();
+}
+
 void WKPreferencesSetResourceTimingEnabled(WKPreferencesRef preferencesRef, bool flag)
 {
     toImpl(preferencesRef)->setResourceTimingEnabled(flag);
index d9cab9d..afd990e 100644 (file)
@@ -500,6 +500,10 @@ WK_EXPORT void WKPreferencesSetUserTimingEnabled(WKPreferencesRef, bool flag);
 WK_EXPORT bool WKPreferencesGetUserTimingEnabled(WKPreferencesRef);
 
 // Defaults to false
+WK_EXPORT void WKPreferencesSetPaintTimingEnabled(WKPreferencesRef, bool flag);
+WK_EXPORT bool WKPreferencesGetPaintTimingEnabled(WKPreferencesRef);
+
+// Defaults to false
 WK_EXPORT void WKPreferencesSetResourceTimingEnabled(WKPreferencesRef, bool flag);
 WK_EXPORT bool WKPreferencesGetResourceTimingEnabled(WKPreferencesRef);
 
index 3bb2021..104159f 100644 (file)
@@ -122,6 +122,7 @@ void WebSWContextManagerConnection::updatePreferencesStore(const WebPreferencesS
     RuntimeEnabledFeatures::sharedFeatures().setCacheAPIEnabled(store.getBoolValueForKey(WebPreferencesKey::cacheAPIEnabledKey()));
     RuntimeEnabledFeatures::sharedFeatures().setFetchAPIEnabled(store.getBoolValueForKey(WebPreferencesKey::fetchAPIEnabledKey()));
     RuntimeEnabledFeatures::sharedFeatures().setUserTimingEnabled(store.getBoolValueForKey(WebPreferencesKey::userTimingEnabledKey()));
+    RuntimeEnabledFeatures::sharedFeatures().setPaintTimingEnabled(store.getBoolValueForKey(WebPreferencesKey::paintTimingEnabledKey()));
     RuntimeEnabledFeatures::sharedFeatures().setResourceTimingEnabled(store.getBoolValueForKey(WebPreferencesKey::resourceTimingEnabledKey()));
     RuntimeEnabledFeatures::sharedFeatures().setFetchAPIKeepAliveEnabled(store.getBoolValueForKey(WebPreferencesKey::fetchAPIKeepAliveEnabledKey()));
     RuntimeEnabledFeatures::sharedFeatures().setRestrictedHTTPResponseAccess(store.getBoolValueForKey(WebPreferencesKey::restrictedHTTPResponseAccessKey()));