Making Sense of LoAF

A presentation at performance.now() in October 2025 in Amsterdam, Netherlands by Andy Davies

Slide 1

Slide 1

Making sense of Long Animation Frames Andy Davies · #PerfNow · Oct 2025 @andydavies.me Photo by Patrick Fore on Unsplash

Slide 2

Slide 2

We’re shipping more and more JavaScript @andydavies.me

Slide 3

Slide 3

Download Size != impact document.addEventListener(“mousemove”, function() { for(var a = Date.now() + 500; Date.now() < a;) ; }); OK, so it’s a bit of a silly example but you get the idea… @andydavies.me

Slide 4

Slide 4

Runtime costs are a hidden danger @andydavies.me Photo by David Clode on Unsplash

Slide 5

Slide 5

Long Tasks API We’ve tried to measure them before… https://w3c.github.io/longtasks/ @andydavies.me

Slide 6

Slide 6

Long Task = Main Thread Task > 50ms @andydavies.me Captured at 4x slowdown in Chrome DevTools

Slide 7

Slide 7

Long Task = Main Thread Task > 50ms @andydavies.me Captured at 4x slowdown in Chrome DevTools

Slide 8

Slide 8

We can measure Long Tasks with RUM @andydavies.me SpeedCurve RUM

Slide 9

Slide 9

But we get no detail on their cause { “name”: “self”, “entryType”: “longtask”, “startTime”: 48723.60000002384, “duration”: 67, “navigationId”: “3ca0c548-7618-4423-87b7-41ae43415040”, “attribution”: [ { “name”: “unknown”, “entryType”: “taskattribution”, “startTime”: 0, “duration”: 0, “navigationId”: “3ca0c548-7618-4423-87b7-41ae43415040”, “containerType”: “window”, “containerSrc”: “”, “containerId”: “”, “containerName”: “” } ] } @andydavies.me

Slide 10

Slide 10

@andydavies.me Photo by Dušan veverkolog on Unsplash

Slide 11

Slide 11

How can Long Animation Frames help us? @andydavies.me Photo by Drew Hastings on Unsplash

Slide 12

Slide 12

Lo-AF @andydavies.me

Slide 13

Slide 13

