173 lines
6.7 KiB
JavaScript
173 lines
6.7 KiB
JavaScript
const image_delay = 2000;
|
|
const delay_pipe_value = image_delay / 1000;
|
|
|
|
const await_with_timeout = async (delay, message, promise, cleanup = ()=>{}) => {
|
|
let timeout_id;
|
|
const timeout = new Promise((_, reject) => {
|
|
timeout_id = step_timeout(() =>
|
|
reject(new DOMException(message, "TimeoutError")), delay)
|
|
});
|
|
let result = null;
|
|
try {
|
|
result = await Promise.race([promise, timeout]);
|
|
clearTimeout(timeout_id);
|
|
} finally {
|
|
cleanup();
|
|
}
|
|
return result;
|
|
};
|
|
|
|
// 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:
|
|
// * 'renderTimeIs0': the renderTime should be 0 (image does not pass Timing-Allow-Origin checks).
|
|
// When not present, the renderTime should not be 0 (image passes the checks).
|
|
// * 'sizeLowerBound': the |expectedSize| is only a lower bound on the size attribute value.
|
|
// When not present, |expectedSize| must be exactly equal to the size attribute value.
|
|
// * 'approximateSize': the |expectedSize| is only approximate to the size attribute value.
|
|
// This option is mutually exclusive to 'sizeLowerBound'.
|
|
function checkImage(entry, expectedUrl, expectedID, expectedSize, timeLowerBound, options = []) {
|
|
assert_equals(entry.name, '', "Entry name should be the empty string");
|
|
assert_equals(entry.entryType, 'largest-contentful-paint',
|
|
"Entry type should be largest-contentful-paint");
|
|
assert_equals(entry.duration, 0, "Entry duration should be 0");
|
|
// The entry's url can be truncated.
|
|
assert_equals(expectedUrl.substr(0, 100), entry.url.substr(0, 100),
|
|
`Expected URL ${expectedUrl} should at least start with the entry's URL ${entry.url}`);
|
|
assert_equals(entry.id, expectedID, "Entry ID matches expected one");
|
|
assert_equals(entry.element, document.getElementById(expectedID),
|
|
"Entry element is expected one");
|
|
if (options.includes('skip')) {
|
|
return;
|
|
}
|
|
if (options.includes('renderTimeIs0')) {
|
|
assert_equals(entry.renderTime, 0, 'renderTime should be 0');
|
|
assert_between_exclusive(entry.loadTime, timeLowerBound, performance.now(),
|
|
'loadTime should be between the lower bound and the current time');
|
|
assert_approx_equals(entry.startTime, entry.loadTime, 0.001,
|
|
'startTime should be equal to renderTime to the precision of 1 millisecond.');
|
|
} else {
|
|
assert_between_exclusive(entry.loadTime, timeLowerBound, entry.renderTime,
|
|
'loadTime should occur between the lower bound and the renderTime');
|
|
assert_greater_than_equal(performance.now(), entry.renderTime,
|
|
'renderTime should occur before the entry is dispatched to the observer.');
|
|
assert_approx_equals(entry.startTime, entry.renderTime, 0.001,
|
|
'startTime should be equal to renderTime to the precision of 1 millisecond.');
|
|
}
|
|
if (options.includes('sizeLowerBound')) {
|
|
assert_greater_than(entry.size, expectedSize);
|
|
} else if (options.includes('approximateSize')) {
|
|
assert_approx_equals(entry.size, expectedSize, 1);
|
|
} 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);
|
|
});
|
|
};
|
|
|
|
const load_video_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 video = document.createElement("video");
|
|
video.id = 'video_id';
|
|
video.src = url;
|
|
video.autoplay = true;
|
|
video.muted = true;
|
|
video.loop = true;
|
|
document.body.appendChild(video);
|
|
});
|
|
};
|
|
|
|
const getLCPStartTime = (identifier) => {
|
|
return new Promise(resolve => {
|
|
new PerformanceObserver((entryList, observer) => {
|
|
entryList.getEntries().forEach(e => {
|
|
if (e.url.includes(identifier)) {
|
|
resolve(e);
|
|
observer.disconnect();
|
|
}
|
|
});
|
|
}).observe({ type: 'largest-contentful-paint', buffered: true });
|
|
});
|
|
}
|
|
|
|
const getFCPStartTime = () => {
|
|
return performance.getEntriesByName('first-contentful-paint')[0];
|
|
}
|
|
|
|
const add_text = (text) => {
|
|
const paragraph = document.createElement('p');
|
|
paragraph.innerHTML = text;
|
|
document.body.appendChild(paragraph);
|
|
}
|
|
|
|
const loadImage = (url, shouldBeIgnoredForLCP = false) => {
|
|
return new Promise(function (resolve, reject) {
|
|
let image = document.createElement('img');
|
|
image.addEventListener('load', () => { resolve(image); });
|
|
image.addEventListener('error', reject);
|
|
image.src = url;
|
|
if (shouldBeIgnoredForLCP)
|
|
image.style.opacity = 0;
|
|
document.body.appendChild(image);
|
|
});
|
|
}
|
|
|
|
const checkLCPEntryForNonTaoImages = (times = {}) => {
|
|
const lcp = times['lcp'];
|
|
const fcp = times['fcp'];
|
|
const lcp_url_components = lcp.url.split('/');
|
|
|
|
if (lcp.loadTime <= fcp.startTime) {
|
|
assert_approx_equals(lcp.startTime, fcp.startTime, 0.001,
|
|
'LCP start time should be the same as FCP for ' +
|
|
lcp_url_components[lcp_url_components.length - 1]) +
|
|
' when LCP load time is less than FCP.';
|
|
} else {
|
|
assert_approx_equals(lcp.startTime, lcp.loadTime, 0.001,
|
|
'LCP start time should be the same as LCP load time for ' +
|
|
lcp_url_components[lcp_url_components.length - 1]) +
|
|
' when LCP load time is no less than FCP.';
|
|
}
|
|
|
|
assert_equals(lcp.renderTime, 0,
|
|
'The LCP render time of Non-Tao image should always be 0.');
|
|
}
|
|
|
|
const raf = () => {
|
|
return new Promise(resolve => requestAnimationFrame(resolve));
|
|
}
|