API Long Animation Frames : Déboguer l'INP en production

Un guide de production pour trouver le script qui a bloqué votre interaction

Arjen Karel Core Web Vitals Consultant
Arjen Karel - linkedin
Last update: 2026-04-30

Pendant des années, la réponse à la question « qu'est-ce qui cause mon INP » a souvent été un regard vide. L'API Long Tasks vous indiquait que le thread principal était bloqué pendant plus de 50 ms. Elle ne vous disait pas quel script. Elle ne vous disait pas quelle ligne. Tout ce que nous avions, c'était un chiffre en ms.

L'API Long Animation Frames corrige cela. Elle est disponible dans les navigateurs Chromium depuis Chrome 123 et vous donne l'anatomie complète d'une frame : la durée totale, la durée de blocage, le moment où le rendu a commencé, le moment où le style et la mise en page ont commencé, et un tableau de chaque script qui s'est exécuté dans la frame pendant plus de 5 ms. Chaque entrée de script inclut l'URL source, le nom de la fonction, la position du caractère dans le fichier et le type de rappel (callback) qui l'a invoqué.

Ce sont les données dont le débogage de l'INP a toujours eu besoin. Le reste de cet article explique comment l'utiliser sur un site en production réel sans aggraver la situation.

Cette page fait partie de notre série Interaction to Next Paint (INP). Le guide LoAF ci-dessous est ce que j'exécute après avoir diagnostiqué la métrique. Si vous débutez avec l'INP, commencez par les trois guides de phase pour l 'input delay, le processing time et le presentation delay, ainsi que par le flux de travail plus large pour trouver et corriger l'INP.

Pourquoi Long Tasks n'était pas suffisant

Une tâche longue se déclenche lorsqu'une seule tâche sur le thread principal s'exécute pendant plus de 50 ms. C'est utile pour repérer les blocages évidents. C'est inutile pour l'INP car la plupart des interactions lentes ne sont pas une seule tâche longue. Ce sont une séquence de tâches moyennes plus un long rendu.

Une interaction peut échouer à l'INP sans jamais déclencher une tâche longue. Un gestionnaire d'événements de 40 ms suivi d'un recalcul de style de 70 ms totalisent 110 ms de délai de présentation. L'API Long Tasks ne signale rien. L'utilisateur ressent le lag.

L'autre échec de Long Tasks est l'attribution. Vous obtenez un type de conteneur, un nom de conteneur et un champ de nom vous indiquant si la tâche longue était "self", "same-origin-ancestor", "same-origin-descendant", "unknown", ou l'une des quelques variantes cross-origin. Vous n'obtenez pas le script qui s'est exécuté. Donc, même lorsque vous détectez une tâche longue, vous devez toujours ouvrir les DevTools et réenregistrer l'interaction pour trouver la cause. Cela fonctionne en laboratoire. Cela ne fonctionne pas pour l'INP sur des utilisateurs réels que vous ne pouvez pas reproduire.

LoAF remplace ces deux limitations. Il capture la frame entière, pas une seule tâche. Et il vous dit exactement quel script s'est exécuté, par URL et nom de fonction.

Les sept champs qui comptent

Une entrée LoAF a plus de champs que ce dont vous avez besoin. Ces sept-là sont ceux que je lis en premier lors du débogage d'une interaction.

duration est la longueur totale de la frame en millisecondes. Une entrée LoAF n'est créée que lorsque cela dépasse 50 ms. Ce nombre seul vous indique si la frame est un problème, mais pas pourquoi.

blockingDuration est plus utile. Il somme les parties de la frame qui ont dépassé le seuil de la tâche longue, en soustrayant 50 ms de chacune. Une frame avec duration: 320 et blockingDuration: 0 est principalement un travail de rendu. Une frame avec duration: 320 et blockingDuration: 270 est un long script. Le premier nécessite une correction du rendu. Le second nécessite un changement de code.

renderStart est l'horodatage où le navigateur a commencé à faire le travail de rendu pour la frame. Tout ce qui se trouve entre startTime et renderStart est l'exécution de script. Tout ce qui suit est le style, la mise en page (layout) et la peinture (paint). C'est la limite la plus utile pour le débogage de l'INP car elle vous indique si le goulot d'étranglement est le JavaScript ou le rendu.

styleAndLayoutStart est l'horodatage où le rendu est passé de l'exécution des rappels de frame d'animation au recalcul effectif du style et de la mise en page. L'écart entre renderStart et styleAndLayoutStart représente vos rappels requestAnimationFrame. L'écart entre styleAndLayoutStart et la fin de la frame représente le travail de style et de mise en page côté navigateur.