Long Animation Frames (LoAF) ls Holidays Way Cars Multi-City Flight Flights Return Hotels One Way Holidays Cars Multi-City Flight Flights Return Hotels One Way Holidays Cars Multi-City Flight Flights Return Hotels One Way Holidays Cars Multi-City Flight Flights Return Hotels One Way Holidays Cars Multi-City Flight Flights Return Hotels One Way Hol M Fligh athrow (LHR) London, UK - Heathrow (LHR) London, UK - Heathrow (LHR) London, UK - Heathrow (LHR) London, UK - Heathrow (LHR) London, UK - Heathrow (LH Praia, Cape Verde, (RAI) Nelson Mandela, Praia, Cape Verde, (RAI) Nelson Mandela, Praia, Cape Verde, (RAI) Nelson Mandela, Praia, Cape Verde, (RAI) Nelson Mandela, Praia, Cape Verde, (RAI) Nelson Mandela, Praia, Cap 14th Nov Sat, 1st Nov – Sat 14th Nov Sat, 1st Nov – Sat 14th Nov Sat, 1st Nov – Sat 14th Nov Sat, 1st Nov – Sat 14th Nov Sat, 1st Nov – Sat 14th Nov 2 Adults, First 2 Adults, First 2 Adults, First 2 Adults, First 2 Adults, First Search Search @andydavies.me Search Search Search Search

Slide 14

Slide 14

in Cars Hotels ai Return One Way P Multi-City ght Fligh ow (LHR) a, Cape Verde, (RAI) h Flights nt Holidays g St ar ts Partial measurement of frame duration London, UK - Heathrow ( LoAF Nov earch @andydavies.me Nelson Mandela, Praia, C Sat, 1st Nov – Sat 14th No

50ms 2 Adults, First Searc

Slide 15

Slide 15

ed se in P ai P

50ms re nt e am Fr LoAF @andydavies.me nt g St ar St t ar ts Start of frame to start of Painting

Slide 16

Slide 16

LoAF ed se re P P Fr ai am nt e in nt g St ar St t ar ts Start of frame to start of Painting Paint Commit GPU @andydavies.me

Slide 17

Slide 17

@andydavies.me LoAF st st tT + t on ar ti St ra ut du yo t La e nd im eA ar yl ar e St im er tT nd ar re st Timestamps for key milestones

Slide 18

Slide 18

e st ar tT im nd eA yl st er nd re LoAF + yo La t ar St e im tT ar st Script Execution du ut ra St ti ar t on In simplified Main Thread terms… Style & Layout Paint Commit GPU @andydavies.me

Slide 19

Slide 19

e st ar tT im nd eA st yl er nd re LoAF + yo La t ar St e im tT ar st Script Execution du ut ra St ti ar t on Scripts execute during render phase too Style & Layout Paint Commit GPU requestAnimationFrame ResizeObserver IntersectionObserver AnimationEvents @andydavies.me

Slide 20

Slide 20

e st ar tT im nd eA yl st er nd re LoAF + yo La t ar St e im tT ar st Script Execution du ut ra St ti ar t on Warning: This is a simplified view! Style & Layout Paint Commit GPU • • @andydavies.me scripts can force Style & Layout browsers sometimes perform Style and Layout mid-frame

Slide 21

Slide 21

Need a refresher on the Event Loop? In the Loop, Jake Archibald https://vimeo.com/254947206

Slide 22

Slide 22

Paint Timing Mixin ta en es pr pa in tT im e ti on Ti m Adds paintTime & presentationTime – Chrome Canary only ATM LoAF https://www.w3.org/TR/paint-timing/#the-paint-timing-mixin @andydavies.me

Slide 23

Slide 23

Work @andydavies.me m pr st Style & Layout Ti en es tT ar yl st LoAF Pre-Style & Layout on ti ta e im nd eA er nd re + yo La t ar St e im tT ar st du ut ra St ti ar t on Calculate durations of internal phases Paint & Compositing

Slide 24

Slide 24

LoAFs can occur across the page lifecycle @andydavies.me Fast 4G, 4x CPU slowdown – https://trace.cafe/t/JcNXq2wneo

Slide 25

Slide 25

Normally at least one before FCP @andydavies.me

Slide 26

Slide 26

Often a LoAF intersecting DCL @andydavies.me

Slide 27

Slide 27

Often a LoAF intersecting DCL Currently… deferred scripts execute in a single task just before DCL (this may change) @andydavies.me

Slide 28

Slide 28

@andydavies.me SpeedCurve RUM

Slide 29

Slide 29

@andydavies.me SpeedCurve RUM

Slide 30

Slide 30

How do LoAFs affect visitor behaviour? @andydavies.me SpeedCurve RUM

Slide 31

Slide 31

Pages can generate many LoAF entries… 100% 75% Desktop Mobile Tablet 50% 25% 0% 0 20 40 60 80 100 120 140 160 180 200 220 240 LoAFs per Page @andydavies.me SpeedCurve RUM

Slide 32

Slide 32

Pages can generate many LoAF entries… 100% 75% Desktop Mobile Tablet 50% 25% 25 0% 0 20 40 60 80 100 120 140 160 180 200 220 240 LoAFs per Page @andydavies.me SpeedCurve RUM

Slide 33

Slide 33

Pages can generate many LoAF entries… 100% 75% Desktop Mobile Tablet 50% 25% 25 0% 0 20 40 60 60 80 100 120 140 160 180 200 220 240 LoAFs per Page @andydavies.me SpeedCurve RUM

Slide 34

Slide 34

Pages can generate many LoAF entries… 100% 75% Desktop Mobile Tablet 50% 25% 25 0% 0 20 40 60 60 100 80 100 120 140 160 180 200 220 240 LoAFs per Page @andydavies.me SpeedCurve RUM

Slide 35

Slide 35

Query entries from Performance Timeline performance.getEntriesByType(“long-animation-frame”); @andydavies.me

Slide 36

Slide 36

Observe entries via PerformanceObserver const observer = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { console.log(entry) } }); observer.observe({ type: ‘long-animation-frame’, buffered: true }); @andydavies.me

