Skip to content

Commit

Permalink
Reland #2 "[LCP] Add animated image support"
Browse files Browse the repository at this point in the history
This is a manual reland of
https://chromium-review.googlesource.com/c/chromium/src/+/3247449

The difference from the previous reland is that the browser tests now
include 2 separate timeouts and a double rAF, to ensure that the
presentation timestamp taken is far enough from both the time the first
frame is sent as well as from the time the second frame is sent.
More importantly, the test now actually is looking at the UKM metric,
rather than at the histogram.

Original change's description:
> [LCP] Add animated image support
>
> This CL adds support for better handling of animated images in LCP:
> * A new attribute is exposing the first animated frame's paint time
> (behind a flag).
> * `startTime` is not changed.
> * The PageLoadMetrics reported for LCP are set to that first frame paint
> time for animated images (behind another flag).
> * Entries are not emitted until the image is loaded.
>
> Relevant spec issue:
> w3c/largest-contentful-paint#83

Bug: 1260953
Change-Id: I34070bd90a74ed44281da63b547f13d9669f389b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3250690
Reviewed-by: Nicolás Peña Moreno <npm@chromium.org>
Commit-Queue: Yoav Weiss <yoavweiss@chromium.org>
Cr-Commit-Position: refs/heads/main@{#936516}
  • Loading branch information
Yoav Weiss authored and chromium-wpt-export-bot committed Oct 29, 2021
1 parent 7e53f8d commit ae80b50
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 0 deletions.
Binary file added images/anim-tao.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions images/anim-tao.png.headers
@@ -0,0 +1,2 @@
Timing-Allow-Origin: *

Binary file added images/webp-animated.webp
Binary file not shown.
@@ -0,0 +1,27 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset=utf-8>
<title>Largest Contentful Paint: observe image.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/largest-contentful-paint-helpers.js"></script>
</head>
<body>
<script>
promise_test(async () => {
assert_implements(window.LargestContentfulPaint,
"LargestContentfulPaint is not implemented");
const beforeLoad = performance.now();
// 136 is the size of the animated GIF up until the first frame.
// The trickle pipe delays the response after the first frame by 1 second.
const url = window.location.origin +
`/images/anim-gr.gif?pipe=trickle(136:d${delay_pipe_value})`;
const entry = await load_and_observe(url);
// anim-gr.gif is 100 by 50.
const size = 100 * 50;
checkImage(entry, url, 'image_id', size, beforeLoad, ["animated"]);
}, "Same origin animated image is observable and has a first frame.");
</script>
</body>
</html>
@@ -0,0 +1,27 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset=utf-8>
<title>Largest Contentful Paint: observe image.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/largest-contentful-paint-helpers.js"></script>
</head>
<body>
<script>
promise_test(async () => {
assert_implements(window.LargestContentfulPaint,
"LargestContentfulPaint is not implemented");
const beforeLoad = performance.now();
// 142 is the size of the animated WebP up until the first frame.
// The trickle pipe delays the response after the first frame by 1 second.
const url = window.location.origin +
`/images/webp-animated.webp?pipe=trickle(142:d${delay_pipe_value})`;
const entry = await load_and_observe(url);
// webp-animated.webp is 11 by 29.
const size = 11 * 29;
checkImage(entry, url, 'image_id', size, beforeLoad, ["animated"]);
}, "Same origin animated image is observable and has a first frame.");
</script>
</body>
</html>
@@ -0,0 +1,29 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset=utf-8>
<title>Largest Contentful Paint: observe image.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/largest-contentful-paint-helpers.js"></script>
</head>
<body>
<script>
promise_test(async () => {
assert_implements(window.LargestContentfulPaint,
"LargestContentfulPaint is not implemented");
const beforeLoad = performance.now();
// 262 is the size of the animated PNG up until the first frame,
// including the chunk that starts the second frame (indicating that
// the first frame data is done).
// The trickle pipe delays the response after the first frame by 1 second.
const url = window.location.origin +
`/images/anim-gr.png?pipe=trickle(262:d${delay_pipe_value})`;
const entry = await load_and_observe(url);
// anim-gr.png is 100 by 50.
const size = 100 * 50;
checkImage(entry, url, 'image_id', size, beforeLoad, ["animated"]);
}, "Same origin animated image is observable and has a first frame.");
</script>
</body>
</html>
@@ -0,0 +1,30 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset=utf-8>
<title>Largest Contentful Paint: observe image.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/largest-contentful-paint-helpers.js"></script>
<script src="/common/get-host-info.sub.js"></script>
</head>
<body>
<script>
promise_test(async () => {
assert_implements(window.LargestContentfulPaint,
"LargestContentfulPaint is not implemented");
const beforeLoad = performance.now();
// 262 is the size of the animated PNG up until the first frame,
// including the chunk that starts the second frame (indicating that
//the first frame data is done).
const {REMOTE_ORIGIN} = get_host_info();
const url = REMOTE_ORIGIN +
'/images/anim-gr.png?pipe=trickle(262:d1)';
const entry = await load_and_observe(url);
// anim-gr.png is 100 by 50.
const size = 100 * 50;
checkImage(entry, url, 'image_id', size, beforeLoad, ["renderTimeIs0", "animated-zero"]);
}, "Same origin animated image is observable and has a first frame.");
</script>
</body>
</html>
@@ -0,0 +1,30 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset=utf-8>
<title>Largest Contentful Paint: observe image.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/largest-contentful-paint-helpers.js"></script>
<script src="/common/get-host-info.sub.js"></script>
</head>
<body>
<script>
promise_test(async () => {
assert_implements(window.LargestContentfulPaint,
"LargestContentfulPaint is not implemented");
const beforeLoad = performance.now();
// 262 is the size of the animated PNG up until the first frame,
// including the chunk that starts the second frame (indicating that
//the first frame data is done).
const {REMOTE_ORIGIN} = get_host_info();
const url = REMOTE_ORIGIN +
'/images/anim-tao.png?pipe=trickle(262:d1)';
const entry = await load_and_observe(url);
// anim-gr.png is 100 by 50.
const size = 100 * 50;
checkImage(entry, url, 'image_id', size, beforeLoad, ["animated"]);
}, "Same origin animated image is observable and has a first frame.");
</script>
</body>
</html>
@@ -0,0 +1,27 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset=utf-8>
<title>Largest Contentful Paint: observe image.</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../resources/largest-contentful-paint-helpers.js"></script>
</head>
<body>
<script>
promise_test(async () => {
assert_implements(window.LargestContentfulPaint,
"LargestContentfulPaint is not implemented");
const beforeLoad = performance.now();
// 262 is the size of the animated PNG up until the first frame,
// including the chunk that starts the second frame (indicating that
//the first frame data is done).
const url = window.location.origin + '/images/blue.png';
const entry = await load_and_observe(url);
// blue.png is 133 by 106.
const size = 133 * 106;
checkImage(entry, url, 'image_id', size, beforeLoad, ["animated-zero"]);
}, "Same origin animated image is observable and has a first frame.");
</script>
</body>
</html>
@@ -1,3 +1,6 @@
const image_delay = 1000;
const delay_pipe_value = image_delay / 1000;

// Receives an image LargestContentfulPaint |entry| and checks |entry|'s attribute values.
// The |timeLowerBound| parameter is a lower bound on the loadTime value of the entry.
// The |options| parameter may contain some string values specifying the following:
Expand Down Expand Up @@ -33,4 +36,33 @@ function checkImage(entry, expectedUrl, expectedID, expectedSize, timeLowerBound
} else {
assert_equals(entry.size, expectedSize);
}
if (options.includes('animated')) {
assert_greater_than(entry.loadTime, entry.firstAnimatedFrameTime,
'firstAnimatedFrameTime should be smaller than loadTime');
assert_greater_than(entry.renderTime, entry.firstAnimatedFrameTime,
'firstAnimatedFrameTime should be smaller than renderTime');
assert_less_than(entry.firstAnimatedFrameTime, image_delay,
'firstAnimatedFrameTime should be smaller than the delay applied to the second frame');
assert_greater_than(entry.firstAnimatedFrameTime, 0,
'firstAnimatedFrameTime should be larger than 0');
}
if (options.includes('animated-zero')) {
assert_equals(entry.firstAnimatedFrameTime, 0, 'firstAnimatedFrameTime should be 0');
}
}

const load_and_observe = url => {
return new Promise(resolve => {
(new PerformanceObserver(entryList => {
for (let entry of entryList.getEntries()) {
if (entry.url == url) {
resolve(entryList.getEntries()[0]);
}
}
})).observe({type: 'largest-contentful-paint', buffered: true});
const img = new Image();
img.id = 'image_id';
img.src = url;
document.body.appendChild(img);
});
};

0 comments on commit ae80b50

Please sign in to comment.