firstUIEventTimestamp est le moment où une entrée utilisateur est arrivée pendant la frame. Si cette valeur est non nulle, la frame contient une interaction. C'est ainsi que web-vitals.js corrèle les entrées LoAF avec les mesures d'INP. L'absence de firstUIEventTimestamp signifie que la frame est lente, mais qu'aucun utilisateur ne l'attendait.

scripts est le tableau que vous lisez pour l'attribution. Chaque entrée est un script qui s'est exécuté pendant au moins 5 ms. Il inclut sourceURL, sourceFunctionName, invoker, invokerType, duration et forcedStyleAndLayoutDuration. Ce dernier est critique : il vous indique si le script a déclenché une mise en page synchrone, ce qui est la cause première de la plupart des goulots d'étranglement de temps de traitement que je vois dans les audits.

Le champ invokerType sur chaque script vous indique comment il a été appelé. La liste complète est event-listener (clics, défilements, frappes de touches), user-callback (setTimeout, setInterval, requestAnimationFrame), resolve-promise et reject-promise (gestionnaires then/catch), classic-script et module-script. Pour l'INP, ceux que vous lisez en premier sont event-listener pour le temps de traitement, et user-callback, classic-script ou module-script pour le délai d'entrée. Les gestionnaires de promesses arrivent rarement en tête de liste.

Mappez ces champs sur les trois phases de l'INP et l'image est claire. L'input delay apparaît sous la forme de scripts s'exécutant avant firstUIEventTimestamp. Le temps de traitement correspond aux scripts de gestionnaire d'événements s'exécutant après l'événement de l'interface utilisateur. Le délai de présentation correspond à tout ce qui se trouve entre renderStart et la fin de la frame.

Capturer LoAF en production avec web-vitals.js

Vous n'avez pas besoin de créer un observateur LoAF de zéro. Le build d'attribution de web-vitals.js le fait pour vous et corrèle les entrées LoAF aux mesures réelles d'INP. C'est la version que j'installe sur les sites des clients.

import { onINP } from 'web-vitals/attribution';

onINP((metric) => {
    const a = metric.attribution;

    const payload = {
        value: metric.value,
        rating: metric.rating,
        url: location.pathname,
        loadState: a.loadState,
        interactionTarget: a.interactionTarget,
        interactionType: a.interactionType,
        inputDelay: a.inputDelay,
        processingDuration: a.processingDuration,
        presentationDelay: a.presentationDelay,
        longestScriptDuration: a.longestScript?.entry?.duration,
        longestScriptInvoker: a.longestScript?.entry?.invoker,
        longestScriptSource: a.longestScript?.entry?.sourceURL,
        longestScriptSubpart: a.longestScript?.subpart,
        longestScriptIntersecting: a.longestScript?.intersectingDuration,
    };

    navigator.sendBeacon('/rum', JSON.stringify(payload));
});

Ce payload est suffisamment petit pour être envoyé sans échantillonnage. Trois champs effectuent la majeure partie du travail de diagnostic : longestScriptInvoker, longestScriptSource et longestScriptSubpart. Les deux premiers vous disent ce qui s'est exécuté. Le troisième vous indique dans quelle phase de l'INP il a atterri.

Si vous souhaitez une attribution complète plutôt que seulement le script le plus long, envoyez également a.longAnimationFrameEntries. Sachez qu'une mesure d'INP peut inclure plusieurs LoAF et qu'un gros LoAF peut inclure dix entrées de script ou plus. L'envoi du tableau complet à chaque page vue est coûteux. En général, je n'envoie le tableau complet que lorsque metric.rating !== 'good'.

Un détail important. Le tableau longAnimationFrameEntries est vide lorsque LoAF et l'interaction candidate INP ne se chevauchent pas, ou lorsque le navigateur ne prend pas en charge LoAF du tout. Vérifiez toujours la prise en charge de la fonctionnalité avant de considérer un tableau vide comme un signal propre.

const loafSupported = PerformanceObserver.supportedEntryTypes?.includes('long-animation-frame');

La question de la confidentialité. Les valeurs sourceURL de script peuvent contenir des paramètres de requête et des chemins complets. Pour le travail client, je supprime tout sauf l'origine et le chemin (pathname) avant de l'envoyer au RUM. Les noms de fichiers de bundle hachés sont utiles pour le débogage au sein d'un déploiement mais inutiles pour le regroupement entre les déploiements, je supprime donc également le segment de hachage.

