2

I need to create a webpage that will generate a bad First Input Delay (FID) value.

In case you aren't aware, FID is part of Google's Web Core Vitals.

I want to simulate a bad FID because I am testing a website scanning tool that is supposed to flag a bad FID value. Therefore I want to simulate a bad value on a webpage to make sure it works.

To be clear - I am NOT trying to fix my First Input Delay. I want to create a webpage that gives a bad First Input Delay value on purpose.

But I'm not sure how to do that.

I have a HTML page with <button id="button">Click Me</button>. And in the <head> I have added this script:

<script type="module">
// Get the First Input Delay (FID) Score 
import {onFID} from 'https://unpkg.com/web-vitals@3/dist/web-vitals.attribution.js?module';

// Get the button element
const button = document.getElementById('button');

// Add a click event listener to the button
button.addEventListener('click', async () => {
  // Make a delay
  await new Promise((resolve) => setTimeout(resolve, 5000));
  // Print the FID score to the console
  onFID(console.log);
});
</script>

The imported onFID method is what Google uses from Web Vitals to report the FID value.

You can see a live version of the above script here: http://seosins.com/extra-pages/first-input-delay/

But when I click the button, 5000 milliseconds later it only prints a FID of about 3 milliseconds.

The 5000 millisecond delay is not included in the FID value.

Why doesn't it report the FID value as 5003 milliseconds?

When I try to simulate a bad FID value I am doing something wrong.

What could it be?

Update:

As suggested in the comments, I have also tried adding a delay on the server using a Cloudflare Worker. That worker delayed the server response by 5000 milliseconds. But it didn't work, because the FID value was unchanged.

Also I do not think this is the correct approach because FID measures the time from when a user first interacts with your site (i.e. when they click a link, tap on a button, etc) to the time when the browser is actually able to respond to that interaction. While the Cloudflare Worker was only slowing down the initial server response. Therefore I have since removed this experiment from the page.

Inigo
  • 12,186
  • 5
  • 41
  • 70
TinyTiger
  • 1,801
  • 7
  • 47
  • 92
  • 1
    This is done on the server side. – mr mcwolf Dec 16 '22 at 15:04
  • @mrmcwolf what needs to be done on the server side? I am using Google Cloud Bucket to host files and Cloudflare to front the domain. Is there anything I can do there? – TinyTiger Dec 17 '22 at 02:15
  • well, you should use `sleep` (or similar function) there **before** returning a response (page) to the client. I don't know if you can implement this with google services (rather than not). – mr mcwolf Dec 17 '22 at 06:51
  • @mrmcwolf I have tried that using a Cloudflare Worker, but unfortunately it does not work. More details on this have been added in the updated question. – TinyTiger Dec 17 '22 at 08:49
  • @mrmcwolf, this is web page responsiveness, not server responsiveness (see my answer). Obviously the latter can impact the former, so one *could* add some code to the server e.g. respond deliberately slowly to the page's synchronous GET request for an external JS file, but why would one do that when you could introduce it much more easily directly on the web page as I suggest in my answer? – Inigo Dec 20 '22 at 23:17
  • @Inigo, I have no idea what the OP is trying to achieve. The delay should be implemented on the server side (at least that's how I understand it). There are now two places where `sleep` can be called. Before anything is sent to the client (the OP says it doesn't work for him) or after any data is sent (forced, with a `flush` say, because usually servers buffer the output). In purely practical terms, such a delay would make sense, for example, in a login script in which a constant response time is fixed regardless of whether the login procedure is successful or not. – mr mcwolf Dec 21 '22 at 07:13
  • Here are the two delay options that can be used on the server. The implementation is on `php` [sleep 1](https://jauntiest-ramps.000webhostapp.com/sleep_1.php), [sleep 2](https://jauntiest-ramps.000webhostapp.com/sleep_2.php) – mr mcwolf Dec 21 '22 at 07:50
  • @mrmcwolf I don't think you read the original question or even my comment with due diligence. Either that or you have the same misunderstanding of `FID` that the OP has. Again, see my answer. If I'm wrong, why don't you write a real answer and explain how to do what is asked (It's right there): "I need to create a webpage that will generate a bad First Input Delay (FID) value." – Inigo Dec 21 '22 at 07:55

