Skip to content

Commit 8a10f91

Browse files
Yoav Weisschromium-wpt-export-bot
Yoav Weiss
authored andcommitted
Reland #2 "[LCP] Add animated image support"
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
1 parent 17e544e commit 8a10f91

10 files changed

+204
-0
lines changed

images/anim-tao.png

460 Bytes
Loading

images/anim-tao.png.headers

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Timing-Allow-Origin: *
2+

images/webp-animated.webp

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

largest-contentful-paint/resources/largest-contentful-paint-helpers.js

+32
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
const image_delay = 1000;
2+
const delay_pipe_value = image_delay / 1000;
3+
14
// Receives an image LargestContentfulPaint |entry| and checks |entry|'s attribute values.
25
// The |timeLowerBound| parameter is a lower bound on the loadTime value of the entry.
36
// The |options| parameter may contain some string values specifying the following:
@@ -33,4 +36,33 @@ function checkImage(entry, expectedUrl, expectedID, expectedSize, timeLowerBound
3336
} else {
3437
assert_equals(entry.size, expectedSize);
3538
}
39+
if (options.includes('animated')) {
40+
assert_greater_than(entry.loadTime, entry.firstAnimatedFrameTime,
41+
'firstAnimatedFrameTime should be smaller than loadTime');
42+
assert_greater_than(entry.renderTime, entry.firstAnimatedFrameTime,
43+
'firstAnimatedFrameTime should be smaller than renderTime');
44+
assert_less_than(entry.firstAnimatedFrameTime, image_delay,
45+
'firstAnimatedFrameTime should be smaller than the delay applied to the second frame');
46+
assert_greater_than(entry.firstAnimatedFrameTime, 0,
47+
'firstAnimatedFrameTime should be larger than 0');
48+
}
49+
if (options.includes('animated-zero')) {
50+
assert_equals(entry.firstAnimatedFrameTime, 0, 'firstAnimatedFrameTime should be 0');
51+
}
3652
}
53+
54+
const load_and_observe = url => {
55+
return new Promise(resolve => {
56+
(new PerformanceObserver(entryList => {
57+
for (let entry of entryList.getEntries()) {
58+
if (entry.url == url) {
59+
resolve(entryList.getEntries()[0]);
60+
}
61+
}
62+
})).observe({type: 'largest-contentful-paint', buffered: true});
63+
const img = new Image();
64+
img.id = 'image_id';
65+
img.src = url;
66+
document.body.appendChild(img);
67+
});
68+
};

0 commit comments

Comments
 (0)