Slide 37

Slide 37

Available in Chromium based browsers @andydavies.me

Slide 38

Slide 38

Entries are only created for visible tabs @andydavies.me

Slide 39

Slide 39

pr fir st es U en IE ta ve ti nt on Ti Ti m m e eS ta m p Did the frame handle an interaction? LoAF @andydavies.me

Slide 40

Slide 40

pr fir st es U en IE ta ve ti nt on Ti Ti m m e eS ta m p Did the frame handle an interaction? LoAF Interaction to Next Paint Measures a wider range of interactions than INP, but also has less detail @andydavies.me

Slide 41

Slide 41

How large could Input Delay be? FCP @andydavies.me

Slide 42

Slide 42

How large could Input Delay be? If someone clicks at this point in time It won’t be handled until at least here Longest LoAF = Maximum Potential Input Delay @andydavies.me

Slide 43

Slide 43

@andydavies.me st st tT + t on ar ti St ra ut du yo t La e nd im eA ar yl ar e St im er tT nd ar re st Estimate responsiveness to User Input

Slide 44

Slide 44

@andydavies.me st st tT + t on ar ti St ra ut du yo t La e nd im eA ar yl ar e St im er tT nd ar re st Estimate responsiveness to User Input

Slide 45

Slide 45

@andydavies.me st st tT + t on ar ti St ra ut du yo t La e nd im eA ar yl ar e St im er tT nd ar re st Estimate responsiveness to User Input

Slide 46

Slide 46

e st ar tT im nd eA yl st er nd re + yo La t ar St e im tT ar st blockingDuration @andydavies.me du ut ra St ti ar t on Estimate responsiveness to User Input

Slide 47

Slide 47

Total Blocking Duration (TBD) totalBlockingDuration = 0; LoAFs .forEach(entry => { totalBlockingDuration += entry.blockingDuration }); It’s a slightly naive approach but we launched TBD in SpeedCurve as a experiment with the aiming of learning and refining in public

Slide 48

Slide 48

Seems to match Main Thread busyness ok @andydavies.me SpeedCurve RUM

Slide 49

Slide 49

Refining Total Blocking Duration FCP @andydavies.me

Slide 50

Slide 50

Exclude LoAFs linked to interactions FCP @andydavies.me

Slide 51

Slide 51

What about LoAFs before FCP? FCP @andydavies.me @andydavies.me

Slide 52

Slide 52

What about LoAFs before FCP? FCP @andydavies.me

Slide 53

Slide 53

What about LoAFs before FCP? FCP

Slide 54

Slide 54

What about LoAFs before FCP? Could have rendered at this point FCP

Slide 55

Slide 55

What about LoAFs before FCP? Could have rendered at this point FCP Currently thinking of just removing the first LoAF but… …really interested in what every one else thinks too @andydavies.me

Slide 56

Slide 56

Updated Total Block Duration calculation FCP

Slide 57

Slide 57

Updated Total Block Duration calculation totalBlockingDuration = 0; LoAFs .slice(1) .filter(entry => entry.firstUIEventTimestamp == 0) .forEach(entry => { totalBlockingDuration += entry.blockingDuration }); @andydavies.me

Slide 58

Slide 58

Default thresholds need reviewing too @andydavies.me SpeedCurve RUM

Slide 59

Slide 59

LoAF has some novel uses… Photo by Jon Tyson on Unsplash

Slide 60

Slide 60

Approximate Frame Rate Calculations LoAF LoAF LoAF FPS = 1,000ms / LoAF.duration @andydavies.me LoAF

Slide 61

Slide 61

Approximate Frame Rate Calculations LoAF LoAF LoAF FPS = > 20 @andydavies.me LoAF

Slide 62

Slide 62

LoAF gives us useful data • How many frames were delayed and how long for? • Where did the frames spend their time? • What was the longest time a visitor might wait for a response? • How long was the main thread unable to respond to user input? • What was the approximate frame rate? • And more… @andydavies.me

Slide 63

Slide 63

It can help understand JS runtime costs @andydavies.me Photo by Jay Heike on Unsplash

