How many of you have heard of the long animation frame or low-FAPI?
A few.
How many of you ship JavaScript on your web pages?
Are you going to have an interesting conversation?
So I've been working in helping people measure the speed of their sites for, oh god, 12 years
I think, it's all I do.
And one of the things I found is it's really tempting to measure or focus on measuring what's
easy to measure.
You know, if we can measure it, we can do something about it.
And often, what we really need to focus on is the things that are difficult to measure.
Robin tells a lot, I was going to say endlessly, but that's probably very generous about
networking.
And we can measure what's happening over the network in lab environments, using dev tools,
or web-page tests, or similar products.
And we can also use APIs like resource timing to measure some of that network activity
in the browser as we deliver content to our visitors.
So we've got a pretty good handle on what's going on from a network point of view.
Other things are kind of a bit more difficult.
And JavaScript has been traditionally more difficult.
As time has gone past, we've ended up shipping more and more JavaScript to people visiting
our pages.
We've built rich applications.
Some of those are our client-side rendered, some are web pages full of marketing tags.
And overall, the matter scripts we're delivering has grown over time.
And the challenge with a script is, it's not like an image.
You download an image, you render it on the page.
With scripts, there's more than just download size to consider.
We have to think about what happens when that script actually gets to run on our vests
as a device, whether it's iPhone 15 or 16, which are fast devices, or a crappy $40 Android
device.
So you have to think about that.
And a while ago, it's about a decade.
Somebody said you don't need to worry about JavaScript size.
What you have to do is ship one less image, one less jpeg, and you can add more JavaScript
to your page.
I think they would, having talked in the bad it, they wouldn't make that statement today.
Because have you ever seen a jpeg that can do this?
And this is, I don't know, 200 bytes.
To save you reading it, what it does is add to three second delay every time you try
and move your mouse.
So, tiny bit of code, you can destroy the experience of your view of this stuff.
And it's a bit of a silly example, but we get real world equivalents.
Five or six years ago, I was working with a US company who had, they built their own clients
I'd rendering framework.
And they were using mutation observers.
And as the page was being constructed, mutation observers were running for so long that
the browser stopped making network requests for two seconds.
So, it's easy to screw up your page with JavaScript.
And we may not really see it because it's on our visit as device.
And we come across all sorts of situations where scripts, running on a page,
can get an interfere with somebody tries to scroll smoothly, or as they try to interact.
And we don't, or up until 18 months ago, we've not really had any way to understand what's
going on.
We tried to measure it once before.
The long-tax API, which is only in Chromium browsers, was an attempt to try and measure
how busy our main thread was.
What the long-task API does is generate an entry in the performance timeline for every
main thread task that's over 50 milliseconds.
And if you use the Chrome DevTools, it's the, when you profile a page, it's the ones that
have that red hashing over the top.
So we had this API, it could tell us when there were tasks over 50 milliseconds.
We could report it back in analytics, and we're using monitoring tools.
So we can understand how much time of visitors spent waiting for long tasks.
And we have this in our products, some of our competitors have it as well.
And the next biggest question we always get from customers is, okay, so what's causing
these long tasks?
Because we want to go fix them.
We want to make others experience better.
And the harsh reality is we don't know, because the API doesn't tell us.
We get a start time when we get a duration, so we can tell when it happened, how long
it lasted for, but in attribution, we get absolutely nothing of use, which kind of makes
me sad, because I care deeply about making people's experiences better.
Fortunately, about 18 months ago, you and a half, perhaps a bit more bit less.
The Chrome team proposed something called the Long Animation for AMZ API, or LOWAF, you
will hear it referred to.
And it takes a slightly different approach to measuring long tasks on the May thread.
What it does is it measures it from a visual perspective.
So when the browser starts to render a frame to the page, it tracks how many times, or how
long a frame, how much longer than 50 milliseconds of frame is?
So we're trying to render frames at 60 frames per second to give a smooth experience.
And what LOWAF does is notify us when frames are starting to be longer than 50 milliseconds.
And in DevTools terms, you can think of it this way.
We have the start of the frame where we pass the HTML.
In this case, where it's passing HTML, this one's quite long, because it's the
start of a page.
And then the end of it is when the browser actually starts to paint.
It's not quite when the frame is presented to this, but it's getting there.
And it's a very big Chromey in-base browsers right now.
It's certainly Chrome and Edge.
It's not available in Safari or Firefox.
It may or may not come to them.
There are some challenges, because there's Robin talked about the network stack being
different in browsers.
How browsers track what work they're doing internally varies enormously as well.
And there are two ways you can factor data.
You can, if you've got Chrome open in front of you, you can type these in a console.
So you can either query it from the former timeline, like you can, with many other types
of performance data, or you can register a performance as a driver, and the browser will
fire an event off whenever one of these entries is created.
And this is what we get in a live entry.
And you can see straight away, it's a lot more richer than what we've gone for long tasks.
We can see when the frame started and how long it took to paint it on the screen.
You'll notice the duration here is one and a half seconds, and that's because for this
example, I chose the first long frame on a page.
So it's point-frame, somebody starts to navigate to a page, so it loads URL to the point where
it first starts to play content, and that's normally quite long.
You'll notice there are way more decimal places than we need.
So quite often you can truncate decimal places to remove them.
So we find out, at the highest level, when we try to paint this frame, how long it took.
We can see how long the visitor was blocked, how long they weren't able to interact
for, and blocking duration is basically all the long tasks you take into the long tasks
that were in the frame, you take 50 milliseconds from them, and you add them up.
We can see when the browser actually wanted to start displaying this frame to the visitor.
So we get to understand, OK, this was some work done to create the frame, this is when
we want to do things like styling and rendering and layout, and in that we can begin to understand
our style sheets, our styles flowing down our page.
If the visitor interacts, so tries to click on something tap, while the frame is being presented,
we get a timestamp, so we can tell whether somebody tried to interact with this point.
And lastly, but not least, any script that executes during that frame, that executes
the longer than five milliseconds we get some data on, and we'll dig into that in a moment.
Even just looking at long animation frames on the run without even getting to scripts,
we get some useful information on our visitors' experience.
We can see how many frames we're delaying, so how smooth our visitors' experience is
if they try to interact.
We can get something approximate to a frame rate.
We can see how long of visitors might have to wait when they go to interact.
We can see the worst case for how long it might take for them to get a response.
So we get some good, just general, high-level information on what others experience is.
But the bit I am really most interested in, and I know plenty of other people are as well,
is what they tell us about the scripts, the executes.
And again, contrasting with the long task example, we get a much richer set of data.
We can tell when that script started, the executing.
We can tell how long it executed for.
We can tell whether it's forcing style and layout, so if it's querying the dimensions
and an element on the page, or querying the CSS properties, and forcing the browser
to do a style and layout calculations before it can return that information.
We can actually tell how long it took to pass the script.
The difference between the start time and the execution start is the cost of actually
passing the JavaScript compiling it.
We can see where it gets stock, making synchronous Ajax requests.
So we get a lot of rich information about this script.
We can also tell why the script is executed.
In this case, it was an event listener that was listening to on message.
It could be a click handler.
It could just be a classic script that's embedded in the page.
So we can understand why this script was executed.
And then, lastly, but not least, we can understand what the script it was.
We get the source URL for most scripts, we'll come on to a few exceptions.
We get a source function name, which is the entry point.
So when you think it's the top of the point where you call the script, you can't see what's
underneath.
You can't see the other calls that were made.
There's something called the JS Profiling API that allows you to do that, but I generally
slow data.
But we can see where it was entered.
In this case, the script is modified, so the entry point isn't actually that useful,
but we can tell what's script was responsible for this.
And pages will generate many, many entries.
I tested this page on Chrome with no CPUs throttling, Chrome with four times slow down,
Chrome with six times slow down.
And we get more data, the longer the page is taken to load in this case.
If I use the slower network connection, it would likely change again, because if somebody
set up a timer that's being pulled every 100 milliseconds, the longer it takes that page
load, the more times that time is going to run.
So we get lots and lots of data.
But fortunately, it's really easy to aggregate.
In this case, all ideas aggregated by source URL.
So I can see, while this page is loading, I can see which scripts on the page are taking
the time.
Some of the self-adjusted bundles are taking time.
There's a run product from Dinospace.
There's Google Tag Manager, there's Content Square.
And we can begin to understand which scripts are having the most impact on our visitors
in their own devices.
And it can help us answer these sorts of questions.
And if we can answer these sorts of questions, we can make choices about what we need to do
to improve our visitors experience.
The big question that I think is going to help answer is, we'll give us better information
on third-party tags.
We all think our site is fast, and then somebody from marketing goes and adds a load of
third-party tags and it gets really slow.
It's a classic developer meme, and it's a very mixed picture.
That is true for some sites.
I can show you newspaper publishers where third-party tags are absolutely their problem.
I can show you next-JS built sites, such as Selfridges, where actually next-JS is a framework
if they're problem, and it will allow us to measure this in the wild to understand
what is the cost of frameworks, what is the cost of some of these tags.
And the other thing it can really help with is measuring interactions.
So who's heard of INP or interactions in next-party?
One of the challenges with interactions in next-party is we know somebody's having a slow
interaction, but we don't know why.
And what INP is doing is measuring from the point somebody interacts in the page, in this
case, clicking on the search button on kayak, to the point where the next frame has
presented.
In kayak's case, it's the frame that changes the color of the search button.
So we can kind of measure how smooth somebody's experience is using INP, but we can
tell people, and we do tell people, we can list the elements people interacting with.
So you can see which elements have the worst INP, but at the moment we can't tell them
why.
And there are three phases to INP, there's some interactions, and then we may have
to wait before our event handler runs, because there's something else running on the
browser's main thread.
Then our event handler runs and does whatever work we're aiming to do in response, and
then we present the result of the interaction to the VISTA.
And the way I like to think about it, it's like this.
So input delay is largely beyond our control, whereas what happens in processing time
and presentation delay is about what we do in our event handler and what we're trying
to update the screen to be.
And we can use their tools to debug through interactions, you can go to DevTools, you go
to pause panel, you can start recording, you can click on whatever element you want to interact
with in the page, stop the trace, and it Chrome will give you a view of what happened
during that interaction.
The other task is kind of more problematic, because we don't know what's running when
somebody interacts when I visit this in the wild interact.
It depends when they interact.
So if I load a page and before that page is loaded, I'm trying to open the menu, there
will probably be some scripts running to do with construct in the page.
But we don't know because we can't see.
And what lower and its script attribution allows us to do is begin to identify which scripts
are causing an issue for us.
Here I've just mapped an example of some long animation frame entries, let's say JavaScript
underneath.
And we can match these scripts up to the different phase.
So first of all, we're only interested in long animation frames that finish within our
INP window.
This last lower from the end confused me for a very long time is how could I have another
frame when I haven't shown the current one.
But what happens in a browser is the browser does the work, it does layout styling, it paints,
and then it sends the frame off to the GPU to be painted onto the screen.
And at the point it goes to the GPU, the main thread is free again.
So the main thread can then pick up whatever tasks are next in its queue while it waits
to the GPU present the frame that's just being built.
So because we're only interested in the last finish within our INP window, we can get rid
of that.
And then we need to look at the beginning because in this case, the visitor interacted
while some other script was running.
And this script was running before the interacted.
But we're only interested in the portion that overlaps.
So we can truncated off and just look at these scripts and the portion of the first
script that happened during INP.
And using this, we can map it to the time, using the time stamps, we can map them to
the phase of the INP.
So we can see that in this case, the first four scripts were actually running when
the visitors are trying to interact.
So they slow our interaction down and prior to low-f we had no way of seeing them.
And then we can see this last one, the selfish is up on click.
That's actually our event handler.
And we can see the duration column that that took 12 milliseconds on the rest of it took
about 150 milliseconds.
So they waited for 150 milliseconds before we actually did an R event held around.
They can script-inspire, speech, and help us answer some really good questions.
We can look across all our interactions and see what scripts are affecting them.
Classic case, I'm working with French newspaper.
And they have an on-click handler from Google Tag Manager, and there's also one in their
Facebook script that runs every time somebody clicks on the page.
Anywhere.
And it's part of the common standard behavior.
And that contributes to slow interaction.
But they have no real way of measuring that at the moment.
We can find our slowest interaction handler across the whole page.
We can begin to understand which scripts are having a negative experience.
But there are gaps in the data.
Today there are gaps in the data, there are less gaps in the data than there were six
months ago.
There are still gaps.
As I showed earlier, the entry points might be modified function names.
In this case, they were all empty.
This happens a bit more often than I would like.
It's a particular problem where a script is injected using a tag manager's surprising
enough.
Sometimes we don't get source URLs.
An example I've seen recently is an event handler being injected by GTM.
We don't get the source URL for the script entry.
We don't get a function for it.
If I put the same event handler directly in the page, we get all the data.
But via GTM, we don't.
Some things deliberately do not have script timing entries at the moment.
We don't get any script timing information for extensions because many as a piece of
JavaScript running on a web page has no rights to know what extensions you've been installed.
Garbage collection is another common place where we get long tasks and we don't get any
attribution for them.
I'm hoping that we may get opaque attribution for them.
I don't need to know what script costs are delay or what extension costs are delay,
but it would be good to know that it was an extension in a course of the delay.
If I get some data from the field that says it was an extension that costs this delay,
I can choose not to investigate that any further.
It's something about the business environment that I have no control of.
The other thing to know is timing or what is referred to as a course.
If or when we could time things on the web very accurately, we could use timing attacks
to discover things about visitors and their browsers that we had no rights and no.
In the case of long animation frames, I think they're a course into 8 milliseconds and I think
the scripts are a course and it's about 4 milliseconds.
Just a fuzzy bit added to the script so you can't time things completely accurately.
There's also some slightly weird things about the way dev tools treat interactions.
If you've ever profiled an interaction in dev tools, you should be, you've probably seen
the pointer interaction that's in the yellow bar across the top and it's supposed to represent
ion piece.
We have the front-wester which is input delay, we have the pointer event running which is
processing time and the end-wester is a presentation delay.
I was looking at this data in JSON and couldn't quite follow it so I decided to visualise
it and one of the things Chrome does and webvitals.js does this as part of its attribution
is if or the event handle has run in the same frame than it merges them together from a
timing point of view.
It starts input delay when the VISTA is interacted.
It starts processing time at the first processing time for the events.
In this case it's that top blue line which is the pointer down event and then it carries
on measuring processing time until the last, until the latest end point for processing
time for all the event handle is so in this case it's the click which is in the middle
row and they merge them together and in this case there's a point of down, there's a
point up, there's a touch start, there's probably a touch end and there's a click.
There's some validity for merging them together but when we map the event handle
is mapping a touch start to processing time seems a reasonable thing to do.
My only challenge is that if you do the same interaction but your timing isn't quite the
same then the event handle is an event timing information gets split across multiple
fronts.
The difference between this devsource trace and the previous one is I held my finger on the
menu icon for an infinitesimal tiny little bit longer of time but what we can see is
that our interactions have been split across three phones.
In the previous one touch start would be associated with processing time and this one
touch start is associated with input delay for the click event so I'm not completely convinced
about Chrome's approach to this.
This stuff took me a while to figure out.
To me quite a few weeks of looking at this API to understand what was going on initially
I tried visualizing the data.
Get a punch Jason, convert it into SVG, put it on a page and you get a chart that looks like
this.
In this case the yellow lines are the long animation frames the pink is the script execution.
I quickly ran into problems with this because although I could tell the long animation frames
how long they were.
I could see the script running within them.
This is the view I'm used to.
I couldn't match the visualization to what I was used to seeing on the main thread.
Couldn't match it up to these core stacks of JavaScript functions so it became quite hard
for me to understand what's going on.
And around this time the Chrome DevSource team had a call me about something related and
they said, oh, if you tried this API and with this API what we can do is we can annotate
the DevSource timeline.
We can add our own data to it.
So I create an extension in this case.
This is the long animation frames, creates a performance measure and that performance measure
is displayed as one of those blocks in the DevTools timeline.
Looks like this.
And immediately I was able to start to understand how the lower fentry is lined up with
the main thread work, how the scripts themselves lined up with or the script attribution
lined up with the scripts on the main thread and could also get to see things like
how much rendering, how much time we're spending in style and layout calculations, which
is quite a lot.
The extensions there, if anybody wants to play with it, you have to load it as an unpack
extension, but it allows you to start visualizing love for yourself.
And within half a day of actually starting to use it, I actually found some Chromebooks.
So it's a lot to be said in starting to visualize data in familiar places and familiar tools.
And this case, lower scripts, lower wasn't picking up scripts with from promised
handles being resolved.
So fans of bugs got it fixed, got the API improved.
And Kisham guessed it, I really like the animation frames, I like the fact we are going
to get to a point where we can see the impact of our scripts in our visitors'
browsers and what impact they're having on their experience.
We get a real insight into the code we're shipping and how it runs and how it varies
for different people in different places on different devices.
I'm working with a customer at the moment and they track large contentful paint.
And depending on where they run their advertising campaigns, large contentful paint changes.
There, I can always tell when they've run a marketing campaign targeting the UAE,
because in the UAE network connectivity is not great and everybody uses
low-ish cost Android phones and often a web view.
And they're shipping lots of JavaScript, but they have no idea of what that JavaScript is costing
them in market terms in the UAE. So it will give us an insight into the runtime costs of the
JavaScript we ship and start allowing us to identify the problem scripts because we can have
conversations about them, we can have conversations about should we carry on using this framework,
we can have conversations with our marketing people about no this marketing tag really is causing
our visitors a problem and damaging their overall experience.
I kind of feel like I've just scratched the surface a little bit when you're looking at this
deeply. I think beyond just looking at script timing, it's quite a lot else
that love can tell us. We can start perhaps to understand what work the browser's
having to do before it presents that first set of initial content to a visitor so first
contentful paint or where the scripts are delaying our large content for paint.
I mentioned it on one of the earlier slides, but how much time was spending
doing layout and styling is largely invisible at the moment. Where we have scripts that
say injector style sheet and causes the whole page to be re-styled. We don't see that.
We can kind of see what the overhead of script compilation will be and how much synchronous work
our scripts are doing. So although I focused on looking at what the straightforward
impact of script execution is, I think this API has a lot more to tell us. If you want to
dig into it further, the W3C spec is a bit dry reading that covers it in detail,
NDN has a good article on it and of course this stuff on Chrome developers. There's a copy of
these slides that are already uploaded onto the phos then page, but you can either ask me
any questions now or if you have any questions later on you can find me on Blizz Gay. Thank you.