🌍  Page loading

Table of contents

πŸ“ Loading a web site is a gradual process

It's important to understand that [...] is a gradual process. For better user experience, the rendering engine will try to display contents on the screen as soon as possible. It will not wait until all HTML is parsed before starting to build and layout the render tree. Parts of the content will be parsed and displayed, while the process continues with the rest of the contents that keeps coming from the network.
— Tali Garsiel, How browsers work

Let's demonstrate that fact by loading a page with lots of text content. Page download is throttled by the server and takes a long time.

Try to scroll down while the page is being downloaded.

The display keeps updating, but DOMContentLoaded event won't trigger until the download and parsing of the HTML is completed.

 ☑ Webpage test result

HTML outline
<html throttle="5">
  <head>
  </head>
  <body>

    <!-- Long HTML contents -->

  </body>
</html>
Timeline
       0.00 ┬ startTime
      18.10 β”œ requestStart
     171.60 β”œ responseStart
            β”Š
    2925.30 β”œ paint: first-paint
    2925.30 β”œ paint: first-contentful-paint
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
   30235.70 β”œ responseEnd
   30236.70 β”œ domInteractive
   30236.80 β”œ domContentLoadedEventStart
   30236.80 β”œ domContentLoadedEventEnd
   30237.10 β”œ domComplete
   30237.20 β”œ loadEventStart
   30237.20 β”” loadEventEnd
Timeline: It shows the first paint happening much earlier than the HTML's response end.

[TOP]


πŸ“ External styles block rendering, but not document parsing

As we have seen in the previous example, the DOM content tree is gradually builded as the HTML is downloaded. What if we have an stylesheet link on that page?

An external stylesheet by itself does not block all of the process. The fetching of the stylesheet starts, but the HTML parsing continues. The DOMContentLoaded event will trigger regardless the style state.

However, although the DOMContentLoaded event fires, the page won't be displayed to the user until the fetching of the style is done.

This means an stylesheet resource doesn't block the document parsing, but will block the rendering. From JavaScript land It feels strange to have no visual feedback when DOMContentLoaded has already fired. There seems to be no way to force the browser into the first paint.

If an style is added dynamically from JavaScript, It won't block the rendering. Even if that is done before the first render.

While there are pending styles to download and parse, the window `load` will not fire. No matter how late they have been added to the DOM.

Syles imported with @import statements from the external styles are considered part of the same loading process.

 ☑ Webpage test result

HTML outline
<head>
  <link rel="stylesheet" href="./assets/new.css" throttle="10">
</head>
<body>

  <!-- Long HTML contents -->

</body>
Timeline
       0.00 ┬ startTime
      27.90 β”œ requestStart
     174.90 β”œ responseStart
     177.90 β”œ responseEnd
     184.30 β”œ resourceStart: link("…mples/assets/new.css")
     194.10 β”œ domInteractive
     194.10 β”œ domContentLoadedEventStart
     194.20 β”œ domContentLoadedEventEnd
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
   10472.40 β”œ resourceEnd: link("…mples/assets/new.css")
   10525.70 β”œ domComplete
   10525.80 β”œ loadEventStart
   10525.90 β”œ loadEventEnd
   10600.20 β”œ paint: first-paint
   10600.20 β”” paint: first-contentful-paint
Timeline: It shows that an external style link does not block the document parsing nor the DOMContentLoaded event. However the first paint won't happen until the download and processing of the style resource ends.
Quirks

[TOP]


πŸ“ Fast styled rendering, with inline styles

We can achieve the gradual rendering effect seen on the first example while displaying already styled content. It is possible by inlining the contents of the stylesheet.

Although inlining styles is a limited option (prevents caching, is difficult to scale), for the shake of the example It is cool to see how the browser can build the DOM and render styled contents as the HTML is dowloaded.

 ☑ Webpage test result

HTML outline
<head>
  <style>
    /* Inlined CSS */
  </style>
</head>
<body>

  <!-- Long HTML contents -->

</body>
Timeline
       0.00 ┬ startTime
      19.60 β”œ requestStart
     539.10 β”œ responseStart
            β”Š
    3240.20 β”œ paint: first-paint
    3240.20 β”œ paint: first-contentful-paint
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
   35312.40 β”œ responseEnd
   35313.20 β”œ domInteractive
   35313.20 β”œ domContentLoadedEventStart
   35313.20 β”œ domContentLoadedEventEnd
   35313.40 β”œ domComplete
   35313.40 β”œ loadEventStart
   35313.40 β”” loadEventEnd
