Maîtriser les requêtes au chargement d'une page web

Quand une page web est chargée, les éléments externes à celle-ci sont appelés tout au long de son chargement pour permettre au navigateur d'afficher le résultat visuel final. Cela prend du temps, et certains chargements bloquent le rendu alors que d'autres non. D'autres encore sont ignorés tant qu'une action de la part de l'utilisateur n'a pas été effectuée.

Pour bien comprendre et manager ses chargements de page, je vous propose de comprendre une liste de comportement au chargement des ressources pour Chrome, Safari, Firefox, IE et Opera.

Les images

Est-ce qu'une image dans la balise <img> qui ne doit pas apparaître au rendu déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Oui Oui Oui Oui Oui

Exemple :

<div style="display: none">
    <img src="image.png" alt"Image">
</div>

Oui. Les navigateurs téléchargent les images indépendamment des styles CSS appliqués, cela fait parti de la spécification HTML.

Ce comportement est la bête noire de pas mal d'implémentation JavaScript pour adapter les images, car l'image est demandée avant même que le JavaScript n'est le temps d'en appeler une autre via la propriété src ou même d'en obtenir sa taille.


Les backgrounds

Inclues dans un élément non affiché

Est-ce qu'un élément possédant une image de background et inclus dans un élément invisible déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Non Non Non Non Non

Exemple :

<div style="display: none">
    <div style="background: url('image.png')"></div>
</div>

Non. Les images de background en CSS diffèrent des balises img, elles ne sont pas réclamées au moment ou le code HTML est analysé.

Si un élément est en display: none, calculer les styles des éléments enfant est une perte de temps car il n'y aura aucun impact sur le rendu du document, ainsi les images de background pour les éléments enfants cachés ne sont jamais calculées, ni même téléchargées.

Sur un élément non affiché

Est-ce qu'un élément possédant une image de background et non visible déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Oui Non Oui Oui Oui

Exemple :

<div style="background: url('image.png'); display: none"></div>

Pas tout le temps. Les navigateurs font parfois des choses différentes les un des autres !

Donc ici la <div> n'est pas rendu, mais il est nécessaire que le style soit analysé pour s'en apercevoir. IE, Safari, Opera et Chrome lance la demande en file d'attente aussitôt qu'ils ont analysé les styles CSS.

Sur un élément transparent

Est-ce qu'un élément possédant une image de background et transparent déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Oui Oui Oui Oui Oui

Exemple :

<div style="background: url('image.png'); visibility: hidden"></div>

Oui. Les éléments avec un visibility: hidden prennent toujours de la place sur la page, pour que cela soit possible, cette place à besoin d'être calculée. Et ce mécanisme lance une requête de chargement.

Inclus dans un élément transparent

Est-ce qu'un élément possédant une image de background et inclus dans un élément transparent déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Oui Oui Oui Oui Oui

Exemple :

<div style="visibility: hidden">
    <div style="background: url('image.png')"></div>
</div>

Oui. Même punition que l'élément du dessus.


Les images via JavaScript

Créer une balise image et lui affecter une valeur dans sa source déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Oui Oui Oui Oui Oui

Exemple :

var img = document.createElement('img');
img.src = 'image.png';

Oui. L'élément img n'attend tout simplement pas pour faire ses requêtes.

La source d'une img est téléchargée aussitôt que l'attribut src est (re)défini. Il n'y a pas besoin pour cela que l'image soit insérée dans le document.

Si vous avez besoin de déclencher rapidement une requête http et que vous vous moquez de la réponse le plus rapide est de faire new Image().src = <url>;.


Les backgrounds via JavaScript

Création simple

Créer une balise et lui affecter une valeur de background dans son style déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Non Non Non Non Non

Exemple :

var div = document.createElement('div');
div.style.background = "url('image.png')";

Non. De la même manière que dans le document HTML, les images de background ne sont pas téléchargées tant que leurs styles ne sont pas résolu. Cela n'arrive pas tant que l'image n'est pas insérée dans le document.

Injection puis retrait

Créer une balise et lui affecter une valeur de background dans son style, puis l'ajouter au DOM et le retirer, déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Non Non Non Non Non

Exemple :

var div = document.createElement('div');
div.style.background = "url('image.png')";
document.body.appendChild(div);
/* ... du code ... */
document.body.removeChild(div);

Non. En effet, la nature mono-thread de JavaScript et du DOM signifie qu'un élément est ajouté et retiré avant même que le navigateur n'ai besoin de se soucier d'en faire le rendu.

Note : Le code ci-dessous déclenche quand même une requête HTTP sous IE à cause de div.innerHTML.

var div = document.createElement('div');
div.style.background = "url('image.png')";
document.body.appendChild(div);
/* ... du code ... */
div.innerHTML;
/* ... du code ... */
document.body.removeChild(div);



### Injection puis retrait avec certaines fonctions ###