1 Answers1

4

I think you misunderstand what FID is

From web.dev's page on First Input Delay (FID):

What is FID?

FID measures the time from when a user first interacts with a page (that is, when they click a link, tap on a button, or use a custom, JavaScript-powered control) to the time when the browser is actually able to begin processing event handlers in response to that interaction.

and

Gotchas

FID only measures the "delay" in event processing. It does not measure the event processing time itself nor the time it takes the browser to update the UI after running event handlers.

also:

In general, input delay (a.k.a. input latency) happens because the browser's main thread is busy doing something else, so it can't (yet) respond to the user. One common reason this might happen is the browser is busy parsing and executing a large JavaScript file loaded by your app.

Here is my understanding: Actual FID measurement is built into Chrome. The web-vitals library simulates this using browser measurement APIs. The measurement isn't based on when onFID is called; onFID simply sets up a measurement event listener with those APIs. What is measured is the time between when a user clicks on something (e.g. the button) and when its event handler is triggered, not how long that handler takes to complete (see second quote above).

First, we need something that occupies (i.e. blocks) the JS Event Loop

setTimeout does not do that. It just delays when something happens. In the meantime the event loop is free to do other work, e.g. process user input. Instead you need code that does exactly what you're not supposed to do: Do some lengthy blocking CPU-bound work synchronously, thus preventing the event loop from handling other events including user input.

Here is a function that will block a thread for a given amount of time:

function blockThread (millis) {
    let start = Date.now()
    let x = 928342343234
    while ((Date.now() - start) < millis) {
        x = Math.sqrt(x) + 1
        x = x * x
    }
}

or maybe just:

function blockThread (millis) {
    let start = Date.now()
    while ((Date.now() - start) < millis) {
    }
}

Now the question is: When/where do we block the event loop?

Before I reached the understanding above, my first thought was to just modify your approach: block the event loop in the button click event listener. Take your code, remove the async, and call blockThread instead of setting a timer. This runnable demo does that:

// this version of blockThread returns some info useful for logging
function blockThread (millis) {
    let start = Date.now()
    let i = 0
    let x = 928342343234
    while (true) {
        i++
        x = Math.sqrt(x) + 1
        x = x * x
        let elapsed = (Date.now() - start)
        if (elapsed > millis) {
            return {elapsed: elapsed, loopIterations: i}
        }
    }
}

const button = document.getElementById('button');

button.addEventListener('click', () => {
  const r = blockThread(5000)
  console.log(`${r.elapsed} millis elapsed after ${r.loopIterations} loop iterations`)
  console.log('calling onFID')
  window.onFID(console.log)
  console.log('done')
})
<!DOCTYPE html>
<html>

<script type="module">
  import {onFID} from 'https://unpkg.com/web-vitals@3/dist/web-vitals.attribution.js?module'
  window.onFID = onFID
</script>

<head>
  <title>Title of the document</title>
</head>

<body>
  <button id='button'>Block for 5000 millis then get the First Input Delay (FID) Score</button>  click me!
</body>

<p>  Notice how the UI (i.e. StackOverflow) will be unresponsive to your clicks for 5 seconds after you press the above button. If you click on some other button or link on this page, it will only respond after the 5 seconds have elapsed</p>

</html>