Timeline: It shows an early painting, given there are no external resources.

[TOP]


πŸ“ Sweet spot: inline critical styles

The best experience comes when only critical styles are inlined, as we get the gradual rendering effect of styled content.

However It forces the loading of the non-critical styles on a deferred stage. The media attribute trick is a well known option for the deferred load. Although It feels hackish and relies on JavaScript.

Another possiblity would be to put the non-critical styles in a link rel="preload" tag. That link wouldn't block document parsing nor rendering. Finally place the regular link to the same style at the very end of the body. Rendering can't be blocked if something has been already painted.

This would be a sweet spot solution, except for the quirk commented on the external styles example. Blink blocks the document parsing when it finds a style link in the body. Thus although the visual exeperience is good, the delay of DOMContentLoaded makes this solution non-generalizable.

This secondary example shows the alternative for Blink (using the onload attribute).

On a more general note, be aware of abusing preloading. On HTTP/1.1 you can easily hit the number of concurrent connections to the same domain (6 in Chrome). Aside from that, remember that you are hand-picking high priority resources. That implicitly de-prioritizes other resources.

 ☑ Webpage test result

HTML outline
<head>
  <link rel="preload" as="style" href="./assets/new.css" throttle="15">
  <style>
    /* Limited critical CSS */
  </style>
</head>
<body>

  <!-- Long HTML contents -->

  <link rel="stylesheet" href="./assets/new.css" throttle="15">
</body>
Timeline
       0.00 ┬ startTime
      83.00 β”œ requestStart
            β”Š
            β”Š
            β”Š
            β”Š
    8135.00 β”œ responseStart
    8199.00 β”œ resourceStart: link("…mples/assets/new.css")
    8224.00 β”œ paint: first-contentful-paint
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
   17041.00 β”œ responseEnd
   17056.00 β”œ domInteractive
   17056.00 β”œ domContentLoadedEventStart
   17056.00 β”œ domContentLoadedEventEnd
            β”Š
            β”Š
            β”Š
            β”Š
   23495.00 β”œ resourceEnd: link("…mples/assets/new.css")
   23667.00 β”œ domComplete
   23667.00 β”œ loadEventStart
   23667.00 β”” loadEventEnd
Timeline: It shows an early paint. Contents are displayed as they arrive, styled with the critical inlined styles. Meanwhile non-critical styles are being preloaded and won't block anything. Once the non-critical styles are downloaded the styles are applied (reflow). This sequence of events is taken from a WebKit based browser.

[TOP]


πŸ“ Scripts block document parsing, but not rendering

When an script is found in the HTML, the browser pauses the document parsing until the script is executed.

If the script is external, It must be fetched and then executed. Document parsing won't continue until the script ends execution.

Givent that an script does not necessarily block the rendering, the location of the script in the document is important. If the script is located at the end of the body, some rendering is possible

.

 ☑ Webpage test result

HTML outline
<head>
</head>
<body>

  <!-- Long HTML contents -->

  <script src="./assets/junk.js" throttle="10"></script>
</body>
Timeline
       0.00 ┬ startTime
      25.20 β”œ requestStart
     174.00 β”œ responseStart
     177.90 β”œ responseEnd
     184.80 β”œ resourceStart: script("…mples/assets/junk.js")
     247.70 β”œ paint: first-paint
     247.70 β”œ paint: first-contentful-paint
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
   10471.40 β”œ resourceEnd: script("…mples/assets/junk.js")
   10473.40 β”œ mark: script execution
   10478.60 β”œ domInteractive
   10478.70 β”œ domContentLoadedEventStart
   10478.70 β”œ domContentLoadedEventEnd
   10641.20 β”œ domComplete
   10641.30 β”œ loadEventStart
   10641.30 β”” loadEventEnd
Timeline: It shows that an external script blocks document parsing. Interactive state won't be reached until the script is fetched and executed. However if enough document content has been already parsed, paint still happens.

[TOP]


πŸ“ Scripts execution requires to finish the ongoing styles loading

As we have seen previously, and external stylesheet will not block the document parsing. However if the external style is followed by an script, things change.