Slide 64

Slide 64

Example LoAF entry { “name”: “long-animation-frame”, “entryType”: “long-animation-frame”, “navigationId”: “95558f7f-38d7-4a06-b15e-67ccf1dec62e”, “startTime”: 300.39999997615814, “duration”: 1573.4000000357628, “blockingDuration”: 605.986, “renderStart”: 1540, “styleAndLayoutStart”: 1540.1000000238419, “paintTime”: 1873.800000011921, “firstUIEventTimestamp”: 0, “scripts”: [] }, @andydavies.me

Slide 65

Slide 65

Script Execution @andydavies.me e st ar tT im nd eA yl st er nd re + yo La t ar St e im tT ar st LoAF du ut ra St ti ar t on Entry for each script execution > 5ms

Slide 66

Slide 66

e nS st ex ar ec tT ut im io e im tT ar st + ta du rt * ra ti on Entry for each script execution > 5ms Script Execution *executionStart is ‘muted’ for Cross Origin scripts @andydavies.me

Slide 67

Slide 67

e nS st ex ar ec tT ut im io e im tT ar st + ta du rt * ra ti on Entry for each script execution > 5ms Script Execution Compile Duration *executionStart is ‘muted’ for Cross Origin scripts @andydavies.me

Slide 68

Slide 68

Other timing information forcedStyleAndLayoutDuration getComputedStyle & getBoundingClientRect for the browser to recalculate Style and Layout pauseDuration Synchronous XHR can force the browser to pause @andydavies.me

Slide 69

Slide 69

What script executed? “sourceURL”: “https://www.example.com/script.js”, “sourceFunctionName”: “M”, “sourceCharPosition”: 98556 ! @andydavies.me

Slide 70

Slide 70

What script executed? “sourceURL”: “https://www.example.com/script.js”, “sourceFunctionName”: “M”, “sourceCharPosition”: 98556 Top level entry point ! @andydavies.me

Slide 71

Slide 71

Why did it execute? “invoker”: “DOMWindow.onclick”, “invokerType”: “event-listener”, @andydavies.me

Slide 72

Slide 72

Aggregate the Data… Roll up the Data… sourceURL https://www.selfridges.com/static-mfe-clp/_next/static/chunks/914-ff512dbd6d857625.js https://js-cdn.dynatrace.com/jstag/164ae1b51de/bf67380nlf/fb4fc3f4b31ef9b_complete.js https://www.googletagmanager.com/gtag/js?id=AW-989335448&l=gDatalayer&cx=c&gtm=4e5am0 https://www.selfridges.com/static-mfe-clp/_next/static/chunks/937-c796c646a443351b.js https://cdn.quantummetric.com/qscripts/quantum-selfridges.js https://www.selfridges.com/static-mfe-clp/_next/static/chunks/d8ec93b9-980f704a97712473.js https://www.googletagmanager.com/gtag/js?id=DC-5921516&l=gDatalayer&cx=c&gtm=4e5am0 https://tags.tiqcdn.com/utag/selfridges/main/prod/utag.js https://tags.pw.adn.cloud/SMVEJB/activation.js https://f.monetate.net/trk/4/s/a-26b02505/p/selfridges.com/1584236768-0? mr=t1640009934&mi=%272.1452019399.1731666309291%27&cs=!t&e=! https://www.googletagmanager.com/gtag/js?id=G-R05V82D63H&l=gDatalayer https://www.selfridges.com/static-mfe-clp/_next/static/chunks/1dd3208c-191771bcb3be71a1.js https://tags.tiqcdn.com/utag/selfridges/main/prod/utag.416.js?utv=ut4.51.202210200808 https://se.monetate.net/js/2/a-26b02505/p/selfridges.com/custom.js https://analytics.tiktok.com/i18n/pixel/static/main.MWI5ZTBkMTQ4MA.js https://www.selfridges.com/GB/en/ https://cdn.attraqt.io/xo.all-2.min.js https://sb.monetate.net/img/1/p/1581/5347147.js/monetate.c.cr.js https://static.queue-it.net/script/queueconfigloader.min.js https://www.selfridges.com/static-mfe-clp/_next/static/chunks/61721e05-4efc862ff71175ec.js Total Duration (ms) Total ForcedStyleAndLayout Duration (ms) Occurrences 4,555 1,328 1,021 938 842 767 618 543 226 204 156 131 60 57 38 29 21 7 6 5 5 139 8 0 0 0 0 0 119 0 0 0 0 0 0 0 0 0 0 0 0 0 30 18 2 1 10 3 1 5 2 1 2 6 1 2 3 1 1 1 1 1 1

