⭐ If you would like to buy me a coffee, well thank you very much that is mega kind! : https://www.buymeacoffee.com/honeyvig Hire a web Developer and Designer to upgrade and boost your online presence with cutting edge Technologies

Thursday, January 30, 2025

Tight Mode: Why Browsers Produce Different Performance Results

 We know that browsers do all sorts of different things under the hood. One of those things is the way they not only fetch resources like images and scripts from the server but how they prioritize those resources. Chrome and Safari have implemented a “Tight Mode” that constrains which resources are loaded and in what order, but they each take drastically different approaches to it. With so little information about Tight Mode available, this article attempts a high-level explanation of what it is, what triggers it, and how it is treated differently in major browsers.

I was chatting with DebugBear’s Matt Zeunert and, in the process, he casually mentioned this thing called Tight Mode when describing how browsers fetch and prioritize resources. I wanted to nod along like I knew what he was talking about but ultimately had to ask: What the heck is “Tight” mode?

What I got back were two artifacts, one of them being the following video of Akamai web performance expert Robin Marx speaking at We Love Speed in France a few weeks ago:

 

The other artifact is a Google document originally published by Patrick Meenan in 2015 but updated somewhat recently in November 2023. Patrick’s blog has been inactive since 2014, so I’ll simply drop a link to the Google document for you to review.

That’s all I have and what I can find on the web about this thing called Tight Mode that appears to have so much influence on the way the web works. Robin acknowledged the lack of information about it in his presentation, and the amount of first-person research in his talk is noteworthy and worth calling out because it attempts to describe and illustrate how different browsers fetch different resources with different prioritization. Given the dearth of material on the topic, I decided to share what I was able to take away from Robin’s research and Patrick’s updated article.

It’s The First of Two Phases #

The fact that Patrick’s original publication date falls in 2015 makes it no surprise that we’re talking about something roughly 10 years old at this point. The 2023 update to the publication is already fairly old in “web years,” yet Tight Mode is still nowhere when I try looking it up.

So, how do we define Tight Mode? This is how Patrick explains it:

“Chrome loads resources in 2 phases. “Tight mode” is the initial phase and constraints [sic] loading lower-priority resources until the body is attached to the document (essentially, after all blocking scripts in the head have been executed).”

— Patrick Meenan

OK, so we have this two-part process that Chrome uses to fetch resources from the network and the first part is focused on anything that isn’t a “lower-priority resource.” We have ways of telling browsers which resources we think are low priority in the form of the Fetch Priority API and lazy-loading techniques that asynchronously load resources when they enter the viewport on scroll — all of which Robin covers in his presentation. But Tight Mode has its own way of determining what resources to load first.

Chrome Tight Mode screenshot
Figure 1: Chrome loads resources in two phases, the first of which is called “Tight Mode.” (Large preview)

Tight Mode discriminates resources, taking anything and everything marked as High and Medium priority. Everything else is constrained and left on the outside, looking in until the body is firmly attached to the document, signaling that blocking scripts have been executed. It’s at that point that resources marked with Low priority are allowed in the door during the second phase of loading.

There’s a big caveat to that, but we’ll get there. The important thing to note is that…

Chrome And Safari Enforce Tight Mode #

Yes, both Chrome and Safari have some working form of Tight Mode running in the background. That last image illustrates Chrome’s Tight Mode. Let’s look at Safari’s next and compare the two.

A screenshot comparing Tight Mode in Chrome with Tight Mode in Safari.
Figure 2: Comparing Tight Mode in Chrome with Tight Mode in Safari. Notice that Chrome allows five images marked with High priority to slip out of Tight Mode. (Large preview)

Look at that! Safari discriminates High-priority resources in its initial fetch, just like Chrome, but we get wildly different loading behavior between the two browsers. Notice how Safari appears to exclude the first five PNG images marked with Medium priority where Chrome allows them. In other words, Safari makes all Medium- and Low-priority resources wait in line until all High-priority items are done loading, even though we’re working with the exact same HTML. You might say that Safari’s behavior makes the most sense, as you can see in that last image that Chrome seemingly excludes some High-priority resources out of Tight Mode. There’s clearly some tomfoolery happening there that we’ll get to.