An script not only pauses the document parsing, but also requires to finish the ongoing styles loading. Only then will start its execution.

So document parsing becomes blocked when it gets to an script. In turn the script execution is blocked by the loading styles.

Note we emphasize the fact that only ongoing loading styles will block the script. If the order is inverted and the script is placed before the style, It will get executed immediately. See that in this secondary example (timeline will be similar to the second case).

This rule only applies to initial styles. That is to stylesheet links that are in the initial HTML response. Stylesheet links added dynamically do not block successive scripts, even if they are added to the document before those scripts.

When the initial stylesheets contain @import statements, those are considered part of the same style loading. Thus importing styles from styles is a serious concern, as it pospones script execution and rendering. Check that in this other secondary example.

 ☑ Webpage test result

HTML outline
<head>
  <link rel="stylesheet" href="./assets/new.css" throttle="10">
</head>
<body>

  <!-- Long HTML contents -->

  <script>
    performance.mark('script execution');
  </script>
</body>
Timeline
       0.00 ┬ startTime
      20.00 β”œ requestStart
     170.80 β”œ responseStart
     177.70 β”œ responseEnd
     184.30 β”œ resourceStart: link("…mples/assets/new.css")
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
   10398.10 β”œ resourceEnd: link("…mples/assets/new.css")
   10523.80 β”œ mark: script execution
   10581.60 β”œ domInteractive
   10581.60 β”œ domContentLoadedEventStart
   10581.60 β”œ domContentLoadedEventEnd
   10604.80 β”œ paint: first-paint
   10604.80 β”œ paint: first-contentful-paint
   10721.30 β”œ domComplete
   10721.50 β”œ loadEventStart
   10721.50 β”” loadEventEnd
Timeline: The external style by itself would not block the document parsing, as seen previously. However the script gets blocked by the style loading, and in turn the script blocks the document parsing. Thus DOM interactive is bounded by the style loading plus the script execution.

[TOP]


πŸ“ Deferred scripts don't block document parsing

Even though deferred scripts don't block document parsing and get downloaded in parallel (with low priority), they share some key behaviours with the "normal" synchronous script tags:

They are all executed at the same time, before DOMContentLoaded. Therefore all deferred scripts need to be completely downloaded before any of them can execute.

Roughtly we can think of deferred scripts as synchronous scripts whose execution is pushed towards the end of the document parsing process, with the benefit of prefetching them.

As a side note, scripts of type=module behave like deferred scripts. They keep order, execute once the document is interative, wait for styles, etc. The most notable difference is that they probably get higher download priority. Also they won't have access to document.currentScript.

 ☑ Webpage test result

HTML outline
<head>
  <link rel="stylesheet" href="./assets/new.css" throttle="10">
</head>
<body>
  <script src="./assets/junk.js?def1" defer throttle="5"></script>

  <script src="./assets/junk.js?def2" defer throttle="2"></script>

  <!-- Long HTML contents -->

</body>
Timeline
       0.00 ┬ startTime
      19.90 β”œ requestStart
     168.20 β”œ responseStart
     174.00 β”œ responseEnd
     178.40 β”œ resourceStart: link("…mples/assets/new.css")
     178.60 β”œ resourceStart: script("…/assets/junk.js?def1")
     178.90 β”œ resourceStart: script("…/assets/junk.js?def2")
     183.70 β”œ domInteractive
            β”Š
            β”Š
            β”Š
            β”Š
    2385.70 β”œ resourceEnd: script("…/assets/junk.js?def2")
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
    5457.80 β”œ resourceEnd: script("…/assets/junk.js?def1")
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
   10372.80 β”œ resourceEnd: link("…mples/assets/new.css")
   10420.90 β”œ mark: script execution -- deferred 1
   10421.20 β”œ mark: script execution -- deferred 2
   10421.20 β”œ domContentLoadedEventStart
   10421.20 β”œ domContentLoadedEventEnd
   10500.00 β”œ paint: first-paint
   10500.00 β”œ paint: first-contentful-paint
   10514.20 β”œ domComplete
   10514.20 β”œ loadEventStart
   10514.20 β”” loadEventEnd
Timeline: It shows two deferred scripts that start to download as the document parses. The document parsing ends and It reaches the interactive state. The first deferred script takes 5s to download and the second takes 2s. They need to wait for the style download to end. Then both get executed in order. Finally DOMContentLoaded is fired. The rendering is hold by the style download, not the scripts. Without the external style, rendering would take place almost immediately.