Modèles d'attribution à partir de 925 000 URL

CoreDash capture l'attribution LoAF pour chaque mesure d'INP sur les sites que nous surveillons. Dans notre ensemble de données de 925 000 URL, la même poignée de catégories de scripts apparaît systématiquement comme la cause dominante des LoAF bloquants.

Tableau de bord CoreDash montrant l'attribution LoAF regroupée par origine de script

Sur les sites e-commerce, les principales origines de blocage sont cohérentes. Les gestionnaires de balises exécutant des balises HTML personnalisées synchrones arrivent en premier. Les plateformes de gestion du consentement effectuant une injection de script à liaison tardive (late-binding) arrivent en deuxième. L'orchestration des scripts publicitaires et d'enchères est troisième. Le quatrième concerne les widgets de recommandation de produits qui s'hydratent lourdement côté client. Le cinquième correspond aux scripts de session replay qui instrumentent chaque interaction.

Ce qui surprend les clients, c'est la part de forcedStyleAndLayoutDuration. Sur une mauvaise frame INP typique, 30 à 60 % de la durée du script le plus long est due au script déclenchant une mise en page synchrone. Le script n'est pas lent parce que le JavaScript est lourd. Il est lent parce que le JavaScript lit la mise en page (offsetHeight, getBoundingClientRect) à l'intérieur d'une boucle qui écrit également dans le DOM. C'est le « layout thrashing ». Le même problème que les développeurs écrivent depuis quinze ans, toujours le contributeur individuel le plus important au temps de traitement sur les sites que j'audite.

Dans notre ensemble de données, les invocateurs event-listener représentent environ la moitié des scripts les plus longs intersectant l'INP. user-callback (setTimeout, setInterval, requestAnimationFrame) représente environ un quart. L'exécution de niveau supérieur de classic-script représente l'essentiel de ce qui reste. Les résolveurs de promesses sont rarement le script le plus long, ce qui contredit une hypothèse commune selon laquelle « le code asynchrone est la cause » de l'INP.

Graphique CoreDash montrant la distribution invokerType pour les scripts LoAF bloquant l'INP

Les cinq modèles LoAF que je vois le plus souvent

Après suffisamment d'audits, les entrées LoAF commencent à paraître familières. La plupart des interactions qui échouent correspondent à l'un de ces cinq modèles. Chacun a une solution différente.

Le gestionnaire d'événements tiers

Le script le plus long a invokerType: "event-listener" et une sourceURL provenant d'une origine tierce. La durée du script occupe la majeure partie de la frame et forcedStyleAndLayoutDuration est faible. Il s'agit d'une balise d'un fournisseur écoutant vos clics et effectuant trop de travail dans le gestionnaire. La solution est rarement de « supprimer la balise ». La solution consiste à différer le travail que fait la balise. Pour les pushs dataLayer en particulier, il existe un modèle qui vous permet de récupérer 20 à 100 ms sans casser l'analyse, que j'ai couvert dans Planifier les événements dataLayer pour optimiser l'INP.

Layout thrashing dans votre propre gestionnaire

Le script le plus long a invokerType: "event-listener", la sourceURL est votre propre bundle, et forcedStyleAndLayoutDuration représente plus de 30 % de la durée du script. Le script lit la mise en page dans une boucle. La solution est de regrouper les lectures avant les écritures, ou de déplacer le travail vers requestAnimationFrame et d'utiliser des valeurs en cache.

Je vois cela le plus souvent dans les pages de filtres de produits sur les sites e-commerce. Le gestionnaire de filtres met à jour le nombre de produits visibles, puis lit la nouvelle hauteur de la liste de résultats pour repositionner un bouton de filtre sticky, puis met à jour une propriété personnalisée CSS basée sur cette hauteur, puis lit à nouveau la hauteur. Quatre mises en page forcées dans un seul gestionnaire. LoAF attribue ces 80 à 150 ms de forcedStyleAndLayoutDuration à une seule entrée de script et la solution devient évidente. Lire une fois, écrire une fois, quitter.

Cascade d'hydratation lors de la première interaction

Le LoAF se déclenche alors que loadState est encore "loading" ou "dom-interactive". firstUIEventTimestamp se trouve à l'intérieur d'une frame où scripts contient des rappels d'hydratation du framework. L'utilisateur a cliqué avant que la page ne soit hydratée. La solution n'est pas de rendre l'hydratation plus rapide, bien que cela aide. La solution est de faire fonctionner les éléments interactifs sans hydratation : liens natifs et soumissions de formulaires, widgets de divulgation (disclosure) natifs, dialogues natifs. L'amélioration progressive (progressive enhancement).

