web-vitals: Target in FID and INP is null

Hi guys,

Im using your nice script to measuer web vitals on our website. To make optimization easier, we are also persisting the target-elemtent. It works fine with CLS and LCP but in FID and INP I get “null” for the target Element as you can see in this screenshot: image

This is how I use it:

        <script type="module">
            import {onCLS, onFID, onLCP, onINP, onFCP, onTTFB} from "https://unpkg.com/web-vitals@3/dist/web-vitals.attribution.js?module";
            function getIdOrNodeNamePlusClassList(node) {
                return (node && node.nodeType !== 9) ? (node.id ?
                    "#" + node.id :
                    node.nodeName.toLowerCase() +
                    ((node.className && node.className.length) ?
                        "." + Array.from(node.classList.values()).join(".") :
                        "")) : "";
            }
            function getLargestLayoutShiftEntry(entries) {
              return entries.reduce((a, b) => a && a.value > b.value ? a : b);
            }

            function getLargestLayoutShiftSource(sources) {
              return sources.reduce((a, b) => {
                return a.node && a.previousRect.width * a.previousRect.height >
                    b.previousRect.width * b.previousRect.height ? a : b;
              });
            }

            function getCausingElementIdentifier(name, entries = []) {
                // In some cases there won't be any entries (e.g. if CLS is 0,
                // or for LCP after a bfcache restore), so we have to check first.
                if (entries.length && name !== "FCP" && name !== "TTFB") {
                    if (name === "LCP") {
                        const lastEntry = entries[entries.length - 1];
                        return getIdOrNodeNamePlusClassList(lastEntry.element);
                    } else if (name === "FID" || name === "INP") {
                        const firstEntry = entries[0];
                        return getIdOrNodeNamePlusClassList(firstEntry.target);
                    } else if (name === "CLS") {
                        const largestEntry = getLargestLayoutShiftEntry(entries);
                        if (largestEntry && largestEntry.sources && largestEntry.sources.length) {
                            const largestSource = getLargestLayoutShiftSource(largestEntry.sources);
                            if (largestSource) {
                                return getIdOrNodeNamePlusClassList(largestSource.node);
                            }
                        }
                    }
                }
                return "";
            }

            function sendRUMData({name, value, attribution, entries}) {
                   if (name === "FID" || name === "INP") {
                       console.log("--- " + name + "(" + value +") ---");
                       console.log("- Entries -");
                       console.log(entries);
                       console.log("- Attribution -");
                       console.log(attribution);
                       console.log("- Causing -");
                       console.log(getCausingElementIdentifier(name, entries));
                    }
}
            onCLS(sendRUMData);
            onFCP(sendRUMData);
            onFID(sendRUMData);
            onINP(sendRUMData);
            onLCP(sendRUMData);
            onTTFB(sendRUMData);
        </script>

Do you know why? Am I doing something wrong?

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 3
  • Comments: 15 (9 by maintainers)

Most upvoted comments

As noted in https://github.com/GoogleChrome/web-vitals/issues/335#issuecomment-1625311650 there were two main outstanding issues that we were aware of - both issues in Chromium rather than anything this library can necessarily resolve.

  1. Certain events in did not have the target attached in Chrome. We’ve particularly send this with pointer or touch events. The reason is a little tricky but my understanding is basically that

The change we made with 3.5.2 to workaround this was to look at other events in the same interaction. For example, and interaction might be made up of a pointer down, pointer up, and click events. If either of the pointer events are the longest events they may be reported as the “INP event” and its target given. As of 3.5.2, if that target is null, we will look at the other events in that interaction to try to find a non-null target.

This is a workaround and though and in some cases there may not be a target even then. This particularly affects short events (where the FID event is reported as INP and it only has the first). We were unable to estimate how much this workaround would help but it was definitely known this would not solve all the issues.

The good news is we’ve made significant progress on the underlying bug where and think these may be reported in a future version of Chrome (probably Chrome 123). This is the better fix rather than the workaround we tried in this library.

  1. The second issue is tougher to solve. When an element is removed from the DOM it is not longer available as a target either. Think of a dialog where to click the button to dismiss it and that button does a lot of work. But the time INP is reported the dialog has been dismissed and so the target is null.

This is technically correct according to spec, but obviously painful for those trying to identify INP causes!

We have further good news here, in that we think we have a way forward here too. Basically we will grab some details at the time of the interaction to use later even if the element doesn’t exist. This may not be the same selector that the web-vitals library would have used, but hopefully will be enough to identify the element (particularly if a unique id, or a class that makes it identifiable is present on the element!).

Not sure when exactly this will land as still some details to be worked out but at earliest Chrome 123.

so to your example, yes in theory it could happen to any element, but it’s more likely to happen to specific ones (ones that have pointer events, very low INP, or for elements which are then removed from the DOM as part of the interaction or shortly afterwards).

Hopefully the workaround in 3.5.2 has helped a little (and certainly shouldn’t have made it any worse), but we need those two fixes implemented to really resolve this (and hopefully not end up revealing other issues we have not considered so far!).

I’ll keep this issue open and update it as the situation changes.

Update on this issue:

Element node is not reported for some FID/INP entries. Chrome bug - 1428899 Element node is not reported when element is disconnected from the DOM https://github.com/w3c/event-timing/issues/126

We’ve released 3.5.2 which attempts to get the first non-null element for the INP event which should work around around the first issue. Will be interesting to see how much this improves the situation and how much the second issue is still a pain point.

@tunetheweb thanks for quick reply. So, at the moment, if there is any re-mounting of a Node (delete and recreate) which happens to be the LCP element, then the element reported in metric.attribution object could be null ? cc: @AbhiPanseriya @girdhariag @jsliang @simon-paris

This issue is when an element is is disconnected from the DOM tree the Performance Observer entry returns null. So it’s not that we can’t look up the element, it’s that we don’t have the element to look up. This needs to be fixed in Chrome before this library can return it.

However, there are a number of different issue here that are easy to confuse:

  • Element node is not reported for some FID/INP entries. Chrome bug - 1428899
  • Element node is not reported when element is disconnected from the DOM Spec discussion
  • Element node ~is~ was not reported correctly for SVG elements. This was a bug in this library and was fixed last month ~but we’ve not included this in a release. Will try to get a release out next week including this. It’s worth checking if this applies to you as that could improve things.~ (Edit: this was included in 3.5.4)