[TOP]


πŸ“ Async scripts block (almost) nothing

Async scripts are like "free agents". They:

 ☑ Webpage test result

HTML outline
<head>
  <link rel="stylesheet" href="./assets/new.css" throttle="1">
</head>
<body>
  <script async src="./assets/junk.js#async1" throttle="5"></script>

  <script async src="./assets/fib.js#async2" throttle="0" data-fibonacci-number="42"></script>

  <script async src="./assets/junk.js#async3" throttle="1"></script>

  <!-- Long HTML contents -->

</body>
Timeline
       0.00 ┬ startTime
      14.10 β”œ requestStart
            β”Š
            β”Š
            β”Š
     612.50 β”œ responseStart
     623.90 β”œ resourceStart: link("…mples/assets/new.css")
     624.80 β”œ resourceStart: script("…ssets/junk.js?async1")
     625.20 β”œ resourceStart: script("…assets/fib.js?async2")
     625.40 β”œ resourceStart: script("…ssets/junk.js?async3")
            β”Š
     811.90 β”œ resourceEnd: script("…assets/fib.js?async2")
     814.30 β”œ mark: script execution -- async 2
     814.30 β”œ mark: start fib(42)
            β”Š
            β”Š
            β”Š
    1399.80 β”œ responseEnd
            β”Š
    1680.70 β”œ resourceEnd: script("…ssets/junk.js?async3")
    1682.20 β”œ resourceEnd: link("…mples/assets/new.css")
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
    2955.60 β”œ mark: end fib(42)
    2959.30 β”œ domInteractive
    2959.30 β”œ domContentLoadedEventStart
    2959.30 β”œ domContentLoadedEventEnd
    2967.30 β”œ mark: script execution -- async 3
            β”Š
    3139.90 β”œ paint: first-paint
    3139.90 β”œ paint: first-contentful-paint
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
    5835.90 β”œ resourceEnd: script("…ssets/junk.js?async1")
    5837.10 β”œ mark: script execution -- async 1
    5837.20 β”œ domComplete
    5837.30 β”œ loadEventStart
    5837.30 β”” loadEventEnd
Timeline: It shows that four external resources start loading (one style, three async scripts) while the document is being received and parsed. The 2nd async script is the first to end its download. At this point none of the other external resources, nor the document itself have been completely received yet. Execution of this 2nd async script starts and takes a while — around 2seconds, a very looong task! While this script is executing, three other resources have finished arriving (the style, the document itself, and the 3rd async script). They can't be processed while the 2nd script is executing. When the execution of the 2nd script ends, we are unblocked to continue processing stuff. The style is processed. The rest of the document is parsed and reaches the interactive state. And the 3rd async script gets executed. Finally some rendering happens. Much later, the 1st async script ends its download and executes. Only then the load event fires.

[TOP]


πŸ“ Dynamic scripts behave like async by default

We consider dynamic scripts those that are created —and added to the DOM— using JavaScript. This can be done at any time, there is no restriction.

There are two types of dynamic scripts: async (the default), or sync. There is no straight way to create a dynamic script that acts as defer.

The insertion of a dynamic script does not block document parsing. Even if they are created in sync mode and are added before the document is interactive state.

Dynamic sync scripts block each other. They execute in the order they were added to document (the insertion position is irrelevant).

Dynamic async scripts behave exactly as non-dynamic async scripts: they almost block nothing, and get executed as soon as they are ready.

In fact any dynamic script may execute in the middle of critical processes like document parsing. It is important to keep your eyes open.

Finally, they do block the window load event. This is true for every dynamic script (async or sync), disregarding when they are added to the document. If they are added before load has fired, the event will be holded until the pending scripts execute.

 ☑ Webpage test result

HTML outline
<head>
  <script async src="./assets/junk.js?async1" throttle="0"></script>