Where’s Firefox in all this? It doesn’t take any extra tightening measures when evaluating the priority of the resources on a page. We might consider this the “classic” waterfall approach to fetching and loading resources.

Comparison of Chrome, Safari, and Firefox Tight Mode
Figure 3: Chrome and Safari have implemented Tight Mode while Firefox maintains a simple waterfall.(Large preview)

Chrome And Safari Trigger Tight Mode Differently #

Robin makes this clear as day in his talk. Chrome and Safari are both Tight Mode proponents, yet trigger it under differing circumstances that we can outline like this:


ChromeSafari
Tight Mode triggeredWhile blocking JS in the <head> is busy.While blocking JS or CSS anywhere is busy.

Notice that Chrome only looks at the document <head> when prioritizing resources, and only when it involves JavaScript. Safari, meanwhile, also looks at JavaScript, but CSS as well, and anywhere those things might be located in the document — regardless of whether it’s in the <head> or <body>. That helps explain why Chrome excludes images marked as High priority in Figure 2 from its Tight Mode implementation — it only cares about JavaScript in this context.

So, even if Chrome encounters a script file with fetchpriority="high" in the document body, the file is not considered a “High” priority and it will be loaded after the rest of the items. Safari, meanwhile, honors fetchpriority anywhere in the document. This helps explain why Chrome leaves two scripts on the table, so to speak, in Figure 2, while Safari appears to load them during Tight Mode.

That’s not to say Safari isn’t doing anything weird in its process. Given the following markup:

<head>
  <!-- two high-priority scripts -->
  <script src="script-1.js"></script>
  <script src="script-1.js"></script>

  <!-- two low-priority scripts -->
  <script src="script-3.js" defer></script>
  <script src="script-4.js" defer></script>
</head>
<body>
  <!-- five low-priority scripts -->
  <img src="image-1.jpg">
  <img src="image-2.jpg">
  <img src="image-3.jpg">
  <img src="image-4.jpg">
  <img src="image-5.jpg">
</body>

…you might expect that Safari would delay the two Low-priority scripts in the <head> until the five images in the <body> are downloaded. But that’s not the case. Instead, Safari loads those two scripts during its version of Tight Mode.

Safari deferred scripts
Figure 4: Safari treats deferred scripts in the <head> with High priority. (Large preview)

Chrome And Safari Exceptions #

I mentioned earlier that Low-priority resources are loaded in during the second phase of loading after Tight Mode has been completed. But I also mentioned that there’s a big caveat to that behavior. Let’s touch on that now.

According to Patrick’s article, we know that Tight Mode is “the initial phase and constraints loading lower-priority resources until the body is attached to the document (essentially, after all blocking scripts in the head have been executed).” But there’s a second part to that definition that I left out:

“In tight mode, low-priority resources are only loaded if there are less than two in-flight requests at the time that they are discovered.”

A-ha! So, there is a way for low-priority resources to load in Tight Mode. It’s when there are less than two “in-flight” requests happening when they’re detected.

Wait, what does “in-flight” even mean?

That’s what’s meant by less than two High- or Medium-priority items being requested. Robin demonstrates this by comparing Chrome to Safari under the same conditions, where there are only two High-priority scripts and ten regular images in the mix:

<head>
  <!-- two high-priority scripts -->
  <script src="script-1.js"></script>
  <script src="script-1.js"></script>
</head>
<body>
  <!-- ten low-priority images -->
  <img src="image-1.jpg">
  <img src="image-2.jpg">
  <img src="image-3.jpg">
  <img src="image-4.jpg">
  <img src="image-5.jpg">
  <!-- rest of images -->
  <img src="image-10.jpg">
</body>

Let’s look at what Safari does first because it’s the most straightforward approach:

Safari Tight Mode
(Large preview)

Nothing tricky about that, right? The two High-priority scripts are downloaded first and the 10 images flow in right after. Now let’s look at Chrome:

Chrome Tight Mode
(Large preview)

We have the two High-priority scripts loaded first, as expected. But then Chrome decides to let in the first five images with Medium priority, then excludes the last five images with Low priority. What. The. Heck.