Slide 73

Slide 73

Aggregate the Data… Roll up the Data… sourceURL https://www.selfridges.com/static-mfe-clp/_next/static/chunks/914-ff512dbd6d857625.js https://js-cdn.dynatrace.com/jstag/164ae1b51de/bf67380nlf/fb4fc3f4b31ef9b_complete.js https://www.googletagmanager.com/gtag/js?id=AW-989335448&l=gDatalayer&cx=c&gtm=4e5am0 https://www.selfridges.com/static-mfe-clp/_next/static/chunks/937-c796c646a443351b.js https://cdn.quantummetric.com/qscripts/quantum-selfridges.js https://www.selfridges.com/static-mfe-clp/_next/static/chunks/d8ec93b9-980f704a97712473.js https://www.googletagmanager.com/gtag/js?id=DC-5921516&l=gDatalayer&cx=c&gtm=4e5am0 https://tags.tiqcdn.com/utag/selfridges/main/prod/utag.js https://tags.pw.adn.cloud/SMVEJB/activation.js https://f.monetate.net/trk/4/s/a-26b02505/p/selfridges.com/1584236768-0? mr=t1640009934&mi=%272.1452019399.1731666309291%27&cs=!t&e=! https://www.googletagmanager.com/gtag/js?id=G-R05V82D63H&l=gDatalayer https://www.selfridges.com/static-mfe-clp/_next/static/chunks/1dd3208c-191771bcb3be71a1.js https://tags.tiqcdn.com/utag/selfridges/main/prod/utag.416.js?utv=ut4.51.202210200808 https://se.monetate.net/js/2/a-26b02505/p/selfridges.com/custom.js https://analytics.tiktok.com/i18n/pixel/static/main.MWI5ZTBkMTQ4MA.js https://www.selfridges.com/GB/en/ https://cdn.attraqt.io/xo.all-2.min.js https://sb.monetate.net/img/1/p/1581/5347147.js/monetate.c.cr.js https://static.queue-it.net/script/queueconfigloader.min.js https://www.selfridges.com/static-mfe-clp/_next/static/chunks/61721e05-4efc862ff71175ec.js Total Duration (ms) Total ForcedStyleAndLayout Duration (ms) Occurrences 4,555 1,328 1,021 938 842 767 618 543 226 204 156 131 60 57 38 29 21 7 6 5 5 139 8 0 0 0 0 0 119 0 0 0 0 0 0 0 0 0 0 0 0 0 30 18 2 1 10 3 1 5 2 1 2 6 1 2 3 1 1 1 1 1 1

Slide 74

Slide 74

Are 3rd-party tags really the problem? Total Script Duration (ms) @andydavies.me 1st Party 3rd Party 6,446 5,111 Captured at 4x slowdown in Chrome DevTools

Slide 75

Slide 75

May generate many entries per page CPU Slowdown 0x 4x 6x LOAF Entries 21 32 37 ScriptTiming Entries 31 39 47 Example only: @andydavies.me Real world values will depend on device CPU, network connectivity and duration of observation

Slide 76

Slide 76

Helps answer questions such as • Which scripts have the most impact on visitors experience? • Which scripts are forcing style and layout operations? • Which scripts delay FCP or LCP or affect a visitor completing a task? • Are my 1st Party or 3rd-party scripts a problem? @andydavies.me

Slide 77

Slide 77

LoAF can help diagnose slow interactions @andydavies.me Photo by Nik on Unsplash

Slide 78

Slide 78