</head>
<body>
  <script async src="./assets/junk.js?async2" throttle="9"></script>

  <script>
    // mark: script execution -- inline 1
    // Adds a dynamic async script "?dyn-async1" throttle=5
  </script>

  <script>
    // mark: script execution -- inline 2
    // Adds a dynamic async script "?dyn-async2" throttle=11
    //
    // After "?dyn-async2" execution, domComplete would arrive
    // We will add a extra dynamic async script "?dyn-async3" to
    // demostrate that domComplete is holded by this last extra script
  </script>

  <script defer src="./assets/junk.js?def1" throttle="6"></script>

  <script>
    // mark: script execution -- inline 3
    // Adds a dynamic sync script "?dyn-sync1" throttle=7
  </script>

  <script defer src="./assets/junk.js?def2" throttle="4"></script>

  <!-- Long HTML contents -->

  <script>
    // mark: script execution -- inline 4
    // Adds a dynamic sync script "?dyn-sync2" throttle=2
  </script>
</body>
Timeline
       0.00 ┬ startTime
      14.90 β”œ requestStart
            β”Š
     423.10 β”œ responseStart
     433.80 β”œ resourceStart: script("…ssets/junk.js?async1")
     434.30 β”œ resourceStart: script("…ssets/junk.js?async2")
     434.70 β”œ resourceStart: script("…/assets/junk.js?def1")
     434.80 β”œ resourceStart: script("…/assets/junk.js?def2")
     442.00 β”œ mark: script execution -- inline 1
     443.00 β”œ resourceStart: script("…/junk.js?dyn-async-1")
     443.60 β”œ mark: script execution -- inline 2
     443.80 β”œ resourceStart: script("…/junk.js?dyn-async-2")
     444.60 β”œ mark: script execution -- inline 3
     444.70 β”œ resourceStart: script("…s/junk.js?dyn-sync-1")
     482.40 β”œ paint: first-paint
     482.40 β”œ paint: first-contentful-paint
            β”Š
            β”Š
    1041.90 β”œ resourceEnd: script("…ssets/junk.js?async1")
    1045.20 β”œ mark: script execution -- async 1
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
    2886.10 β”œ mark: script execution -- inline 4
    2886.60 β”œ resourceStart: script("…s/junk.js?dyn-sync-2")
    3004.30 β”œ responseEnd
    3004.90 β”œ domInteractive
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
    4723.90 β”œ resourceEnd: script("…/assets/junk.js?def2")
            β”Š
    4921.80 β”œ resourceEnd: script("…s/junk.js?dyn-sync-2")
            β”Š
            β”Š
    5646.10 β”œ resourceEnd: script("…/junk.js?dyn-async-1")
    5647.60 β”œ mark: script execution -- dynamic async 1
            β”Š
            β”Š
            β”Š
    6618.30 β”œ resourceEnd: script("…/assets/junk.js?def1")
    6619.60 β”œ mark: script execution -- deferred 1
    6619.80 β”œ mark: script execution -- deferred 2
    6619.90 β”œ domContentLoadedEventStart
    6619.90 β”œ domContentLoadedEventEnd
            β”Š
            β”Š
            β”Š
    7796.70 β”œ resourceEnd: script("…s/junk.js?dyn-sync-1")
    7798.10 β”œ mark: script execution -- dynamic sync 1
    7798.20 β”œ mark: script execution -- dynamic sync 2
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
    9640.20 β”œ resourceEnd: script("…ssets/junk.js?async2")
    9641.60 β”œ mark: script execution -- async 2
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
   11739.20 β”œ resourceEnd: script("…/junk.js?dyn-async-2")
   11740.30 β”œ mark: script execution -- dynamic async 2
   11740.50 β”œ mark: callback execution -- onload dynamic async 2
   11741.00 β”œ resourceStart: script("…/junk.js?dyn-async-3")
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
   14862.00 β”œ resourceEnd: script("…/junk.js?dyn-async-3")
   14863.70 β”œ mark: script execution -- dynamic async 3
   14863.70 β”œ domComplete
   14863.80 β”œ loadEventStart
   14863.80 β”” loadEventEnd
Timeline: Shows that all async scripts (initials or dynamically added) block nothing and get executed when they are ready. Deferred scripts hold the DOMContentLoaded event, and must wait other deferred scripts to be downloaded before executing. Dynamic sync scripts are executed in the order they where added to the document; in the example the dyn-sync-1 script takes a long time and holds the execution of dyn-sync-2. Finally domComplete won't happen until all pending scripts execute, disregarding how late they where added. In the example dyn-async-2 would have been the last resource, but we added a new script dyn-async-3 in its onload callback, thus posposing the load event.

[TOP]