The reason is a noble one: Chrome wants to load the first five images because, presumably, the Largest Contentful Paint (LCP) is often going to be one of those images and Chrome is hedging bets that the web will be faster overall if it automatically handles some of that logic. Again, it’s a noble line of reasoning, even if it isn’t going to be 100% accurate. It does muddy the waters, though, and makes understanding Tight Mode a lot harder when we see Medium- and Low-priority items treated as High-priority citizens.

Even muddier is that Chrome appears to only accept up to two Medium-priority resources in this discriminatory process. The rest are marked with Low priority.

That’s what we mean by “less than two in-flight requests.” If Chrome sees that only one or two items are entering Tight Mode, then it automatically prioritizes up to the first five non-critical images as an LCP optimization effort.

Truth be told, Safari does something similar, but in a different context. Instead of accepting Low-priority items when there are less than two in-flight requests, Safari accepts both Medium and Low priority in Tight Mode and from anywhere in the document regardless of whether they are located in the <head> or not. The exception is any asynchronous or deferred script because, as we saw earlier, those get loaded right away anyway.

How To Manipulate Tight Mode #

This might make for a great follow-up article, but this is where I’ll refer you directly to Robin’s video because his first-person research is worth consuming directly. But here’s the gist:

  • We have these high-level features that can help influence priority, including resource hints (i.e., preload and preconnect), the Fetch Priority API, and lazy-loading techniques.
  • We can indicate fetchpriority="high" and fetchpriority="low" on items.
<img src="lcp-image.jpg" fetchpriority="high">
<link rel="preload" href="defer.js" as="script" fetchpriority="low">
  • Using fetchpriority="high" is one way we can get items lower in the source included in Tight Mode. Using fetchpriority="low is one way we can get items higher in the source excluded from Tight Mode.
  • For Chrome, this works on images, asynchronous/deferred scripts, and scripts located at the bottom of the <body>.
  • For Safari, this only works on images.

Again, watch Robin’s talk for the full story starting around the 28:32 marker.

That’s Tight… Mode #

It’s bonkers to me that there is so little information about Tight Mode floating around the web. I would expect something like this to be well-documented somewhere, certainly over at Chrome Developers or somewhere similar, but all we have is a lightweight Google Doc and a thorough presentation to paint a picture of how two of the three major browsers fetch and prioritize resources. Let me know if you have additional information that you’ve either published or found — I’d love to include them in the discussion.