Interaction to Next Paint (INP) Time between a visitor interacting and the next frame being presented Flights Return Hotels Holidays One Way Cars Multi-City Return Hotels One Way Holidays Cars Multi-City London, UK - Heathrow (LHR) London, UK - Heathrow (LHR) Nelson Mandela, Praia, Cape Verde, (RAI) Nelson Mandela, Praia, Cape Verde, (RAI) Sat, 1st Nov – Sat 14th Nov Sat, 1st Nov – Sat 14th Nov 2 Adults, First 2 Adults, First Flight Search @andydavies.me Flights Flight

Slide 79

Slide 79

INP has Three Phases Input Delay Waiting for event handler to execute @andydavies.me Processing Duration Presentation Delay Event handler execution Waiting for new frame to be presented

Slide 80

Slide 80

INP has Three Phases Input Delay Other Tasks @andydavies.me Processing Duration Presentation Delay Tasks related to the event handler

Slide 81

Slide 81

INP has Three Phases Input Delay Other Tasks Processing Duration Presentation Delay Tasks related to the event handler Can use DevTools to debug some slow interactions @andydavies.me

Slide 82

Slide 82

INP has Three Phases Input Delay Processing Duration Other Tasks But this depends on what’s executing when someone interacts @andydavies.me Presentation Delay Tasks related to the event handler

Slide 83

Slide 83

@andydavies.me SpeedCurve RUM

Slide 84

Slide 84

No direct way to link EventTiming & LoAF Photo by GeoJango Maps on Unsplash

Slide 85

Slide 85

Use timestamps to map scripts to phases Input Delay Processing Duration LoAF JS @andydavies.me Presentation Delay LoAF JS JS JS LoAF JS JS JS

Slide 86

Slide 86

Discard LoAFs ending after Presentation Input Delay Processing Duration LoAF JS Presentation Delay LoAF JS JS JS LoAF JS JS JS Tasks in next frame can execute once current frame is sent to GPU @andydavies.me

Slide 87

Slide 87

Discard LoAFs ending after Presentation Input Delay Processing Duration LoAF JS @andydavies.me LoAF JS JS JS JS Presentation Delay

Slide 88

Slide 88

Exclude any script execution before Input Input Delay Processing Duration LoAF JS LoAF JS Script started executing before visitor interacted JS JS JS Presentation Delay

Slide 89

Slide 89

Exclude script execution before Input Input Delay JS Processing Duration LoAF JS JS @andydavies.me LoAF JS JS JS JS Presentation Delay

Slide 90

Slide 90

Assign scripts that are left to INP phases Input Delay JS JS JS Processing Duration LoAF LoAF JS ! @andydavies.me JS JS JS Presentation Delay

Slide 91

Slide 91

Assign scripts that are left to INP phases phase InputDelay InputDelay InputDelay InputDelay ProcessingTime @andydavies.me invokerType classic-script event-listener event-listener event-listener event-listener duration (ms) 119 10 5 12 12 sourceURL https://tags.tiqcdn.com/utag/selfridges/main/prod/utag.js https://js-cdn.dynatrace.com/jstag/164ae1b51de/bf67380nlf/fb4fc3f4b31ef https://www.selfridges.com/NL/en/features/etc/designs/zg/selfridges-new/e https://www.selfridges.com/NL/en/features/etc/designs/zg/selfridges-new/e

Slide 92

Slide 92

Can help us answer questions such as: • How are scripts affecting our visitors interactions? • Which scripts have have the most impact across all interactions? • Which are the slowest interaction handlers? • Which scripts commonly delay interaction handlers @andydavies.me

Slide 93

Slide 93

Attribution Issues: Empty sourceFunction phase ID ID ID ID PT invoker invokerType entryPoint duration (ms) sourceURL https://tags.tiqcdn.com/utag/selfridges/main/prod/utag.js classic-script 119 https://tags.tiqcdn.com/utag/selfridges/main/p SCRIPT[src=//tags.tiqcdn.com/utag/selfridges/main/prod/utag.js].onload event-listener 10 #document.ontouchstart event-listener 5 https://js-cdn.dynatrace.com/jstag/164ae1b51 #document.ontouchstart event-listener 12 https://www.selfridges.com/NL/en/features/etc BODY#selfridges-app.onclick event-listener 12 https://www.selfridges.com/NL/en/features/etc Entry points may be empty or minified function names @andydavies.me