πŸ“ Execute inline scripts without waiting for styles

We have seen that inlined scripts can't execute until the ongoing styles loading ends.

It is possible to overcome that limitation by mocking the inlined script as an external async script, using the src attribute to contain the code as a data URL. The downside is that the code must be URL encoded. It is not easy to manage.

Note that this method would not be useful if we use defer, given that deferred scripts also await for the ongoing styles to finish loading.

 ☑ Webpage test result

HTML outline
<head>
  <link rel="stylesheet" href="./assets/new.css" throttle="10">
</head>
<body>

  <script async src="data:application/javascript,<YOUR_CODE_HERE>"></script>

  <!-- alternative as base64
    <script async src="data:application/javascript;base64,cGVyZm9ybWFuY2UubWFyaygnc2NyaXB0IGV4ZWN1dGlvbicp"></script>
  -->

  <!-- Long HTML contents -->

</body>
Timeline
       0.00 ┬ startTime
      14.70 β”œ requestStart
      18.00 β”œ responseStart
      22.10 β”œ responseEnd
      27.10 β”œ resourceStart: link("…mples/assets/new.css")
      34.10 β”œ mark: script execution
      34.20 β”œ domInteractive
      34.20 β”œ domContentLoadedEventStart
      34.20 β”œ domContentLoadedEventEnd
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
   10446.10 β”œ resourceEnd: link("…mples/assets/new.css")
   10498.40 β”œ domComplete
   10498.50 β”œ loadEventStart
   10498.50 β”œ loadEventEnd
   10548.10 β”œ paint: first-paint
   10548.10 β”” paint: first-contentful-paint
Timeline: An style starts to load very early. It blocks the load event, the rendering, and would block inline scripts too. The inline script of the example mocks an external async script behaviour, thus It can execute immediately without waiting for the style to load. Compare it with the regular inline script timeline.

[TOP]


πŸ“ The "load" event fires once all resources in turn have fired "load"

Most of the resources like scripts (any kind), styles, images, iframes, etc. have a load event (usually alternative exclusive to the error event).

When all the resources of the page currently loading have fired load, then the load event will take place.

Resources that are declared lazy (images or iframes) and have not started loading won't count.

 ☑ Webpage test result

HTML outline
<head>
  <link rel="stylesheet" href="./assets/inter.css">
  <link rel="stylesheet" href="./assets/new.css" throttle="2">
</head>
<body>
  <img src="./assets/www-letshare.png" />
  
  <!-- Long HTML contents -->

  <img loading="lazy" src="./assets/www-letshare.png?lazy" />

  <iframe src="./basic-html-style/index.html" onload="performance.mark('iframe loaded')"></iframe>

  <iframe loading="lazy" src="../index.html?lazy"></iframe>

</body>
Timeline
       0.00 ┬ startTime
       5.70 β”œ requestStart
      37.40 β”œ responseStart
      42.60 β”œ responseEnd
      48.30 β”œ resourceStart: link("…les/assets/inter.css")
      48.60 β”œ resourceStart: link("…mples/assets/new.css")
      48.80 β”œ resourceStart: img("…ets/www-letshare.png")
      54.50 β”œ resourceStart: iframe("…tml-style/index.html")
      56.00 β”œ domInteractive
      56.00 β”œ domContentLoadedEventStart
      56.10 β”œ domContentLoadedEventEnd
     147.40 β”œ resourceEnd: link("…les/assets/inter.css")
     148.70 β”œ resourceEnd: iframe("…tml-style/index.html")
     151.10 β”œ resourceEnd: img("…ets/www-letshare.png")
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
    2234.40 β”œ resourceEnd: link("…mples/assets/new.css")
    2237.10 β”œ resourceStart: css("…ter/Inter-Bold.woff2")
    2237.50 β”œ resourceStart: css("…/Inter-Regular.woff2")
    2273.70 β”œ paint: first-paint
    2273.70 β”œ paint: first-contentful-paint
    2316.90 β”œ resourceEnd: css("…ter/Inter-Bold.woff2")
    2323.10 β”œ resourceEnd: css("…/Inter-Regular.woff2")
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
            β”Š
   10200.60 β”œ mark: iframe loaded
   10222.90 β”œ domComplete
   10223.00 β”œ loadEventStart
   10223.00 β”” loadEventEnd
Timeline:

[TOP]