> Créer une balise et lui affecter une valeur de background dans son style, puis l'ajouter au DOM et le retirer tout en demandant entre les deux une information sur la balise, déclenche une requête HTTP ?

| Chrome | Firefox | IE  | Safari | Opera |
|:------:|:-------:|:---:|:------:|:-----:|
| Oui    | Oui     | Oui | Oui    | Oui   |

**Exemple :**

```js
var div = document.createElement('div');
div.style.background = "url('image.png')";
document.body.appendChild(div);
/* ... du code ... */
div.offsetWidth;
/* ... du code ... */
document.body.removeChild(div);

Oui. Pour récupérer la taille d'un élément dans le navigateur, il faut résoudre ses styles et calculer leurs affichages. Comme vu précédemment, cela induit une requête.


Les scripts

Avec un type inconnu

Est-ce qu'un fichier dans la balise <script> avec un type MIME inconnu déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Non Oui Oui Non Non

Exemple :

<script src="script.js" type="foo/bar"></script>

Pas toujours. Cela induit une requête dans Firefox et IE. Aucun des navigateurs n'est censé exécuter des scripts dont le type est inconnu, mais Firefox et IE vont télécharger le script indépendamment de son type.

La specification HTML dit aux navigateurs d'ignorer les scripts avec un type non supporté.

Note : Bien que le type text/html soit supporté par les navigateurs, cela n'est pas le cas via la balise <script>.

Note 2 : l'attribut ancien language suit le même procéder dans les 5 navigateurs.

Sans type

Est-ce qu'un fichier dans la balise <script> sans type MIME déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Oui Oui Oui Oui Oui

Exemple :

<script src="script.js"></script>

Oui. Si un script n'a pas d'attribut type ou language, text/javascript est utilisé. C'est un comportement commun des navigateurs qui fait parti de la specification HTML.


Chargement dynamique de scripts

Création simple

Créer un script et lui affecter un fichier déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Non Non Oui Non Non

Exemple :

var script = document.createElement('script');
script.src = 'script.js';

Pas toujours. Ce que dit la specification :

Quand un élément script... rencontre l'un des événements listés... celui-ci doit être synchronisé quand l'élément sera inséré dans le document...

IE ne suit pas les règles en effectuant la requête avant que le script ne soit inséré dans le document, bien que cela permette de télécharger un script et l'analyser de manière séparé.

Injection puis retrait

Créer un script et lui affecter un fichier, puis l'ajouter au DOM et le retirer, déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Oui Oui Oui Oui Oui

Exemple :

var script = document.createElement('script');
script.src = 'script.js';
document.body.appendChild(script);
document.body.removeChild(script);

Oui. Regardons de nouveau la specification :

Quand un élément script... rencontre l'un des événement listé... celui-ci doit être synchronisé quand l'élément sera inséré dans le document...

La préparation du script est synchrone, elle n'attend pas la fin de l'exécution du code.


Chargement dynamique de fichiers

Création simple

Créer une balise link et lui affecter un fichier déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Non Non Non Non Non

Exemple :

var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'style.css';

Non. Pas de requête cette fois ci, en accord avec la specification :

Le moment approprié pour obtenir une ressource externe via un link créé est le moment où celui-ci est inséré dans le document...

...donc on ne s'attend pas à voir partir une requête tant que l'élément n'est pas inséré dans le DOM.

Injection puis retrait

Créer une balise link et lui affecter un fichier, puis l'ajouter au DOM et le retirer, déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Oui Oui Oui Oui Oui

Exemple :

var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'style.css';
document.body.appendChild(link);
document.body.removeChild(link);

Oui. Le style par défaut donné à une ressource link est text/css donc ne pas mettre de type n’empêche pas la requête d'être faites.

Aussi, comme pour les scripts, les stylesheets requièrent une action synchrone. Retirer l'élément juste après n’empêche pas la requête d'être faites.

Avec un mauvais type

Créer une balise link avec un mauvais type MIME et lui affecter un fichier déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Oui Non Non Oui Oui

Exemple :

var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'style.css';
link.type = 'text/javascript';
document.body.appendChild(link);

Pas toujours. La spécification ne couvre pas ce qui arrive quand un content-type non supporté par link est mentionné. Chrome, Opera et Safari font tout de même la requête ici, bien qu'ils n'analysent même pas le style...


Les iframes

Sans rien

Est-ce qu'une balise <iframe> déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Non Non Non Non Oui

Exemple :

<iframe></iframe>

Pas toujours. La specification dit à propos d'une iframe sans src :

Si un élément n'a pas d'attribut src de spécifié... simplement levé un événement nommé « load » sur l'élément iframe.

Seul Opera lance une requête sur la page « about:blank ».

Avec une source vide

Est-ce qu'une balise <iframe> avec une source vide déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Non Non Non Non Oui

Exemple :

<iframe src=""></iframe>

Pas toujours. Continuons avec la specification :

De plus si la valeur de l'attribut est une string vide l'url sera about:blank.

Aucune requête n'est faites, sauf pour Opera.

Avec un identificateur de fragment (hash)

Est-ce qu'une balise <iframe> avec un identificateur de fragment déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Oui Non Non Oui Oui

Exemple :

<iframe src="#"></iframe>

Pas toujours. Lisons encore plus de specification :

De plus, résoudre la valeur de la source relativement à la source de l'iframe parent.

On s'attend donc à une requête, cependant...

Si il existe un ancêtre dans le contexte du document actif à cette adresse il faut ignorer l'identificateur de fragment.

Et donc il ne devrait pas y avoir de requête à cet endroit. Finalement personne ne fait ce qu'il faut sur ce point.

Avec une chaîne GET

Est-ce qu'une balise <iframe> avec une chaîne GET déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Oui Oui Oui Oui Oui

Exemple :

<iframe src="?"></iframe>

Oui. ? est ure url différente aussi cela lève une requête.


Changer le DOM avec document.write

Commentaires

Ne pas ouvrir un commentaire directement mais l'écrire en script autour d'une balise image déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Non Oui Oui Non Non

Exemple :

<script>document.write('<'+'!--')</script>
<img src="image.png">
-->

Pas toujours. Si un commentaire HTML est écrit, le img à l'intérieur n'est jamais analysé et donc il n'y a aucune requête. Cependant, plusieurs navigateurs améliore leur performances en regardant plus en avant. Si ils tombent sur un script inline, au lieu de s'arrêter ils continuent afin de gagner en temps, quitte à faire de l'affichage inutile (car inhibé par le script précédent). On appel cela de l'analyse spéculative.

Firefox et IE continue d'analyser le document pendant que le script est en cours d'exécution. Ils vont donc lancer une requête en rencontrant l'image. Quand le script à fini son analyse il va inhiber la balise img car un commentaire HTML se sera inséré entre temps. Malheureusement la requête aura déjà été faites.

Chrome fait également de l'analyse spéculative pour les scripts externes mais pas dans le cas de script inline.

Commentaires asynchrones

Ne pas ouvrir un commentaire directement mais l'écrire en script asynchrone autour d'une balise image déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Non Oui Oui Non Non

Exemple :

<script async>document.write('<'+'!--')</script>
<img src="image.png">
-->

Pas toujours. Même en async les scripts inline sont bloquant comme je l'ai déjà expliqué dans un précédent billet.

Note 2 : cela est la même chose en ce qui concerne l'attribut * defer.*


Recalcule d'affichage de page

Avec un disperseur d’événement

Retailler la fenêtre avec une demande de rechargement par dispersement au redimensionnement déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Oui Non Oui Oui Oui

Exemple :

window.onresize = function() {
  window.location.reload();
};
var event = document.createEvent('Event');
event.initEvent('resize', false, false);
window.dispatchEvent(event);

Pas toujours. Disperser l’événement resize déclenche un rechargement de page ? Non, cela est une violation de la specification.

Quand une méthode reload() est invoquée... Si l'exécution courante est un événement dispersé de redimensionnement... abandonner cette étape.

Tout le monde à faux sauf Firefox cette fois.

Note : Le code ci-dessous déclenche bien une requête chez tout le monde.

window.onresize = function() {
  window.location.reload();
};
window.onresize();

Chargement de police

Seulement avec @font-face

La simple déclaration d'une police déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Non Non Non Non Non

Exemple :

<style>
  @font-face {
    font-family: 'myfont';
    src: url('font.woff');
    unicode-range: U+61-7A; /* lowercase a-z */
  }
</style>

Non. Pas de requête si on ne fait que la déclarer.

Avec application CSS

La déclaration d'une police appelé depuis un sélecteur CSS déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Non Non Non Non Non

Exemple :

<style>
  @font-face {
    font-family: 'myfont';
    src: url('font.woff');
    unicode-range: U+61-7A; /* lowercase a-z */
  }
  p { font-family: myfont, sans-serif; }
</style>

Non. Pas de requête si on ne fait que la déclarer et l'associer.

Avec présence d'une balise

La déclaration d'une police appelé depuis un sélecteur CSS sur une balise présente déclenche une requête HTTP ?

Chrome Firefox IE Safari Opera
Oui Oui Oui Oui Oui

Exemple :

<style>
  @font-face {
    font-family: 'myfont';
    src: url('font.woff');
    unicode-range: U+61-7A; /* lowercase a-z */
  }
  p { font-family: myfont, sans-serif; }
</style>
<p></p>

Oui. Effectivement si une balise vide a une police d'appliquée, une requête est lancée même si visuellement elle ne rend rien.


Conclusion

Ce qu'il ressort c'est que l'on ne peut pas se fier aux spécifications pour prévoir avec certitude le comportement de chaque navigateur, mais ça, ça n'étonne personne ! Dans le domaine des requêtes HTTP il n'y a aucun bon élève.

Lire dans une autre langue