Délai de présentation dû à un DOM lourd

Celui-ci a une apparence différente dans les données. blockingDuration est faible. Le tableau scripts est court. Mais l'écart entre styleAndLayoutStart et la fin de la frame est de plus de 100 ms. Aucune correction JavaScript n'y fera rien. Le moteur de rendu s'étouffe sur le recalcul du style sur des milliers de nœuds. Utilisez content-visibility: auto sur les sections hors écran, contain: layout style sur les composants lourds, et réduisez la taille de la portée de style qui doit se recalculer à chaque interaction.

La tempête rAF

Plusieurs LoAFs s'enchaînent, chacun avec un script user-callback invoqué par Window.requestAnimationFrame. La page exécute une animation JavaScript qui entre en concurrence avec l'utilisateur. Le comportement de défilement piloté par JavaScript en est la version la plus courante. J'ai vu des sites ajouter 200 ms de délai de présentation à chaque clic sur chaque page à cause d'un polyfill de smooth-scroll que personne ne se souvient avoir intégré. J'ai couvert le cas du défilement dans Améliorer l'INP en abandonnant le défilement JavaScript. Même logique pour toute animation JavaScript : passez au CSS ou à la Web Animations API et les LoAFs disparaissent.

À quoi ressemble un payload LoAF sur le terrain

Voici une véritable entrée LoAF provenant d'un audit de page de paiement. J'ai anonymisé les URL mais la structure et les timings sont intacts.

{
    "name": "long-animation-frame",
    "entryType": "long-animation-frame",
    "startTime": 5902,
    "duration": 320,
    "blockingDuration": 268,
    "renderStart": 6170,
    "styleAndLayoutStart": 6172,
    "firstUIEventTimestamp": 5913,
    "scripts": [
        {
            "name": "script",
            "entryType": "script",
            "invoker": "BUTTON#checkout-submit.onclick",
            "invokerType": "event-listener",
            "sourceURL": "https://cdn.example.com/app.HASH.js",
            "sourceFunctionName": "handleCheckoutSubmit",
            "duration": 248,
            "forcedStyleAndLayoutDuration": 142,
            "executionStart": 5914,
            "startTime": 5914
        }
    ]
}

Lisez-le comme ceci. La frame a commencé à 5902 ms après le chargement de la page. À 5913 ms, l'utilisateur a cliqué sur le bouton de validation du paiement. Le gestionnaire de clics a commencé à 5914 ms et s'est exécuté pendant 248 ms. Sur ces 248 ms, 142 ms correspondaient à une mise en page synchrone forcée. Le moteur de rendu n'a pas pu démarrer avant 6170 ms, soit plus d'un quart de seconde après le clic. L'INP pour cette interaction est d'environ 270 ms, dans la zone « à améliorer ».

La solution n'est pas « rendre handleCheckoutSubmit plus rapide ». La solution est « arrêter de forcer la mise en page à l'intérieur de handleCheckoutSubmit ». 142 ms sur 248 ms ce n'est pas le JavaScript. Il s'agit de la lecture de valeurs de mise en page que les écritures JavaScript ont invalidées. Sur l'engagement réel d'où cela provient, la mise en cache des valeurs lues une fois avant la boucle d'écriture a fait chuter la durée du script de 248 ms à 110 ms. L'INP sur la page de paiement est passé du p75 270 ms à moins de 200 ms. La page a été validée.

C'est le genre de correction qui n'apparaît dans aucun outil de laboratoire. Lighthouse ne peut pas vous parler de forcedStyleAndLayoutDuration car Lighthouse n'interagit pas avec la page. LoAF sur le terrain est le seul moyen de le voir.

Sortie de la Console DevTools montrant le tableau récapitulatif LoAF affiché par le script webperf-snippets

Piloter un agent IA avec les données LoAF

La raison pour laquelle LoAF est important pour le débogage assisté par l'IA est qu'il donne à un agent quelque chose de concret sur lequel raisonner. Un agent sans données de terrain peut deviner les causes de l'INP à partir de votre code. Un agent avec des entrées LoAF issues de sessions d'utilisateurs réels peut nommer le script, la fonction et la phase. Le diagnostic cesse d'être une supposition.

