Skip to content

Commit

Permalink
[LCP] Add animated image support
Browse files Browse the repository at this point in the history
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

Change-Id: I6bb01eacb4f200f9c032ffcfcd9a1a41126a7773
Bug: 1260953
  • Loading branch information
Yoav Weiss authored and chromium-wpt-export-bot committed Oct 26, 2021
1 parent 1b3124b commit d993fee
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 0 deletions.
Binary file added images/anim-tao.png
Loading
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Timing-Allow-Origin: *

Binary file added images/webp-animated.webp
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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 d993fee

Please sign in to comment.