I'd give the above a try to confirm, but I'd expect it to NOT impact the FID measurement because:

  1. Both your version and mine blocks during the execution of the event handler, but does NOT delay its start.

    What is measured is the time between when a user clicks on something (e.g. the button) and when its event handler is triggered.

    I'm sure we need to block the event loop before the user clicks on an input, and for long enough that it remains blocked during and after that click. How long the event loop remains blocked after the click will be what the FID measures.

  2. I'm also pretty sure we need to import and call onFID before we block the event loop.

    The web-vitals library simulates Chrome's internal measurement. It needs to initialize and attach itself to the browser's measurement APIs as a callback in order for it to be able to measure anything. That's what calling onFID does.

So let's try a few options...

start blocking the event loop while the page is loaded

Looking at the Basic usage instructions for web-vitals I arrived at this:

<!-- This will run synchronously during page load -->
<script>
  import {onFID} from 'web-vitals.js'

  // Setup measurement of FID and log it as soon as it is
  // measured, e.g. after the user clicks the button.
  onFID(console.log)

  // !!! insert the blockThread function declaration here !!!

  // Block the event loop long enough so that you can click
  // on the button before the event loop is unblocked. We are
  // simulating page resources continuing to load that delays
  // the time an input's event handler is ever called.
  blockThread(5000)
</script>

But I suspect that calling blockThread as above will actually also block the page/DOM from loading so you won't even have a button to click until it's too late. In that case:

start blocking after the page is loaded and before the DOMContentLoaded event is triggered    (my bet is on this one)

<script>
  import {onFID} from 'web-vitals.js'
  onFID(console.log)
</script>
<script defer>
  // !!! insert the blockThread function declaration here !!!
  blockThread(5000)
</script>

If that still doesn't work, try this:

start blocking when the DOMContentLoaded event is triggered

<script>
  import {onFID} from 'web-vitals.js'
  onFID(console.log)

  // !!! insert the blockThread function declaration here !!!

  window.addEventListener('DOMContentLoaded', (event) => {
    blockThread(5000)
  });
</script>

Check out this this excellent answer to How to make JavaScript execute after page load? for more variations.


Let me know if none of these work, or which one does. I don't have the time right now to test this myself.

Inigo
  • 12,186
  • 5
  • 41
  • 70
  • Your last three solutions all work, but only if I reload and immediately start clicking the page. It has nothing to do with the button on the page. You can click anywhere to get it to work. Having to click something makes sense since the [Web Vitals repo](https://github.com/GoogleChrome/web-vitals) even says "FID is not reported if the user never interacts with the page". But is there anyway to automate a click using JavaScript? I have tried using something like `document.getElementById('button').click();` without success. – TinyTiger Dec 22 '22 at 07:03
  • I'm 99% sure that's not possible because in the browser all user Javascript executes on a single thread. Your `click()` call will run after blockThread [runs to completion](https://stackoverflow.com/search?q=javascript%20%22run%20to%20completion%22), or before it even starts depending on where you put it, but not during. So you can't simulate a human browser side with JS; A real human "runs on their own thread". What I solved above is your ask: "I want to create a webpage that gives a bad First Input Delay value on purpose. But I'm not sure how to do that." and "But when I click the button..." – Inigo Dec 22 '22 at 21:58
  • So believe I answered your question as best as possible given how you asked it. If you change your question by adding, "I need this to work automatically without a human involved and my solution must be implemented entirely within an HTML page with Javascript and must use the `web-vitals` library", then I will update my answer to state that it is not possible and explain why (not possible, at least, unless you hack the `web-vitals` library, but that's just a raw idea that may not work). – Inigo Dec 22 '22 at 22:41
  • But maybe you asked the wrong question entirely. See [XY problem](https://meta.stackexchange.com/a/66378/399009). Maybe your question is really "How do I test my website scanning tool?" If so, that's a whole new [SO] question and you'd need to provide sufficient info on how your tool works, what the testing requirements are and what you've tried so far. I have some thoughts but they're based on guesses of what you are trying to do. I've already spent a lot of time on this so I can't spend it making and answering guesses. I've tried to be as helpful as possible. – Inigo Dec 22 '22 at 22:42