Le flux de travail que j'utilise avec Claude Code : extraire la pire page INP depuis CoreDash via le serveur MCP, joindre les entrées LoAF pour les interactions les plus lentes de cette URL, et demander à l'agent d'identifier à laquelle des cinq formes ci-dessus chaque entrée correspond. L'agent classifie chaque LoAF, pointe vers la fonction dans la base de code, et propose une correction. Je vérifie la correction. Je l'applique. CoreDash mesure si le p75 INP bouge.

Cela fonctionne car les données LoAF sont structurées et petites. Un seul payload JSON LoAF tient dans une fraction de la fenêtre de contexte d'un agent. Une poignée de LoAFs représentatifs pour une seule page suffit pour piloter une correction. J'ai écrit sur le modèle plus large dans Corriger l'INP avec un agent IA : la métrique que les outils de laboratoire ne peuvent pas mesurer et Agent IA Core Web Vitals : pourquoi les données de terrain changent tout.

La réalité cross-browser

LoAF est exclusif à Chromium. Safari ne l'implémente pas. Firefox ne l'implémente pas. Avec l'INP lui-même désormais disponible dans Safari 26.2 et Firefox 144, vous avez un écart de mesure. Vos données INP RUM sont cross-browser. Vos données d'attribution sont Chrome et Edge.

Je vois des clients hésiter à adopter LoAF à cause de cela. Ils veulent attendre que Safari le publie. C'est le mauvais choix. Chrome est l'endroit où la plupart des bugs de performance sont d'abord reproduits et où la plupart des corrections de performance sont d'abord validées. La forme du problème (gestionnaire d'événements long, layout thrashing, cascade d'hydratation) ne change pas d'un navigateur à l'autre. Seule la lentille de diagnostic diffère. Les corrections que vous déployez sur la base des données LoAF de Chromium améliorent l'INP pour les utilisateurs de Safari et de Firefox également.

Si vous voulez une sorte d'attribution dans Safari aujourd'hui, EventTiming est ce que vous avez. Il vous indique quel événement a été lent mais pas quel script. C'est suffisant pour savoir où regarder mais pas quoi corriger. Pour l'attribution dans Safari, le profilage en laboratoire dans le Safari Web Inspector est le fallback pratique.

Ce que LoAF ne peut toujours pas vous dire

Il est utile de connaître trois angles morts, car ils vous surprendront à un moment donné.

Le premier concerne les iframes cross-origin. LoAF attribue uniquement les scripts s'exécutant sur le thread principal des fenêtres qui partagent l'origine du document. Un script bloquant à l'intérieur d'une iframe tierce apparaît comme une attribution opaque : une longue frame et un tableau scripts vide. La solution est la même que pour tout problème de performance d'iframe tierce : différez le chargement de l'iframe, donnez-lui des dimensions explicites et utilisez loading="lazy" si elle est sous la ligne de flottaison.

Les workers sont le deuxième angle mort. Les web workers n'apparaissent pas dans les entrées LoAF car LoAF est une API pour le thread principal. Si votre INP est mauvais parce qu'un worker renvoie de lourds messages au thread principal, LoAF vous montre le gestionnaire de messages sur le thread principal mais pas le travail du worker qui l'a déclenché. Vous effectuez un recoupement manuellement à l'aide de marques de performance à l'intérieur du worker.

Et enfin, le délai du compositeur GPU. La fin d'une entrée LoAF correspond au moment où le moteur de rendu termine son travail sur le thread principal. Le moment réel d'affichage des pixels à l'écran intervient plus tard, dans le compositeur et le GPU. Sur les appareils Android bas de gamme, le délai du compositeur peut ajouter 30 à 100 ms que LoAF ne voit pas. Le champ presentationTime sur les entrées LoAF (actuellement derrière l'indicateur expérimental PaintTimingMixin) est censé résoudre ce problème, mais n'est pas encore stable d'une version de Chrome à l'autre.

Aucun de ces problèmes n'est fatal. Ils signifient simplement que LoAF ne dit pas tout. Pour 80 % des problèmes d'INP sur les sites clients réels, c'est suffisant.

Par où commencer

Si vous n'avez jamais regardé les données LoAF auparavant, commencez sur le terrain, pas dans le laboratoire. Installez le build d'attribution de web-vitals.js, envoyez (beacon) le script le plus long par mesure d'INP et examinez les dix premières valeurs de longestScriptSource sur une semaine de trafic. Ce qui se trouve en haut de la liste est votre première correction, indépendamment de ce que vous dit votre rapport Lighthouse.