Slide 94

Slide 94

Attribution Issues: Empty sourceURLs phase ID ID ID ID PT invoker invokerType entryPoint duration (ms) sourceURL https://tags.tiqcdn.com/utag/selfridges/main/prod/utag.js classic-script 119 https://tags.tiqcdn.com/utag/selfridges/main/p SCRIPT[src=//tags.tiqcdn.com/utag/selfridges/main/prod/utag.js].onload event-listener 10 #document.ontouchstart event-listener 5 https://js-cdn.dynatrace.com/jstag/164ae1b51 #document.ontouchstart event-listener 12 https://www.selfridges.com/NL/en/features/etc BODY#selfridges-app.onclick event-listener 12 https://www.selfridges.com/NL/en/features/etc @andydavies.me Sometimes sourceURLs are empty

Slide 95

Slide 95

Attribution Issues: Wrappers invoker invokerType sourceFunctionName sourceURL Total Duration Occurences #document.ontouchend event-listener nrWrapper …com/browser-performance/newrelic.js 13 1 svg.onclick event-listener nrWrapper …com/browser-performance/newrelic.js 16 2 DOMWindow.onmousemove event-listener nrWrapper …com/browser-performance/newrelic.js 8 1 #document.onmousedown event-listener nrWrapper …com/browser-performance/newrelic.js 10 1 #document.onpointerdown event-listener nrWrapper …com/browser-performance/newrelic.js 28 2 DOMWindow.onclick event-listener nrWrapper …com/browser-performance/newrelic.js 136 1 A.onclick event-listener nrWrapper …com/browser-performance/newrelic.js 10 2 BODY.onclick event-listener nrWrapper …com/browser-performance/newrelic.js 26 2 #document.ontouchstart event-listener nrWrapper …com/browser-performance/newrelic.js 13 1 #document.onclick event-listener nrWrapper …com/browser-performance/newrelic.js 14 2 DIV#root.onclick event-listener nrWrapper …com/browser-performance/newrelic.js 167 1

Slide 96

Slide 96

Attribution Issues: Versioned scripts Versioned / hashed resources are common in modern build processes …/_next/static/chunks/1dd3208c-191771bcb3be71a1.js …/_next/static/chunks/914-ff512dbd6d857625.js …/_next/static/chunks/937-c796c646a443351b.js They can make it hard to track resource performance over time @andydavies.me

Slide 97

Slide 97

(program: 2) @andydavies.me

Slide 98

Slide 98

Name sources using //# sourceURL <script type=”text/javascript”> document.body.addEventListener(“click”, function(){ for(var a=Date.now()+2E3;Date.now()<a;); }); //# sourceURL=Use-the-sourceURL-Luke </script> Will be used for sourceURL, works with external scripts too Example: https://andydavies.github.io/agent-tests/loaf/sourceurl.html @andydavies.me

Slide 99

Slide 99

No Script Entries for • Extensions For privacy reasons we can’t know what extensions a visitor is using Chrome on Android doesn’t support extensions • Garbage Collection Tickets open for opaque attribution of these @andydavies.me

Slide 100

Slide 100

https://webperf-snippets.nucliweb.net/Interaction/Long-Animation-Frames-Helpers

Slide 101

Slide 101

https://github.com/andydavies/perf-timeline-to-devtools-profile

Slide 102

Slide 102

I ♥ LoAF

Slide 103

Slide 103

LoAF provides insights into the runtime costs of the code we ship And allow us to identify our problem scripts Within the context of our visitors environment @andydavies.me

Slide 104

Slide 104

You’re going to need RUM Photo by Ash Edmonds on Unsplash

Slide 105

Slide 105

Feel like I’ve just scratched the surface… … and there’s more for LoAF to reveal @andydavies.me Photo by Mat Ranson on Unsplash

Slide 106

Slide 106

Finally… if you’re flying out of Schipol… Photo by Erwin Hofman

Slide 107

Slide 107

Thanks! @andydavies.me andy.davies@speedcurve.com