Lesser Known Uses Of Better Known Attributes

 

  • HTML attributes are like little instructions that we add to the markup of elements to make them do certain things or behave in certain ways. For example, most of us know that the target attribute with a value of _blank opens the link in a new tab or window. But did you know that you can use it on the form element, too? John Rhea presents several lesser-known uses for common HTML attributes.

    The list of attributes available in HTML is long and well-documented. Even if you haven’t memorized them (and there’s totally nothing wrong with an author… er… random person off the street, who has), there are a bunch of places where HTML attributes you’re familiar with will have different or more specific jobs. Let’s take a look.

    name #

    You may have heard of the name attribute, giving a name/label to information sent through a form. And more specifically you may have used it to bring together a set of radio buttons, but you can also use it with the details element to have only one of a set of accordions open at a time.

    Like if you have more than one refrigerator in the office at work, any respectable person would only open one door at a time. Right, Bob?

    <details name="office-kitchen">
      <summary>Refrigerator 1</summary>
      Lunches, condiments, yogurt et al.
    </details>
    <details name="office-kitchen">
      <summary>Refrigerator 2</summary>
      More Lunches, leftovers from client meeting, stray cans of off-brand soda et al.
    </details>
    <details name="office-kitchen">
      <summary>Refrigerator 3</summary>
      Cookie dough someone bought from somebody’s child’s fundraiser, expired milk, unidentifiable meat et al.
    </details>
    
    See the Pen Name [forked] by Undead Institute.

    title #

    You’ve probably heard of the universal attribute title. It’s typically thought of as the built-in tooltip text, but three elements have special semantics for the title attribute: input and the rarely used gems, the definition (dfn) element, and the abbreviation (abbr) element.

    If you’re using a pattern attribute on an input to provide some regex-based error correction, then you should definitely tell the user how to meet the criteria you’re using. The title attribute can be used both as a tooltip and as the message shown when the user doesn’t meet that criteria.

    <form method="post" action="#">
      <label>Who took my <em>well labeled</em> yogurt from the company fridge? I know it was you, Bob.<br>
        <input type="text" pattern="Bob [A-Za-z].+" title="There are many Bobs. The only question is which one it was. Please limit your answers to Bob and his/her last name.">
      </label><br>
      <button type="submit">Submit</submit>
    </form>
    
    See the Pen Title - Input [forked] by Undead Institute.

    For a dfn element, if you use the title attribute, then it has to include the term being defined. dfn should still have text in it, but it can be an abbreviation or a different form of the term.

    <p><dfn title="Office Yogurt Thief">OYG</dfn>’s are a pox on humanity. Stealing yogurts from the office fridge even when they are labeled.</p>
    
    See the Pen Title - dfn [forked] by Undead Institute.

    A title on an abbr element not only sets the tooltip but explicitly has the expansion of the abbreviation or acronym. As it says in the spec: “The [title] attribute, if specified, must contain an expansion of the abbreviation, and nothing else.” That’s the specification’s equivalent of threatening a mafia hit if you aren’t careful with your title attributes. You have been warned, Bob.

    When dealing with a suspected yogurt thief, we must create a <abbr title="Human Resources Special Task Force on Yogurt Theft">HRSTFYT</abbr> to deal with the problem.
    
    See the Pen Title - abbr [forked] by Undead Institute.

    value #

    The value attribute is well known for setting default values on inputs, but you can also use it on a list item (li) within an ordered list (ol) to change the number of that item and all that follow it. It only takes integers, but you can use it no matter what type of ordered list you use (numeric, alphabetical, or Roman numerals).

    <h1>Favorite Co-workers</h1>
    <ol>
      <li>Tina</li>
      <li>Keesha</li>
      <li>Carlos</li>
      <li>Jamal</li>
      <li>Scott</li>
      <li value="37">Bob</li>
      <li>Bobbie</li>
      <li>Bobby</li>
      <li>"Rob"</li>
    </ol>
    
    See the Pen Value [forked] by Undead Institute.

    datetime #

    You might have used a datetime attribute on a time element to provide a machine-readable format of the time and/or date represented in the time element’s text:

    <time datetime="2024-12-09">Dec. 12, 2024</time>
    

    The same attribute can also be used with the ins and del elements (used for notating an addition/insertion and deletion of content, respectively). The datetime attribute on ins and del provides a machine-readable date (and optionally a time) for when the change was made. You can show it to the visitor if you like, but it’s mainly intended for private use.

    Check out the edits of the original version of an earlier example:

    When dealing with a <ins datetime="2024-11-17">suspected</ins> yogurt thief <del datetime="2024-11-17">, like Bob,</del> we must create a <abbr title="Human Resources Special Task Force on Yogurt Theft">HRSTFYT</abbr> to deal with <del datetime="2024-11-17">Bob</del> <ins datetime="2024-11-17">the problem</ins>.
    
    See the Pen Datetime [forked] by Undead Institute.

    As an added note, screenreaders do not announce the datetime attribute in this context.

    cite #

    Sticking with our oft-neglected friends ins and del, the cite attribute that you use on blockquote and q elements to provide a URL of the source of the quotation can also be used on ins and del to provide the URL of a document explaining the changes.

    When dealing with a <ins datetime="2024-11-17" cite="https://codepen.io/undeadinstitute/full/VYZeaZm">suspected</ins> yogurt thief <del datetime="2024-11-17" cite="https://codepen.io/undeadinstitute/full/VYZeaZm">, like Bob,</del> we must create a <abbr title="Human Resources Special Task Force on Yogurt Theft">HRSTFYT</abbr> to deal with <del datetime="2024-11-17" cite="https://codepen.io/undeadinstitute/full/VYZeaZm">Bob</del> <ins datetime="2024-11-17" cite="https://codepen.io/undeadinstitute/full/VYZeaZm">the problem</ins>.
    
    See the Pen Cite [forked] by Undead Institute.

    Again, these dates are not announced in assistive tech, like screen readers.

    multiple #

    You probably know the multiple attribute as that more-than-meets-the-eye attribute that transforms a dropdown menu into a multi-select box, like a co-worker who lets you choose two donuts from the box. (I’m lookin’ at you, Tina.) But it has two other uses as well. You can add it to both a file input and an email input to accept multiple files and emails, respectively.

    <form method="post" action="#">
      <label>Upload complaint forms (about Bob) <input type="file" multiple> </label><br>
      <label>Email all of Bob’s bosses: <input type="email" multiple></label><br>
      <button type="submit">Submit</submit>
    </form>
    

    Just be sure that the emails are comma-separated.

    See the Pen Multiple [forked] by Undead Institute.

    for #

    In your travels across the internet, you’ve probably come across the for attribute when it’s used to associate a label element with a form field (input, select, textarea, etc.), but you can also use it to explicitly associate the elements of a calculation with an output element.

    Unlike a label-input relationship, which is a one-to-one relationship (i.e., one label for one field), the for attribute used on an output can hold an unordered, space-separated list of IDs of the fields that contributed to the calculation.

    <form method="post" action="#" id="comeuppance"> 
      <fieldset><legend>Defendant name</legend>
        <label>First Name: <input name="fname" type="text"></label>
        <label>Last Name: <input name="lname" type="text"></label>
      </fieldset>
      <label>Number of yogurts stolen (unlabeled): 
        <input name="numunlbld" id="numstolen-unlbld" type="number">
      </label> * 
      <label>Unlabeled Multiplier: 
        <input name="multiunlbld" id="multi-unlbld" type="number" value="0.5">
      </label> + 
      <label>Number of yogurts stolen (labeled): 
        <input name="numlbld" id="numstolen-lbld" type="number">
      </label> * 
      <label>Labeled Multiplier: 
        <input name="multilbld" id="multi-lbld" type="number" value="1.5">
      </label> = 
      <label>Suggested prison term (in years):
        <output id="answer" for="numstolen-unlbld numstolen-lbld multi-unlbld multi-lbld"></output>
      </label>
    </form>
    
    See the Pen For [forked] by Undead Institute.

    target #

    Just like you can use a target attribute to open a link in a new tab/window, you can use the same target attribute and value _blank to have a form open the response in a new tab/window.

    <form method="post" action="#" id="comeuppance" target="_blank"> 
      [...]
    </form>
    

    disabled #

    You may have used the disabled attribute to knock out an individual form field, but you can also use it to disable every descendant of a fieldset element.

    No matter what HR says and its innocent-until-proven-guilty ethos, we all know Bob did it. Don’t we?

    <form method="post" action="#" id="comeuppance"> 
      <fieldset disabled><legend>Defendant name</legend>
        <label>First Name: <input name="fname" type="text" value="Bob"></label>
        <label>Last Name: <input name="lname" type="text" value="McBobberson"></label>
      </fieldset>
      [...]
    </form>
    
    See the Pen Disabled [forked] by Undead Institute.

    Attribute Selectors #

    While not technically an HTML tip, attributes can also be used as selectors in CSS. You put square brackets around the attribute name and you’ll select all elements that contain that attribute.

    <style>
      [title] {
        background-color: red;
      }
    </style>
    <h1>List of <del>Suspects</del><ins>Co-workers</ins></h1>
    <ol>
      <li>Fred</li>
      <li>Rhonda</li>
      <li>Philomina</li>
      <li>Cranford</li>
      <li>Scott</li>
      <li title="the guilty one">Bob</li>
      <li>Bobbie</li>
      <li>Bobby</li>
      <li>"Rob"</li>
    </ol>
    
    See the Pen Attribute Selector [forked] by Undead Institute.

    There’s actually a whole lot more you can do with attribute selectors, but that’s the topic of another article — literally.

    Wrapping Up #

    Okay, now that we’ve learned some trivia we can use to properly prosecute Bob’s office and yogurtorial transgressions, do you have a favorite HTML attribute I didn’t discuss? Show off your personal life-of-the-party energy in the comments. (And, yes, cool people have a favorite HTML attribute… really cool people… right? Right?? Right!?!?!?!?!)