Si vous voulez la vue en laboratoire, le panneau Performance des Chrome DevTools n'affiche pas LoAF nativement, mais les données sont disponibles via PerformanceObserver. L'approche de la piste personnalisée utilisant performance.measure avec le champ de détail devtools fait remonter les entrées LoAF à l'intérieur du panneau. Le projet webperf-snippets a un snippet de console qui imprime un tableau récapitulatif des entrées LoAF avec les interactions, ce que j'exécute généralement en premier lors du débogage d'une page spécifique.

Explorer le reste de la série INP

Le guide LoAF s'intègre dans le flux de travail plus large. Pour approfondir chaque phase ou étape :

Sources : Chrome for Developers, API Long Animation Frames, MDN, Long animation frame timing, Spécification W3C Long Animation Frames, Bibliothèque GoogleChrome/web-vitals.

About the author

Arjen Karel is a web performance consultant and the creator of CoreDash, a Real User Monitoring platform that tracks Core Web Vitals data across hundreds of sites. He also built the Core Web Vitals Visualizer Chrome extension. He has helped clients achieve passing Core Web Vitals scores on over 925,000 mobile URLs.

J'écris le code, pas le rapport.

Je m'intègre à votre équipe pendant 1 à 2 sprints. Je branche le monitoring pour que vos métriques restent au vert une fois parti.

Contactez-moi

Réponses à vos questions sur l'API Long Animation Frames

Support navigateur et ce que LoAF remplace

L'API Long Animation Frames est-elle supportée dans tous les navigateurs ?

Non. LoAF est exclusif à Chromium. Chrome, Edge, Opera et Brave l'ont. Safari et Firefox ne l'ont pas. Vérifiez toujours la prise en charge des fonctionnalités avec PerformanceObserver.supportedEntryTypes?.includes('long-animation-frame') avant de vous y fier.

LoAF remplace-t-il l'API Long Tasks ?

Pour le débogage de l'INP, oui. Long Tasks vous donne la durée de blocage du thread principal sans aucune attribution de script. LoAF vous donne le même signal de blocage plus le script réel qui s'est exécuté. Il n'y a aucune raison d'utiliser Long Tasks pour les nouveaux travaux sur l'INP. Le Total Blocking Time pourrait éventuellement être redéfini en termes de blockingDuration de LoAF.

Lire les données LoAF

Comment puis-je corréler une entrée LoAF avec une mesure d'INP spécifique ?

Le build d'attribution de web-vitals.js fait cela pour vous via attribution.longAnimationFrameEntries. Il trouve les entrées LoAF dont la fenêtre de temps chevauche l'interaction candidate de l'INP. Si vous créez votre propre observateur, comparez entry.startTime et entry.startTime + entry.duration avec le moment de l'interaction, et incluez tout LoAF dont la fenêtre s'intersecte.

Que signifie réellement forcedStyleAndLayoutDuration ?

C'est le temps passé par le script à déclencher une mise en page synchrone. Lorsque votre JavaScript lit offsetHeight, getBoundingClientRect, ou toute autre propriété nécessitant une mise en page à jour après une mutation du DOM, le navigateur doit effectuer un travail de mise en page de manière synchrone à l'intérieur du script. Ce travail est réattribué au script. Une valeur élevée de forcedStyleAndLayoutDuration correspond presque toujours à du layout thrashing.

Pourquoi le tableau scripts est-il vide pour certaines entrées LoAF ?

Trois raisons. Premièrement, l'entrée ne contient que les scripts qui se sont exécutés pendant au moins 5 ms. Une frame remplie de scripts courts sous ce seuil ne montrera aucune attribution. Deuxièmement, les scripts s'exécutant dans des iframes cross-origin, des web workers, des service workers ou des extensions de navigateur ne sont pas attribués car LoAF ne voit que le thread principal de la même origine (same-origin). Troisièmement, la frame peut n'avoir aucun travail de script du tout, uniquement du style et de la mise en page, ce qui correspond à une frame de présentation-delay (délai de présentation).

Au-delà de l'INP

LoAF peut-il aider avec le LCP, pas seulement avec l'INP ?

Oui, indirectement. Un LoAF qui s'exécute avant le rendu de l'élément LCP est un long script qui retarde le rendu du LCP. Filtrez les LoAF pour ne conserver que ceux dont l'heure de fin est antérieure à l'horodatage du LCP, et le script le plus long qui s'y trouve est le principal contributeur au retard de rendu du LCP. Ce sont les mêmes données d'attribution, appliquées à une métrique différente.