Bidouilleux d'Web
Le blog technique de la communauté Mozilla francophone
2022-02-04T14:07:43+01:00
Communauté Mozilla francophone
urn:md5:935a9b6df47b29d6dc8c2ca47296b179
Dotclear
Rétrospective et détails techniques sur la récente panne de Firefox
urn:md5:d3bef47e7f29228094b47cd954463e35
2022-02-02T22:18:00+01:00
2022-02-04T15:07:43+01:00
sphinx
Général
Firefox
HTTP-3
Incident
Mozilla
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/fxstuck/mozilla_hacks.png"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/fxstuck/mozilla_hacks.png" alt="Mozilla Hacks" style="float: right; margin: 0 0 1em 1em; width: 300px;" /></a> <em>Ce billet est une traduction de l’article écrit par Christian Holler et publié originalement sur Hacks.mozilla.org :</em> « <a href="https://hacks.mozilla.org/2022/02/retrospective-and-technical-details-on-the-recent-firefox-outage/" title="Mozilla Hacks : Retrospective and Technical Details on the recent Firefox Outage (2 févr. 2022) Christian Holler" hreflang="en">Retrospective and Technical Details on the recent Firefox Outage</a> ».</p>
<p>Le 13 janvier 2022, Firefox a été inutilisable pendant presque deux heures pour les utilisatrices et utilisateurs du monde entier. Cet incident a interrompu l’activité de nombreuses personnes. Ce billet met en lumière la séquence d’évènements complexe et les circonstances qui, combinées, ont déclenché un bug profondément enfoui dans le code réseau de Firefox.</p>
<h3>Que s’est-il passé ?</h3>
<p>Firefox dispose de plusieurs serveurs et d’une infrastructure gérant plusieurs services internes. Ceux-ci incluent les mises à jour, la télémétrie, la gestion des certificats, les rapports de plantage et d’autres fonctionnalités analogues. Cette infrastructure est hébergée par différents fournisseurs de services dans le cloud qui utilisent des répartiteurs de charge afin d’équilibrer la charge entre les serveurs. Pour ces services hébergés sur <em>Google Cloud Platform</em> (GCP), ces répartiteurs de charge possèdent des paramètres relatifs au protocole HTTP qu’ils doivent communiquer. L’un de ces paramètres concerne la prise en charge de HTTP/3 et dispose de trois états : « Activé », « Désactivé » ou « Automatique (par défaut) ». Nos répartiteurs de charge étaient réglés sur « Automatique (par défaut) » et le 13 janvier 2022 à 07 h 28 UTC, GCP a déployé un changement imprévu pour utiliser HTTP/3 par défaut. Firefox utilisant HTTP/3 lorsqu’il est pris en charge, à partir de cet instant, certaines connexions de Firefox à l’infrastructure de services ont utilisé HTTP/3 plutôt que le protocole HTTP/2, utilisé jusqu’alors.</p>
<p>Peu après, nous avons remarqué un pic de plantages via notre outil de rapport sur les plantages et nous avons également reçu différents signalements, tant à l’intérieur qu’à l’extérieur de Mozilla, décrivant l’absence de réponse du navigateur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/fxstuck/crashes-foxstuck2.png" title="Graphe des rapports d’erreur"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/fxstuck/.crashes-foxstuck2_m.png" alt="Graphe des rapports d’erreur montrant un pic vers 300 000" style="margin: 0 auto; display: table;" /></a></p>
<p><em>La file des rapports de plantage à traiter a augmenté jusqu’à atteindre environ 300 000 rapports non traités.</em></p>
<p>Lors de la procédure de réponse à incident, nous avons rapidement découvert que le client plantait au sein d’une requête réseau vers un des services internes de Firefox. Toutefois, nous n’avions aucune explication sur la raison du déclenchement de ce problème à cet instant, ni sur sa portée. Nous avons continué à chercher l’élément déclencheur : un changement avait dû se produire pour que le problème commence. Nous avons déterminé que nous n’avions pas diffusé des mises à jour ou des changements de configuration qui auraient pu causer ce problème. En même temps, nous gardions en tête que HTTP/3 était activé depuis Firefox 88 et était activement utilisé par certains sites web populaires.</p>
<p>Bien que nous ne puissions pas le voir, nous suspections alors un genre de changement « invisible » déployé par l’un de nos fournisseurs qui aurait modifié, d’une façon ou d’une autre, le comportement des répartiteurs de charge. Après un examen approfondi, aucun de nos paramètres n’avait changé. Nous avons alors découvert, grâce aux journaux, que, pour une raison inconnue, les répartiteurs de charge pour notre service de télémétrie servaient désormais des connexions HTTP/3 alors qu’ils ne le faisaient pas auparavant. Nous avons désactivé de façon explicite HTTP/3 sur GCP à 09 h 12 UTC. Cela a débloqué nos utilisatrices et utilisateurs, mais nous n’étions pas certains de la cause principale, et sans la connaître, il nous était impossible de dire si cela aurait un impact sur d’autres connexions HTTP/3.</p>
<p>¹ <em>Certains services hautement critiques, comme les mises à jour, utilisent un marqueur spécial, <code>beConservative</code>, qui empêche l’utilisation de toute technologie pour leurs connexions (par exemple HTTP/3).</em></p>
<h3>Un cocktail d’ingrédients spéciaux</h3>
<p>Il est vite devenu clair que certaines circonstances particulières devaient être réunies pour que le plantage se produise. Nous avons réalisé différents tests avec plusieurs outils et services distants et nous ne sommes pas parvenus à reproduire le problème, même avec une connexion normale au serveur de recette pour la télémétrie (un serveur uniquement utilisé pour tester les déploiements, que nous avions laissé avec sa configuration originale à des fins de tests). Avec Firefox, en revanche, nous pouvions reproduire le problème avec le serveur de recette.</p>
<p>En déboguant, nous avons trouvé « l’ingrédient secret » qui nous manquait pour que le bug se produise. Toutes les connexions HTTP/3 passaient par Necko, notre pile réseau. Toutefois, les composants Rust qui ont besoin d’un accès direct au réseau n’utilisent pas Necko directement, ils l’appellent à travers une bibliothèque intermédiaire intitulée <a href="https://github.com/mozilla/application-services/tree/main/components/viaduct" hreflang="en"><code>viaduct</code></a>.</p>
<p>Pour comprendre l’intérêt de cette nuance, nous devons d’abord comprendre certains aspects internes de Necko et notamment les requêtes d’upload HTTP/3. Pour de telles requêtes, les API haut niveau de Necko vérifient si l’en-tête <code>Content-Length</code> est présent et, s’il ne l’est pas, l’ajoutent automatiquement. Le code HTT/3 de plus bas niveau repose ensuite sur ce traitement afin de déterminer la taille de la requête. Cela fonctionne correctement pour le contenu web et les autres requêtes de notre code.</p>
<p>En revanche, lorsque les requêtes passent d’abord par <code>viaduct</code>, ce dernier convertira en minuscules chaque en-tête avant de le passer à Necko. Et c’est là qu’est le problème : les vérifications d’API de Necko <strong>ne sont pas sensibles à la casse</strong> tandis que le code bas niveau pour HTTP/3 <strong>est sensible à la casse</strong>. Ainsi, si du code ajoutait l’en-tête <code>Content-Length</code> et passait la requête via <code>viaduct</code>, celle-ci passerait les vérifications des API Necko mais le code HTTP/3 ne trouverait pas l’en-tête.</p>
<p>En l’occurrence, le module de télémétrie est actuellement le seul composant de Firefox sur ordinateur à base de Rust qui utilise la pile réseau et qui ajoute un en-tête <code>Content-Length</code>. C’est pour cela que les personnes qui ont désactivé la télémétrie ont vu le problème résolu bien qu’il ne soit pas lié à la fonctionnalité de télémétrie même et qu’il aurait pu être déclenché par ailleurs.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/fxstuck/foxstuck-diagram4.png" title="Diagramme du chemin de code d’erreur à travers les différents composants réseau de Firefox"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/fxstuck/.foxstuck-diagram4_m.png" alt="Un diagramme montrant les différents composants réseau dans Firefox." style="margin: 0 auto; display: table;" /></a></p>
<p><em>Un chemin de code spécifique était nécessaire pour déclencher le problème dans l’implémentation du protocole HTTP/3.</em></p>
<p>² <em>Il s’agit d’API internes, qui ne sont pas accessibles depuis le contenu web.</em></p>
<h3>La boucle infinie</h3>
<p>Nous avons : le changement sur les répartiteurs de charge qui est en place, un chemin de code spécifique dans un nouveau service Rust désormais actif. Il reste l’ingrédient final pour déclencher le problème auprès des utilisateurs et utilisatrices qui était plongé dans le code HTTP/3 de Necko.</p>
<p>Lors de la gestion d’une requête, <a href="https://searchfox.org/mozilla-central/rev/435a77f1a1aaf1a78d30a2aaa81c6158a2f83dba/netwerk/protocol/http/Http3Stream.cpp#71,79-83">le code cherchait l’en-tête de façon sensible à la casse</a> et échouait lorsque l’en-tête avait été mis en minuscules par <code>viaduct</code>. Sans l’en-tête, la requête était considérée comme complète par le code de Necko, le corps de la requête n’était alors pas envoyé. Toutefois, ce code ne finissait son exécution lorsqu’il n’y avait plus de contenu supplémentaire à envoyer. Cet <a href="https://searchfox.org/mozilla-central/rev/435a77f1a1aaf1a78d30a2aaa81c6158a2f83dba/netwerk/protocol/http/Http3Stream.cpp#223,228,272-274" hreflang="en">état inattendu a provoqué une boucle infinie plutôt que de déclencher une erreur</a>. Comme l’ensemble des requêtes réseau passent par un seul <em>thread</em> de <em>socket</em>, cette boucle bloquait toute communication réseau ultérieure, rendant Firefox sans réaction, incapable de charger du contenu web.</p>
<h3>Les leçons apprises</h3>
<p>Comme c’est souvent le cas, le problème était beaucoup plus complexe que ce qu’il paraissait à première vue et de nombreux facteurs contributifs ont été réunis. Certains des facteurs principaux que nous avons identifiés comprennent :</p>
<ul>
<li>Le déploiement imprévu de HTTP/3 par défaut par GCP. Nous travaillons activement avec eux afin d’améliorer la situation. Nous avons conscience qu’une annonce (comme c’est généralement le cas) n’aurait pas complètement réduit le risque d’incident. Elle aurait en revanche déclenché des expérimentations (par exemple avec un environnement de recette) et un déploiement plus contrôlés.</li>
<li>Le paramètre « Automatique (par défaut) » de nos répartiteurs de charge, plutôt qu’un choix explicite, a permis que ce déploiement ait lieu automatiquement. Nous passons en revue toutes les configurations de nos services pour éviter des erreurs similaires à l’avenir.</li>
<li>La combinaison particulière de HTTP/3 et de <code>viaduct</code> sur Firefox pour ordinateur n’était pas couverte par notre système d’intégration continue. Bien que nous ne puissions pas tester l’ensemble des combinaisons de configurations et de composants possibles, le choix de la version de HTTP est un changement plutôt majeur qui aurait dû être testé. Il en va de même de l’utilisation d’une couche réseau supplémentaire comme <code>viaduct</code>. Les tests HTTP/3 actuels couvrent le comportement bas niveau et celui de Necko tel qu’ils sont utilisés pour du contenu web. Nous devrions exécuter plus de tests systèmes avec différentes versions de HTTP, cela aurait pu révéler ce problème.</li>
</ul>
<p>Nous étudions également un plan d’action pour que le navigateur soit plus résilient face à de tels problèmes et pour que la réponse à incident soit plus rapide. Apprendre le plus possible de cet incident nous aidera à améliorer la qualité de nos produits. Nous remercions toutes les personnes qui ont envoyé leurs rapports de plantage, qui ont travaillé avec nous sur Bugzilla ou qui ont aidé les autres à contourner le problème.</p>
<h3>À propos de Christian Holler</h3>
<p>Christian est responsable technique de Firefox et ingénieur sécurité expérimenté à Mozilla.</p>
<hr/>
<p><em>Merci à Mozinet et cajuteq pour la relecture !</em></p>
<p>Comme la version originale, cette traduction est disponible sous la licence <a href="https://creativecommons.org/licenses/by-sa/3.0/deed.fr" hreflang="fr" title="Creative Commons — Attribution – Partage dans les mêmes conditions 3.0 non transposé — CC BY-SA 3.0">CC By-SA 3.0</a>.</p>
MDN sur GitHub, comment contribuer ?
urn:md5:9e959facfe82fe566a15f9ff291707e3
2021-03-16T19:47:00+01:00
2021-03-18T10:35:57+01:00
sphinx
Général
Git
GitHub
Localisation
MDN
<p><a href="https://developer.mozilla.org/">MDN Web Docs</a> est un site collaboratif qui documente les différentes technologies web, de <a href="https://developer.mozilla.org/fr/docs/Web/HTML">HTML</a> à <a href="https://developer.mozilla.org/fr/docs/Web/CSS">CSS</a> en passant par <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript">JavaScript</a>, <a href="https://developer.mozilla.org/fr/docs/Web/HTTP">HTTP</a>, les différentes API (DOM, WebGL, etc.). Pendant de nombreuses années, le contenu de MDN était organisé dans une base de données et éditable page par page via un éditeur sur le site. Fin 2020, MDN a effectué une mue et si le site n’a pas changé d’adresse, le contenu sous-jacent réside désormais sur GitHub : toute la documentation est contenue dans un dépôt Git. Après une période de gel hivernal, la localisation est aujourd’hui de nouveau possible (ce billet fait suite <a href="https://tech.mozfr.org/post/2020/12/08/Une-mise-a-jour-quant-a-la-strategie-de-localisation-pour-MDN">au précédent</a>). Bref, la méthode de contribution et les outils associés ont bien changé. Voyons ce qu’il en est ici.</p>
<h3>L’organisation</h3>
<p>Pour commencer, voyons comment s’organise la plateforme de MDN (qui n’est pas l’organisation du contenu au sein de MDN). Il existe trois dépôts Git :</p>
<ul>
<li><a href="https://github.com/mdn/yari">mdn/yari</a> : Yari est <a href="https://hacks.mozilla.org/2020/10/mdn-web-docs-evolves-lowdown-on-the-upcoming-new-platform/">le remplaçant de Kuma</a>, c’est le serveur web qui s’occupe du rendu du site tant pour le contenu des articles que pour le reste de l’interface utilisateur.</li>
<li><a href="https://github.com/mdn/content">mdn/content</a> : ici on trouve tous les documents anglophones qui constituent le contenu de référence de MDN.</li>
<li><a href="https://github.com/mdn/translated-content">mdn/translated-content</a> : enfin, ce dépôt contient les localisations, toutes locales confondues, de mdn/content. C’est ici qu’on retrouve un répertoire pour tout le contenu francophone de MDN.</li>
</ul>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/mdn_yari_contribuer/mdn_repos.png" title="Schéma d'organisation des depôts Git pour MDN, mar. 2021"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/mdn_yari_contribuer/mdn_repos.png" alt="Schéma d'organisation des depôts Git pour MDN, mar. 2021" style="margin: 0 auto; display: block;" /></a></p>
<p>Comme <a href="https://tech.mozfr.org/post/2020/12/08/Une-mise-a-jour-quant-a-la-strategie-de-localisation-pour-MDN">expliqué précédemment</a>, les locales sont réparties en deux catégories : celles qui sont ouvertes aux contributions et celles qui sont « gelées ». À l’heure actuelle, le français fait partie des locales ouvertes aux contributions (avec le chinois, le japonais et le russe).</p>
<p>Après une période de transition, cette ouverture est effective depuis le 06 mars 2021.</p>
<h3>Comment contribuer ?</h3>
<p>C’est l’objet de ce billet : vous fournir les informations nécessaires pour que vous puissiez (re)commencer à contribuer à MDN, notamment à la localisation francophone du contenu.</p>
<h4>Le format</h4>
<p>Avant de plonger dans la pratique, voyons rapidement comment sont structurés les documents dans les dépôts mdn/translated-content ou mdn/content. L’arborescence des fichiers suit celle du site et correspond à celle utilisée sur le dépôt mdn/content (il n’y a pas de traduction des noms de répertoire) :</p>
<pre><code>files
<locales>
fr
<sections>
web
...
<page> // répertoire
index.html
</code></pre>
<p>Chaque page possède donc un répertoire dans lequel sont stockés les fichiers et surtout le contenu de la page : <code>index.html</code>.</p>
<p>Ce fichier ne contient pas que du HTML. En effet, il commence par un préambule de métadonnées décrites en YAML. Ces métadonnées décrivent notamment le titre, le fragment d’URL de la page (<em>slug</em>), les étiquettes associées et le lien avec la page anglaise. Par exemple :</p>
<pre><code>---
title: Structures de données
slug: Web/JavaScript/Data_structures
tags:
- Beginner
- JavaScript
- Types
translation_of: Web/JavaScript/Data_structures
original_slug: Web/JavaScript/Structures_de_données
---
</code></pre>
<p>On trouve ensuite le contenu de l’article en HTML.</p>
<h4>Contribuer occasionnellement</h4>
<p>Si vous souhaitez corriger une coquille ou contribuer sur une page en particulier, vous pouvez utiliser directement GitHub (il vous y faudra un compte pour contribuer).
Sur la page de MDN qui vous intéresse, utilisez le lien en bas de page pour vous rendre directement sur le document :</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/mdn_yari_contribuer/mdn_yari_1_link.png" title="mdn_yari_1_link.png, mar. 2021"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/mdn_yari_contribuer/mdn_yari_1_link.png" alt="Lien de MDN vers GitHub " style="margin: 0 auto; display: block;" /></a></p>
<p>Sur GitHub, vous pouvez alors utiliser la fonctionnalité d’édition pour modifier la page :</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/mdn_yari_contribuer/mdn_yari_2_edit.png" title="mdn_yari_2_edit.png, mar. 2021"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/mdn_yari_contribuer/mdn_yari_2_edit.png" alt="Bouton d'édition avec un crayon sur l'interface de GitHub." style="margin: 0 auto; display: block;"/></a></p>
<p>Une fois les modifications apportées, <a href="https://docs.github.com/en/github/managing-files-in-a-repository/editing-files-in-your-repository">l’interface de GitHub vous guidera</a> pour finaliser votre contribution et cela créera automatiquement une requête qui sera revue avant d’être fusionnée et publiée.</p>
<h4>Contribuer de façon plus conséquente</h4>
<p>Si vous souhaitez éditer et/ou traduire plus fréquemment ou si votre modification porte sur plusieurs documents, il sera nécessaire de mettre en place un environnement sur votre ordinateur.</p>
<h5>Prérequis</h5>
<p>Commençons par voir ce qu’il faut au préalable :</p>
<ul>
<li>Git : selon votre système d’exploitation, il est peut-être déjà installé. Sinon, vous pouvez <a href="https://git-scm.com/">le télécharger</a> pour l’installer. Il est aussi préférable d’avoir quelques notions de Git pour se lancer.</li>
<li>Compte GitHub : à l’instar des modifications mineures, il faudra un compte sur <a href="https://github.com/">https://github.com</a>.</li>
<li><a href="https://nodejs.org/en/download/">Node.js et npm</a> : le dépôt mdn/content utilise Node.js comme système de serveur web.</li>
<li>yarn : <a href="https://www.npmjs.com/package/yarn">yarn</a> est un paquet npm. C’est le gestionnaire de dépendances pour mdn/content et une fois npm installé, vous devrez lancer <code>npm install -g yarn</code> pour installer yarn.</li>
</ul>
<h5>Initialisation</h5>
<p>Il est nécessaire de récupérer les deux dépôts sur votre ordinateur : mdn/content et mdn/translated-content. Le premier nous permettra d’avoir le contenu en anglais pour comparer et surtout de pouvoir lancer le site en local pour vérifier l’affichage du contenu édité. Le second contient les documents en français que nous allons pouvoir éditer.</p>
<ol>
<li>Sur GitHub, faites un <em>fork</em> de <a href="https://github.com/mdn/content">mdn/content</a> et de <a href="https://github.com/mdn/translated-content">mdn/translated-content</a></li>
<li><p>Clonez les deux dépôts pour créer des dépôts locaux :</p>
<pre><code>git clone git@github.com:<votrenomsurgit>/content.git
git clone git@github.com:<votrenomsurgit>/translated-content.git
</code></pre></li>
<li><p>Rajoutez le dépôt de référence avec une référence distante/<em>remote</em> <code>upstream</code>. Dans les répertoires respectifs :</p>
<pre><code>// Dans le répertoire de translated-content
git remote add upstream git@github.com:mdn/translated-content.git
// Déplacez vous dans le répertoire de content
git remote add upstream git@github.com:mdn/content.git
</code></pre></li>
<li><p>Allez dans le répertoire <code>content</code>. Pour installer les dépendances, lancez :</p>
<pre><code>yarn install
</code></pre></li>
<li><p>Afin que mdn/content « sache » où se trouve le contenu du contenu traduit, on utilise un fichier d’environnement <code>.env</code> situé à la racine du répertoire <code>content</code>. Créez un fichier <code>.env</code> avec deux variables :</p></li>
</ol>
<ul>
<li><code>EDITOR</code> pour l’éditeur de texte que vous souhaitez utiliser. La variable <code>EDITOR</code> doit correspondre au chemin du programme ou à la commande lancée pour le démarrer, elle sera utilisée pour ouvrir votre éditeur via un bouton sur le site local.</li>
<li><code>CONTENT_TRANSLATED_ROOT</code> pour le chemin du répertoire du dépôt local pour translated-content.</li>
</ul>
<p>Par exemple :</p>
<pre><code> EDITOR=code
CONTENT_TRANSLATED_ROOT=../translated-content/files
</code></pre>
<ol start="6">
<li><p>Lancez le serveur avec</p>
<pre><code>yarn start
</code></pre></li>
</ol>
<p>Vous pouvez maintenant vous rendre sur votre <a href="http://localhost:5000">http://localhost:5000</a> pour parcourir le contenu de MDN. Vous pouvez éditer les fichiers sur votre système de fichier avec votre éditeur de prédilection ou utiliser les fonctionnalités de Yari qui intègre un éditeur (ou encore utiliser Yari pour lancer votre éditeur depuis le site local).</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/mdn_yari_contribuer/local_yari.png" title="Capture de l'interface de mdn/yari en environnement local, mar. 2021"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/mdn_yari_contribuer/local_yari.png" alt="Capture de l'interface de mdn/yari en environnement local, mar. 2021" style="margin: 0 auto; display: block;" /></a></p>
<h5>Préparer une contribution</h5>
<ol>
<li><p>Si vous avez déjà fait une contribution ou si quelques jours ont passé depuis le clonage des dépôts, commencez par <a href="https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/syncing-a-fork">rafraîchir vos dépôts locaux</a> à partir des dépôts de référence/<em>upstream</em> puis relancez <code>yarn install</code>.</p></li>
<li><p>Créez une branche avec</p>
<pre><code>git checkout -b correction-coquille-fr-jean-biche
</code></pre></li>
<li><p>Modifiez et gérez vos fichiers avec Git afin de faire un ou des commits.</p></li>
<li><p>Poussez la modification sur votre <em>fork</em> avec</p>
<pre><code>git push -u origin correction-coquille-fr-jean-biche
</code></pre></li>
<li><p>Utilisez GitHub pour créer une <em>merge request</em></p></li>
<li><p>Attendez la revue puis la fusion. Ajustez éventuellement le contenu en fonction des retours.</p></li>
<li>Vous pouvez alors guetter <a href="https://whatsdeployed.io/s/16d/mdn/translated-content">What’sDeployed</a> pour savoir quand votre contribution sera visible sur https://developer.mozilla.org (pour être tout à fait précis, il peut y avoir quelques heures de décalage avec les caches CDN).</li>
</ol>
<h4>Qu’y a-t-il à faire ?</h4>
<p>Il existe de nombreuses façons de contribuer et d’aider. Voici une liste (non exhaustive) :</p>
<ul>
<li>Vous pouvez vérifier s’il existe des <a href="https://github.com/mdn/translated-content/issues?q=is%3Aissue+is%3Aopen+label%3Al10n-fr+label%3A%22good+first+issue%22"><em>issues</em> idéales pour commencer, sur le contenu francophone</a>.</li>
<li>Rapporter des bugs : en vous baladant sur MDN, si vous croisez une erreur, vous pouvez cliquer sur le lien « <em>Report a problem with this content on GitHub</em> » et indiquer ce qui est incorrect.</li>
<li>Contribuer de façon générale : si une page de MDN n’existe pas en français sur un sujet qui vous passionne, vous pouvez la traduire en français afin de la rendre accessible à plus de personnes.</li>
</ul>
<p>Bref, toute aide est la bienvenue !</p>
<h3>Ensuite</h3>
<p>Si vous souhaitez discuter ou si vous avez des questions à ce sujet, n’hésitez pas à nous rejoindre sur <a href="https://chat.mozilla.org/#/room/#l10n-fr:mozilla.org">Matrix sur le salon #l10n-fr</a>.</p>
<style>
pre { white-space: pre;}
</style>
Mise à jour à propos de la stratégie de localisation de MDN
urn:md5:440a579c226570a46e693517530755f5
2020-12-08T20:45:00+01:00
2020-12-09T19:32:23+01:00
sphinx
Général
localisation
MDN
traduction
<h2><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/MDN_fr/MDN_Web_Docs_dev.png"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/MDN_fr/MDN_Web_Docs_dev.png" alt="MDN web docs Mozilla : Des ressources pour les développeurs, par les développeurs." style="float: right; margin: 0 0 1em 1em; width: 260px;" /></a> <em>Cet article est une traduction d’<a href="https://hacks.mozilla.org/2020/12/an-update-on-mdn-web-docs-localization-strategy/">An update on MDN Web Docs’ localization strategy</a>, écrit par <a href="https://hacks.mozilla.org/author/cmillsmozilla-com/">Chris Mills</a>. Si vous souhaitez contribuer aux traductions de MDN en français, venez nous faire signe sur <a href="https://chat.mozilla.org/#/room/#l10n-fr:mozilla.org">le salon #l10n-fr</a> !</em></h2>
<p>Dans un billet précédent (<a href="https://hacks.mozilla.org/2020/10/mdn-web-docs-evolves-lowdown-on-the-upcoming-new-platform/">Évolution de MDN Web Docs, les dernières infos sur la nouvelle plateforme</a>), nous évoquions de nombreux aspects de la nouvelle plateforme MDN qui sera lancée le 14 décembre. Dans ce billet, nous nous pencherons sur un aspect en particulier : la gestion de la localisation pour MDN à l’avenir. Nous verrons comment notre réflexion a évolué depuis le précédent billet et comment nous revoyons notre approche.</p>
<h2>Un plan d’action revu</h2>
<p>Suite aux retours attentifs de la communauté, nous avons mené des recherches supplémentaires et déterminé un chemin plus clair et solide.</p>
<p>Tout d’abord, nous souhaitons rester focalisés sur le travail nécessaire au lancement de notre nouvelle plateforme et nous assurer que l’ensemble du système fonctionne sans problème. Cela signifie que lors du lancement, nous prévoyons toujours d’afficher les traductions pour l’ensemble des locales existantes mais dans un état gelé / en lecture seule pour commencer.</p>
<p>Nous envisagions d’utiliser principalement les traductions automatiques sur cette nouvelle plateforme. Un problème majeur reste que si les traductions automatiques peuvent être une solution acceptable pour les langues européennes, ce n’est pas le cas pour <a href="https://fr.wikipedia.org/wiki/Chinois,_japonais_et_cor%C3%A9en">les langues asiatiques CJC</a>. La structure de ces dernières est fondamentalement différente de l’anglais et des langues européennes. De plus, si de nombreux européens savent lire l’anglais et peuvent utiliser de la documentation en anglais en recours, les communautés des langues CJC n’ont pas cette habitude de lire de l’anglais et ne peuvent se permettre ce luxe.</p>
<p>Pour de nombreuses personnes avec qui nous avons discuté, les traductions automatiques ne seraient pas acceptables pour leurs langues. Non seulement, ces traductions seraient médiocres mais les nombreuses communautés MDN qui travaillent à la traduction de ces documents seraient également vouées à disparaître. Nous voulions assurément éviter cela !</p>
<p>Aussi, nous nous concentrerons principalement sur un ensemble restreint de traductions manuelles afin de déverrouiller l’édition de certaines locales essentielles dès que possible après le lancement de la nouvelle plateforme.</p>
<h2>Des traductions manuelles restreintes</h2>
<p>Des tests rigoureux ont été réalisés et il semble faisable de générer le contenu traduit dans la chaîne de production principale. Nous distinguons les locales en deux catégories afin de déterminer celles qui seront déverrouillées et celles qui resteront en lecture seule.</p>
<ul>
<li>Catégorie 1 : ces locales seront déverrouillées et pourront être éditées à l’aide de <em>pull requests</em>. Ces locales devront avoir <strong>au moins</strong> un représentant comme responsable de la communauté associée. Les membres de la communauté seront responsables du contrôle des pages localisées, de la mise à jour du contenu principal lorsque les versions anglaises seront mises à jour, de la relecture des contributions, etc. Le responsable de la communauté sera également en charge des décisions au sujet de cette locale et sera le point de contact entre l’équipe employée de MDN et la communauté.</li>
<li>Catégorie 2 : ces locales seront gelées et aucune <em>pull request</em> ne sera acceptée en l’absence de communautés pour les maintenir.</li>
</ul>
<p>Les locales de catégorie 1 que nous commencerons par déverrouiller sont :</p>
<ul>
<li>le chinois de Chine continentale (zh-CN)</li>
<li>le chinois de Taïwan (zh-TW)</li>
<li>le français (fr)</li>
<li>le japonais (ja)</li>
</ul>
<p>Si vous souhaitez qu’une locale de catégorie 2 soit déverrouillée, vous devrez <a href="https://developer.mozilla.org/en-US/docs/MDN/Contribute/Getting_started#Step_4_Ask_for_help">fournir une proposition</a> démontrant la volonté d’une équipe active de prendre en charge le travail associé à cette locale. Le cas échéant, nous pourrons promouvoir cette locale en catégorie 1 et le travail de traduction pourra commencer.</p>
<p>Nous surveillerons l’activité des locales de catégorie 1 et rétrograderons en catégorie 2 celles qui ne sont plus maintenues par leurs communautés ; elles seront alors à nouveau gelées.</p>
<p>Nous pensons que ce nouveau système est un compromis acceptable, permettant à la communauté de travailler sur les traductions de MDN lorsque l’intérêt est présent tout en nous assurant que la maintenance de ces locales est viable et que le contenu ne devienne pas obsolète. Aujourd’hui, la plupart des locales ne sont pas maintenues et, en réalité, les modifications correspondantes ne sont pas revues : les lecteurs de ces locales ne savent alors plus s’il vaut mieux utiliser leur locale ou l’anglais par défaut et ne parviennent pas à tirer efficacement parti de MDN.</p>
<h2>Processus de revue</h2>
<p>Le processus de revue sera relativement simple :</p>
<ul>
<li>Le contenu de chaque locale de catégorie 1 sera conservé dans un dépôt distinct.</li>
<li>Lorsqu’une PR est proposée sur ce dépôt, la communauté de localisation est alertée pour effectuer une revue.</li>
<li>Lorsque le contenu a été revu, un administrateur MDN est contacté afin de fusionner la modification. Nous devrions être en mesure de configurer cela afin que ce soit automatique.</li>
<li>Des bugs relatifs au contenu pourront être rapportés sur <a href="https://github.com/mdn/sprints/issues">https://github.com/mdn/sprints/issues</a> ainsi que sur le système de ticket du dépôt de chaque locale. Lors du triage, les <em>issues</em> du dépôt <em>sprint</em> seront affectées à l’équipe de localisation correspondante tandis que l’équipe de localisation sera responsable de trier et de résoudre les demandes rapportées sur le dépôt de la locale.</li>
</ul>
<h2>Traductions automatiques en parallèle des traductions manuelles</h2>
<p>Nous avons précédemment abordé l’utilisation de traductions automatiques afin d’améliorer le nouveau processus de localisation. Nous n’oublions pas ce sujet mais souhaitons garder un système initial simple et réalisable. Au premier trimestre de 2021, nous verrons comment tirer le meilleur parti des traductions automatiques. Nous fournirons une autre mise à jour à la moitié du trimestre lorsque nous aurons progressé sur ce sujet.</p>
<h2>À propos de Chris Mills</h2>
<p>Chris Mills est écrivain technique senior à Mozilla où il rédige de la documentation et construit des démonstrations sur les applications web ouvertes, HTML/CSS/JavaScript, l’accessibilité, WebAssembly, etc. Il aime bricoler avec les technos web, donner des présentations en conférence ou auprès d’universités. Il a travaillé auparavant pour Opera et le W3C et apprécie du métal et une bonne bière. Il réside près de Manchester avec sa chère et tendre et ses trois enfants.</p>
<hr />
<p><em>Illustration ajoutée à la traduction</em></p>
Les types d'interfaçage WebAssembly : une interopérabilité pour les unir tous
urn:md5:70acf5101d0285aabaf3f0bed08b33ac
2019-09-02T12:30:00+02:00
2019-09-02T18:57:15+02:00
sphinx
Général
Interface
IR
JavaScript
Rust
WASM
WebAssembly
<h2><em>Cet article est une traduction de <a href="https://hacks.mozilla.org/2019/08/webassembly-interface-types/">WebAssembly Interface Types: Interoperate with All the Things!</a>, écrit par <a href="http://twitter.com/linclark">Lin Clark</a>. Merci à elle pour le travail de rédaction originale !</em></h2>
<p>WebAssembly fait parler de lui, y compris en dehors du navigateur.
Cet engouement n’est pas seulement lié à un environnement d’exécution WebAssembly isolé mais aussi parce qu’on peut exécuter du code WebAssembly depuis des langages comme Python, Ruby ou Rust.</p>
<p>Pour quoi faire ? Voici quelques raisons :</p>
<ul>
<li><strong>Rendre les modules « natifs » moins compliqués</strong><br/>Les environnements d’exécution tels que Node ou CPython pour Python permettent également d’écrire des modules dans des langages bas niveau tels que C++. Une telle approche permet de profiter de la vitesse de ces langages bas niveaux. On peut ainsi utiliser des modules natifs en Node ou des modules d’extension en Python. Toutefois ces modules sont souvent difficiles à utiliser car ils doivent être compilés sur l’appareil de l’utilisateur. Avec un module « natif » WebAssembly, on obtient une bonne partie de cette vitesse sans compliquer la mise en œuvre.</li>
<li><strong>Isoler plus facilement le code natif dans des bacs à sable</strong><br/>D’un autre côté, pour des langages bas niveau tels que Rust, pas besoin d’utiliser WebAssembly pour gagner de la vitesse. En revanche, cela peut servir pour la sécurité. Comme nous en parlions lors de <a href="https://hacks.mozilla.org/2019/03/standardizing-wasi-a-webassembly-system-interface/">l’annonce de WASI</a>, WebAssembly fournit un bac à sable léger par défaut et un langage comme Rust pourrait utiliser WebAssembly afin de placer ses modules natifs dans un bac à sable.</li>
<li><strong>Partager du code natif à travers différentes plateformes</strong><br/>Les développeurs peuvent s’épargner du temps et des coûts de maintenance s’ils peuvent réutiliser la même base de code sur différentes plateformes (entre une application web et une application pour le bureau par exemple). Cela concerne aussi bien les langages de script que les langages bas niveaux. De plus, WebAssembly apporte une solution sans ralentir quoi que ce soit sur les plateformes en question.</li>
</ul>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/01-01-why.png" title="4 personnages représentant Python, Ruby, Rust et C++ disent : Nous aimons la vitesse de WebAssembly, la sécurité qu'il pourrait nous apporter et nous souhaitons tous que les développeurs puissent travailler efficacement"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.01-01-why_m.png" alt="4 personnages représentant Python, Ruby, Rust et C++ disent : Nous aimons la vitesse de WebAssembly, la sécurité qu'il pourrait nous apporter et nous souhaitons tous que les développeurs puissent travailler efficacement" style="margin: 0 auto; display: block;" /></a></p>
<p>WebAssembly pourrait donc aider d’autres langages à résoudre des problèmes majeurs.</p>
<p>Malgré cela, convertir une valeur d’un type vers l’autre est possible en suivant certaines règles cette façon. WebAssembly peut être exécuté dans ces environnements mais ce n’est pas suffisant.</p>
<p>Aujourd’hui, WebAssembly ne dialogue avec l’extérieur qu’avec des nombres et ses fonctions peuvent être appelées depuis un autre langage et vice versa.</p>
<p>Mais si une fonction prend des arguments ou renvoie une valeur qui ne sont pas des nombres, ça devient vite compliqué. On peut alors :</p>
<ul>
<li>Mettre à disposition un module dont l’API est ultra-compliqué et ne manipule que des nombres : tant pis pour l’utilisateur du module…</li>
<li>Ajouter du code intermédiaire (de la « <em>glue</em> ») pour chaque environnement dans lequel on souhaite que ce module puisse être exécuté : tant pis pour le développeur du module.</li>
</ul>
<p>Faut-il s’en satisfaire ?</p>
<p>On devrait pouvoir fournir un seul module WebAssembly qui puisse être exécuté n’importe où… sans pour autant compliquer la vie de l’utilisateur du module ou de son développeur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/01-02-user-and-dev.png" title="D'un côté un utilisateur de module qui se demande : Qu'est-ce que c'est encore que cette API ? et d'un autre côté, un développeur qui dit Pff, encore tout un tas de glue à créer pour que ça fonctionne. En dessous, les deux disent simplement : Attendez, ça fonctionne ??"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.01-02-user-and-dev_m.png" alt="D'un côté un utilisateur de module qui se demande : Qu'est-ce que c'est encore que cette API ? et d'un autre côté, un développeur qui dit Pff, encore tout un tas de glue à créer pour que ça fonctionne. En dessous, les deux disent simplement : Attendez, ça fonctionne ??" style="margin: 0 auto; display: block;" /></a></p>
<p>Le même module WebAssembly pourrait utiliser des API riches et des types complexes afin de dialoguer avec :</p>
<ul>
<li>Des modules s’exécutant dans leur environnement natif (ex. des modules Python s’exéutant dans un environnement Python)</li>
<li>D’autres modules WebAssembly écrits depuis d’autres langages sources (ex. un module Rust et un module Go s’exécutant de concert dans le navigateur)</li>
<li>Le système sous-jacent (ex. un module WASI fournissant une interface système avec le système d’exploitation ou avec les API du navigateur).</li>
</ul>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/01-03-star-diagram.png" title="Un diagramme avec un fichier .wasm qui dialogue avec : des modules qui s'exécutent dans leurs environnements (ex. PHP, Ruby, Python) ; des modules écrits depuis d'autres langages sources (ex. Rust, Go) ; des systèmes hôtes qui fonctionnent directement avec le système d'exploitation. En haut : le slogan : Les types d'interfaçage WebAssembly : l'interopérabilité pour tous."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.01-03-star-diagram_m.png" alt="Un diagramme avec un fichier .wasm qui dialogue avec : des modules qui s'exécutent dans leurs environnements (ex. PHP, Ruby, Python) ; des modules écrits depuis d'autres langages sources (ex. Rust, Go) ; des systèmes hôtes qui fonctionnent directement avec le système d'exploitation. En haut : le slogan : Les types d'interfaçage WebAssembly : l'interopérabilité pour tous." style="margin: 0 auto; display: block;" /></a></p>
<p>Avec une nouvelle proposition, nous pouvons voir comment cela peut fonctionner (et ça fonctionne :)). Voici par exemple une démo :</p>
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/Qn_4F3foB3Q" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p>Voyons comment cela fonctionne. Mais avant regardons la situation actuelle et les problèmes que nous essayons de résoudre.</p>
<h2>Discussion entre WebAssembly et JavaScript</h2>
<p>WebAssembly ne se limite pas au Web mais jusqu’à présent, une grande partie du développement de WebAssembly concernait le Web.</p>
<p>En effet, on conçoit mieux lorsqu’on se concentre sur la résolution de problèmes concrets. Ce langage devait être exécuté sur le Web et c’était donc un point de départ pertinent.</p>
<p>On a ainsi obtenu un produit minimum viable (MVP) avec un périmètre bien défini. WebAssembly devait alors seulement être capable de dialoguer avec un autre langage : JavaScript.</p>
<p>Ce fut relativement facile à obtenir. Au sein du navigateur, WebAssembly et JS s’exécutent dans le même moteur et le moteur peut donc <a href="https://hacks.mozilla.org/2018/10/calls-between-javascript-and-webassembly-are-finally-fast-%f0%9f%8e%89/">les aider à discuter efficacement</a>.
<a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-01-js-interop-01.png" title="À droite, un bonhomme-fichier JS qui demande à un intermédiaire : Peux-tu demander au wasm de générer les pixels d'une image pour moi ?. L'intermédiaire répond : Sans problème. À gauche le bonhomme-fichier WASM attend."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-01-js-interop-01_m.png" alt="À droite, un bonhomme-fichier JS qui demande à un intermédiaire : Peux-tu demander au wasm de générer les pixels d'une image pour moi ?. L'intermédiaire répond : Sans problème. À gauche le bonhomme-fichier WASM attend." style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-01-js-interop-02.png" title="L'intermédiaire au centre demande au fichier-bonhomme WASM : j'aimerais que tu exécutes imageGenerate."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-01-js-interop-02_m.png" alt="L'intermédiaire au centre demande au fichier-bonhomme WASM : j'aimerais que tu exécutes imageGenerate." style="margin: 0 auto; display: block;" /></a></p>
<p>Malgré tout, il y a un problème lorsque ces deux-là essaient de dialoguer : ils utilisent des types différents.</p>
<p>Actuellement, WebAssembly ne s’exprime qu’avec des nombres. JavaScript sait ce qu’est un nombre mais possède également quelques autres types.</p>
<p>Et même les nombres ne sont pas vraimeent les mêmes. WebAssembly possède quatre types de nombres : <code>int32</code>, <code>int64</code>, <code>float32</code>, <code>float64</code>. JavaScript possède quant à lui un seul type <code>Number</code> (<a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/BigInt/">BigInt</a> sera bientôt un nouveau type numérique en JS).</p>
<p>La différence entre ces types ne s’arrête pas aux noms. Les valeurs sont aussi stockées différemment en mémoire.</p>
<p>Pour commencer, n’importe quelle valeur JavaScript (quel que soit son type) est placée dans une boîte (voir <a href="https://hacks.mozilla.org/2018/10/calls-between-javascript-and-webassembly-are-finally-fast-%f0%9f%8e%89/#js-to-wasm">ce précédent article</a> où j’expliquais le concept).</p>
<p>En revanche, WebAssembly utilise des types statiques pour les nombres et il n’utilise ni ne comprend les boîtes de JavaScript.</p>
<p>Cette différence rend le dialogue un peu compliqué.
<a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-03-number-mismatch.png" title="L'intermédiaire au milieu demande au fichier WASM : 'le fichier JS voudrait que tu ajoutes 5 et 7'. Le module WASM répond : 'Aucun problème, ça fait 9.2368828e
+18'. L'intermédiaire rétorque : 'Pardon ?!'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-03-number-mismatch_m.png" alt="L'intermédiaire au milieu demande au fichier WASM : 'le fichier JS voudrait que tu ajoutes 5 et 7'. Le module WASM répond : 'Aucun problème, ça fait 9.2368828e
+18'. L'intermédiaire rétorque : 'Pardon ?!'" style="margin: 0 auto; display: block;" /></a>
Malgré cela, convertir une valeur d’un type vers l’autre est possible en suivant quelques règles simples.</p>
<p>Les règles simples sont facilement écrites et on peut les retrouver dans <a href="https://www.w3.org/TR/wasm-js-api/#tojsvalue">la spécification de l’API entre WebAssembly et JavaScript</a>.
<a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-04-mapping-book.png" title="Un livre ouvert où la page de gauche contient 'WASM vers JS' et où la page de droite contient 'int32 -> Number', 'int64 -> BigInt', 'float32 -> Number' et 'float64 -> Number'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-04-mapping-book_m.png" alt="Un livre ouvert où la page de gauche contient 'WASM vers JS' et où la page de droite contient 'int32 -> Number', 'int64 -> BigInt', 'float32 -> Number' et 'float64 -> Number'" style="margin: 0 auto; display: block;" /></a></p>
<p>Cette correspondance est inscrite dans les moteurs d’exécution.</p>
<p>C’est un peu comme si le moteur possédait un manuel. Lorsque le moteur doit passer des paramètres ou des valeurs de retour entre JavaScript et WebAssembly, il sort le manuel et le consulte afin de savoir comment convertir ces valeurs.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-05-number-conversion.png" title="Le fichier JS demande à l'intermédiaire 'Peux-tu demander au WASM d'ajouter 5 et 7 ?' puis l'intermédiaire réfléchit 'Alors il faut que je convertisse le nombre 5 de type Number en un int32, pour ça il va falloir que...'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-05-number-conversion_m.png" alt="02-05-number-conversion.png, sept. 2019" style="margin: 0 auto; display: block;" /></a></p>
<p>Avoir aussi peu de types à gérer (uniquement des nombres) rend la chose facile. Ce fut une bonne chose pour un MVP et ça a réduit le nombre de questions difficiles à trancher.</p>
<p>En contrepartie, ce fut plus compliqué pour les développeurs d’utiliser WebAssembly. Pour passer des chaînes de caractères entre JavaScript et WebAssembly, il a fallu trouver une méthode pour transformer des chaînes de caractères en tableaux de nombres puis de faire l’opération inverse. Nous avions couvert sur ce sujet dans <a href="https://hacks.mozilla.org/2018/03/making-webassembly-better-for-rust-for-all-languages/">un précédent billet</a>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/04_wasm_bindgen_02.png" title="Un diagramme montrant comment la chaîne de caractères 'Hello' JavaScript est convertie en nombres puis placée dans un objet mémoire pour WebAssembly"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.04_wasm_bindgen_02_m.png" alt="Un diagramme montrant comment la chaîne de caractères 'Hello' JavaScript est convertie en nombres puis placée dans un objet mémoire pour WebAssembly" style="margin: 0 auto; display: block;" /></a></p>
<p>Ce n’est pas difficile mais c’est laborieux. Des outils ont naturellement été construits afin de rendre cette conversion transparente.</p>
<p>Entre autres, on pourra trouver des outils tels que <a href="https://rustwasm.github.io/docs/wasm-bindgen/">wasm-bindgen (en Rust)</a> et <a href="https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#embind">Embind d’Emscripten</a> qui enveloppent automatiquement le module WebAssembly avec du code JavaScript de liaison qui s’occupe de la traduction des chaînes de caractères en nombres.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-07-js-glue.png" title="Un nouveau fichier fait son apparition entre le fichier WASM et le fichier JS. Le fichier JS tout à droite dit 'Zut, je vais encore devoir passer une chaîne de caractères au WASM...'. Là le fichier JS pour le code de liaison intervient et dit 'Je peux aider, je vais placer la chaîne en mémoire et indiquer au module WASM son emplacement'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-07-js-glue_m.png" alt="Un nouveau fichier fait son apparition entre le fichier WASM et le fichier JS. Le fichier JS tout à droite dit 'Zut, je vais encore devoir passer une chaîne de caractères au WASM...'. Là le fichier JS pour le code de liaison intervient et dit 'Je peux aider, je vais placer la chaîne en mémoire et indiquer au module WASM son emplacement'" style="margin: 0 auto; display: block;" /></a>
Ces outils ont également permis d’effectuer des transformations pour des types de plus haut niveau comme des objets complexes avec des propriétés.</p>
<p>Cela fonctionne mais pour certains cas triviaux, ce n’est pas suffisant.</p>
<p>Imaginons qu’on veuille passer une chaîne de caractères entre deux scripts JS via un module WebAssembly. On doit avoir une fonction JavaScript qui passe une chaîne à une fonction WebAssembly puis le module WebAssembly doit passer cette chaîne à une autre fonction JavaScript.</p>
<p>Pour que tout cela fonctionne, il faut :</p>
<ol>
<li>Que la première fonction JavaScript passe la chaîne de caractères au code JS qui s’occupe de la liaison (“glue code”)</li>
<li>Que le code de liaison transforme cette chaîne de caractères en nombre et passe ces nombres en mémoire linéaire</li>
<li>Qu’il envoie un nombre (le pointeur vers le début de la zone mémoire) au module WebAssembly</li>
<li>Que la fonction WebAssembly passe ce nombre au code de liaison JS de l’autre côté</li>
<li>Que le code de liaison JavaScript retire ces nombres de la mémoire linéaire pour les décoder en chaîne de caractères</li>
<li>Que le deuxième script de liaison fournisse cette chaîne à la deuxième fonction JS.</li>
</ol>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-08-encode-decode-01.png" title="Première étape : le fichier JS tout à gauche passe la chaîne 'Hello' au fichier JS de liaison à sa droite."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-08-encode-decode-01_m.png" alt="Première étape : le fichier JS tout à gauche passe la chaîne 'Hello' au fichier JS de liaison à sa droite." style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-08-encode-decode-02.png" title="Deuxième étape, ce fichier avec le code de liaison fait le nécessaire pour convertir la chaîne en nombres en mémoire."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-08-encode-decode-02_m.png" alt="Deuxième étape, ce fichier avec le code de liaison fait le nécessaire pour convertir la chaîne en nombres en mémoire." style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-08-encode-decode-03.png" title="Troisième étape, le fichier avec le code liaison indique à l'intermédiaire : 'Veuillez envoyer l'index 2 au WASM'. L'intermédiaire réfléchit alors 'OK pour passer d'une valeur JSValue à un int32, je dois...'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-08-encode-decode-03_m.png" alt="Troisième étape, le fichier avec le code liaison indique à l'intermédiaire : 'Veuillez envoyer l'index 2 au WASM'. L'intermédiaire réfléchit alors 'OK pour passer d'une valeur JSValue à un int32, je dois...'" style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-08-encode-decode-04.png" title="Quatrième étape, le fichier WASM demande à l'intermédiaire : 'Peux-tu passer l'index 2 ?' pour le script JS à sa droite. L'intermédiaire réfléchit 'Alors pour passer d'un int32 à un Number, je dois...'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-08-encode-decode-04_m.png" alt="Quatrième étape, le fichier WASM demande à l'intermédiaire : 'Peux-tu passer l'index 2 ?' pour le script JS à sa droite. L'intermédiaire réfléchit 'Alors pour passer d'un int32 à un Number, je dois...'" style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-08-encode-decode-05.png" title="Cinquième étape, le deuxième fichier de glue JS (situé à droite du WASM) calcule pour convertir le tableau mémoire en chaîne de caractères. Il produit la valeur 'Hello'."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-08-encode-decode-05_m.png" alt="Cinquième étape, le deuxième fichier de glue JS (situé à droite du WASM) calcule pour convertir le tableau mémoire en chaîne de caractères. Il produit la valeur 'Hello'." style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-08-encode-decode-06.png" title="Sixième étape, le fichier de glue JS passe la valeur 'Hello' au script JS situé à sa droite"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-08-encode-decode-06_m.png" alt="Sixième étape, le fichier de glue JS passe la valeur 'Hello' au script JS situé à sa droite" style="margin: 0 auto; display: block;" /></a></p>
<p>On a donc un code de liaison JS qui effectue “simplement” l’opération inverse de celle effectuée plus tôt pour la conversion. Cela fait beaucoup de travail pour en arriver là.</p>
<p>Si la chaîne de caractères pouvait directement être passée au module WebAssembly sans toutes ces transformations, ce serait bien plus simple.</p>
<p>WebAssembly ne pourrait pas manipuler cette valeur, il ne connaît pas ce type : on ne résout pas ce problème de compréhension.</p>
<p>Mais si on pouvait simplement passer la valeur au module WebAssembly comme un passe-plat, cela suffirait aux deux fonctions JavaScript, car elles savent quoi faire avec une valeur d’un tel type.</p>
<p>Il s’agit ici d’une raison de <a href="https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md#language-extensions">la proposition pour les types de référence WebAssembly</a>. Cette proposition ajoute un nouveau type de base à WebAssembly intitulé <code>anyref</code>.</p>
<p>Avec une valeur <code>anyref</code>, un script JS fournirait au WebAssembly une référence objet (en fait un pointeur qui ne révèle pas l’adresse mémoire). Cette référence pointera vers l’objet sur le tas JS. Le module WebAssembly pourrait alors passer cette valeur à d’autres fonctions JS qui sauraient l’utiliser.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-09-anyref-01.png" title="Première étape. Le fichier JS tout à gauche tend la valeur 'Hello' demande à l'intermédiaire 'Peux-tu passer cette chaîne de caractères au WASM ?'. L'intermédiaire réfléchit 'Hmm on dirait qu'il suffit de founir un pointeur au WASM, facile'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-09-anyref-01_m.png" alt="Première étape. Le fichier JS tout à gauche tend la valeur 'Hello' demande à l'intermédiaire 'Peux-tu passer cette chaîne de caractères au WASM ?'. L'intermédiaire réfléchit 'Hmm on dirait qu'il suffit de founir un pointeur au WASM, facile'" style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/02-09-anyref-02.png" title="Deuxième étape. Le module WASM au centre, tendant la valeur 'Hello', demande à l'intermédiaire 'Peux-tu passer ça au module JavaScript ? Je ne sais pas ce que c'est mais le JS devrait comprendre'. L'intermédiaire réfléchit 'C'est déjà un pointeur vers un objet d'un tas de mémoire JS, plutôt simple'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.02-09-anyref-02_m.png" alt="Deuxième étape. Le module WASM au centre, tendant la valeur 'Hello', demande à l'intermédiaire 'Peux-tu passer ça au module JavaScript ? Je ne sais pas ce que c'est mais le JS devrait comprendre'. L'intermédiaire réfléchit 'C'est déjà un pointeur vers un objet d'un tas de mémoire JS, plutôt simple'" style="margin: 0 auto; display: block;" /></a></p>
<p>Cela résout un problème d’interopérabilité avec JavaScript, mais il en existe d’autres dans le navigateur.</p>
<p>Un navigateur possède un ensemble beaucoup plus large de types et WebAssembly doit être capable d’inter-opérer avec ces types si on veut que les performances soient décentes.</p>
<h2>Discussion directe entre WebAssembly et le navigateur</h2>
<p>JavaScript ne représente qu’une partie du navigateur. Ce dernier possède de nombreuses autres fonctions qu’on peut utiliser : les API Web.</p>
<p>Sous le capot, les fonctions de ces API Web sont généralement écrites en C++ ou en Rust. Ces deux langages stockent chacun à leur façon les objets en mémoires.</p>
<p>Les paramètres et valeurs de retour de ces API Web sont décrites par de nombreux types. Il sera fastidieux de décrire des conversions pour chacun de ces types. Pour simplifier les choses, il existe un standard pour la structure de ces types : <a href="https://developer.mozilla.org/docs/Mozilla/WebIDL_bindings">Web IDL</a>.</p>
<p>Lorsque vous utilisez ces fonctions, c’est généralement depuis du code JavaScript. Cela signifie que vous passez des valeurs exprimées sur des types JavaScript. Comment un type JavaScript se retrouve converti en type Web IDL ?</p>
<p>À l’instar des correspondances établies entre les types WebAssembly et les types JavaScript, il existe des correspondances entre les types JavaScript et Web IDL.</p>
<p>Là encore, on peut voir cela comme un autre manuel qui explique comment passer de Web IDL à JavaScript. Là aussi ces correspondances font partie intégrant du moteur du navigateur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-02-mapping-book.png" title="Un livre ouvert où la page de gauche contient 'JS vers Web IDL' et où la page de droite contient 'String -> DOMString', 'String -> ByteString', 'String -> USVString', 'Object -> object'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-02-mapping-book_m.png" alt="Un livre ouvert où la page de gauche contient 'JS vers Web IDL' et où la page de droite contient 'String -> DOMString', 'String -> ByteString', 'String -> USVString', 'Object -> object'" style="margin: 0 auto; display: block;" /></a></p>
<p>Pour la plupart des types, la correspondance entre JavaScript et Web IDL est assez simple. Ainsi, un type tel que <code>DOMString</code> est compatible avec le type JS <code>String</code> car les deux ont une correspondance directe.</p>
<p>Que se passe-t-il lorsqu’on essaie d’appeler une API Web depuis du code WebAssembly ?
Il y a un problème.</p>
<p>À l’heure actuelle, il n’existe pas de correspondance entre les types WebAssembly et les types Web IDL. Cela signifie que même pour les types simples comme les nombres, l’appel doit passer par JavaScript.</p>
<p>Voici ce qui se produit :</p>
<ol>
<li>WebAssembly passe la valeur au JavaScript</li>
<li>Pour ce faire, le moteur convertit la valeur en un type JavaScript et la place sur le tas de la mémoire JavaScript</li>
<li>La valeur JavaScript est ensuite passée à la fonction de la Web API. Ici, le moteur convertit la valeur JS en un type Web IDL et la place sur une autre zone mémoire, le tas du <code>renderer</code>.</li>
</ol>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-03-wasm-to-browser-01.png" title="Première étape, le module WASM à gauche tend la valeur 38."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-03-wasm-to-browser-01_m.png" alt="Première étape, le module WASM à gauche tend la valeur 38." style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-03-wasm-to-browser-02.png" title="Deuxième étape, l'intermédiaire réfléchit 'Pour commencer, on passe du type int32 au type Number et on place la valeur sur le tas de la mémoire pour JS'. La valeur 38 est ajoutée au tas de la mémoire."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-03-wasm-to-browser-02_m.png" alt="Deuxième étape, l'intermédiaire réfléchit 'Pour commencer, on passe du type int32 au type Number et on place la valeur sur le tas de la mémoire pour JS'. La valeur 38 est ajoutée au tas de la mémoire." style="margin: 0 auto; display: block;" /></a></p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-03-wasm-to-browser-03.png" title="Troisième étape, l'intermédiaire réfléchit 'et ensuite on convertit ça en double... et je peux enfin exécuter la fonction'. La valeur 38 est ajoutée au tas du renderer avec le type double."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-03-wasm-to-browser-03_m.png" alt="Troisième étape, l'intermédiaire réfléchit 'et ensuite on convertit ça en double... et je peux enfin exécuter la fonction'. La valeur 38 est ajoutée au tas du renderer avec le type double." style="margin: 0 auto; display: block;" /></a></p>
<p>Ce n’est pas optimal : plus de tâches à effectuer et plus de mémoire consommée.</p>
<p>Une solution a priori évidente consisterait à créer des correspondances entre WebAssembly et Web IDL. Toutefois, ce n’est pas aussi trivial qu’il y paraît.</p>
<p>Pour les types Web IDL simples tels que <code>boolean</code>et <code>unsigned long</code> (un nombre), il existe des correspondances évidentes entre WebAssembly et Web IDL.</p>
<p>Mais une bonne partie des paramètres utilisées par les API Web ont des types complexes. Une API peut, par exemple, prendre un dictionnaire (comme un objet avec des propriétés) ou une série (un tableau) en entrée.</p>
<p>Pour créer une correspondance directe entre les types WebAssembly et les types Web IDL, il faudrait ajouter des types de plus haut niveau. C’est ce que nous faisons avec <a href="https://github.com/WebAssembly/gc">la proposition d’ajout d’un ramasse-miettes à WebAssembly</a>. Grâce à ceci, les modules WebAssembly pourront créer des objets pour le ramasse-miettes tels que des structures et des tableaux qui pourront servir aux correspondances pour les types Web IDL.</p>
<p>Mais si la seule façon d’interagir avec les API Web consiste à utiliser les objets du ramasse-miettes, cela complique la tâche pour les langages tels que Rust et C++ qui n’utilisent pas les objets du ramasse-miettes en temps normal. À chaque interaction avec une API Web, il faudrait créer un objet du ramasse-miettes et copier les valeurs depuis la mémoire linéaire dans l’objet.</p>
<p>Le résultat ainsi obtenu est légèrement mieux que la situation actuelle avec le code de liaison JavaScript.</p>
<p>On ne souhaite pas avoir de code de liaison JavaScript pour construire les objets du ramasse-miettes : c’est un gaspillage de temps et de ressources. Réciproquement, on ne veut pas que le module WebAssembly construise ces objets pour les mêmes raisons.</p>
<p>On souhaite qu’appeler les API Web soit aussi simple pour les langages qui utilisent une mémoire linéaire (tels que Rust ou C++) que pour les langages qui utilisent un ramasse-miettes intégré. Il faut donc également une méthode pour créer une correspondance entre les objets en mémoire linéaire et les types Web IDL.</p>
<p>Mais il y a un hic. Chaque langage représente des choses en mémoire linéaire de façon différente. On ne peut pas choisir une de ces représentations spécifiquement, tous les autres langages en pâtiraient.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-07-picking-lang.png" title="Un bonhomme au centre, entourés de langages comme C++, Kotlin, Go, D, Rust, Haskell et qui s'exclame : 'Je choisis... celui-là' en pointant Rust. Une annotation rouge indique avec une flèche 'Mauvaise idée'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-07-picking-lang_m.png" alt="Un bonhomme au centre, entourés de langages comme C++, Kotlin, Go, D, Rust, Haskell et qui s'exclame : 'Je choisis... celui-là' en pointant Rust. Une annotation rouge indique avec une flèche 'Mauvaise idée'" style="margin: 0 auto; display: block;" /></a></p>
<p>Bien que l’organisation mémoire soit différente, il y a certains concepts abstraits qui sont généralement partagés.</p>
<p>Ainsi, pour les chaînes de caractères, un langage possède souvent un pointeur vers le début de la chaîne de caractères et sa longueur. Si la chaîne de caractères possède une représentation plus complexe, il est généralement utile de convertir les chaînes vers ce format pour appeler des API externes.</p>
<p>De cette façon, on peut réduire la chaîne en un type que WebAssembly comprend : deux valeurs <code>i32</code>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-08-types-wasm-understands.png" title="La chaîne de caractères 'Hello' encodée en mémoire et deux valeurs 'offset=2' et 'length=5' avec deux flèches vers elles et l'indication 'des types que WebAssembly comprend'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-08-types-wasm-understands_m.png" alt="La chaîne de caractères 'Hello' encodée en mémoire et deux valeurs 'offset=2' et 'length=5' avec deux flèches vers elles et l'indication 'des types que WebAssembly comprend'" style="margin: 0 auto; display: block;" /></a></p>
<p>Là encore, un petit hic. WebAssembly est un langage fortement typé. Pour des raions de <a href="https://webassembly.org/docs/security/">sécurité</a>, le moteur vérifie que le code appelant passe des valeurs dont les types correspondent à ceux attendus par l’appelé.</p>
<p>Cela empêche les attaquants d’exploiter des incohérences de type pour détourner le moteur.</p>
<p>Si vous appelez une fonction qui utilise une chaîne de caractère et que vous tentez de lui passer un entier, le moteur vous criera dessus. Et ça tombe bien, c’est ce qu’il <em>devrait</em> faire.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-09-type-mismatch.png" title="Le module WASM tend un entier (57) et dit au moteur 'tiens, voilà un entier, utilise le avec une fonction qui prend une chaîne en entrée'. Le moteur rétorque : 'tu essaies quoi au juste ? de permettre aux attaquants de me pirater moi ?!'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-09-type-mismatch_m.png" alt="Le module WASM tend un entier (57) et dit au moteur 'tiens, voilà un entier, utilise le avec une fonction qui prend une chaîne en entrée'. Le moteur rétorque : 'tu essaies quoi au juste ? de permettre aux attaquants de me pirater moi ?!'" style="margin: 0 auto; display: block;" /></a></p>
<p>Il nous faut donc une façon pour un module de dire au moteur quelque chose comme “Je sais que <code>Document.createElement()</code> prend une chaîne de caractères, mais je vais l’appeler et vous envoyer deux entiers. Prenez ces deux entiers pour créer un objet <code>DOMString</code> à partir des données en mémoire linéaire. Le premier entier sera l’adresse de départ de la chaîne de caractères et le second correspondra à sa longueur.”</p>
<p>C’est tout l’objectif de la proposition pour les types d’interfaçage Web IDL. On fournit à un module WebAssembly une façon d’indiquer une correspondance entre les types qu’il utilise et les types Web IDL.</p>
<p>Ces correspondances ne sont pas enregistrées en dur dans le moteur. C’est le module qui fournit un petit livret expliquant les correspondances qu’il utilise.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-10-booklet.png" title="Le module WASM s'incline pour tendre un livret au moteur et lui dit 'Voici quelques notes qui t'indiqueront comment traduire mes types en types d'interfaçage et vice versa'."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-10-booklet_m.png" alt="Le module WASM s'incline pour tendre un livret au moteur et lui dit 'Voici quelques notes qui t'indiqueront comment traduire mes types en types d'interfaçage et vice versa'." style="margin: 0 auto; display: block;" /></a></p>
<p>Le moteur a donc une méthode pour dire “pour cette fonction, la vérification des types pour les chaînes de caractères consistera à vérifier deux entiers”.</p>
<p>Le couplage entre le module et ce livret d’explication est aussi utile pour une autre raison.</p>
<p>Parfois, un module qui stocke normalement ses chaînes en mémoire linéaire pourra vouloir utilise une <code>anyref</code> ou un type du ramasse-miettes pour un cas spécifique. C’est le cas notamment pour un module qui passe un objet qu’il a obtenu d’une fonction JavaScript (un nœud du DOM par exemple) vers une API Web.</p>
<p>Ainsi, un module doit pouvoir choisir au cas par cas entre les fonctions (voire entre les arguments) la façon dont la correspondance de type est gérée. La correspondance étant fournie par le module, ce dernier peut décrire une correspondance sur-mesure.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-11-granularity.png" title="Le module WASM indique au moteur : 'Attention à bien lire. Pour certaines fonctions qui utilisent des DOMString, je te fournirai deux nombres mais pour les autres je t'enverrai simplement la valeur DOMString que m'a fourni le JavaScript."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-11-granularity_m.png" alt="Le module WASM indique au moteur : 'Attention à bien lire. Pour certaines fonctions qui utilisent des DOMString, je te fournirai deux nombres mais pour les autres je t'enverrai simplement la valeur DOMString que m'a fourni le JavaScript." style="margin: 0 auto; display: block;" /></a></p>
<p>Comment faire pour générer ce livret ?</p>
<p>Le compilateur prend en charge cette opération. Il ajoute une section spécifique au module WebAssembly. Pour la plupart des chaînes de compilation des différents langages, le développeur n’aura pas un grand travail supplémentaire.</p>
<p>Prenons un exemple avec la chaîne de compilation Rust et comment celle-ci gère le passage d’une chaîne de caractères à la fonction <code>alert</code>.</p>
<pre><code>#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
}
</code></pre>
<p>Le développeur doit juste indiquer au compilateur d’ajouter cette fonction au livret avec l’annotation <code>#[wasm_bindgen]</code>. Par défaut, le compilateur considèrera qu’il s’agit d’une chaîne de caractères représentée en mémoire linéaire et ajoutera la bonne correspondance. Si on avait souhaité la gérer différemment (comme un <code>anyref</code> par exemple), on aurait écrit une autre annotation à destination du compilateur.</p>
<p>Grâce à ça, on peut enlever le code JavaScript intermédiaire pour la liaison. Le passage de valeur entre WebAssembly et les API Web est plus rapide. De plus, cela fait moins de JavaScript à distribuer.</p>
<p>Au passage, aucun compromis n’a été effectué quant aux langages pris en charges. On peut utiliser n’importe quel langage qui compile vers WebAssembly. Tous ces langages peuvent définir leur correspondance vers les types Web IDL, peu importe qu’ils utilisent une mémoire linéaire, des objets de ramasse-miettes ou les deux.</p>
<p>En prenant un peu de recul sur cette solution, on peut voir qu’elle résout un bien plus grand problème.</p>
<h2>WebAssembly : un langage pour tous leur parler</h2>
<p>Revenons à la promesse que nous évoquions au début de ce billet.</p>
<p>Existe-t-il une méthode réaliste afin que WebAssembly puisse parler à ces différents systèmes quels que soient les types qu’ils utilisent ?</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/04-01-star-diagram.png" title="Un diagramme avec un module WASM à droite et trois doubles flèches qui partent vers : des modules PHP/Python/Ruby qui s'exécutent dans leurs environnements ; des modules Rust/Go qui sont écrits avec un autre langage ; des systèmes hôtes qui fonctionnent directement avec le système d'exploitation"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.04-01-star-diagram_m.png" alt="Un diagramme avec un module WASM à droite et trois doubles flèches qui partent vers : des modules PHP/Python/Ruby qui s'exécutent dans leurs environnements ; des modules Rust/Go qui sont écrits avec un autre langage ; des systèmes hôtes qui fonctionnent directement avec le système d'exploitation" style="margin: 0 auto; display: block;" /></a></p>
<p>Quelles sont les options ?</p>
<p>On <em>pourrait</em> essayer de créer des correspondances inscrites en dur dans le moteur (à la façon de ce qui est fait entre WebAssembly et JavaScript d’une part et entre JavaScript et WebIDL d’autre part).</p>
<p>Mais pour ce faire, il faudrait une correspondance spécifique par langage. Le moteur aurait à prendre en charge chacune de ces correspondances explicitement et les mettre à jour à chaque changement de chaque langage. Bref, c’est la pagaille.</p>
<p>C’est de cette façon que furent conçus les premiers compilateurs. Il existait une trajectoire différente entre chaque langage source et chaque langage machine. Nous en parlions plus en détails dans <a href="https://tech.mozfr.org/post/2017/03/08/Une-introduction-cartoonesque-a-WebAssembly">un des premiers billets sur WebAssembly</a>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-05-langs05.png" title="Un diagramme avec différents langages sources sur la gauche vers différentes plateformes matérielles (x86 / ARM) représentées par une créature protéiforme"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-05-langs05_m.png" alt="Un diagramme avec différents langages sources sur la gauche vers différentes plateformes matérielles (x86 / ARM) représentées par une créature protéiforme" style="margin: 0 auto; display: block;" /></a></p>
<p>On ne veut pas avoir quelque chose d’aussi compliqué. On veut que chaque langage puisse parler à chaque plateforme. Et en même temps, on veut que cette approche soit extensible.</p>
<p>Il nous faut donc une autre approche et on peut s’inspirer des architectures des compilateurs modernes. Pour ceux-ci, il y a une division entre le front-end et le back-end. La partie front-end porte sur le langage source traduit en une représentation intermédiaire abstraite. La partie back-end part de cette représentation intermédiaire jusqu’au code machine cible.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/03-06-langs06.png" title="Contrairement au diagramme précédent, les flèches convergent vers une zone intermédiaire avec 'IR' de là repartent de nouvelles flèches vers les plateformes."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.03-06-langs06_m.png" alt="Contrairement au diagramme précédent, les flèches convergent vers une zone intermédiaire avec 'IR' de là repartent de nouvelles flèches vers les plateformes." style="margin: 0 auto; display: block;" /></a></p>
<p>C’est de cette méthode dont s’inspirent les types Web IDL. Quand on le regarde d’un autre angle, Web IDL ressemble un peu à une représentation intermédiaire.</p>
<p>Ceci étant posé, Web IDL est assez spécifique au Web. Et il existe de nombreux cas d’usage pour WebAssembly en dehors du Web. Web IDL n’est donc pas la représentation intermédiaire qu’il faut.</p>
<p>Malgré cela, pouvons-nous nous inspirer de Web IDL et créer un nouvel ensemble de types abstraits ?</p>
<p>C’est ainsi qu’on arrive à la proposition pour les types d’interfaçage WebAssembly.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/04-06-types-as-IR.png" title="Un diagramme avec un module WASM à gauche, une liste de langages qui compilent vers WASM et des doubles flèches qui pointent vers le texte 'Types d'interfaçage WebAssembly'. De là repartent des doubles flèches vers des langages, des environnements, des systèmes d'exploitation, etc."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.04-06-types-as-IR_m.png" alt="Un diagramme avec un module WASM à gauche, une liste de langages qui compilent vers WASM et des doubles flèches qui pointent vers le texte 'Types d'interfaçage WebAssembly'. De là repartent des doubles flèches vers des langages, des environnements, des systèmes d'exploitation, etc." style="margin: 0 auto; display: block;" /></a></p>
<p>Ces types ne sont pas des types concrets. Ils ne ressemblent pas aux types qu’on trouve aujourd’hui dans WebAssembly comme <code>int32</code> ou <code>float64</code>. On ne peut pas les manipuler avec des opérations en WebAssembly.</p>
<p>On n’ajoutera par exemple pas de méthode de concaténation de chaînes de caractères dans WebAssembly. Toutes les opérations seront effectuées sur les types concrets à chaque extrêmité.</p>
<p>La clef de voûte de ce fonctionnement est la copie des valeurs d’un côté à l’autre. Plutôt que de partager une représentation commune, les deux parties utilisent les types d’interfaçage pour copier les valeurs.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/04-07-copy.png" title="Le module WASM dit 'vu que c'est une chaîne de caractères en mémoire linéaire, je sais comment la manipuler' et le navigateur dit 'vu que c'est une DOMString, je sais comment la manipuler'. Sous le capôt, le moteur copie le tampon de mémoire linéaire WASM dans le tas du renderer."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.04-07-copy_m.png" alt="Le module WASM dit 'vu que c'est une chaîne de caractères en mémoire linéaire, je sais comment la manipuler' et le navigateur dit 'vu que c'est une DOMString, je sais comment la manipuler'. Sous le capôt, le moteur copie le tampon de mémoire linéaire WASM dans le tas du renderer." style="margin: 0 auto; display: block;" /></a></p>
<p>Il existe un point qui pourrait constituer une exception à cette règle : les nouvelles valeurs de référence (telles que <code>anyref</code>) que nous avons mentionnées plus haut. Dans ce cas, c’est le pointeur vers l’objet qui est copié entre les deux côtés. Les deux pointeurs pointent donc vers la même chose. En théorie, cela peut vouloir dire qu’ils ont besoin de partager une représentation.</p>
<p>Dans les cas où la référence ne fait que “traverser” un module WebAssembly (comme l’exemple que nous avons vu avec <code>anyref</code>), les deux interlocuteurs n’ont pas à partager une représentation. Le module n’est pas supposé comprendre ce type mais simplement le passer entre les fonctions.</p>
<p>Il existe cependant des scénarios où on souhaite que les interlocuteurs partagent une représentation. Par exemple, la proposition pour le ramasse-miettes ajoute une méthode pour <a href="https://github.com/WebAssembly/gc/blob/master/proposals/gc/MVP-JS.md#type-definition-objects">créer des défintions de type</a> afin que les deux parties puissent partager des représentations. Dans ces cas, le choix de la représentation et de ce qu’il faut partager est effectué par les développeurs qui conçoivent l’API.</p>
<p>Cette approche rend le dialogue beaucoup plus simple entre un module WebAssembly et de nombreux langages.</p>
<p>Dans certains cas (comme celui du navigateur), la correspondance entre les types d’interfaçage et les types du système sous-jacent sera inscrite en dur.</p>
<p>Ainsi, une partie des correspondances est construite à la compilation tandis que l’autre est fourni au moteur lors du chargement du contenu.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/04-08-mapping-symmetry-host.png" title="Le moteur lit le livret et dit 'OK, donc ça ça correspond à une chaîne ? Je peux donc utiliser mes correspondances en dur afin de la convertir en DOMString pour la fonction qui le demande'"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.04-08-mapping-symmetry-host_m.png" alt="Le moteur lit le livret et dit 'OK, donc ça ça correspond à une chaîne ? Je peux donc utiliser mes correspondances en dur afin de la convertir en DOMString pour la fonction qui le demande'" style="margin: 0 auto; display: block;" /></a></p>
<p>Dans les autres cas, par exemple quand deux modules WebAssembly échangent entre eux, les deux envoient leurs livrets d’instruction qui décrivent chacun leurs correspondances entre les types de fonction et les types abstraits.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/04-09-mapping-symmetry-wasm.png" title="Un module Rust compilé en WASM et un module Go compilé en WASM fournissent chacun un livret au moteur qui dit : 'OK, voyons voir comment assembler tout ça'."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.04-09-mapping-symmetry-wasm_m.png" alt="Un module Rust compilé en WASM et un module Go compilé en WASM fournissent chacun un livret au moteur qui dit : 'OK, voyons voir comment assembler tout ça'." style="margin: 0 auto; display: block;" /></a></p>
<p>Ce n’est pas la seule chose nécessaire pour que des modules écrits avec différents langages sources se parlent (nous reviendrons sur ce sujet) mais c’est un grand pas dans cette direction.</p>
<h2>À quoi ressemblent ces types d’interfaçage ?</h2>
<p>Avant d’aller plus loin dans les détails, rappelons que cette proposition est toujours en cours de développement. Le résultat final pourrait s’avérer complètement différent.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/05-01-construction.png" title="Deux bonhommes sont en train de placer des plots de chantier et l'un tient un panneau avec 'À utiliser avec précaution'."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.05-01-construction_m.png" alt="Deux bonhommes sont en train de placer des plots de chantier et l'un tient un panneau avec 'À utiliser avec précaution'." style="margin: 0 auto; display: block;" /></a></p>
<p>De plus, tout est géré par le compilateur. Même après que cette proposition ait été finalisée, vous aurez uniquement à connaître les annotations attendues par la chaîne de compilation pour les mettre dans votre code (à la façon de ce que nous avons fait avec <code>wasm-bindgen</code> plus haut). Il n’est pas vraiment nécessaire de savoir comment ça fonctionne sous le capot.</p>
<p>Vu que <a href="https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md">les détails exposés par la proposition</a> sont assez clairs, profitons-en pour voir comment tout cela s’articule.</p>
<h3>Le problème à résoudre</h3>
<p>Le problème consiste à traduire des valeurs entre différents types lorsqu’un module dialogue avec un autre module (ou avec un hôte comme le navigateur).</p>
<p>On a quatre endroits où on peut avoir besoin de traduire :</p>
<ul>
<li>Pour les fonctions exportées
<ul>
<li>la réception de paramètres depuis l’appelant</li>
<li>l’envoi des valeurs de retour vers l’appelant</li>
</ul></li>
<li>Pour les fonctions importées
<ul>
<li>le passage des paramètres à la fonction</li>
<li>la réception des valeurs de retour</li>
</ul></li>
</ul>
<p>On peut voir chacun de ces cas comme un mouvement sur deux directions :</p>
<ul>
<li>La montée pour les valeurs qui quittent le module. Elles passent d’un type concret à un type d’interfaçage.</li>
<li>La descente pour les valeurs qui arrivent dans le module. Elles passent d’un type d’interfaçage à un type concret.</li>
</ul>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/05-02-incoming-outgoing.png" title="Un schéma avec deux modules WASM qui s'échangent des infos. Les valeurs envoyées remontent et les valeurs reçues redescendent le long des flèches"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.05-02-incoming-outgoing_m.png" alt="Un schéma avec deux modules WASM qui s'échangent des infos. Les valeurs envoyées remontent et les valeurs reçues redescendent le long des flèches" style="margin: 0 auto; display: block;" /></a></p>
<h3>Indiquer au moteur les transformations à effectuer entre les types concrets et les types d’interfaçage</h3>
<p>Il faut donc une méthode pour indiquer au moteur les transformations à appliquer aux paramètres et aux valeurs de retour d’une fonction. Comment faire ?</p>
<p>En définissant un adaptateur d’interface.</p>
<p>Prenons l’exemple d’un module Rust compilé en WebAssembly. Ce module exporte une fonction <code>greeting_</code> qui peut être appelée sans paramètre et qui renvoie un message de salutation.</p>
<p>Voici ce qu’on aurait actuellement (avec le format textuel WebAssembly).</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/05-03-original-function.png" title="L'équivalent texte WebAssembly avec une annotation sur le retour : la fonction renvoie deux entiers."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.05-03-original-function_m.png" alt="L'équivalent texte WebAssembly avec une annotation sur le retour : la fonction renvoie deux entiers." style="margin: 0 auto; display: block;" /></a></p>
<p>Pour le moment, la fonction renvoie deux entiers.</p>
<p>Mais on voudrait qu’elle renvoie une valeur pour le type d’interfaçage <code>string</code>. On ajoute donc quelque chose qu’on appelle un adaptateur d’interface.</p>
<p>Si un moteur prend en charge les types d’interfaçage, lorsqu’il verra un adaptateur d’interface, il enveloppera le module dans cette interface.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/05-04-interface.png" title="Le code précédent est grisé et une partie avec l'interface est ajoutée. L'annotation indique que celle-ci renvoie une chaîne de caractères."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.05-04-interface_m.png" alt="Le code précédent est grisé et une partie avec l'interface est ajoutée. L'annotation indique que celle-ci renvoie une chaîne de caractères." style="margin: 0 auto; display: block;" /></a></p>
<p>Le module n’exporte plus la fonction <code>greeting_</code> mais la fonction <code>greeting</code> qui enveloppe l’originale. La nouvelle fonction <code>greeting</code> renvoie une chaîne de caractères et plus deux entiers.</p>
<p>On obtient une compabilité ascendante, car les moteurs qui ne comprennent pas les types d’interface exporteront la fonction originale <code>greeting_</code> (celle qui renvoie deux entiers).</p>
<p>Comment l’adaptateur d’interface explique au moteur comment transformer deux entiers en une chaîne ?</p>
<p>Il utilise une séquence d’instructions d’adaptateur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/05-05-adapter-inst-return.png" title="Le code précédent est grisé et deux nouvelles lignes sont ajoutée. La première indique l'appel de la fonction adaptée et la deuxième indique comment adapter la valeur de retour afin de produire une chaîne de caractères"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.05-05-adapter-inst-return_m.png" alt="Le code précédent est grisé et deux nouvelles lignes sont ajoutée. La première indique l'appel de la fonction adaptée et la deuxième indique comment adapter la valeur de retour afin de produire une chaîne de caractères" style="margin: 0 auto; display: block;" /></a></p>
<p>Les instructions d’adaptateur présentées dans cette image sont deux exemples d’un ensemble d’instructions qui sont définies dans cette proposition.</p>
<p>Voici ce que font les instructions précédentes :</p>
<ol>
<li>Utiliser l’instruction d’adaptateur <code>call-export</code> afin d’appeler la méthode originale <code>greeting_</code>. C’est la fonction exportée par le module original qui renvoie deux nombres. Ces deux nombres sont placés sur la pile.</li>
<li>Utiliser l’instruction d’adaptateur <code>memory-to-string</code> qui convertit les nombres en une séquence d’octets qui composent la chaînes de caractères. On doit ici préciser <code>"mem"</code> à la suite car un module WebAssembly pourrait demain avoir plusieurs espaces mémoire. On indique ainsi au moteur l’espace mémoire à consulter. Le moteur prend alors les deux nombres sur le dessus de la pile (qui correspondent au pointeur et à la longueur) et les utilise afin de déterminer les octets à utiliser.</li>
</ol>
<p>Cela ressemble un peu à un langage de programmation, mais il n’y a pas de contrôle du flux d’instructions ici (pas de boucles ou d’instructions conditionnelles). Il s’agit d’un langage déclaratif qui nous permet de fournir des instructions au moteur.</p>
<p>À quoi cela ressemblerait-il si notre fonction prenait une chaîne en paramètre (le nom de la personne à saluer par exemple).</p>
<p>Eh bien c’est assez proche. On modifie l’interface de la fonction d’adaptation afin d’ajouter le paramètre et on ajoute ensuite deux instructions d’adaptateur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/05-06-adapter-inst-param.png" title="Les lignes précédentes sont grisées. On voit l'ajout d'un premier fragment pour le paramètre qui est une chaîne de caractères. Ensuite une ligne ajoute une référence sur la pile pour référencer la chaîne passée en argument. Enfin, la troisième ligne indique comment interpréter les octets de la chaînes pour les placer en mémoire linéaire."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.05-06-adapter-inst-param_m.png" alt="Les lignes précédentes sont grisées. On voit l'ajout d'un premier fragment pour le paramètre qui est une chaîne de caractères. Ensuite une ligne ajoute une référence sur la pile pour référencer la chaîne passée en argument. Enfin, la troisième ligne indique comment interpréter les octets de la chaînes pour les placer en mémoire linéaire." style="margin: 0 auto; display: block;" /></a></p>
<p>Voilà ce que font ces nouvelles instructions :</p>
<ol>
<li>Utiliser l’instruction <code>arg.get</code> afin d’obtenir une référence à l’objet qu’est la chaîne de caractères qu’on place sur la pile.</li>
<li>Utiliser l’instruction <code>string-to-memory</code> afin de récupérer les octets de cet objet pour les placer en mémoire linéaire. Là encore, on précise l’espace mémoire dans lequel inscrire ces octets. On précise également comment allouer ces octets. Pour cela on fournit une fonction d’allocation (qui pourrait être un export fourni par le module).</li>
</ol>
<p>Si vous souhaitez en savoir plus sur ce fonctionnement, vous pouvez consulter <a href="https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md">cette explication qui va plus en détails</a>.</p>
<h3>Envoyer les instructions au moteur</h3>
<p>Comment envoyer tout cela au moteur ?</p>
<p>Ces annotations sont ajoutées au fichier binaire dans une section spécifique (<em>custom</em>).</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/05-07-custom-section.png" title="Un fichier divisé en deux parties : la première (la plus grande) contient les sections « connues » avec le code et les données : la seconde, « spécifique », contient les adaptateurs d'interface."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.05-07-custom-section_m.png" alt="Un fichier divisé en deux parties : la première (la plus grande) contient les sections « connues » avec le code et les données : la seconde, « spécifique », contient les adaptateurs d'interface." style="margin: 0 auto; display: block;" /></a></p>
<p>Si un moteur sait exploiter les types d’interfaçage, il pourra utiliser cette section. Sinon, il pourra l’ignorer et vous pourrez utiliser une prothèse (<em>polyfill</em>) afin de lire la section et écrire du code de liaison.</p>
<h2>Quelles différences avec CORBA, <em>Protocol buffers</em>, etc. ?</h2>
<p>Il existe actuellement d’autres standards qui semblent résoudre ce même problème dont CORBA, Protocol buffers, Cap’n Proto.</p>
<p>En quoi ceux-ci sont différents ? Ils résolvent un problème beaucoup plus difficile.</p>
<p>Ils ont été conçus afin de pouvoir interagir avec un système avec lequel on ne partage pas de mémoire (soit parce qu’il s’agit d’un autre processus ou d’une toute autre machine sur le réseau).</p>
<p>Cela signifie qu’il faut pouvoir envoyer cette représentation intermédiaire par-delà cette frontière.</p>
<p>Ces standards visent à définir un format de sérialisation qui puisse efficacement voyager sur cette frontière. C’est là un des aspects essentiels de ces standards.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/06-01-cross-boundary-ir.png" title="Deux machines qui se parlent avec chacune un module WASM. Entre ces deux machines une flèche annotée « IR » pour la représentation intermédiaire qui voyage sur cet axe."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.06-01-cross-boundary-ir_m.png" alt="Deux machines qui se parlent avec chacune un module WASM. Entre ces deux machines une flèche annotée « IR » pour la représentation intermédiaire qui voyage sur cet axe." style="margin: 0 auto; display: block;" /></a></p>
<p>Bien que le problème semble similaire, il s’agit en fait de l’exact inverse.</p>
<p>Avec les types d’interfaçage, la représentation intermédiaire (l’« IR ») ne quitte jamais le moteur. Elle n’est même pas visible pour les modules.</p>
<p>Les modules ne voient que ce le moteur leur fournit à la fin (ce qui a été copié sur leur mémoire linéaire ou fourni comme référence). Il n’est pas nécessaire d’indiquer au moteur l’organisation de ces types, car elle n’est pas définie.</p>
<p>Ce qui est défini, en revanche, est la façon de parler au moteur. Il s’agit du langage déclaratif utilisé pour écrire ce livret envoyé au moteur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/06-02-no-boundary-ir.png" title="Les deux modules WASM ne sont plus reliés par une flèche, l'IR ne voyage plus le long d'un axe."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.06-02-no-boundary-ir_m.png" alt="Les deux modules WASM ne sont plus reliés par une flèche, l'IR ne voyage plus le long d'un axe." style="margin: 0 auto; display: block;" /></a></p>
<p>De cet aspect déclaratif découle un effet de bord appréciable : le moteur peut détecter lorsqu’une « traduction » entre types est superflue. Ainsi si les deux modules qui discutent utilisent le même type, le moteur évitera cette double transformation.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/06-03-opt.png" title="Le moteur, entouré d'un module Rust compilé en WASM et d'un module Go compilé en WASM, dit 'Ohoh, vous utilisez tous les deux une mémoire linéaire pour cette chaîne de caractères. Dans ce cas, je vais juste faire une rapide copie entre vos mémoires"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.06-03-opt_m.png" alt="Le moteur, entouré d'un module Rust compilé en WASM et d'un module Go compilé en WASM, dit 'Ohoh, vous utilisez tous les deux une mémoire linéaire pour cette chaîne de caractères. Dans ce cas, je vais juste faire une rapide copie entre vos mémoires" style="margin: 0 auto; display: block;" /></a></p>
<h2>Comment utiliser tout ça aujourd’hui ?</h2>
<p>Comme nous l’avons indiqué plus haut, il s’agit d’une proposition au stade encore expérimental. Certaines choses risquent de changer rapidement et il serait risqué d’utiliser tout ça en production.</p>
<p>Ceci étant posé, si vous souhaitez manipuler tout ça, nous avons implémenté le nécessaire sur l’ensemble de la chaîne de compilation : de la production de code à la consommation :</p>
<ul>
<li>La chaîne de compilation Rust</li>
<li><code>wasm-bindgen</code> </li>
<li>L’environnement d’exécution WebAssembly Wasmtime</li>
</ul>
<p>Comme nous maintenons ces outils et que nous travaillons sur le standard, nous pouvons maintenir le nécessaire pendant le développement du standard.</p>
<p>Bien que tout ça continue d’évoluer, nous nous assurons de synchroniser ces évolutions avec ces outils. Ainsi, tant que vous utilisez des versions à jour de ces outils, vous ne devriez pas rencontrer trop de problèmes.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/07-01-construction.png" title="Un bonhomme avec un casque et à proximité de plots de chantier qui dit « faites simplement attention à bien rester sur le chemin balisé »."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM_Interface/.07-01-construction_m.png" alt="Un bonhomme avec un casque et à proximité de plots de chantier qui dit « faites simplement attention à bien rester sur le chemin balisé »." style="margin: 0 auto; display: block;" /></a></p>
<p>Voici donc les nombreuses façons dont vous pouvez utiliser tout ça aujourd’hui. Pour une version à jour, vous pouvez consulter <a href="https://github.com/CraneStation/wasmtime-demos">ce dépôt de démonstrations</a>.</p>
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/Qn_4F3foB3Q" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<h2>Remerciements</h2>
<ul>
<li>Merci à l’équipe qui a assemblé toutes ces pièces pour tous ces langages et tous ces environnements d’exécution : Alex Crichton, Yury Delendik, Nick Fitzgerald, Dan Gohman et Till Schneidereit</li>
<li>Merci aux porteurs de cette proposition et à leurs collègues pour leur travail dessus : Luke Wagner, Francis McCabe, Jacob Gravelle, Alex Crichton et Nick Fitzgerald</li>
<li>Merci à mes merveilleux collègues : Luke Wagner et Till Schneidereit pour leurs retours et contributions inestimables à cet article.</li>
</ul>
<h2>À propos de Lin Clark</h2>
<p>Lin travaille au sein de l’équipe ‘Advanced Development’ de Mozilla et notamment sur Rust et WebAssembly.</p>
<ul>
<li><a href="https://twitter.com/linclark">@linclark</a></li>
</ul>
<p><style>
pre { white-space: pre;}
</style></p>
Détails techniques sur la panne des modules complémentaires de Firefox
urn:md5:792560e8e1288351b86614afaf2e346b
2019-05-10T18:52:00+02:00
2019-05-11T16:08:31+02:00
sphinx
Général
Correctif
Firefox
Hacks
modules complémentaires
Mozilla
<p><em>Cet article est une traduction de</em> <a href="https://hacks.mozilla.org/2019/05/technical-details-on-the-recent-firefox-add-on-outage/">Technical Details on the Recent Firefox Add-on Outage</a>, <em>écrit par Eric Rescorla, directeur technique de l’équipe de Firefox. Pour un aperçu moins technique, vous pouvez lire <a href="https://blog.mozfr.org/post/2019/05/Ce-que-nous-faisons-quand-les-choses-tournent-mal" title="Ce que nous faisons quand les choses tournent mal (10 mai 2019) Communauté Mozilla francophone">cet article</a>. Merci à Vincent pour la relecture !</em></p>
<hr />
<p>Il y a quelques jours, Firefox a rencontré un problème empêchant la plupart des modules complémentaires de fonctionner. Nous avons commis une erreur en laissant expirer un des certificats utilisés pour la signature des modules. Cela a eu pour effet de désactiver une majeure partie des modules. Maintenant que ce problème a été corrigé pour la plupart des utilisateurs et que la plupart des modules sont revenus à la normale, nous souhaitions revenir sur les détails de ce problème, les raisons de son origine et la façon dont nous l’avons résolu.</p>
<h2>Un peu de contexte : les modules complémentaires et leur signature</h2>
<p>Bien que de nombreuses personnes utilisent Firefox tel quel, il est également possible d’étendre ses fonctionnalités grâce à des « modules complémentaires » (aussi appelées <em>add-ons</em> ou extensions). Les modules permettent ainsi aux utilisateurs d’ajouter des fonctionnalités tierces qui complètent les fonctionnalités par défaut. Il existe aujourd’hui plus de 15 000 modules pour Firefox qui permettent par exemple de <a href="https://addons.mozilla.org/firefox/addon/ublock-origin/" title="Extension uBlock Origin">bloquer des publicités</a> ou de <a href="https://addons.mozilla.org/firefox/addon/tree-style-tab/" title="Extension Tree Style Tab">gérer des centaines d’onglets</a>.</p>
<p>Pour qu’un module puisse être installé dans Firefox, il faut qu’il soit <a href="https://blog.mozilla.org/addons/2015/02/10/extension-signing-safer-experience/">signé numériquement</a>. Cette condition fait partie des mesures visant à protéger les utilisateurs de modules malveillants et implique au moins une revue de certains standards dans le module par l’équipe Mozilla. Cette mesure a été introduite en 2015 après avoir rencontré de <a href="https://blog.mozilla.org/addons/2015/04/15/the-case-for-extension-signing/">graves problèmes</a> avec des modules malveillants.</p>
<p>Pour signer numériquement un module, Firefox est configuré avec un « certificat racine » préinstallé. Cette racine est stockée hors ligne dans une <a href="https://fr.wikipedia.org/wiki/Hardware_Security_Module">boîte noire transactionnelle</a> (ou <em>hardware security module</em> en anglais, abrégé en HSM). Toutes les quelques années, ce certificat racine est utilisé afin de signer un nouveau « certificat intermédiaire » qui est conservé en ligne et utilisé dans le processus de signature. Lorsqu’un module est envoyé pour être signé, nous générons un nouveau certificat temporaire : le certificat d’entité final et nous le signons avec le certificat intermédiaire. C’est ce certificat d’entité final qui est utilisé afin de signer le module.</p>
<p>Voici un schéma qui illustre ce fonctionnement :</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/armagaddon2/Add-on-blog-post-visual-May-7-2019_fr.png" title="Illustration des liens de signatures entre les certificats et les modules"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/armagaddon2/.Add-on-blog-post-visual-May-7-2019_fr_m.png" alt="Illustration des liens de signatures entre les certificats et les modules" style="margin: 0 auto; display: block;"/></a></p>
<p>On pourra voir que chaque certificat possède un sujet (l’entité à laquelle le certificat appartient) et un émetteur (celui qui l’a signé). Pour le certificat racine, l’émetteur et le sujet sont les mêmes. Pour les autres certificats en revanche, c’est l’émetteur du certificat est le sujet du certificat ayant servi à la signature.</p>
<p>On pourra noter un point important : chaque module est signé par un certificat d’entité final qui lui est propre mais la quasi-intégralité des modules partage le même certificat intermédiaire <a href="https://tech.mozfr.org/post/2019/05/10/Details-techniques-sur-la-panne-des-modules-complementaires-de-Firefox#exp1"><sup>[1]</sup></a>. C’est ce certificat qui a posé problème. En effet, chaque certificat est valide pendant une période de temps donnée. Avant ou après cette période, le certificat ne sera plus accepté et les modules signés avec ce certificat ne pourront plus être chargés dans Firefox. Malheureusement, le certificat intermédiaire que nous utilisions a expiré le 4 mai après 01:00 <abbr lang="en" title="Universal Time Coordinated">UTC</abbr>. Après cet instant, chaque module signé avec ce certificat intermédiaire est devenu invérifiable et ne pouvait plus être chargé dans Firefox.</p>
<p>Toutefois, bien que les modules aient expiré autour de minuit, l’impact de cet arrêt ne s’est pas fait ressentir immédiatement. En fait, les modules sont vérifiés toutes les 24 heures et l’heure de vérification est différente pour chaque utilisateur. Aussi, certaines personnes ont eu le problème immédiatement tandis que d’autres ne l’ont rencontré que bien plus tard. À Mozilla, nous avons réalisé ce problème le 3 mai à 18 h 00 (heure du Pacifique) et avons immédiatement regroupé une équipe afin de le résoudre.</p>
<h2>Circonscrire les dégâts</h2>
<p>Après avoir réalisé de quoi il en retournait, nous avons pris plusieurs mesures pour éviter d’empirer les choses.
Pour commencer, nous avons désactivé la signature pour les nouveaux modules. Nous avons pris cette décision, car nous savions que le certificat utilisé pour la signature avait expiré. Avec le recul, nous aurions pu laisser poursuivre cette signature mais cela aurait interféré avec une solution d’atténuation consistant à inscrire une date en dur (cf. ci-après, au final, nous n’avons pas utilisé cette solution).</p>
<p>Ensuite, nous avons immédiatement envoyé un correctif afin de supprimer le mécanisme consistant à revérifier les modules. L’idée visait à éviter de casser le fonctionnement des modules pour les utilisateurs pour lesquels la validation quotidienne n’avait pas encore eu lieu. Nous avons appliqué ce correctif avant d’en avoir d’autres et nous l’avons retiré maintenant que des correctifs plus pérennes sont disponibles.</p>
<p>À l’heure actuelle, la signature des nouveaux modules fonctionne à nouveau.</p>
<h2>Travailler en parallèle</h2>
<p>En théorie, résoudre un tel problème semble plutôt simple : on crée un nouveau certificat valide puis on publie à nouveau les modules avec ce certificat.</p>
<p>Malheureusement, nous avons vite constaté que cela ne fonctionnerait pas pour plusieurs raisons :</p>
<ol>
<li>Il existe une multitude de modules (plus de 15 000) et le service utilisé pour la signature n’est pas optimisé pour signer en masse, resigner chaque module prendrait plus de temps que ce que nous voulions ;</li>
<li>Une fois les modules signés, les utilisateurs auraient dû récupérer les nouvelles versions de leurs modules. Certains de ces modules sont hébergés sur les serveurs de Mozilla et Firefox aurait mis à jour ces modules en 24 heures. Toutefois, les utilisateurs auraient dû mettre à jour manuellement les modules installés depuis d’autres sources : cela se serait avéré plus que gênant.</li>
</ol>
<p>À la place, nous nous sommes concentrés sur le développement d’un correctif que nous pourrions fournir à l’ensemble de nos utilisateurs et qu’il y ait le minimum d’intervention manuelle.</p>
<p>Après avoir étudié différentes approches, nous avons rapidement convergé vers deux stratégies principales que nous avons menées en parallèle :</p>
<ol>
<li>Corriger Firefox afin de modifier la date utilisée pour valider le certificat. Cela permettrait aux modules de fonctionner à nouveau par enchantement, mais il fallait produire et distribuer une nouvelle version de Firefox ;</li>
<li>Générer un certificat de remplacement toujours valide et, d’une certaine façon, convaincre Firefox de l’accepter plutôt que le certificat existant expiré.</li>
</ol>
<p>Nous n’étions pas certains que l’une de ces deux solutions fonctionnerait et nous avons décidé de les mener en parallèle et de déployer la première qui semblerait fonctionner. En fin de journée, nous avons fini par déployer ce deuxième correctif, un nouveau certificat de remplacement, que nous décrirons ensuite en détail.</p>
<h2>Un certificat de remplacement</h2>
<p>Comme expliqué ci-avant, cette solution se décomposait en deux étapes :</p>
<ol>
<li>Générer un nouveau certificat qui soit valide ;</li>
<li>L’installer à distance dans Firefox.</li>
</ol>
<p>Pour comprendre comment cela fonctionne, il nous faut plonger plus en détails dans la façon dont Firefox valide les modules. Le module est constitué d’un ensemble de fichier incluant la chaîne de certificat utilisée pour le signer. Ainsi, le module peut être vérifié indépendamment tant qu’on connaît le certificat racine (configuré dans Firefox lors de la compilation). Toutefois, comme nous l’avons dit, le certificat intermédiaire ayant expiré, le module ne pouvait être vérifié.</p>
<p>En réalité, lorsque Firefox tente de valider un module, il ne se limite pas à utiliser les certificats contenus dans le module. Il essaie en fait de construire une chaîne de certificats valide en commençant par le certificat d’entité final et en remontant jusqu’à la racine. L’algorithme même est compliqué, mais on peut le résumer ainsi : on commence par le certificat d’entité final et on trouve ensuite un certificat dont le sujet est égal à l’émetteur du certificat final (ici il s’agit normalement du certificat intermédiaire). Dans un scénario simple, le navigateur remonte au certificat intermédiaire, mais il pourrait tout à fait s’agir d’un certificat que le navigateur connaît. Si nous pouvons ajouter à distance un nouveau certificat, valide, Firefox pourrait vérifier ce certificat plutôt que celui qui est expiré. Le schéma qui suit illustre la situation avant et après l’installation du nouveau certificat :</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/armagaddon2/Add-on-blog-post-visual-2-May-7-2019_fr.png" title="Schéma avant/après illustrant l'ajout d'un maillon avec un certificat intermédiaire valide"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/armagaddon2/.Add-on-blog-post-visual-2-May-7-2019_fr_m.png" alt="Schéma avant/après illustrant l'ajout d'un maillon avec un certificat intermédiaire valide" style="margin: 0 auto; display: block;" /></a></p>
<p>Une fois le nouveau certificat installé, Firefox a désormais le choix entre deux certificats pour valider la chaîne de certificats : le certificat expiré (invalide) et le nouveau certificat (valide). Un élément important permet que cela fonctionne : le nouveau certificat possède le même sujet et la même clé publique que l’ancien certificat et sa signature sur le certificat d’entité final est donc valide. Heureusement, Firefox est suffisamment intelligent pour essayer chacune des pistes jusqu’à trouver un chemin qui fonctionne afin que le module soit à nouveau valide.</p>
<p>On notera ici que c’est la même logique qui est à l’œuvre pour valider les certificats <abbr lang="en" title="Transport Layer Security">TLS</abbr> : il s’agit donc d’un code bien connu que nous avons pu utiliser.<a href="https://tech.mozfr.org/post/2019/05/10/Details-techniques-sur-la-panne-des-modules-complementaires-de-Firefox#exp2"><sup>[2]</sup></a></p>
<p>Le grand avantage de cette méthode est qu’elle ne nécessite pas de modifier les modules. Tant que le nouveau certificat peut être fourni à Firefox, les modules (y compris ceux portant le certificat expiré) pourront être vérifiés automatiquement. Là où ça devient donc compliqué, c’est qu’il faut envoyer le nouveau certificat dans Firefox, automatiquement et à distance puis faire le nécessaire afin que Firefox revérifie les modules qui auraient pu être désactivés.</p>
<h2>Normandy et le système d’études</h2>
<p>Avec une certaine ironie, le véhicule utilisé pour la solution à ce problème a été un « module système » (ou <em>system add-on</em> abrégé en SAO en anglais) qui est un type de module spécial. Afin de pouvoir mener des études de recherche, nous avons développé un système intitulé Normandy qui nous permet de distribuer des modules système aux utilisateurs de Firefox. Ces modules système sont exécutés automatiquement dans le navigateur de l’utilisateur. Bien qu’ils soient généralement utilisés pour lancer des tests, ils possèdent également un accès privilégié aux API internes de Firefox. Ils peuvent notamment ajouter de nouveaux certificats à la base de données utilisée par Firefox pour vérifier les modules.<a href="https://tech.mozfr.org/post/2019/05/10/Details-techniques-sur-la-panne-des-modules-complementaires-de-Firefox#exp3"><sup>[3]</sup></a></p>
<p>Le correctif consiste donc à construire un module système qui réalise deux choses :</p>
<ol>
<li>Installer le nouveau certificat ;</li>
<li>Forcer le navigateur à revérifier chaque module afin que les modules désactivés puissent être activés à nouveau.</li>
</ol>
<p>Mais… si les modules ne fonctionnent plus, comment exécuter ce module système ? Eh bien en le signant avec le nouveau certificat !</p>
<h2>Récapitulons… pourquoi tout ce temps ?</h2>
<p>Nous avons donc un plan : émettre un nouveau certificat afin de remplacer l’ancien, construire un module système à installer sur Firefox et le déployer via Normandy. Après avoir commencé à travailler sur le sujet le 3 mai à 18 h 00 (heure du Pacifique), nous déployions le correctif via Normandy à 2 h 44 le lendemain matin (soit moins de 9 heures), 6 à 12 heures se sont ensuite écoulées avant que la plupart de nos utilisateurs en bénéficient.</p>
<p>Il s’agit d’une durée assez courte, mais nous avons vu plusieurs questions sur Twitter nous demandant pourquoi nous n’avions pas pu aller plus vite.</p>
<p>Plusieurs étapes ont été chronophages.</p>
<p>Premièrement, il a fallu un certain temps pour émettre le nouveau certificat intermédiaire. Comme indiqué ci-avant, le certificat racine est situé dans une boîte noire transactionnelle stockée hors ligne. Il s’agit d’une règle de sécurité importante : le certificat racine est rarement utilisé et on veut qu’il soit sécurisé. En revanche, ce n’est pas idéal lorsqu’on souhaite émettre un nouveau certificat en urgence. En tout cas, un de nos ingénieurs a dû conduire à l’endroit où la boîte noire était stockée. Ensuite, nous avons eu quelques faux départs où nous n’avons pas exactement émis le bon certificat, chaque tentative demandant une à deux heures de tests avant de pouvoir être certains.</p>
<p>Deuxièmement, le développement d’un module système prend du temps. Conceptuellement, c’est quelque chose de très simple mais même les programmes simples nécessitent une certaine attention et nous voulions à tout prix éviter d’empirer les choses. Puis, avant de livrer le module système, il a fallu le tester. Ces tests ont pris du temps, notamment parce qu’il fallait signer ce module et que le système de signature était désactivé : il nous a donc fallu trouver des méthodes de contournement.</p>
<p>Enfin, une fois le module système prêt à être livré, il y a toujours un temps incompressible au déploiement. Les clients Firefox contactent Normandy toutes les 6 heures et, bien entendu, de nombreux clients sont déconnectés : le correctif prendra donc un certain temps à se propager à l’ensemble de la population utilisant Firefox. À l’heure actuelle (9 mai 2019), nous pensons que la plupart des personnes ont reçu cette mise à jour et/ou la mise à jour mineure effectuée ensuite.</p>
<h2>Les dernières étapes</h2>
<p>Bien que le module système déployé avec Normandy et les études devrait corriger le problème pour la plupart des utilisateurs, il n’est pas parvenu jusqu’à tout le monde. Certains utilisateurs restent notamment affectés et pour ceux-là, une autre approche sera nécessaire :</p>
<ul>
<li>les utilisateurs ayant désactivé la télémétrie ou les études ;</li>
<li>les utilisateurs de Firefox pour Android (Fennec) qui ne possède pas de système d’étude ;</li>
<li>les utilisateurs des versions dérivant de Firefox ESR qui n’activent pas la télémétrie ;</li>
<li>les utilisateurs situés derrière des <em>proxies</em> HTTPS en « homme du milieu » : notre système d’installation de module oblige la vérification de certaines clés épinglées (<em>key pinning</em>) et les <em>proxies</em> interfèrent avec celles-ci ;</li>
<li>les utilisateurs de très anciennes versions de Firefox que ne peut pas atteindre le système d’études.</li>
</ul>
<p>En ce qui concerne ce dernier groupe, nous ne pouvons pas vraiment agir : ces utilisateurs devraient mettre à jour leur version de Firefox, car les anciennes versions sont sujettes à de graves vulnérabilités de sécurité non corrigées. Nous sommes conscients des personnes ayant conservé une ancienne version de Firefox afin de pouvoir exécuter d’anciens modules, mais la plupart fonctionne maintenant avec les nouvelles versions de Firefox.</p>
<p>Pour les autres groupes, nous avons développé un correctif pour Firefox qui installera le nouveau certificat lorsque les utilisateurs mettront à jour leur navigateur. Ce correctif a été émis avec une version mineure et la plupart pourront en bénéficier via le mécanisme de mise à jour classique (en réalité, ce devrait déjà être le cas). Si vous utilisez une version dérivée, vous devrez attendre une nouvelle mise à jour de la part du responsable de cette version dérivée.</p>
<p>Nous sommes conscients qu’aucune de ces solutions n’est parfaite. Des données relatives aux modules et aux utilisateurs ont notamment pu être perdues (par exemple avec le module <em><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1549204" title="bug 1549204">Firefox Multi-Account Containers</a></em>).</p>
<p>Nous n’avons pas été capables de développer un correctif qui aurait pu empêcher cet effet de bord et nous pensons que l’approche utilisée a été la meilleure pour nos utilisateurs à court terme. Sur le long terme, nous étudierons d’autres approches architecturales afin de gérer ce type de problème.</p>
<h2>Ce que nous avons appris</h2>
<p>Pour commencer, je souhaiterais féliciter l’équipe qui a accompli un travail extraordinaire : ils ont construit et déployé un correctif en moins de 12 heures après la détection du problème. Pour avoir assisté à ce travail, je peux dire que ces personnes ont travaillé d’arrache-pied, dans un contexte difficile et que peu de secondes ont été gaspillées.</p>
<p>Ceci étant dit, il est évident que cette situation est loin d’être idéale et n’aurait pas dû se produire pour commencer. Nous devons ajuster nos procédures pour réduire la probabilité de tels incidents et aussi pour les corriger plus facilement. Nous réaliserons une analyse rétrospective la semaine prochaine et publierons les modifications que nous souhaitons apporter. En attendant, voici mes premières réflexions sur ce que nous devons faire. Pour commencer, nous devons avoir une meilleure méthode pour vérifier le statut de chaque élément temporel contenu dans Firefox, sans quoi, cela risque d’exploser à tout moment : nous ne devons pas être pris par surprise. Nous travaillons encore là-dessus, mais il nous faut au moins inventorier tous les éléments de cette nature.</p>
<p>Ensuite, il nous faut un mécanisme qui permette de rapidement diffuser des mises à jour à nos utilisateurs lorsque tout le reste est cassé (surtout quand tout le reste est cassé). Nous avons pu tirer parti du système des études, mais il s’agissait d’un outil imparfait que nous avons mobilisé et qui a entraîné certains effets de bord indésirables.</p>
<p>Nous savons notamment que de nombreux utilisateurs activent les mises à jour automatiques mais préfèrent ne pas participer aux études : ce choix est respectable (pour être honnête, je l’avais également désactivé). En même temps, nous devons être capables de diffuser des mises à jour à nos utilisateurs quel que soit le véhicule technique. Les utilisateurs devraient pouvoir activer les mises à jour (y compris les correctifs d’urgence) et pouvoir désactiver tout le reste.</p>
<p>De plus, le canal de mise à jour doit être plus réactif. Nous avons eu lundi des utilisateurs qui n’avaient pas encore récupéré le correctif ou la version mineure : c’est loin d’être idéal. Certains travaux sont et étaient déjà en cours à cet égard mais cet incident montre combien cet effort est important.</p>
<p>Enfin, nous étudierons plus généralement l’architecture relative à la sécurité des modules pour nous assurer qu’un niveau de sécurité suffisant est respecté tout en minimisant les risques de panne.</p>
<p>Nous donnerons suite la semaine prochaine avec les résultats d’une analyse rétrospective plus poussée mais en attendant, je serai ravi de répondre à vos questions (en anglais) à ekr-blog[AD]mozilla[POINT]com.</p>
<p>[1]<a name="exp1"></a> Quelques modules très anciens étaient signés avec un certificat intermédiaire différent.</p>
<p>[2]<a name="exp2"></a> Les personnes familières avec WebPKI reconnaîtront la méthode également utilisée pour la certification croisée.</p>
<p>[3]<a name="exp3"></a> Note technique : nous n’ajoutons pas un certificat avec un privilège quelconque. L’autorité de ce certificat provient de la racine avec laquelle il a été signé. Nous ajoutons uniquement un nouveau certificat intermédiaire à l’ensemble de certificats qui peuvent être utilisés dans Firefox. Autrement dit, nous n’ajoutons pas un nouveau certificat, privilégié d’une quelconque façon, dans Firefox.</p>
Retrait de la confiance envers les certificats TLS de Symantec
urn:md5:c41e9a6fd1b04cd2394bd63485da383f
2018-08-19T01:40:00+02:00
2018-08-25T12:55:09+02:00
Mozinet
Sécurité
Apple
Firefox
Google
Mozilla
Nightly
<div style="float:right; margin: 0 0 1em 1em; width: 364px;"><a href="https://tech.mozfr.org/dotclear/public/secu/CA/symantec_ca_chrome_canary_firefox_nightly.png.png" title="Sites avec certificat émis par Symantec dans Chrome Canary à gauche et Firefox Nightly à droite"><img alt="Sites avec certificat émis par Symantec dans Chrome Canary à gauche et Firefox Nightly à droite" src="https://tech.mozfr.org/dotclear/public/secu/CA/symantec_ca_chrome_canary_firefox_nightly.png.png" style="width: 360px;" title="Sites avec certificat émis par Symantec dans Chrome Canary à gauche et Firefox Nightly à droite" /></a><em style="display: block; text-align:center; font-size: 0.9em;">Sites avec certificat émis par Symantec<br />
dans Chrome Canary à gauche<br />
et Firefox Nightly à droite</em></div>
<p>La plupart des grands éditeurs de navigateurs ont annoncé des plans, qui arrivent rapidement à échéance, pour retirer la confiance accordée aux certificats émis par Symantec. Les admins de sites web dont la navigation sécurisée (HTTPS) repose sur de tels certificats doivent en changer rapidement, faute de quoi leurs sites ne vont rapidement plus s’afficher dans la plupart des navigateurs. La communauté Mozilla francophone a traduit le <a href="https://blog.mozilla.org/security/2018/07/30/update-on-the-distrust-of-symantec-tls-certificates/" hreflang="en" title="Update on the Distrust of Symantec TLS Certificates (30 juil. 2018) Wayne Thayer, Mozilla Security Blog">billet du blog Sécurité</a> de Mozilla expliquant l’urgence de cette action.</p>
<p> </p>
<p>Les utilisateurs et utilisatrices de <a href="https://nightly.mozfr.org/" hreflang="fr" title="Téléchargez Firefox Nightly en français">Firefox Nightly</a> peuvent <a href="https://blog.nightly.mozilla.org/2018/08/14/symantec-distrust-in-firefox-nightly-63/" hreflang="en" title="Symantec Distrust in Firefox Nightly 63 (14 août 2018) Pascal Chevrel, Firefox Nightly News">aider à la transition</a> en contactant les canaux de communication ou d’assistance des sites qui seront affectés par ces retraits de confiance.</p> <hr style="clear: right;" />
<h3>Mise à jour sur le retrait de la confiance envers les certificats TLS de Symantec</h3>
<p>Depuis <a href="https://www.mozilla.org/en-US/firefox/60.0/releasenotes/" hreflang="en" title="Firefox 60.0, See All New Features, Updates and Fixes (9 mai 2018)">la version 60</a>, Firefox affiche une erreur « <a href="https://support.mozilla.org/fr/kb/que-signifie-votre-connexion-nest-pas-securisee" hreflang="fr" title="Que signifie : « Votre connexion n'est pas sécurisée » ? – Assistance de Firefox">connexion non sécurisée</a> » pour tout site utilisant un certificat <abbr title="Transport Layer Security">TLS</abbr>/<abbr title="Secure Sockets Layer"> SSL</abbr> émis avant le 1<sup style="font-size: 60%;">er</sup> juin 2016 dont la chaîne de confiance remonte à un certificat racine de Symantec. Cela fait suite à une <a href="https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/eUAKwjihhBs%5B251-275%5D" hreflang="en" title="Intent to Deprecate and Remove: Trust in existing Symantec-issued Certificates in blink-dev via Google Groupes">proposition de consensus</a>, adoptée en 2017 par Mozilla, de retirer sa confiance aux certificats TLS de Symantec. Cette proposition a également été adoptée par l’équipe de Google Chrome et, plus récemment, <a href="https://support.apple.com/fr-fr/HT208860" hreflang="fr" title="Informations pour les opérateurs de site Web concernant la méfiance à l’égard des autorités de certification Symantec – Assistance Apple">Apple a annoncé son intention</a> de ne plus accorder sa confiance aux certificats TLS de Symantec. Comme <a href="https://blog.mozilla.org/security/2017/10/31/statement-digicerts-proposed-purchase-symantec/" hreflang="en" title="Statement on DigiCert’s Proposed Purchase of Symantec’s CA (31 oct. 2017) Gervase Markham, Mozilla Security Blog">annoncé précédemment</a>, l’acquisition par DigiCert de l’autorité de certification de Symantec ne change rien à ces plans.</p>
<p>Début mars, quand <a href="https://blog.mozilla.org/security/2018/03/12/distrust-symantec-tls-certificates/" hreflang="en" title="Distrust of Symantec TLS Certificates (12 mars 2018) Kathleen Wilson, Mozilla Security Blog">nous avons écrit sur ce sujet</a>, environ 1 % des sites web ne fonctionnaient plus sous Firefox à cause du changement décrit ci-dessus. Juste avant la sortie de Firefox 60 le 9 mai 2018, moins de 0,15 % des sites étaient touchés – une amélioration majeure en l’espace de seulement quelques mois.</p>
<p>La prochaine phase du plan de consensus consiste à ne plus faire confiance à aucun des certificats TLS remontant à une racine Symantec, quelle que soit la date à laquelle il a été émis (notez qu’il y a une petite <a href="https://chromium.googlesource.com/chromium/src/+/master/net/data/ssl/symantec" hreflang="en" title="net/data/ssl/symantec – chromium/src – Git at Google">exception</a> pour les certificats TLS émis par un petit nombre de certificats intermédiaires gérés par certaines entreprises, et cette phase ne concerne pas les certificats <abbr lang="en" title="Secure/Multipurpose Internet Mail Extensions">S/MIME</abbr>). Ce <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1460062" hreflang="en" title="Bugzilla@Mozilla : Bug 1460062 – Enforce Symantec distrust in Firefox 63 (RESOLVED FIXED)">changement</a> est planifié pour Firefox 63, dont les <a href="https://wiki.mozilla.org/Release_Management/Calendar" hreflang="en" title="Firefox Release Calendar – Mozilla Wiki">dates de sortie</a> prévues sont :</p>
<ul>
<li>bêta – 5 septembre</li>
<li>version finale – 23 octobre</li>
</ul>
<p>Nous avons commencé à mesurer l’impact du changement à venir sur Firefox 63. Nous avons constaté que 3,5 % des <a href="https://s3-us-west-1.amazonaws.com/umbrella-static/index.html" hreflang="en" title="Cisco Popularity List">1 million des sites web les plus populaires</a> utilisent toujours des certificats Symantec qui vont être marqués comme indignes de confiance en septembre et octobre (et même avant pour <a href="https://wiki.mozilla.org/Nightly" hreflang="en" title="Nightly – Mozilla Wiki">Firefox Nightly</a>) ! Ce nombre représente un impact considérable sur les utilisatrices et utilisateurs de Firefox, mais il a diminué de plus de 20 % dans les deux derniers mois, et alors que la sortie de Firefox 63 approche, nous prévoyons un rythme d’amélioration aussi rapide que celui observé avec la sortie de Firefox 60.</p>
<p>Nous encourageons fortement les administrateurs et administratrices de sites web à remplacer immédiatement tout certificat TLS Symantec restant afin d’éviter que leurs utilisatrices et utilisateurs soient touché⋅e⋅s quand ces certificats deviendront indignes de confiance pour Firefox Nightly et bêta dans les prochains mois. Ce changement à venir peut d’ores et déjà être testé avec <a href="https://wiki.mozilla.org/Nightly" hreflang="en" title="Nightly – Mozilla Wiki">Firefox Nightly</a>. ■</p>
<h3>Aller plus loin</h3>
<p>Téléchargez Firefox en version :</p>
<ul>
<li><a href="https://www.mozilla.org/fr/firefox/" hreflang="fr" title="Le nouveau navigateur rapide, pour Mac, PC et Linux – Mozilla Firefox">finale</a></li>
<li><a href="https://www.mozilla.org/fr/firefox/channel/desktop/#beta" hreflang="fr" title="Firefox bêta – Essayez les dernières fonctionnalités du navigateur grâce aux préversions – Mozilla Firefox">bêta</a></li>
<li><a href="https://www.mozilla.org/fr/firefox/channel/desktop/#nightly" hreflang="fr" title="Firefox Nightly – Essayez les dernières fonctionnalités du navigateur grâce aux préversions – Mozilla Firefox">Nightly</a></li>
</ul>
<p><a href="https://blog.mozfr.org/pages/Calendrier-versions-Firefox" hreflang="fr" title="Calendrier des versions de Firefox – Communauté Mozilla francophone">Calendrier des versions de Firefox</a></p>
<address><br />
Traduction et relecture : Watilin, Nerovart, <a href="https://twitter.com/Mozinet" hreflang="fr" title="Mozinet (@Mozinet) sur Twitter">Mozinet</a>, Alpha, <a href="https://twitter.com/t_chevalier" hreflang="fr" title="Théo Chevalier (@t_chevalier) sur Twitter">Théo</a>, Goofy et anonymes</address>
Une plongée illustrée dans les modules ECMAScript
urn:md5:bd76f81d14a21c270a6f686781001bb5
2018-04-09T07:45:00+02:00
2018-04-09T07:04:35+02:00
sphinx
JavaScript
CommonJS
ECMAScript
Hacks
import
JavaScript
Module
Node.JS
require
<h2><em>Cet article est une traduction de <a href="https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/">ES modules: A cartoon deep-dive</a>, écrit par <a href="http://twitter.com/linclark">Lin Clark</a>. Merci à goofy pour la relecture !</em></h2>
<p>Les modules ECMAScript (ou modules ES) sont un système de modules, standard et officiel, pour JavaScript. Ce système est le fruit d’un travail de standardisation qui a duré 10 ans.</p>
<p>Nous pourrons bientôt profiter de tout cela avec l’arrivée des modules dans Firefox 60 en mai (<a href="https://www.mozilla.org/firefox/developer/">cette version est actuellement en bêta</a>). Les navigateurs principaux prennent tous en charge cette fonctionnalité et le groupe de travail pour les modules Node contribue à l’ajout des modules ES dans <a href="https://nodejs.org">Node.js</a>. <a href="https://www.youtube.com/watch?v=qR_b5gajwug">L’intégration des modules ES dans WebAssembly</a> a également démarré.</p>
<p>De nombreux développeurs JavaScript savent que les modules ES ont été à l’origine de différentes controverses mais peu comprennent réellement le fonctionnement des modules ES.</p>
<p>Voyons ici les problèmes résolus par les modules ES et leurs différences avec les autres systèmes de modules.</p>
<h2>Les problèmes résolus par les modules</h2>
<p>Quand on y pense, coder en JavaScript revient principalement à gérer des variables. On leur affecte des valeurs, on leur ajoute des nombres, on combine deux variables pour construire une nouvelle variable.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/01_variables.png" title="Code illustrant la manipulation de variables"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.01_variables_m.png" style="margin: 0 auto; display: block;" title="Code illustrant la manipulation de variables"/></a></p>
<p>Étant donné la proportion de code consacrée à la modification des variables, l’organisation de ces variables aura un impact non-négligeable sur la qualité du code… mais aussi sur la facilité à le maintenir.</p>
<p>S’occuper d’un nombre de variables limité permet de simplifier les choses. En JavaScript, on peut s’aider des portées (<em>scope</em>) pour limiter le périmètre des variables qu’on doit gérer. En JavaScript, les portées formées par les fonctions empêchent ces dernières d’accéder à des variables qui seraient définies dans d’autres fonctions.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/02_module_scope_01.png" title="Deux portées de fonction qui essaient d'accéder au contenu de l'autre"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.02_module_scope_01_m.png" style="margin: 0 auto; display: block;" title="Deux portées de fonction qui essaient d'accéder au contenu de l'autre"/></a></p>
<p>C’est une bonne chose. Cela signifie que lorsqu’on travaille sur une fonction, on peut ne penser qu’à cette fonction. Il n’est pas nécessaire de s’encombrer l’esprit avec l’impact potentiel des autres fonctions sur les variables de la fonction courante.</p>
<p>Toutefois, cela a un prix : il est plus difficile de partager des variables entre différentes fonctions.</p>
<p>Comment partager une variable en dehors de sa portée ? Pour répondre au problème, on utilise généralement une portée plus grande… comme la portée globale.</p>
<p>Peut-être vous souvenez vous de l’époque où jQuery était utilisé. Avant de pouvoir utiliser un <em>plugin</em> jQuery, il fallait s’assurer que jQuery faisait bien partie de la portée globale.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/02_module_scope_02.png" title="Deux portées de fonction au sein d'une portée globale qui contient jQuery"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.02_module_scope_02_m.png" style="margin: 0 auto; display: block;" title="Deux portées de fonction au sein d'une portée globale qui contient jQuery"/></a></p>
<p>On a répondu à ce problème de partage des variables mais cela n’est pas sans conséquence.</p>
<p>Tout d’abord, il faut que les balises de script soient dans le bon ordre. Ensuite, il faut s’assurer que personne ne vienne chambouler cet ordre.</p>
<p>Si cet ordre vient à être chamboulé, l’application pourra causer une belle erreur au plein milieu de l’exécution. Lorsque la fonction qui va chercher jQuery où elle s’attend à l’avoir (c’est-à-dire dans la portée globale) et ne le trouve pas, elle déclenchera une erreur et arrêtera son exécution.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/02_module_scope_03.png" title="La portée de la première fonction a été supprimée et la portée de la seconde fonction ne peut pas trouver jQuery dans la portée globale"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.02_module_scope_03_m.png" style="margin: 0 auto; display: block;" title="La portée de la première fonction a été supprimée et la portée de la seconde fonction ne peut pas trouver jQuery dans la portée globale"/></a></p>
<p>La maintenance du code devient plus épineuse. Retirer du vieux code ou des balises <code><script></code> s’apparente à un pari risqué. Personne ne peut savoir ce qui va être cassé. Les dépendances entre les différentes parties du code sont implicites. N’importe quelle fonction peut manipuler n’importe quoi appartenant à la portée globale et on ne sait plus de quels scripts dépendent ces fonctions.</p>
<p>Ce n’est pas tout, les variables qui sont dans la portée globale peuvent très bien être modifiées par n’importe quel code situé au sein de cette portée. Du code malveillant pourrait modifier cette variable globale à dessein afin de détourner l’exécution. Du code <em>a priori</em> innocent pourrait également, accidentellement, modifier la variable.</p>
<h2>Qu’apportent les modules ?</h2>
<p>Les modules permettent de mieux organiser les variables et les fonctions. Avec les modules, on peut regrouper les variables et les fonctions de façon pertinente.</p>
<p>Ainsi, ces fonctions et variables font partie de la portée d’un module et cette portée peut être utilisée afin de partager des variables entre les fonctions du module.</p>
<p>Cependant, à la différence des portées créées par les fonctions, les portées des modules permettent de rendre leurs variables disponibles pour d’autres modules. On peut indiquer, explicitement, les variables, les classes ou les fonctions qui seront disponibles pour être utilisées ailleurs.</p>
<p>Quelque chose qui est rendu disponible pour un autre module est appelé un <strong>export</strong>. Lorsqu’on dispose d’un export, les autres modules peuvent, de façon explicite, indiquer qu’ils dépendent de cette variable, classe ou fonction.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/02_module_scope_04.png" title="Deux portées de modules avec la deuxième utilisant un export du premier"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.02_module_scope_04_m.png" style="margin: 0 auto; display: block;" title="Deux portées de modules avec la deuxième utilisant un export du premier"/></a></p>
<p>Cette relation étant explicite, on est cette fois en mesure de déterminer ce qui va casser si on retire un des modules.</p>
<p>Une fois capable d’exporter et d’importer des variables entre les modules, on peut diviser le code en petits fragments qui peuvent fonctionner de façon indépendante. On peut alors combiner et recombiner ces fragments, tels des briques de Lego, afin de créer différentes applications à partir d’un même ensemble de modules.</p>
<p>L’organisation en module apportant de nombreux bénéfices, plusieurs tentatives ont été lancées afin d’incorporer ces modules dans JavaScript. Il existe aujourd’hui deux systèmes de module activement utilisés :
* CommonJS (CJS), utilisé par Node.js jusqu’à présent.
* ESM (<em>ECMAScript modules</em>), un système plus récent qui a été ajouté à la spécification JavaScript.</p>
<p>Les navigateurs prennent déjà en charge les modules ES et Node travaille à leur implémentation.</p>
<p>Voyons ensuite le détail du fonctionnement de ce nouveau système de modules.</p>
<h2>Le fonctionnement des modules ES</h2>
<p>Lorsqu’on développe en utilisant des modules, on construit un graphe de dépendances. Les liens entre ces dépendances proviennent des instructions <code>import</code> utilisées.</p>
<p>Ces instructions <code>import</code> indiquent, au moteur du navigateur ou à celui de Node, les fragments de code à charger. Il suffit de donner le nom d’un fichier comme point d’entrée pour le graphe puis le moteur suivra chacune des instructions <code>import</code> afin de récupérer l’ensemble du code nécessaire.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/04_import_graph.png" title="Un module avec deux dépendances. Le module du haut est le point d'entrée et les deux autres sont liés par la présence d'instructions import"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.04_import_graph_m.png" style="margin: 0 auto; display: block;" title="Un module avec deux dépendances. Le module du haut est le point d'entrée et les deux autres sont liés par la présence d'instructions import"/></a></p>
<p>Mais le navigateur ne peut pas utiliser les fichiers directement. Il faut qu’il analyse ces fichiers afin de les transformer en structures de données appelées <em>Module Records</em>. Ainsi, il peut savoir ce qui se passe dans chaque fichier.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/05_module_record.png" title="Un module record avec différents champs et notamment RequestedModules et ImportEntries"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.05_module_record_m.png" style="margin: 0 auto; display: block;" title="Un module record avec différents champs et notamment RequestedModules et ImportEntries"/></a></p>
<p>Après cette analyse, le <em>module record</em> doit être converti en une <em>instance de module</em>. Une instance représente deux choses : le code et un état.</p>
<p>Le code est, pour résumer, un ensemble d’instructions (à la manière d’une recette). Mais le code seul ne permet pas de faire grand-chose, il lui faut de la matière première à traiter avec ces instructions.</p>
<p>L’état est cette matière première. L’état permet de stocker les valeurs des variables à un instant donné. Quant aux variables, ce ne sont en réalité que des alias pour les espaces mémoire qui contiennent ces valeurs.</p>
<p>Reformulons : l’instance d’un module combine le code (la liste des instructions) avec un état (l’ensemble des valeurs des variables).</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/06_module_instance.png" title="Une instance de module combinant du code et un état"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.06_module_instance_m.png" style="margin: 0 auto; display: block;" title="Une instance de module combinant du code et un état"/></a></p>
<p>Il nous faut donc une instance de module pour chaque module. Le processus de chargement d’un module part du point d’entrée fourni par le fichier jusqu’à avoir un graphe complet avec l’ensemble des instances de module nécessaires.</p>
<p>Pour les modules ES, ce processus se décompose en trois étapes :</p>
<ol>
<li>Construction — le moteur trouve, télécharge et analyse l’ensemble des fichiers pour le convertir en <em>module records</em>.</li>
<li>Instanciation — le moteur détermine les zones mémoire dans lesquelles il va placer les valeurs exportées (il ne remplit pas ces zones pour l’instant). Ainsi, les exports et les imports peuvent pointer vers ces zones mémoire. C’est ce qu’on appelle aussi l’édition de lien ou <em>linking</em>.</li>
<li>Évaluation — le code est exécuté afin de remplir ces espaces mémoire avec les valeurs réelles des variables.</li>
</ol>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/07_3_phases.png" title="Une illustration des trois phases : la construction qui part d'un fichier JS afin de construire plusieurs module records ; l'instanciation qui relie ces records et l'évaluation qui exécute le code."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.07_3_phases_m.png" style="margin: 0 auto; display: block;" title="Une illustration des trois phases : la construction qui part d'un fichier JS afin de construire plusieurs module records ; l'instanciation qui relie ces records et l'évaluation qui exécute le code."/></a></p>
<p>On peut entendre dire que les modules ES sont <em>asynchrones</em>. En fait, cette asynchronicité vient du fait que chacune de ces trois étapes peut être exécutée séparément.</p>
<p>Cela signifie que la spécification introduit un niveau d’asynchronicité qui n’existait pas avec les modules CommonJS. Nous y reviendrons ensuite, mais il faut retenir qu’avec CJS, un module et ses dépendances sont chargées, instanciées et évaluées d’un coup, sans interruption entre ces étapes.</p>
<p>Ceci étant dit, les étapes individuelles ne sont pas nécessairement asynchrones et elles peuvent isolément être exécutées de façon synchrone. Cela dépend du chargement. En fait, la spécification pour les modules ES ne décrit pas l’intégralité du processus. Celui-ci se découpe en deux moitiés dont chacune est décrite par une spécification différente.</p>
<p><a href="https://tc39.github.io/ecma262/#sec-modules">La spécification pour les modules ES</a> indique comment analyser les fichiers pour les convertir en <em>module records</em> et comment instancier puis évaluer le module. Toutefois, elle ne dit rien de la façon dont on doit récupérer les fichiers pour commencer.</p>
<p>C’est au chargeur (<em>loader</em>) de récupérer les fichiers. Or, le comportement de ce chargeur est décrit dans une autre spécification. Pour les navigateurs, la spécification utilisée est <a href="https://html.spec.whatwg.org/#fetch-a-module-script-tree">la spécification HTML</a>. Pour d’autres plates-formes, on pourrait tout à fait avoir d’autres chargeurs qui devraient également être spécifiés.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/07_loader_vs_es.png" title="Deux personnages : celui de gauche représente la spécification qui définit comment charger des modules (c'est-à-dire la spécification HTML) et celui de droite représente la spécification des modules ES."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.07_loader_vs_es_m.png" style="margin: 0 auto; display: block;" title="Deux personnages : celui de gauche représente la spécification qui définit comment charger des modules (c'est-à-dire la spécification HTML) et celui de droite représente la spécification des modules ES."/></a></p>
<p>Le chargeur contrôle exactement la façon dont les modules sont chargés. Il appelle les méthodes associées aux modules ES : <code> ParseModule</code>, <code>Module.Instantiate</code> et <code>Module.Evaluate</code>. On pourrait le comparer à un marionnettiste qui tire les ficelles du moteur JavaScript.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/08_loader_as_puppeteer.png" title="Le personnage du chargeur agit comme un marionnettiste tirant les ficelles du personnage représentant la spécification des modules ES."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.08_loader_as_puppeteer_m.png" style="margin: 0 auto; display: block;" title="Le personnage du chargeur agit comme un marionnettiste tirant les ficelles du personnage représentant la spécification des modules ES."/></a></p>
<p>Étudions maintenant en détail chacune de ces trois étapes.</p>
<h3>Construction</h3>
<p>La phase de construction se décompose en trois sous-étapes :</p>
<ol>
<li>Déterminer où télécharger le fichier contenant le module (aussi appelée la résolution)</li>
<li>Récupérer le fichier (en le téléchargeant via une URL ou en le chargeant depuis le système de fichier)</li>
<li>Analyser le fichier et le convertir en un <em>module record</em></li>
</ol>
<h4>Trouver et récupérer le fichier</h4>
<p>C’est le chargeur qui va déterminer l’emplacement du fichier et le télécharger. Pour commencer, il lui faut un point d’entrée. En HTML, ce point d’entrée est fourni par le document indiqué via un élément <code><script></code>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/08_script_entry.png" title="Un élément \<script\> avec un attribut type=module et un attribut src contenant une URL. Cette URL pointe vers le fichier qui est le point d'entrée"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.08_script_entry_m.png" style="margin: 0 auto; display: block;" title="Un élément \<script\> avec un attribut type=module et un attribut src contenant une URL. Cette URL pointe vers le fichier qui est le point d'entrée"/></a></p>
<p>Mais comment faire pour trouver les modules suivants, ceux dont dépend <code>main.js</code> ?</p>
<p>C’est ici qu’intervient l’instruction <code>import</code>. Une partie de cette instruction est l’indicateur du module (<em>module specifier</em>). Cette partie permet au chargeur de connaître l’emplacement du prochain module.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/09_module_specifier.png" title="Une instruction import avec une URL en fin de ligne qui est identifiée comme indicateur du module"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.09_module_specifier_m.png" style="margin: 0 auto; display: block;" title="Une instruction import avec une URL en fin de ligne qui est identifiée comme indicateur du module"/></a></p>
<p>On notera ici qu’il y a une différence entre Node et les navigateurs pour ces indicateurs. Chaque environnement hôte dispose de sa propre méthode pour interpréter la chaîne de caractères fournissant l’indicateur. Pour cela, chaque plateforme utilise un algorithme de résolution qui lui est propre. À l’heure actuelle, certains indicateurs de module qui fonctionnent avec Node ne fonctionneront pas dans le navigateur (<a href="https://github.com/domenic/package-name-maps">des pistes sont à l’étude pour résoudre ce problème</a>).</p>
<p>Jusqu’à ce que ce problème soit résolu, les navigateurs acceptent uniquement des URL comme indicateurs de module. C’est le fichier vers lequel pointe cette URL qui sera chargé.</p>
<p>Toutefois, la récupération des différents modules ne se fait pas simultanément. On ne peut pas savoir les dépendances dont on a besoin tant qu’on a pas analysé le contenu du module et on ne peut pas analyser le fichier tant qu’on ne l’a pas récupéré.</p>
<p>Cela signifie qu’il faut progresser niveau par niveau : analyser le premier fichier, déterminer ses dépendances, trouver ces dépendances, les charger et ainsi de suite.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/10_construction.png" title="Un diagramme qui illustre un fichier récupéré puis analysé puis deux dépendances récupérées et analysées"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.10_construction_m.png" style="margin: 0 auto; display: block;" title="Un diagramme qui illustre un fichier récupéré puis analysé puis deux dépendances récupérées et analysées"/></a></p>
<p>S’il fallait que le <em>thread</em> principal patiente le temps que chacun de ces fichiers soit téléchargé, de nombreuses tâches s’ajouteraient à la queue.</p>
<p>En effet, lorsqu’on travaille dans un navigateur, le téléchargement dure longtemps.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/11_latency.png" title="Un graphe illustrant les durées d'accès et de téléchargement si un cycle processeur durait 1 seconde : l'accès à la mémoire principale prendrait 6 minutes et la récupération d'un fichier provenant d'un serveur à l'autre extrémité des États-Unis prendrait 4 ans."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.11_latency_m.png" style="margin: 0 auto; display: block;" title="Un graphe illustrant les durées d'accès et de téléchargement si un cycle processeur durait 1 seconde : l'accès à la mémoire principale prendrait 6 minutes et la récupération d'un fichier provenant d'un serveur à l'autre extrémité des États-Unis prendrait 4 ans."/></a></p>
<p><em>Illustration basée sur <a href="https://twitter.com/srigi/status/917998817051541504">ce graphique</a>.</em></p>
<p>Bloquer le <em>thread</em> principal de cette façon ralentirait considérablement une application qui utilise les modules. C’est pour cette raison que la spécification pour les modules ES découpe l’algorithme en plusieurs phases. Placer la construction à part permet aux navigateurs de récupérer les fichiers et de construire le graphe des modules avant de s’attaquer à la tâche, synchrone, d’instanciation.</p>
<p>Cette approche (de découpe d’algorithme) est une des différences fondamentales entre les modules ES et les modules CommonJS.</p>
<p>CommonJS peut gérer les choses différemment, car les fichiers proviennent du système de fichier et l’accès à ces ressources prend moins de temps que de les télécharger via Internet. Cela signifie que Node peut bloquer le <em>thread</em> principal pendant qu’il charge le fichier. Lorsque le fichier est chargé, on peut directement l’instancier et l’évaluer (encore une fois, ces phases ne sont pas distinctes avec CommonJS). Cela signifie également qu’on parcourt tout l’arbre des dépendances, qu’on les charge, qu’on les instancie et qu’on les évalue avant de renvoyer l’instance du module.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/12_cjs_require.png" title="Un diagramme illustrant l'évaluation d'un module Node jusqu'à l'instruction require puis suivant le chargement synchrone et l'évaluation du module et de ses dépendances"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.12_cjs_require_m.png" style="margin: 0 auto; display: block;" title="Un diagramme illustrant l'évaluation d'un module Node jusqu'à l'instruction require puis suivant le chargement synchrone et l'évaluation du module et de ses dépendances"/></a></p>
<p>L’approche adoptée par CommonJS implique différentes choses que nous verrons ensuite. Entre autres, cela signifie qu’avec Node et les modules CommonJS, on peut utiliser des variables dans l’indicateur du module. On exécute l’ensemble du code du module (y compris l’instruction <code>require</code>) avant d’aller consulter le prochain module. Ainsi, la variable utilisée aura une valeur lorsqu’on passera à la résolution de module.</p>
<p>En revanche, avec les modules ES, tout le graphe des dépendances doit être construit avant l’évaluation. Aussi, on ne peut pas avoir de variable au sein des identificateurs, car ces variables n’ont pas encore de valeurs.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/13_static_import.png" title="Une instruction require utilisant une variable ne pose pas de problème. Par contre, on ne peut pas utiliser de variable dans une instruction import."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.13_static_import_m.png" style="margin: 0 auto; display: block;" title="Une instruction require utilisant une variable ne pose pas de problème. Par contre, on ne peut pas utiliser de variable dans une instruction import."/></a></p>
<p>Cependant, il est parfois utile d’utiliser des variables pour les chemins des modules. On peut, par exemple, vouloir charger un module ou un autre en fonction de l’état du code ou de l’environnement dans lequel on exécute le code.</p>
<p>Une proposition, <a href="https://github.com/tc39/proposal-dynamic-import"><em>dynamic import</em></a>, vise à rendre cela possible et permettrait d’écrire une instruction <code>import</code> de la forme</p>
<pre><code>import(`${chemin}/toto.js`).
</code></pre>
<p>Pour cela, chaque fichier chargé grâce à <code>import()</code> est considéré comme un point d’entrée pour un autre graphe. Le module importé dynamiquement devient ainsi une racine d’un nouveau graphe, traité séparément.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/14dynamic_import_graph.png" title="Les graphes de deux modules avec une dépendance entre eux, étiquetée avec une instruction import dynamique."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.14dynamic_import_graph_m.png" style="margin: 0 auto; display: block;" title="Les graphes de deux modules avec une dépendance entre eux, étiquetée avec une instruction import dynamique."/></a></p>
<p>Pour un graphe donné, on aura un cache contenant les instances de ces modules. Aussi, si un module A est présent dans deux graphes, on aura deux caches avec deux instances. Pour chaque module d’une portée globale, il n’y aura qu’une seule instance du module.</p>
<p>Charger une seule instance pour chaque module minimise le travail du moteur JavaScript : le module n’est récupéré qu’une seule fois même lorsque plusieurs modules dépendent de ce premier (une des raisons pour la mise en cache des modules, nous en verrons une autre dans le détail consacré à la phase d’évaluation).</p>
<p>Le chargeur gère ce cache avec ce qu’on appelle <a href="https://html.spec.whatwg.org/multipage/webappapis.html#module-map">la carte des modules (<em>module map</em>)</a>. Chaque portée globale maintient le registre de ses modules dans une carte distincte.</p>
<p>Lorsque le chargeur récupère une URL, il place cette URL dans la carte des modules et note qu’il est en train de récupérer le fichier. Il envoie ensuite la requête puis démarre la récupération du prochain fichier.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/15_module_map.png" title="Le personnage du chargeur qui renseigne le tableau de la carte des modules avec l'URL et avec le mot "fetching" pour indiquer qu'il la récupère."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.15_module_map_m.png" style="margin: 0 auto; display: block;" title="Le personnage du chargeur qui renseigne le tableau de la carte des modules avec l'URL et avec le mot "fetching" pour indiquer qu'il la récupère."/></a></p>
<p>Que se passe-t-il lorsqu’un autre module dépend du même fichier ? Le chargeur vérifiera la présence de l’URL dans la carte. Si celle-ci est déjà en cours de récupération, il passera à l’URL suivante.</p>
<p>La carte des modules ne sert pas uniquement à indiquer les modules qui sont en train d’être récupérés, elle joue également le rôle de cache pour les modules comme nous le verrons par la suite.</p>
<h4>L’analyse (<em>parsing</em>)</h4>
<p>Nous avons désormais récupéré ce fichier et il faut le convertir en <em>module record</em> afin que le navigateur comprenne les différentes partie du module.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/25_file_to_module_record.png" title="Un diagramme illustrant l'analyse du fichier main.js pour le convertir en module record"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.25_file_to_module_record_m.png" style="margin: 0 auto; display: block;" title="Un diagramme illustrant l'analyse du fichier main.js pour le convertir en module record"/></a></p>
<p>Une fois le <em>module record</em> créé, il est placé dans la carte des modules. De cette façon, dès que le module est demandé, le chargeur peut le récupérer directement depuis la carte.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/25_module_map.png" title="Les cellules de la carte des modules, auparavant marquées avec "fetching", sont progressivement remplies avec des module records"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.25_module_map_m.png" style="margin: 0 auto; display: block;" title="Les cellules de la carte des modules, auparavant marquées avec "fetching", sont progressivement remplies avec des module records"/></a></p>
<p>Précisons un léger détail qui n’est peut-être pas si léger. Tous les modules sont analysés comme s’ils utilisaient <code>"use strict"</code> au début. D’autres différences (par rapport à l’analyse des scripts « classiques ») sont également présentes : le mot-clé <code>await</code> est réservé au niveau le plus haut d’un module ; la valeur de <code>this</code> vaut <code>undefined</code>.</p>
<p>Cette différence d’analyse est aussi appelée <em>parse goal</em>. Si on analyse le même fichier pour un autre usage, on pourrait obtenir un résultat différent. Aussi, avant l’analyse, il est nécessaire de savoir si on analyse un module ou non.</p>
<p>Pour les navigateurs, c’est plutôt facile. Il suffit de placer l’attribut <code>type="module"</code> dans la balise <code>script</code>. Le navigateur pourra alors savoir que le fichier doit être analysé comme un module. Au sein des scripts, seuls les modules peuvent être importés, le navigateur sait donc que tout ce qui est importé doit être analysé comme un module.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/26_parse_goal.png" title="Le navigateur détermine que main.js est un module, car la valeur de l'attribut type de la balise script l'indique. Pour counter.js, c'est aussi un module car il est importé"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.26_parse_goal_m.png" style="margin: 0 auto; display: block;" title="Le navigateur détermine que main.js est un module, car la valeur de l'attribut type de la balise script l'indique. Pour counter.js, c'est aussi un module car il est importé"/></a></p>
<p>Pour Node, c’est une autre histoire. Il n’y a pas d’éléments HTML et on ne peut donc pas utiliser un attribut <code>type</code>. Une des solutions utilisées par la communauté a été d’utiliser l’extension <code>.mjs</code>. Avec cette extension, on indique à Node que le fichier est un module. Vous pourrez lire certaines discussions où cette extension est un <em>signal</em> pour le <em>parse goal</em>. La discussion à ce sujet est toujours en cours et le <em>signal</em> qui sera utilisé par la communauté Node n’est pas encore fixé.</p>
<p>Dans tous les cas, le chargeur déterminera s’il faut analyser le fichier comme un module ou non. Si c’est un module et que celui-ci a des imports, le chargeur recommencera le processus jusqu’à ce que l’ensemble des fichiers ait été récupéré et analysé.</p>
<p>Et voilà ! Au début de cette étape, nous avions un fichier comme point d’entrée et maintenant nous avons un ensemble de <em>module records</em>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/27_construction.png" title="Un fichier JS à gauche avec trois module records analysés sur la droite pour modéliser le résultat de la phase de construction"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.27_construction_m.png" style="margin: 0 auto; display: block;" title="Un fichier JS à gauche avec trois module records analysés sur la droite pour modéliser le résultat de la phase de construction"/></a></p>
<p>La prochaine étape consiste à instancier ce module et à lier toutes ces instances ensembles.</p>
<h3>L’instanciation</h3>
<p>Comme évoqué plus haut, une instance combine du code avec un état. Cet état est en mémoire. Aussi, l’étape d’instanciation concernera principalement la mémoire.</p>
<p>Pour commencer, le moteur JavaScript crée un environnement pour le module. Cet environnement va gérer les variables pour le <em>module record</em>. Ensuite, il trouve les zones mémoire où placer l’ensemble des exports. C’est l’environnement du module qui maintiendra le registre d’association entre les zones mémoire et les exports.</p>
<p>Ces zones mémoire n’ont pas encore de valeur à l’intérieur. Ce n’est qu’après l’évaluation que les valeurs seront renseignées. Seule une exception échappe à cette règle : les déclarations de fonction sont initialisées pendant cette phase afin de faciliter la phase d’évaluation.</p>
<p>Pour instancier le graphe des modules, le moteur effectue un parcours postfixe (en profondeur) de l’arbre (<em>depth first post-order traversal</em>). Cela signifie qu’il commence par parcourir le bas de l’arbre (les dépendances qui n’ont aucune dépendance) et traite leurs exports.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/30_live_bindings_01.png" title="Illustration d'une colonne de mémoire vie au milieu. Les module records pour l'affichage et le comptage sont câblés à des zones mémoire sur cette colonne"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.30_live_bindings_01_m.png" style="margin: 0 auto; display: block;" title="Illustration d'une colonne de mémoire vie au milieu. Les module records pour l'affichage et le comptage sont câblés à des zones mémoire sur cette colonne"/></a>.</p>
<p>Une fois que le moteur a fini de câbler l’ensemble des exports du niveau inférieur, il monte d’un niveau pour câbler les imports du module correspondant.</p>
<p>On notera qu’un export et un import pointent vers le même emplacement en mémoire. Commencer par câbler les exports garantit la capacité à connecter les imports aux exports associés.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/30_live_bindings_02.png" title="Le même diagramme que précédemment mais sur lequel les imports de main.js sont désormais câblés sur les zones mémoire où sont aussi câblés les exports"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.30_live_bindings_02_m.png" style="margin: 0 auto; display: block;" title="Le même diagramme que précédemment mais sur lequel les imports de main.js sont désormais câblés sur les zones mémoire où sont aussi câblés les exports"/></a></p>
<p>Il y a une ici une différence avec les modules CommonJS. En CommonJS, tout l’objet exporté est copié à l’export. Cela signifie que les valeurs (les nombres par exemple) sont exportées sous forme de copies.</p>
<p>Cela signifie que si le module qui exporte modifie une valeur plus tard, le module qui l’import ne verra pas ce changement.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/31_cjs_variable.png" title="Le diagramme avec la colonne mémoire au milieu, à gauche l'export CommonJS qui est câblé sur un espace contenant la valeur 5 et à droite l'import qui est câble sur un autre espace contenant la copie de la valeur 5"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.31_cjs_variable_m.png" style="margin: 0 auto; display: block;" title="Le diagramme avec la colonne mémoire au milieu, à gauche l'export CommonJS qui est câblé sur un espace contenant la valeur 5 et à droite l'import qui est câble sur un autre espace contenant la copie de la valeur 5"/></a></p>
<p>Les modules ES utilisent à la place des liaisons dynamiques (<em>live bindings</em>). Les deux modules (l’export et l’import) pointent vers le même emplacement en mémoire. Cela signifie que lorsque le module exportant modifie une valeur, cette modification est également présente dans le module important.</p>
<p>Les modules qui exportent des valeurs peuvent modifier ces valeurs à tout moment mais les modules qui importent des valeurs ne peuvent pas modifier les valeurs importées. Ceci étant dit, si un module importe un objet, il peut modifier les valeurs des propriétés de cet objet.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/30_live_bindings_04.png" title="Le module exportant peut modifier la valeur en mémoire mais le module qui importe la valeur échoue s'il essaie."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.30_live_bindings_04_m.png" style="margin: 0 auto; display: block;" title="Le module exportant peut modifier la valeur en mémoire mais le module qui importe la valeur échoue s'il essaie."/></a></p>
<p>Avec ces liaisons dynamiques, il est possible de câbler tous les modules sans exécuter aucun code. Comme on le verra ensuite, cela facilite l’évaluation lorsqu’on a des dépendances cycliques.</p>
<p>À la fin de cette étape, toutes les instances de modules pour les imports et les exports sont câblées aux zones mémoire.</p>
<p>Nous voilà alors capables d’évaluer le code et de remplir ces zones mémoire avec leurs valeurs.</p>
<h3>L’évaluation</h3>
<p>L’étape finale consiste à remplir ces zones mémoire. Le moteur JS s’attaque à cette tâche en exécutant le code « de plus haut niveau » (<em>top level</em>), c’est-à-dire le code qui est situé en dehors des fonctions.</p>
<p>En plus de remplir ces espaces en mémoire, l’évaluation du code peut déclencher certains effets de bord. Un module peut, par exemple, communiquer avec un serveur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/40_top_level_code.png" title="Un module avec du code situé en dehors des fonctions qui suivent. Ce code est marqué comme étant au plus haut niveau"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.40_top_level_code_m.png" style="margin: 0 auto; display: block;" title="Un module avec du code situé en dehors des fonctions qui suivent. Ce code est marqué comme étant au plus haut niveau"/></a></p>
<p>À cause de ces effets de bord, il peut être préférable de n’évaluer le module qu’une seule fois. Contrairement à l’édition de lien qui se produit pendant l’instanciation et qui peut être effectuée plusieurs fois en produisant toujours le même résultat, l’évaluation pourra produire des résultats différents selon le nombre de fois qu’on l’a appliquée.</p>
<p>C’est l’une des raisons à l’origine de la carte des modules. La carte des modules met en cache les modules et expose une URL canonique pour chacun des modules. Ainsi, on s’assure que chaque module est exécuté une seule fois. Comme avec l’instanciation, l’exécution est effectuée en suivant un parcours postfixe (en profondeur) de l’arbre.</p>
<p>Mais que se passe-t-il avec les cycles que nous avons évoqués plus tôt ?</p>
<p>Si on a une dépendance cyclique, on aura une boucle dans le graphe. Généralement, cette boucle est plutôt longue. Toutefois, afin de pouvoir expliquer le problème ici, nous allons utiliser un exemple simplifié avec une boucle plus courte.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/41_cyclic_graph.png" title="Un graphe de module complexe avec un cycle de 4 modules à gauche. Sur la droite, un cycle avec 2 modules."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.41_cyclic_graph_m.png" style="margin: 0 auto; display: block;" title="Un graphe de module complexe avec un cycle de 4 modules à gauche. Sur la droite, un cycle avec 2 modules."/></a></p>
<p>Voyons ce qui se serait passé avec des modules CommonJS. Tout d’abord, le module principal est exécuté jusqu’à l’instruction <code>require</code>. Ensuite, on charge le module <code>counter</code>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/41_cjs_cycle.png" title="Un module CommonJS avec une variable exportée depuis main.js après une instruction require pour charger counter.js (qui dépend de l'import)."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.41_cjs_cycle_m.png" style="margin: 0 auto; display: block;" title="Un module CommonJS avec une variable exportée depuis main.js après une instruction require pour charger counter.js (qui dépend de l'import)."/></a></p>
<p>Le module <code>counter</code> essaie alors d’accéder à la variable <code>message</code> provenant de l’object d’export. Cette variable n’ayant pas encore été initialisée dans le module principal, elle renvoie <code>undefined</code>. Le moteur JavaScript alloue de la mémoire pour la variable locale et indique <code>undefined</code> comme valeur.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/42_cjs_variable_2.png" title="La colonne représentant la zone mémoire au milieu : aucun lien entre main.js et la mémoire mais un lien d'import entre counter.js et un emplacement mémoire qui contient la valeur undefined"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.42_cjs_variable_2_m.png" style="margin: 0 auto; display: block;" title="La colonne représentant la zone mémoire au milieu : aucun lien entre main.js et la mémoire mais un lien d'import entre counter.js et un emplacement mémoire qui contient la valeur undefined"/></a></p>
<p>L’évaluation continue jusqu’à la fin du code de plus haut niveau pour le module <code>counter</code>. On veut pouvoir voir si la valeur correcte de <code>message</code> y parvient (après que main.js a été évalué) et on utilise donc <code>setTimeout</code> pour patienter. L’évaluation reprend alors du côté de <code>main.js</code>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/43_cjs_cycle.png" title="counter.js rend le contrôle à main.js, qui peut finir son évaluation"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.43_cjs_cycle_m.png" style="margin: 0 auto; display: block;" title="counter.js rend le contrôle à main.js, qui peut finir son évaluation"/></a></p>
<p>La variable <code>message</code> est initialisée et ajoutée à un emplacement mémoire mais aucun lien n’est créé entre cet emplacement et celui utilisé précédemment. Aussi, la valeur utilisée dans <code>counter</code> restera (indéfiniment) <code>undefined</code>.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/44_cjs_variable_2.png" title="main.js envoie son export en mémoire et crée une connexion : la bonne valeur est bien inscrite en mémoire. Toutefois, counter.js continue de pointer vers le premier emplacement qui contient toujours undefined."><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/ES_Modules/.44_cjs_variable_2_m.png" style="margin: 0 auto; display: block;" title="main.js envoie son export en mémoire et crée une connexion : la bonne valeur est bien inscrite en mémoire. Toutefois, counter.js continue de pointer vers le premier emplacement qui contient toujours undefined."/></a></p>
<p>Si les exports avaient été créés avec des liaisons dynamiques, le module <code>counter</code> aurait fini par voir arriver la bonne valeur pour <code>message</code>. En effet, après que l’évaluation de <code>main.js</code> a eu lieu et remplit la valeur, une fois que le <code>setTimeout</code> se déclenche, il utilise la bonne valeur.</p>
<p>La prise en charge de ces cycles a été une composante majeure lors de la conception des modules ES. C’est ce découpage en trois phases qui permet de gérer les dépendances cycliques.</p>
<h2>Quelle est la prise en charge actuelle des modules ES ?</h2>
<p>Avec la sortie de Firefox 60 en mai, l’ensemble des principaux navigateurs prendra en charge les modules ES par défaut. Node travaille également sur le sujet et un groupe de travail dédié se concentre sur les problèmes de compatibilité entre les modules CommonJS et les modules ES.</p>
<p>Cela signifie qu’on pourra utiliser la balise <code>script</code> avec <code>type=module</code> ainsi que des imports et des exports. D’autres fonctionnalités relatives aux modules sont dans les tuyaux. <a href="https://github.com/tc39/proposal-dynamic-import">La proposition concernant l’import dynamique</a> est au niveau 3 du processus de spécification. Il en va de même avec <a href="https://github.com/tc39/proposal-import-meta">import.meta</a> qui aidera à gérer certains cas d’utilisation pour Node.js. Enfin, <a href="https://github.com/domenic/package-name-maps">la proposition sur la résolution des modules</a> aidera à atténuer les différences entre les navigateurs et Node.js. En bref, le meilleur reste à venir.</p>
<h2>Remerciements</h2>
<p>Merci aux différentes personnes qui ont fourni leurs retours sur ce billet ou dont les écrits ou discussions ont aidé à sa rédaction : Axel Rauschmayer, Bradley Farias, Dave Herman, Domenic Denicola, Havi Hoffman, Jason Weathersby, JF Bastien, Jon Coppeard, Luke Wagner, Myles Borins, Till Schneidereit, Tobias Koppers, Yehuda Katz, les membres du groupe communautaire WebAssembly, les membres du groupe de travail pour les modules Node ainsi que les membres du TC39.</p>
<h2>À propos de <a href="http://code-cartoons.com/">Lin Clark</a></h2>
<p>Lin est ingénieure au sein de l’équipe Mozilla Developer Relations. Elle bricole avec JavaScript, WebAssembly, Rust, Servo et dessine des bandes dessinées à propos du code.</p>
IntersectionObserver arrive dans Firefox
urn:md5:daaa2d551db32deb3c9d2f86fc6cea77
2017-08-21T19:42:00+02:00
2017-08-22T11:35:59+02:00
sphinx
Développement Web
Défilement
IntersectionObserver
<p><em>Cet article est la traduction de Intersection Observer comes to Firefox, écrit par <a href="http://dancallahan.info/">Dan Callahan</a> et publié sur le blog Hacks. <a href="https://hacks.mozilla.org/2017/08/intersection-observer-comes-to-firefox/">La version anglaise est disponible ici</a>. Merci à <a href="https://twitter.com/marie_ototoi">Marie Destandau</a> pour cette traduction !</em></p>
<hr />
<p>Qu’ont en commun le défilement infini, le <em>lazy loading</em> (ou chargement à la demande) et la publicité en ligne ?</p>
<p>Ils ont tous besoin de connaître - et de réagir à - l’état de visibilité des éléments d’une page.</p>
<p>Malheureusement, savoir si un élément est visible a toujours été une chose compliquée sur le Web. La plupart des solutions écoutent les évènements <a href="https://developer.mozilla.org/fr/docs/Web/Events/scroll"><code>scroll</code></a> et <a href="https://developer.mozilla.org/fr/docs/Web/Events/resize"><code>resize</code></a> (défilement et redimensionnement), puis utilisent des API du DOM telles que <a href="https://developer.mozilla.org/fr/docs/Web/API/Element/getBoundingClientRect"><code>getBoundingClientRect()</code></a> afin de calculer manuellement où les éléments se situent par rapport à la fenêtre visible. En général, cette méthode fonctionne mais elle n’est pas très efficace et ne prend pas en compte les autres facteurs qui peuvent modifier la visibilité, par exemple une image qui finit de se charger plus haut dans la page, décalant tout le reste vers le bas.</p>
<p>Les choses sont particulièrement compliquées avec la publicité, car il y a de l’argent en jeu. Comme l’explique <a href="https://twitter.com/cramforce">Malte Ubl</a> dans <a href="https://www.youtube.com/watch?v=jO1TNGNTwpc#t=3m30">sa présentation</a> à JSConf Iceland, les annonceurs ne veulent pas payer pour des publicités qui ne sont jamais vues. Pour s’assurer qu’elles soient vues, ils les recouvrent de dizaines d’animation Flash de 1 pixel, dont la visibilité peut être déduite à partir du <em>framerate</em>. Pour les systèmes qui ne disposent pas de Flash, comme les téléphones, des <em>timers</em> sont ajoutés pour forcer les navigateurs à recalculer la position de chaque publicité toutes les quelques millisecondes.</p>
<p>Ces techniques nuisent beaucoup à la performance, vident les batteries et seraient complètement inutiles si le navigateur pouvait simplement nous informer lorsque la visibilité d’un élément change.</p>
<p>C’est ce que fait <a href="https://developer.mozilla.org/fr/docs/Web/API/IntersectionObserver">l’API IntersectionObserver</a>.</p>
<h2>Et voici <code>new IntersectionObserver()</code></h2>
<p>Dans le cas le plus simple, utiliser l’API IntersectionObserver ressemble à :</p>
<pre><code>let observer = new IntersectionObserver(handler);
observer.observe(target); // <— Elément à écouter
</code></pre>
<p>La <a href="https://codepen.io/SphinxKnight/pen/zdjxZQ">démonstration</a> ci-dessous illustre le fonctionnement un gestionnaire d’évènement simple.
Un seul observateur peut surveiller de nombreux éléments simultanément ; il suffit de répéter la commande <code>observer.observe()</code> pour chaque cible.</p>
<p data-height="265" data-theme-id="dark" data-slug-hash="zdjxZQ" data-default-tab="result" data-user="SphinxKnight" data-embed-version="2" data-pen-title="Voici IntersectionObserver" class="codepen">See the Pen <a href="https://codepen.io/SphinxKnight/pen/zdjxZQ/">Voici IntersectionObserver</a> by SphinxKnight (<a href="https://codepen.io/SphinxKnight">@SphinxKnight</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
<h2>« Intersection » ? et la visibilité ?</h2>
<p>Par défaut, un objet <code>IntersectionObserver</code> calcule dans quelle proportion une cible chevauche la portion visible de la page.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/Blank-Diagram-Page-1-500x355.png" alt="Illustration d'un élément cible avec une intersection partielle sur le viewport du navigateur" /></p>
<p>Mais un objet <code>IntersectionObserver</code> peut aussi surveiller dans quelle mesure une cible chevauche un élément parent défini de façon arbitraire, quelle que ce soit la réelle visibilité de cet élément. Ceci peut être utile pour des widgets qui chargent le contenu à la demande comme une liste défilant à l’infini à l’intérieur d’un <code><div></code> et qui pourrait utiliser un objet <code>IntersectionObserver</code> pour charger juste assez de contenu afin de remplir son conteneur.</p>
<p>Par souci de simplicité, la suite de cet article parlera de visibilité, mais gardez en tête qu’il ne s’agit pas forcément de visibilité au sens littéral du terme.</p>
<h2>Gestionnaires simples</h2>
<p>Les gestionnaires associés à un observateur sont des <em>callbacks</em> qui acceptent deux arguments :</p>
<ul>
<li>Une liste d’objets <a href="https://developer.mozilla.org/fr/docs/Web/API/IntersectionObserverEntry"><code>IntersectionObserverEntry</code></a> contenant chacun les métadonnées indiquant les changements de la zone de chevauchement d’une cible depuis le dernier appel du gestionnaire</li>
<li>Une référence à l’observateur.</li>
</ul>
<p>Par défaut, les observateurs surveillent la fenêtre du navigateur. Cela signifie que la démo ci-dessus doit uniquement consulter la propriété <code>isIntersecting</code> pour déterminer si une partie d’une cible est visible.</p>
<p>Par défaut, les gestionnaires sont appelés uniquement lorsque les éléments cibles passent d’un état non visible à un état partiellement visible ou vice versa mais comment faire si l’on souhaite distinguer les éléments partiellement visibles des éléments visibles dans leur totalité ?</p>
<p>Les seuils sont là pour ça !</p>
<h2>Travailler avec les seuils</h2>
<p>En plus du <em>callback</em>, le constructeur d’un <code>IntersectionObserver</code> peut recevoir un objet avec différentes options de configuration. L’une de ces options est <code>threshold</code>, qui définit les valeurs limites pour l’invocation du gestionnaire.</p>
<pre><code>let observer = new IntersectionObserver(handler, {
threshold: 0 // <-- This is the default
});
</code></pre>
<p>La valeur par défaut de <code>threshold</code> est 0; ce qui revient à invoquer le gestionnaire lorsqu’une cible devient partiellement visible ou complètement invisible. Régler cette valeur à 1 provoque le déclenchement du gestionnaire lorsque l’élément cible passe d’un état complètement visible à un état partiellement visible, tandis qu’une valeur de 0.5 déclenche le gestionnaire lorsque la cible passe le seuil de 50% de visibilité, dans l’une ou l’autre direction.</p>
<p>Il est possible de spécifier un tableau de seuils, comme on le voit avec le paramètre <code>threshold: [0, 1]</code> dans <a href="https://codepen.io/SphinxKnight/pen/OjZPxx">l’exemple</a> ci-après :</p>
<p data-height="265" data-theme-id="dark" data-slug-hash="OjZPxx" data-default-tab="result" data-user="SphinxKnight" data-embed-version="2" data-pen-title="IntersectionObserver Thresholds" class="codepen">See the Pen <a href="https://codepen.io/SphinxKnight/pen/OjZPxx/">IntersectionObserver Thresholds</a> by SphinxKnight (<a href="https://codepen.io/SphinxKnight">@SphinxKnight</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
<p>Faites défiler doucement la cible et observez son comportement.</p>
<p>Dans un premier temps la cible est entièrement visible —<code>intersectionRatio</code> vaut alors 1. La valeur change deux fois pendant qu’elle défile vers l’extérieur de l’écran : d’abord avec une valeur comme 0.87 puis pour avec une valeur égale à 0. Lorsque la cible revient dans la zone visible, <code>intersectionRatio</code> devient 0.05, puis 1. Les valeurs 0 et 1 sont plutôt simples à comprendre, mais d’où viennent les deux autres et qu’en est-il de toutes les valeurs comprises entre 0 et 1 ?</p>
<p>Les seuils sont définis en terme de transitions : le gestionnaire est appelé dès que le navigateur se rend compte que la valeur <code>intersectionRatio</code> d’une cible a passé le seuil. Définir les seuils à <code>[0, 1]</code> indique au navigateur « préviens-moi quand une cible passe les lignes d’invisibilité (<code>0</code>) et de visibilité complète (<code>1</code>) ». Cela définit donc trois états : complètement visible, partiellement visible et invisible.</p>
<p>La valeur qu’on observe pour <code>intersectionRatio</code> varie d’un test à l’autre car le navigateur doit attendre une période d’inactivité afin de vérifier et notifier les intersections. Ce type de calcul est traité en tâche de fond, avec une priorité inférieure au défilement et à l’interaction utilisateur.</p>
<p>Essayez d’éditer le codepen pour ajouter ou enlever les seuils et observez les changements dans la façon dont le gestionnaire est appelé.</p>
<h2>Autres options</h2>
<p>Le constructeur d’un <code>IntersectionObserver</code> accepte deux autres options :</p>
<ul>
<li><code>root</code> : la surface à observer (par défaut: la fenêtre du navigateur)</li>
<li><code>rootMargin</code> : une chaîne de caractères qui indique quelle zone considérer en plus ou en moins par rapport à la taille logique de l’élément <code>root</code> lorsqu’on calcule les intersections (par défaut, c’est <code>"0px 0px 0px 0px"</code> qui sera utilisée).</li>
</ul>
<p>Modifier <code>root</code> permet à un observateur de surveiller l’intersection avec un élément parent donné plutôt qu’avec la fenêtre du navigateur.</p>
<p>Utiliser la propriété <code>rootMargin</code> permet de détecter lorsqu’une cible s’approche de la zone surveillée. Par exemple, un observateur pourrait déclencher le chargement des images juste avant qu’elles ne deviennent visibles.</p>
<h2>Prise en charge des navigateurs</h2>
<p>L’API IntersectionObserver est disponible par défaut dans Edge 15, Chrome 51, et Firefox 55.</p>
<p>Un <code>polyfill</code> qui fonctionne avec tous les navigateurs est disponible, mais la performance obtenue est bien moins bonne qu’avec les implémentations natives.</p>
<h2>Autres ressources</h2>
<ul>
<li><a href="https://developer.mozilla.org/fr/docs/Web/API/Intersection_Observer_API">MDN : Intersection Observer</a>, n’hésitez pas à <a href="https://developer.mozilla.org/fr/docs/MDN/Doc_status/API/IntersectionObserver">contribuer à la traduction de ces ressources</a></li>
<li><a href="https://github.com/WICG/IntersectionObserver/tree/gh-pages/polyfill">Un polyfill pour les différents navigateurs</a></li>
<li><em><a href="http://caniuse.com/#feat=intersectionobserver">Can I Use</a></em></li>
</ul>
<p><style>
pre { white-space: pre;}
</style></p>
Bienvenue au MozFest de Londres
urn:md5:000aeaab634eac88bea238e699075f23
2017-06-21T18:00:00+02:00
2017-07-24T11:28:08+02:00
tchevalier
Événement
<p><a href="https://tech.mozfr.org/dotclear/public/mozilla/mozfest/2017/about-1.png" title="MozFest : photo de groupe"><img alt="MozFest : photo de groupe" src="https://tech.mozfr.org/dotclear/public/mozilla/mozfest/2017/about-1.png" style="float:right; margin: 0 0 1em 1em; width:200px; border:none;" /></a>Le MozFest, pour <em>Mozilla Festival</em>, tiendra sa 8ᵉ édition à Londres cet automne. Le mouvement du Web ouvert s’y retrouvera du vendredi 27 au dimanche 29 octobre. Des passionné·e·s des technologies, des éducateur·rice·s et des makers se réuniront pour explorer <strong>le futur du Web ouvert</strong>.</p>
<p>Le MozFest, c’est plus de 1 600 visiteur·se·s, des participant·e·s de plus de 50 pays et 325 ateliers et conférences.</p>
<p>Vous avez jusqu’au <strong>1<sup>er</sup> août</strong> pour répondre à <a href="https://mzl.la/francais" hreflang="fr" title="Bienvenue sur l’appel à propositions pour le MozFest 2017 – Mozilla Festival">l’appel à propositions</a> avec des activités interactives et inclusives.</p>
<p>Il s’agit d’une formidable opportunité. Et, <a href="https://mozillafestival.org/about" hreflang="en" title="A Note from Mark Surman, Mozilla’s Executive Director – Mozilla Festival">pour vous en convaincre</a>, qui de mieux placé que le directeur général de la <i lang="en">Mozilla Foundation</i>, Mark Surman ?</p> <hr />
<p><a href="https://tech.mozfr.org/dotclear/public/mozilla/mozfest/2017/img-mark.jpg" title="Mark"><img alt="Mark" src="https://tech.mozfr.org/dotclear/public/mozilla/mozfest/2017/img-mark.jpg" style="float:left; margin: 0 1em 1em 0; width:150px;" /></a>Chers amis,</p>
<p>J’aime penser à la communauté Mozilla comme un reflet de l’internet : créatif, éclectique, parlant de nombreuses langues, couvrant de nombreuses disciplines, toujours ouvert à de nouvelles idées.</p>
<p>C’est encore plus frappant au MozFest, la célébration annuelle de notre communauté et plus largement, du mouvement de l’internet ouvert. Tous les ans, pendant trois jours, nous nous rassemblons, discutons, débattons, créons et bricolons pour créer un internet meilleur. Nous sommes une équipe diversifiée : des scientifiques du Royaume-Uni, des enseignants du Kenya, des technophiles de Shenzhen…</p>
<p>Lorsque nous nous réunissons cette année, nous voulons célébrer notre diversité. Nous voulons montrer que les idées les plus audacieuses et les plus évolutives pour le mouvement de l’Internet ouvert peuvent provenir de n’importe qui, de n’importe où. Nous en avons la preuve : l’année passée, la nouvelle génération de leaders de l’internet ouvert a fait campagne pour la neutralité du Net en Inde ; a enseigné des compétences numériques à Nairobi et à Cape Town ; et a exploré l’avenir de l’internet des objets à Berlin.</p>
<p><img alt="MozFest : atelier" src="https://tech.mozfr.org/dotclear/public/mozilla/mozfest/2017/mozfest-atelier.jpg" style="display:table; margin:0 auto;" /></p>
<p>Cette année au MozFest, nous voulons également accomplir notre devoir tel que décrit dans le Manifeste de Mozilla : faire en sorte que l’internet demeure une ressource publique mondiale, ouverte et accessible à tous. Cela signifie que nous devons enseigner des compétences web à plus de personnes et à plus d’endroits. Cela implique également de poser des questions difficiles sur ce qu’est réellement un « internet inclusif ». Nous devons aborder activement les défis auxquels sont confrontées les personnes qui ne se sentent pas encore les bienvenues sur le Web.</p>
<p><a href="https://tech.mozfr.org/dotclear/public/mozilla/mozfest/2017/foxlogo.png" title="Fox logo"><img alt="Fox logo" src="https://tech.mozfr.org/dotclear/public/mozilla/mozfest/2017/.foxlogo_m.png" style="float:right; margin: 0 0 1em 1em; width:200px;" /></a>Nous avons beaucoup de travail et la tâche ne sera pas aisée : les menaces à un internet ouvert ne manquent pas. La confidentialité en ligne et d’importants garde-fous comme le chiffrement sont menacés. Et les espaces protégés (« walled gardens ») rendent le Web plus fermé et moins propice à être modifié.</p>
<p>Au MozFest cette année, nous travaillerons sur des solutions à ces problèmes et à de nombreux autres. Nos échanges, nos sessions et notre créativité au MozFest m’inspirent toujours, et j’ai hâte de voir ce que nous construirons ensemble cette année.</p>
<p>J’espère vous voir à Londres,</p>
<p>— Mark Surman</p>
<hr />
<address><br />
Traduction et relecture : <a href="https://twitter.com/hellosct1" hreflang="fr" title="Christophe Villeneuve (@hellosct1) sur Twitter">Hellosct1</a>, <a href="https://twitter.com/t_chevalier" hreflang="fr" title="Théo Chevalier (@t_chevalier) sur Twitter">Théo</a>, <a href="https://twitter.com/Mozinet" hreflang="fr" title="Mozinet (@Mozinet) sur Twitter">Mozinet</a> et anonymes</address>
<p><em>Images du MozFest intégrées par le traducteur</em></p>
Une introduction cartoonesque à WebAssembly
urn:md5:2cee041d928c086a548a3be16be135c7
2017-03-08T19:10:00+01:00
2017-03-09T20:26:00+01:00
sphinx
JavaScript
JavaScript
WASM
WebAssembly
<p><em>Cet article est le premier d’une série de traductions d’articles écrits par <a href="http://code-cartoons.com/">Lin Clark</a> et publiés sur le blog Hacks. <a href="https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/">La version anglaise est disponible ici</a>. Merci à Adam, dattaz, Jeremie et à goofy et Benjamin pour la relecture :)</em></p>
<hr />
<p>WebAssembly est rapide. Vous avez sans doute déjà entendu ça. Mais qu’est-ce qui rend WebAssembly si rapide ?
Dans cette série d’articles, c’est exactement ce que je compte vous expliquer.</p>
<h2>Mhmmm, c’est quoi au juste WebAssembly ?</h2>
<p>WebAssembly est une façon de prendre du code écrit dans un langage de programmation différent de JavaScript et de le faire s’exécuter dans le navigateur. Ainsi, quand les gens disent que WebAssembly est rapide, c’est en le comparant à JavaScript.</p>
<p>Maintenant, que l’on soit clair, je ne veux pas dire qu’il s’agit d’une situation binaire où il faudrait utiliser uniquement WebAssembly ou uniquement JavaScript. En fait c’est l’inverse, on s’attend à ce que les développeurs utilisent les deux dans la même application.</p>
<p>Cependant, il est utile de comparer les deux afin de mieux comprendre le potentiel de WebAssembly.</p>
<h2>Un peu d’histoire des performances</h2>
<p>JavaScript est né en 1995. Il n’a pas été conçu pour être rapide et en effet il ne l’était pas pendant les dix premières années.
À partir de ce moment, la compétition entre navigateurs s’amplifia.</p>
<p>En 2008, commença alors une période qu’on a appelée « la guerre de la performance ». Plusieurs navigateurs se sont dotés de compilateurs à la volée (ou « JIT » pour « Just In Time »). Lors de l’exécution de JavaScript, le compilateur JIT peut identifier différents motifs et rendre le code bien plus rapide grâce à ces derniers.</p>
<p>L’introduction de ces compilateurs JIT a conduit à un point d’inflexion pour les performances de JavaScript. Son exécution est devenue 10 fois plus rapide.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/01-01-perf_graph05.png" alt="Courbe décrivant la progression des performances pour JavaScript" /></p>
<p>Avec cette performance accrue, JavaScript commença à être utilisé dans des domaines jusqu’alors insoupçonnés, par exemple pour de la programmation côté serveur avec Node.js.
L’amélioration des performances a rendu possible l’utilisation de JavaScript pour traiter un tout nouvel ensemble de problèmes.</p>
<p>Avec WebAssembly, nous pourrions bien être à un nouveau point d’inflexion.
<img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/01-02-perf_graph10.png" alt="Courbe décrivant la progression des performances pour JavaScript jusqu'à aujourd'hui" /></p>
<p>Rentrons dans les détails afin de comprendre ce qui rend WebAssembly si rapide.</p>
<h3>Contexte</h3>
<ul>
<li><a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-de-compilation-a-la-volee-(JIT)">Cours accéléré de JIT (compilation à la volée)</a></li>
<li><a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-d-assembleur">Cours accéléré d’assembleur</a></li>
</ul>
<h3>WebAssembly, aujourd’hui</h3>
<ul>
<li><a href="https://tech.mozfr.org/post/2017/03/08/Creer-et-manipuler-des-modules-WebAssembly">Créer et travailler avec les modules WebAssembly</a></li>
<li><a href="https://tech.mozfr.org/post/2017/03/08/D-ou-vient-la-rapidite-de-WebAssembly">Qu’est-ce qui rend WebAssembly si rapide ?</a></li>
</ul>
<h3>L’avenir de WebAssembly</h3>
<ul>
<li><a href="https://tech.mozfr.org/post/2017/03/08/WebAssembly-aujourd-hui-et-demain">Où en est WebAssembly à l’heure actuelle et quelles sont les prochaines étapes ?</a></li>
</ul>
<h2>À propos de <a href="http://twitter.com/linclark">Lin Clark</a></h2>
<p>Lin est ingénieure au sein de l’équipe Mozilla Developer Relations. Elle bidouille avec JavaScript, WebAssembly, Rust et Servo et crée des bandes dessinées sur le code.</p>
Un petit cours accéléré de compilation à la volée (JIT)
urn:md5:00b41d08284befeebc4f39d117e25f1d
2017-03-08T18:54:00+01:00
2017-03-09T20:25:32+01:00
sphinx
JavaScript
JavaScript
WASM
WebAssembly
<p><em>Cet article est le deuxième d’une série de traductions d’articles écrits par <a href="http://code-cartoons.com/">Lin Clark</a> et publiés sur le blog Hacks. <a href="https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/">La version anglaise est disponible ici</a>. Merci à dattaz, Jeremie et à goofy et Benjamin pour la relecture :) Si vous n’avez pas lu les autres articles, nous vous conseillons de démarrer <a href="https://tech.mozfr.org/post/2017/03/08/Une-introduction-cartoonesque-a-WebAssembly">depuis le début</a>.</em></p>
<hr />
<p>Si les débuts de JavaScript sont marqués par une lenteur, il est devenu sensiblement plus rapide grâce à un truc appelé JIT. OK, mais comment fonctionne ce fameux JIT ?</p>
<h2>Comment JavaScript est exécuté par les navigateurs</h2>
<p>Quand le développeur que vous êtes ajoute du JavaScript dans une page web, vous avez un objectif et un problème.
Objectif : vous voulez dire à l’ordinateur ce qu’il doit faire.
Problème : l’ordinateur et vous ne parlez pas du tout le même langage.</p>
<p>Vous, vous parlez un langage humain ; l’ordinateur, lui, parle un langage de machine. Même si vous pensez que JavaScript ou n’importe quel autre langage de programmation de haut niveau n’est pas un langage humain, ne vous y trompez pas, c’est bien le cas. Ils ont été créés pour se conformer au mode de pensée des humains, pas des machines.</p>
<p>Ainsi, le travail du moteur JavaScript consiste à prendre votre langage humain et le convertir en quelque chose qu’une machine peut comprendre.
Je vois ça comme dans le film <a href="https://fr.wikipedia.org/wiki/Premier_Contact_(film)">Premier Contact</a>, dans lequel des humains et des extraterrestres essaient de se parler.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-01-alien03.png" alt="Communiquer avec un alien ?" /></p>
<p>Dans ce film, les humains et les extraterrestres ne font pas de traduction mot à mot. Les deux groupes ont différentes façons de penser le monde. Eh bien figurez-vous que c’est la même chose entre les humains et les machines (on verra ça en détail dans le prochain article).</p>
<p>Donc, comment se fait cette traduction?</p>
<p>Dans le monde de la programmation il existe généralement deux façons de faire une traduction vers du langage machine : en utilisant un interpréteur ou en utilisant un compilateur.</p>
<p>Avec un interpréteur, cette traduction se fait en temps réel, quasiment ligne par ligne.
<img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-02-interp02.png" alt="Interpréter un langage" /></p>
<p>D’un autre côté, un compilateur ne fait pas une traduction en temps réel, il travaille en amont pour créer sa traduction et la retranscrire intégralement.
Avec un interpréteur, cette traduction se fait en temps réel, quasiment ligne par ligne.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-03-compile02.png" alt="Compiler un langage" /></p>
<p>Chacune de ces deux façons de procéder présente des avantages et des inconvénients.</p>
<h3>Le pour et le contre des interpréteurs</h3>
<p>Les interpréteurs sont rapides à l’allumage. Ils n’ont pas franchir toutes les étapes de compilation avant de pouvoir exécuter quoi que ce soit. Ils commencent la traduction de la première ligne et l’exécutent immédiatement.</p>
<p>Grâce à ça, un interpréteur semble naturellement être un bon choix pour exécuter quelque chose comme JavaScript. C’est important pour un développeur web de pouvoir commencer à exécuter son code aussi vite que possible. C’est pour cette raison les navigateurs ont utilisé des interpréteurs pour exécuter JavaScript à leur début.</p>
<p>Le problème avec les interpréteurs survient quand vous voulez exécuter le même code plus d’une fois. Typiquement quand vous utilisez une boucle. Dans ce cas, l’interpréteur doit faire la même traduction encore et encore.</p>
<h3>Le pour et le contre des compilateurs</h3>
<p>Un compilateur choisit les compromis opposés.</p>
<p>Il a besoin d’un peu plus de temps au démarrage parce qu’il doit passer par toutes les étapes de compilation avant de pouvoir faire quoi que ce soit. Cependant exécuter le code d’une boucle est bien plus rapide puisqu’il n’est plus nécessaire de refaire le travail de traduction à chaque passage dans la boucle.</p>
<p>Une autre différence tient à ce que les compilateurs ont plus de temps pour observer le code et le modifier pour qu’il puisse s’exécuter plus rapidement. Ces modifications ne sont ni plus ni moins que des optimisations. Comme les interpréteurs font le travail de traduction en même temps qu’ils exécutent le code, ils ne peuvent pas se permettre de prendre beaucoup de temps pour faire des optimisations.</p>
<h2>Les compilateurs « juste à temps » : le meilleur des deux mondes</h2>
<p>Afin de passer outre l’inefficacité des interpréteurs — devoir traduire le même code encore et encore — les navigateurs ont commencé à leur adjoindre des compilateurs.</p>
<p>Chaque navigateur le fait de manière légèrement différente, cependant l’idée de base reste la même. On ajoute une nouvelle pièce au moteur JavaScript : un profileur de code. Ce profileur observe le code pendant qu’il s’exécute et prend des notes sur le nombre de fois qu’est exécuté un bout de code et sur les types utilisés.</p>
<p>Au début le profileur fait tout passer dans l’interpréteur.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-04-jit02.png" alt="Passage dans l'interpréteur" /></p>
<p>Si les mêmes lignes de code sont exécutées quelques fois, ce bout de code est considéré comme « tiède ». S’il est exécuté très souvent il est considéré comme « chaud ».</p>
<h3>Compilateur de base</h3>
<p>Quand une fonction devient tiède, le JIT va l’envoyer au compilateur et va stocker le résultat de la compilation.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-05-jit06.png" alt="Le compilateur de base (BC pour Baseline Compiler)" /></p>
<p>Chaque ligne de code est compilée sous forme d’un « extrait » (NDT <em>stub</em> en anglais). Les extraits sont indexés par numéro de ligne et par type de variable (j’expliquerai pourquoi c’est important plus tard). Si le profileur remarque que le même code avec les mêmes types de variables est exécuté à nouveau il utilisera simplement l’extrait compilé.</p>
<p>Ça aide à accélérer les choses. Mais comme je le disais, un compilateur peut faire bien plus. Il peut prendre le temps de comprendre la façon la plus efficace de faire certaines choses… de faire des optimisations. Le compilateur de base va faire quelques-unes de ces optimisations (j’en donne un exemple ci-après). Cela ne doit pas prendre trop de temps, car il ne veut pas bloquer l’exécution trop longtemps.</p>
<p>Cependant, si ce code est vraiment chaud — s’il est exécuté vraiment très souvent — alors ça vaut la peine de prendre le temps de faire davantage d’optimisations.</p>
<h3>Compilateur optimisant</h3>
<p>Quand un bout de code est vraiment chaud, le profileur va demander une compilation optimisée. Cela va créer une autre version encore plus rapide de ce code qui sera lui aussi stocké.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-06-jit09.png" alt="Le compilateur optimisant (OC pour Optimizing Compiler)" /></p>
<p>Pour pouvoir réaliser une version plus rapide du code, le compilateur va devoir émettre quelques hypothèses.
Par exemple, s’il peut supposer que tous les objets créés par un constructeur donné auront toujours la même structure — en clair, s’ils ont toujours les même propriétés et que ces propriétés sont toujours instanciées dans le même ordre — alors il va prendre des raccourcis pour ce cas spécifique.</p>
<p>Le compilateur utilise les informations que le profileur a glanées à force d’observations pour formuler de telles hypothèses. Si quelque chose s’est révélé vrai pour toutes les boucles précédentes, alors il partira du principe que ça continuera à être vrai.</p>
<p>Bien évidemment, avec JavaScript il n’y a jamais de telles garanties. Vous pouvez avoir 99 objets qui ont tous la même structure mais le centième peut avoir une propriété manquante.</p>
<p>Ainsi, le compilateur a besoin de vérifier la validité des hypothèses avant de pouvoir exécuter le code. Si elles sont valides, alors on exécute le code compilé. Mais dans le cas contraire, le JIT va partir du principe que les hypothèses sont fausses et va mettre le code optimisé à la poubelle.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-07-jit11.png" alt="Rejet du code mal optimisé" /></p>
<p>À ce moment-là, l’exécution du code va à nouveau se faire soit au niveau de l’interpréteur soit via le code de base compilé précédemment. On appelle ce processus la dé-optimisation (ou encore le rappel).</p>
<p>Habituellement, la compilation optimisée produit du code plus rapide, cependant, dans certains cas cela peut conduire à des problèmes de performance inattendus. Si vous avez du code qui n’arrête pas d’être optimisé puis dé-optimisé, vous pouvez vous retrouver avec du code plus lent à s’exécuter que la version compilée de base.</p>
<p>La plupart des navigateurs ont mis en place des limites pour sortir de ces cycles optimisation/dé-optimisation lorsqu’ils se présentent. Si le JIT a réalisé, disons, dix tentatives d’optimisation pour finalement devoir s’en débarrasser à chaque fois, alors il arrêtera de vouloir faire de l’optimisation.</p>
<h3>Un exemple d’optimisation : la spécialisation de type</h3>
<p>Il y a tout un tas d’optimisations possibles, je vais cependant vous en montrer une pour vous donner une idée de la manière dont les choses se passent. Un des gains les plus notables lors d’une compilation optimisée vient de ce que l’on appelle la spécialisation de type.</p>
<p>Le système de type dynamique utilisé par JavaScript requiert un peu plus de travail qu’il n’y paraît lors de l’exécution. Par exemple, prenons le code suivant:</p>
<pre><code>function arraySum(arr) {
var sum = 0;
for (var i = 0; i < arr.length; i++) {
sum += arr[i];
}
}
</code></pre>
<p>L’étape <code>+=</code> dans la boucle semble assez simple au premier abord. On pourrait penser que cela se calcule en une étape, malheureusement, à cause de la nature dynamique des types, ça va prendre plus d’étapes qu’on ne le croirait. Partons du principe que <code>arr</code> est un tableau (<code>Array</code>) de 100 entiers. Dès que le code va se réchauffer, le compilateur de base va créer un bout de code compilé pour chacune des opérations de la fonction. On va donc obtenir un bout de code pour <code>sum += arr[i]</code> qui va s’occuper de gérer l’opération <code>+=</code> comme une addition d’entiers.</p>
<p>Cependant, il n’y a aucune garantie que <code>sum</code> et <code>arr[i]</code> soient des entiers. Puisque les types sont dynamiques en JavaScript, il est toujours possible que, lors d’une des itérations de la boucle, <code>arr[i]</code> soit une chaîne de caractères. Additionner des entiers et concaténer des chaînes sont deux opérations très différentes qui donneront lieu à des codes compilés très différents.</p>
<p>Le JIT résout ce problème en compilant un grand nombre de bouts de code différents. Si du code est monomorphique (c’est-à-dire qu’il est appelé toujours avec les mêmes types) on aura un extrait de code compilé spécifique. Si du code est polymorphique (c’est-à-dire qu’il est appelé avec différents types d’une exécution à l’autre), alors on aura un bout de code compilé pour chaque combinaison de type utilisée dans cette opération.</p>
<p>Ça signifie que le JIT va devoir poser pas mal de questions afin de pouvoir choisir le bon bout de code compilé à exécuter.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-08-decision_tree01.png" alt="Arborescence des choix possibles" /></p>
<p>Puisque chaque ligne de code a son propre ensemble de bouts de code compilé, le JIT va devoir vérifier les types en jeu à chaque fois que la ligne de code est exécutée. Ainsi pour chaque itération de boucle, il devra sans cesse reposer les même questions.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-09-jit_loop02.png" alt="Se répéter encore et encore" /></p>
<p>Le code s’exécuterait beaucoup plus vite si le JIT n’avait pas à répéter ces vérifications tout le temps. C’est une des choses que les compilations optimisées améliorent. Lors d’une compilation optimisée, la fonction est compilée comme un tout et la plupart des vérifications de type sont faites avant de lancer la boucle.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/02-10-jit_loop02.png" alt="Une stratégie plus intelligente pour faciliter l'analyse des types" /></p>
<p>Certains JIT vont même encore plus loin. Par exemple, dans Firefox, il existe un traitement spécial réservé aux tableaux d’entiers. Si <code>arr</code> est un tableau de ce genre, alors le JIT n’a plus besoin de vérifier si <code>arr[i]</code> est un entier. L’avantage c’est que le JIT peut alors se permettre de faire toutes les vérifications de type avant le démarrage de la boucle.</p>
<h2>Conclusion</h2>
<p>Voilà pour une présentation rapide du fonctionnement d’un JIT. Il permet d’exécuter JavaScript plus vite en observant le code pendant son exécution et en optimisant les parties de code les plus chaudes. À bien des égards cela a conduit à une amélioration significative des performances de JavaScript pour la plupart des applications.</p>
<p>Et pourtant, malgré ces améliorations les performances de JavaScript restent difficiles à prédire. En plus de ça, pour rendre les chose plus rapides, le JIT ajoute de la complexité notable lors de l’exécution. En particulier :</p>
<ul>
<li>L’optimisation et la dé-optimisation</li>
<li>L’augmentation de l’usage mémoire pour garder les informations du profileur et les informations nécessaires à le dé-optimisation</li>
<li>L’augmentation de l’usage mémoire nécessaire pour stocker les différentes versions compilées d’un même code.</li>
</ul>
<p>Il y a donc une marge de progression pour améliorer les choses : on pourrait supprimer cette complexité pour rendre les performances plus prédictibles. Et c’est justement une des choses que fait WebAssembly. Dans le prochain article, je rentrerai dans le détail de ce qu’est <a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-d-assembleur">l’assembleur et ce qu’en font les compilateurs</a>.</p>
<h2>À propos de <a href="http://twitter.com/linclark">Lin Clark</a></h2>
<p>Lin est ingénieure au sein de l’équipe Mozilla Developer Relations. Elle bidouille avec JavaScript, WebAssembly, Rust et Servo et crée des bandes dessinées sur le code.</p>
<p><style>
pre { white-space: pre;}
</style></p>
Un petit cours accéléré d'assembleur
urn:md5:7529e714d3f6a95c4cc76c07f48d3cb6
2017-03-08T18:53:00+01:00
2017-03-09T20:25:11+01:00
sphinx
JavaScript
JavaScript
WASM
WebAssembly
<p><em>Cet article est le troisième d’une série de traductions d’articles écrits par <a href="http://code-cartoons.com/">Lin Clark</a> et publiés sur le blog Hacks. <a href="https://hacks.mozilla.org/2017/02/a-crash-course-in-assembly/">La version anglaise est disponible ici</a>. Merci à Jeremie et à goofy et Benjamin pour la relecture :) Si vous n’avez pas lu les autres articles, nous vous conseillons de démarrer <a href="https://tech.mozfr.org/post/2017/03/08/Une-introduction-cartoonesque-a-WebAssembly">depuis le début</a>.</em></p>
<hr />
<p>Pour comprendre comment WebAssembly fonctionne, il peut être utile de comprendre ce qu’est l’assembleur et comment les compilateurs le produisent.</p>
<p>Dans <a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-de-compilation-a-la-volee-(JIT)">l’article sur la compilation à la volée (JIT)</a>, j’expliquais que communiquer avec une machine, c’était un peu comme communiquer avec un extraterrestre.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/03-01-alien03.png" alt="Discuter avec un alien" /></p>
<p>Nous allons maintenant voir comment ce cerveau extraterrestre fonctionne, comment la machine analyse et comprend ce qui lui est communiqué.</p>
<p>Une partie du cerveau est dédié à la réflexion (effectuer des additions, des soustractions, des opérations logiques). Il y a aussi non loin de là, une partie du cerveau qui fournit de la mémoire à court terme. Enfin, il y en a une dernière qui fournit de la mémoire à long terme.</p>
<p>Ces différentes parties ont chacune un nom :</p>
<ul>
<li>La partie dédiée à la réflexion est l’unité arithmétique et logique (UAL ou ALU en anglais).</li>
<li>La mémoire à court terme est fournie par les registres.</li>
<li>La mémoire à long terme est fournie par la mémoire vive (aussi appelée RAM en anglais pour <em>Random Access Memory</em>).</li>
</ul>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/03-02-computer_architecture09.png" alt="Un schéma avec la mémoire vive (RAM), les registres, le processeur (CPU) et l'AUL (ALU)" /></p>
<p>Les phrases formées par le code machine sont appelées des instructions.
Que se passe-t-il lorsqu’une de ces instructions parvient jusqu’au cerveau ? Elle est découpée en différentes parties qui ont chacune leur signification.</p>
<p>La façon dont cette instruction est découpée est propre au câblage de ce cerveau.
Ainsi, un cerveau câblé de cette façon prendrait toujours les six premiers bits pour les transmettre à l’UAL. L’UAL, en fonction de l’emplacement des zéros et des uns, comprendrait qu’il faut additionner deux trucs.</p>
<p>Ce morceau est appelé code de l’opération (ou « <em>opcode</em> » en anglais et dans le jargon informatique) car il indique à l’UAL l’opération qui doit être exécutée.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/03-03-computer_architecture12.png" alt="Comment l'opération est stockée dans l'UAL" /></p>
<p>Ensuite, le cerveau prend les deux prochains morceaux, de trois bits chacun, afin de déterminer les nombres qu’il faut additionner. Ce sont les adresses des registres à utiliser.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/03-04-computer_architecture17.png" alt="L'utilisation des registres dans l'opération" /></p>
<p>Vous voyez les annotations écrites au-dessus du code machine ? Elles sont ici pour nous aider, nous les humains, à mieux comprendre ce qui se passe. Ces annotations sont de l’assembleur. Ce sont des symboles mnémoniques qui permettent aux humains de donner du sens au code machine.</p>
<p>On peut voir ici qu’il existe une relation assez directe entre l’assembleur et le code machine de cette machine. À cause de cette relation, il existe différentes sortes d’assembleurs, chacun correspondant au type d’architecture d’une machine donnée. Lorsqu’on utilise une machine avec une architecture différente, il est fort probable qu’on ait besoin d’un autre « dialecte » d’assembleur.</p>
<p>Notre traduction ne vise donc pas une seule cible. Il n’existe pas de langue unique qui soit du code machine. Il existe différents codes machines. À l’instar de nous qui parlons différentes langues, les machines parlent différents codes.</p>
<p>Pour la traduction humain-extraterrestre, on pourrait partir de l’anglais, du russe ou du mandarin comme langue source et le traduire en langue extraterrestre A ou en langue extraterrestre B. En programmation, on peut poursuivre l’analogie en partant d’un programme écrit en C ou en C++ ou en Rust et vouloir le traduire en x86 ou en ARM.</p>
<p>On veut être capable de traduire depuis n’importe lequel de ces langages de programmation de haut niveau vers n’importe lequel de ces langages assembleurs (dont chacun correspond à une architecture différente). Une solution à ce problème serait de créer un ensemble de traducteurs qui permettent de passer de chaque langage de programmation à chaque langage assembleur.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/03-05-langs05.png" alt="Un échange impossible entre deux polyglottes ?" /></p>
<p>Ça se révèle plutôt inefficace. Pour résoudre ce problème, la plupart des compilateurs introduisent au moins une couche intermédiaire. Le compilateur prend en entrée le langage de programmation haut niveau et le traduit en quelque chose qui n’est ni un langage de haut niveau, ni du code machine. C’est ce qu’on appelle la représentation intermédiaire (RI ou IR en anglais).</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/03-06-langs06.png" alt="Un langage intermédiaire pour faciliter les échanges" /></p>
<p>Ça signifie que le compilateur peut prendre n’importe lequel de ces langages de haut niveau et le traduire dans un des langages de RI. À partir de là, un autre composant du compilateur peut traiter cette RI et la compiler en quelque chose de plus spécifique à l’architecture cible.</p>
<p>La partie frontale du compilateur traduit le langage de programmation de haut niveau en RI et la partie en arrière-plan traite cette RI pour la transformer en code assembleur pour l’architecture cible.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/03-07-langs09.png" alt="Chacun sa tâche : le front-end d'un côté et le back-end de l'autre" /></p>
<h2>Conclusion</h2>
<p>Voici ce qu’est l’assembleur et comment les compilateurs traduisent des langages de programmation de haut niveau en assembleur. Dans le prochain article, nous verrons <a href="https://tech.mozfr.org/post/2017/03/08/Creer-et-manipuler-des-modules-WebAssembly">comment WebAssembly s’inscrit dans cet ensemble</a>.</p>
<h2>À propos de <a href="http://twitter.com/linclark">Lin Clark</a></h2>
<p>Lin est ingénieure au sein de l’équipe Mozilla Developer Relations. Elle bidouille avec JavaScript, WebAssembly, Rust et Servo et crée des bandes dessinées sur le code.</p>
<p><style>
pre { white-space: pre;}
</style></p>
Créer et manipuler des modules WebAssembly
urn:md5:f81ad560c7f4548c1d6d522f0616a34a
2017-03-08T18:52:00+01:00
2017-03-09T20:24:41+01:00
sphinx
JavaScript
JavaScript
WASM
WebAssembly
<p><em>Cet article est le quatrième d’une série de traductions d’articles écrits par <a href="http://code-cartoons.com/">Lin Clark</a> et publiés sur le blog Hacks. <a href="https://hacks.mozilla.org/2017/02/creating-and-working-with-webassembly-modules/">La version anglaise est disponible ici</a>. Merci à dattaz, Jeremie et à goofy et Benjamin pour la relecture :) Si vous n’avez pas lu les autres articles, nous vous conseillons de démarrer <a href="https://tech.mozfr.org/post/2017/03/08/Une-introduction-cartoonesque-a-WebAssembly">depuis le début</a>.</em></p>
<hr />
<p>WebAssembly est un outil permettant d’exécuter d’autres langages que JavaScript sur des pages web. Auparavant, lorsqu’on souhaitait exécuter du code dans le navigateur afin d’interagir avec les différents composants d’une page web, JavaScript était la seule solution.</p>
<p>C’est pourquoi, lorsqu’on dit que WebAssembly est rapide, on compare sa rapidité à celle de JavaScript. Cela ne signifie pas pour autant qu’il faut utiliser l’un ou l’autre et pas les deux.</p>
<p>En fait, on s’attend à ce que les développeurs utilisent aussi bien WebAssembly et JavaScript au sein de la même application. Même si vous n’écrivez pas du WebAssembly, vous pouvez en tirer parti.</p>
<p>Les modules WebAssembly définissent des fonctions qui peuvent être utilisées depuis JavaScript. Si aujourd’hui, vous téléchargez un module npm comme lodash et que vous utilisez les fonctions qu’il fournit via son API, demain, vous serez aussi capable de télécharger et d’exploiter des modules WebAssembly.</p>
<p>Voyons maintenant comment créer des modules WebAssembly et comment les utiliser depuis JavaScript.</p>
<h2>Quelle place pour WebAssembly ?</h2>
<p>Dans <a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-d-assembleur">l’article précédent sur l’assembleur</a>, nous avons vu comment les compilateurs traitaient les langages de programmation de haut niveau pour les traduire en code machine.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/04-01-langs09.png" alt="L'utilisation de la représentation intermédiaire avec les différents composants" /></p>
<p>Quel est le rôle de WebAssembly dans cet environnement ?</p>
<p>On peut penser qu’il s’agit simplement d’un autre langage assembleur vers lequel compiler. D’une certaine façon, c’est vrai mais chacun de ces autres langages (x86, ARM) correspond à une architecture machine particulière.</p>
<p>Lorsqu’on envoie du code à exécuter sur une machine à travers le Web, on ne connaît pas l’architecture cible sur laquelle le code sera exécuté.</p>
<p>WebAssembly est donc légèrement différent des autres langages assembleurs. Il s’agit d’un langage machine pour une machine théorique et non pour une machine physique.</p>
<p>Pour cette raison, les instructions WebAssembly sont parfois appelées instructions virtuelles. Elles sont beaucoup plus proches du code machine que n’importe quel code source JavaScript avec un langage qui ressemble à l’intersection de ce qui est effectué efficacement sur les architectures matérielles répandues. Mais ces instructions ne correspondent pas non plus à un langage machine spécifique d’une architecture matérielle donnée.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/04-02-langs08.png" alt="La place de WebAssembly par rapport à cette représentation intermédiaire" /></p>
<p>C’est le navigateur qui télécharge le code WebAssembly. Ensuite, il effectue la transition (plus courte) entre WebAssembly et le code assembleur de la machine sur laquelle il est exécuté.</p>
<h2>Compiler vers .wasm</h2>
<p>L’ensemble d’outils de compilation qui prend le mieux en charge WebAssembly actuellement s’appelle LLVM. Il existe différents environnements frontaux (<em>front-ends</em>) ou de fin de chaîne (<em>back-ends</em>) qui peuvent être utilisés avec LLVM.</p>
<p><em>Note : la plupart des développeurs de modules WebAssembly utiliseront des langages tels que C et Rust avant de compiler en WebAssembly. Toutefois, il existe d’autres méthodes qui permettent de créer des modules WebAssembly. Il existe par exemple <a href="https://github.com/rsms/wasm-util">un outil expérimental qui permet de compiler un module WebAssembly en utilisant TypeScript</a>. On peut aussi <a href="https://developer.mozilla.org/fr/docs/WebAssembly/Understanding_the_text_format">écrire directement du WebAssembly en utilisant sa représentation textuelle</a>.</em></p>
<p>Prenons le scénario où on développe un module en C pour le compiler en WebAssembly. On pourrait utiliser le module frontal clang pour passer de la représentation en C à la représentation intermédiaire LLVM. Une fois qu’on a obtenu la RI LLVM, LLVM peut la comprendre et effectuer certaines optimisations.</p>
<p>Pour passer de la RI (<a href="https://fr.wikipedia.org/wiki/Langage_interm%C3%A9diaire#Repr.C3.A9sentation_interm.C3.A9diaire">représentation intermédiaire</a>) LLVM à celle de WebAssembly, il nous faut un composant de fin de chaîne. Il existe un composant en cours de développement pour le projet LLVM. Ce composant devrait être finalisé sous peu mais reste délicat à utiliser aujourd’hui.</p>
<p>Il existe un autre outil, intitulé Emscripten, qui est actuellement plus facile à utiliser. Cet outil possède son propre composant de fin de chaîne qui peut produire du code WebAssembly en compilant vers une cible intermédiaire (appelée asm.js) puis en convertissant ce résultat en WebAssembly. Sous le capot d’Emscripten, on retrouve en fait LLVM et on peut donc passer d’un composant de fin de chaîne à l’autre à partir d’Emscripten.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/04-03-toolchain07.png" alt="L'ensemble d'outils de compilation" /></p>
<p>Emscripten inclut de nombreux outils et bibliothèques supplémentaires pour le portage de bases de code en C/C++. Il s’agit donc plus d’un kit de développement logiciel (NDT ou SDK pour <em>Software Developer Kit</em>, plus fréquemment utilisé) que d’un simple compilateur. Les développeurs système ont par exemple l’habitude d’utiliser un système de fichiers depuis lequel on peut lire des fichiers et sur lequel on peut en écrire. Pour ce faire, Emscripten peut simuler un système de fichier en utilisant IndexedDB.</p>
<p>Quels que soient les outils que vous utilisez, le résultat final sera un fichier dont l’extension sera .wasm. Nous verrons par la suite la structure d’un fichier .wasm mais pour commencer, voyons comment on peut l’utiliser en JavaScript.</p>
<h2>Charger un module WebAssembly en JavaScript</h2>
<p>Le fichier .wasm contient le module WebAssembly et peut être chargé en JavaScript. Au moment de l’écriture de ces lignes, le processus de chargement est un peu compliqué :</p>
<pre><code>function fetchAndInstantiate(url, importObject) {
return fetch(url).then(response =>
response.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, importObject)
).then(results =>
results.instance
);
}
</code></pre>
<p>Pour plus de détails, vous pouvez consulter <a href="https://developer.mozilla.org/en-US/docs/WebAssembly">la documentation associée</a>.</p>
<p>Nous travaillons à simplifier cette étape en améliorant les outils et en intégrant les modules WebAssembly dans des gestionnaires de modules comme webpack ou dans des outils de chargement comme SystemJS. Nous pensons que le chargement des modules WebAssembly peut être aussi simple que celui que nous connaissons aujourd’hui pour les modules JavaScript.</p>
<p>Il existe toutefois une différence fondamentale entre les modules WebAssembly et les modules JavaScript. Actuellement, les fonctions WebAssembly permettent uniquement d’utiliser des nombres (entiers ou flottants) comme paramètres et comme valeurs de retour.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/04-04-memory04.png" alt="On ne passe que des entiers" /></p>
<p>Pour manipuler des types de donnée plus complexes (des chaînes de caractères par exemple), il faut utiliser la mémoire du module WebAssembly.</p>
<p>Si vous travaillez principalement avec JavaScript, l’accès direct à la mémoire n’est pas forcément un concept très familier. Des langages de plus bas niveau tels que C, C++ ou Rust permettent de gérer la mémoire manuellement. La mémoire d’un module WebAssembly permet de simuler le tas (NDT ou « <em>heap</em> » en anglais, également usité) qu’on trouverait dans ces langages.</p>
<p>Pour cela, on utilise un type d’objet JavaScript : les <code>ArrayBuffer</code>. Un tableau tampon (NDT « <em>array buffer</em> » en anglais) est un tableau d’octets. Les indices des positions dans ce tableau servent d’adresses mémoire.</p>
<p>Si on veut passer une chaîne de caractères depuis le code JavaScript vers le code WebAssembly, on convertit les caractères en utilisant les codes d’encodage correspondants. Ensuite, on écrit ces codes dans le tableau représentant la mémoire. Les indices du tableau étant des entiers, on peut les passer à la fonction WebAssembly. Cela permet ainsi d’utiliser l’indice du première caractère de la chaîne comme un pointeur.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/04-05-memory12.png" alt="Pour compenser, on sert des entiers comme pointeurs" /></p>
<p>Il est probable que lorsque quelqu’un développera un module WebAssembly destiné à des développeurs web, il ajoutera une enveloppe (wrapper) avec des fonctions utilitaires pour ce module afin que le développeur web n’ait pas à se soucier de la gestion de la mémoire.</p>
<p>Si vous souhaitez en savoir plus, n’hésitez pas à consulter notre documentation à propos de la gestion de la mémoire en <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/WebAssembly/Memory">WebAssembly</a>.</p>
<h2>La structure d’un fichier .wasm</h2>
<p>Si vous écrivez du code avec un langage de programmation de haut niveau pour le compiler en WebAssembly, vous n’avez pas besoin de savoir quelle est la structure d’un module WebAssembly. Ceci étant dit, comprendre les notions de base s’avère souvent utile.</p>
<p>Si ce n’est pas déjà fait, nous vous conseillons de lire <a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-d-assembleur">l’article précédent sur l’assembleur (le troisième de cette série)</a>.</p>
<p>Voici une fonction, écrite en C, que nous allons transformer en WebAssembly :</p>
<pre><code>int add42(int num) {
return num + 42;
}
</code></pre>
<p>Vous pouvez essayer d’utiliser <a href="http://mbebenita.github.io/WasmExplorer/">WASM Explorer</a> afin de compiler cette fonction.</p>
<p>Si vous ouvrez le fichier .wasm obtenu (et que votre éditeur le permet), vous verrez alors quelque chose comme :</p>
<pre><code>00 61 73 6D 0D 00 00 00 01 86 80 80 80 00 01 60
01 7F 01 7F 03 82 80 80 80 00 01 00 04 84 80 80
80 00 01 70 00 00 05 83 80 80 80 00 01 00 01 06
81 80 80 80 00 00 07 96 80 80 80 00 02 06 6D 65
6D 6F 72 79 02 00 09 5F 5A 35 61 64 64 34 32 69
00 00 0A 8D 80 80 80 00 01 87 80 80 80 00 00 20
00 41 2A 6A 0B
</code></pre>
<p>Ce qu’on voit ici est la représentation « binaire » du module (avec des guillemets de précaution car généralement, le contenu est affiché en notation hexadécimale, mais on peut facilement la convertir en notation binaire ou dans un format plus lisible pour un humain).</p>
<p>Voici par exemple à quoi ressemble <code>num + 42</code> :</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/04-06-hex_binary_asm01.png" alt="La décomposition de l'opération selon les différents niveaux de représentation" /></p>
<h3>Le fonctionnement du code : un processeur à pile</h3>
<p>Au cas où vous vous demanderiez, voici ce que feraient ces instructions :</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/04-07-hex_binary_asm02.png" alt="La décomposition de l'opération selon les différents niveaux de représentation" /></p>
<ul>
<li>On prend la valeur du premier paramètre et on la met sur la pile.</li>
<li>On met une valeur constante sur la pile</li>
<li>On prend les deux valeurs sur le haut de la pile, on les additionne et on met le résultat sur la pile.</li>
</ul>
<p>On peut voir ici que l’opération <code>add</code> n’indique pas l’origine des valeurs qu’elle manipule. En effet, WebAssembly est ce qu’on appelle un automate à pile. Cela signifie que les valeurs nécessaires à une opération sont empilées avant que l’opération soit appliquée.</p>
<p>Pour l’addition, WebAssembly sait combien de valeurs sont nécessaires. L’addition a besoin de deux valeurs et on prend donc les deux valeurs situées sur le haut de la pile. Cela signifie que l’instruction pour l’addition peut être courte (un seul octet) car il n’est pas nécessaire d’indiquer les registres de source ou de destination. Cela permet de réduire la taille du fichier .wasm et ainsi de réduire le temps nécessaire à son téléchargement.</p>
<p>Bien que WebAssembly soit conçu comme un automate à pile, ce n’est pas comme ça qu’il fonctionne réellement sur la machine physique. Lorsque le navigateur traduit le code WebAssembly en code machine pour l’architecture sur laquelle il est exécuté, le code utilisera les registres. Étant donné que le code WebAssembly ne détaille pas les registres, cela fournit une plus grande flexibilité au navigateur qui peut choisir la meilleure stratégie d’allocation des registres pour la machine utilisée.</p>
<h3>Les sections du module</h3>
<p>En plus de la fonction add42, on trouve d’autres parties dans le fichier .wasm. Ces parties sont appelées des « sections ». Certaines de ces sections sont nécessaires quel que soit le module et d’autres sont optionnelles.</p>
<p>Voici la liste des sections obligatoires :</p>
<ol>
<li><strong>Type</strong> : cette section contient la signature des fonctions qui sont définies dans ce module ou importées.</li>
<li><strong>Function</strong> : cette section contient un index de chaque fonction qui est définie dans ce module.</li>
<li><strong>Code</strong> : cette section contient le corps de chaque fonction définie dans ce module.</li>
</ol>
<p>Voici la liste des sections optionnelles :</p>
<ol>
<li><strong>Export</strong> : cette section permet de rendre accessibles la mémoire, les tables et les variables globales pour d’autres modules WebAssembly et pour JavaScript. Cela permet d’avoir des modules compilés séparément et de les lier dynamiquement. C’est en quelque sorte la version WebAssembly d’une .dll</li>
<li><strong>Import</strong> : cette section définit les fonctions, mémoires, tables et variables globales qui doivent être importées depuis d’autres modules WebAssembly ou depuis du JavaScript.</li>
<li><strong>Start</strong> : une fonction qui sera automatiquement exécutée au chargement du module WebAssembly (l’équivalent d’une fonction main)</li>
<li><strong>Global</strong> : cette section définit les variables globales du module.</li>
<li><strong>Memory</strong> : cette section définit la mémoire utilisée par ce module.</li>
<li><strong>Table</strong> : cette section permet de faire un pont avec des fonctions situées en dehors du module WebAssembly telles que des fonctions JavaScript. Cela est notamment utile pour permettre des appels de fonction indirects.</li>
<li><strong>Data</strong> : cette section initialise la mémoire locale ou importée.</li>
<li><strong>Element</strong> : cette section initialise une table locale ou importée.</li>
</ol>
<p>Pour plus de détails quant au fonctionnement des sections, vous trouverez <a href="https://rsms.me/wasm-intro">plus d’explications dans la documentation</a>.</p>
<h2>La suite</h2>
<p>Maintenant qu’on sait comment fonctionnent les modules WebAssembly, voyons <a href="https://tech.mozfr.org/post/2017/03/08/D-ou-vient-la-rapidite-de-WebAssembly">pourquoi WebAssembly est rapide</a>.</p>
<h2>À propos de <a href="http://twitter.com/linclark">Lin Clark</a></h2>
<p>Lin est ingénieure au sein de l’équipe Mozilla Developer Relations. Elle bidouille avec JavaScript, WebAssembly, Rust et Servo et crée des bandes dessinées sur le code.</p>
<p><style>
pre { white-space: pre;}
</style></p>
D'où vient la rapidité de WebAssembly ?
urn:md5:93bcf99b0ae17807405caedc30f0d3d2
2017-03-08T18:51:00+01:00
2017-03-09T20:24:21+01:00
sphinx
JavaScript
JavaScript
WASM
WebAssembly
<p><em>Cet article est le cinquième d’une série de traductions d’articles écrits par <a href="http://code-cartoons.com/">Lin Clark</a> et publiés sur le blog Hacks. <a href="https://hacks.mozilla.org/2017/02/what-makes-webassembly-fast/">La version anglaise est disponible ici</a>. Merci à goofy et Benjamin pour la relecture :) Si vous n’avez pas lu les autres articles, nous vous conseillons de démarrer <a href="https://tech.mozfr.org/post/2017/03/08/Une-introduction-cartoonesque-a-WebAssembly">depuis le début</a>.</em></p>
<hr />
<p>Dans <a href="https://tech.mozfr.org/post/2017/03/08/Creer-et-manipuler-des-modules-WebAssembly">l’article précédent</a>, nous avons vu que la programmation avec WebAssembly et la programmation en JavaScript ne s’excluaient pas mutuellement. Nous ne pensons pas qu’il y aura beaucoup de développeurs qui écriront des bases de code complètes en WebAssembly.</p>
<p>Les développeurs n’ont donc pas à choisir entre WebAssembly et JavaScript pour développer leurs applications. Toutefois, nous pensons que les développeurs échangeront certaines parties du code JavaScript pour des modules WebAssembly.</p>
<p>Ainsi, l’équipe qui travaille sur React pourrait remplacer le code du DOM virtuel avec une version WebAssembly. Cela n’aurait aucun impact pour les personnes qui utilisent React. Leurs applications continueraient de fonctionner comme avant, tout en bénéficiant des avantages de WebAssembly.</p>
<p>Pourquoi les développeurs de React passeraient-ils cette partie du code sur un composant WebAssembly ? Parce que WebAssembly est plus rapide. Certes… mais pourquoi est-il plus rapide ?</p>
<h2>Quel est l’état actuel des performances de JavaScript ?</h2>
<p>Avant de pouvoir comprendre les différences de performance entre JavaScript et WebAssembly, il faut comprendre comment fonctionne un moteur JavaScript.
Ce diagramme dresse un rapide tableau des performances actuellement observées au démarrage d’une application.</p>
<p><em>Le temps consommé par le moteur JavaScript pour chacune de ces pages dépend du code JavaScript de la page. Ce diagramme n’a pas pour but d’indiquer des mesures de performance précises et chiffrées, mais de fournir un modèle général pour comparer les performances de JavaScript et celles de WebAssembly sur une phase analogue.</em></p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-01-diagram_now01.png" alt="Les différentes tâches à réaliser pour lancer une application JavaScript" /></p>
<p>Chaque barre indique le temps consommé pour une tâche donnée.</p>
<ul>
<li>Analyse (<em>parsing</em>) : le temps nécessaire pour analyser le code et le transformer en quelque chose qui puisse être exécuté par l’interpréteur.</li>
<li>Compilation et optimisation : le temps consommé par le compilateur et l’optimiseur. Certaines des tâches d’optimisation ne sont pas exécutées sur le thread principal, le temps correspondant n’est pas inclus ici.</li>
<li>Ré-optimisation : le temps que passe le compilateur à la volée (JIT) à réajuster les hypothèses incorrectes, optimiser le code à nouveau et rediriger l’exécution vers un code moins optimisé.</li>
<li>Exécution : le temps nécessaire à l’exécution du code.</li>
<li>Ramasse-miettes : le temps passé à nettoyer la mémoire.</li>
</ul>
<p>Une chose importante à noter : ces tâches ne forment pas chacune un bloc distinct et elles ne s’exécutent pas non plus dans un ordre bien défini. On a plutôt des tâches qui se recoupent, un peu d’analyse puis de l’exécution, puis de la compilation et encore de l’analyse et ensuite de l’exécution, etc.</p>
<p>Cette décomposition représente une avancée fondamentale par rapport aux débuts de JavaScript où on avait plutôt quelque chose comme :</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-02-diagram_past01.png" alt="L'enchaînement n'est pas si linéaire" /></p>
<p>Au début, lorsqu’il y avait uniquement un interpréteur qui exécutait le code JavaScript, la phase d’exécution était plutôt lente. Lorsque les compilateurs à la volée sont apparus, cela a fortement réduit le temps d’exécution.</p>
<p>Le prix à payer est qu’il faut désormais surveiller et compiler le code. Si les développeurs avaient continué à développer du JavaScript sur des projets de tailles analogues, les temps d’analyse et de compilation seraient très courts, mais ces améliorations de performance ont conduit les développeurs à créer des applications plus vastes. Il y a donc encore de la marge pour des améliorations.</p>
<h2>Et WebAssembly alors ?</h2>
<p>Voici une approximation qui illustre comment WebAssembly se comporterait pour une application web typique.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-03-diagram_future01.png" alt="JS contre WASM, qui est le plus rapide ?" /></p>
<p>Il existe quelques variations entre les navigateurs pour ces différentes phases. Ici, j’utilise SpiderMonkey (NDT le moteur JavaScript de Firefox) comme modèle.</p>
<h2>Le téléchargement (<em>fetching</em>)</h2>
<p>Cela n’est pas montré dans le diagramme mais lorsqu’on télécharge le fichier depuis le serveur, cela prend également du temps.</p>
<p>WebAssembly étant plus compact que JavaScript, la récupération des fichiers est plus rapide. Bien que les algorithmes de compression puissent drastiquement réduire la taille d’un paquet de ressources JavaScript, la représentation binaire compressée d’un code WebAssembly sera tout de même plus légère.</p>
<p>Cela signifie qu’il faut moins de temps pour transférer les ressources depuis le serveur vers le client, notamment pour les connexions avec un débit moins élevé.</p>
<h2>L’analyse (<em>parsing</em>)</h2>
<p>Une fois que le navigateur a récupéré le fichier, le code source JavaScript est analysé afin de créer un arbre syntaxique abstrait (NDT <em>Abstract Syntax Tree</em> ou AST en anglais).</p>
<p>Les navigateurs effectuent cette analyse uniquement lorsqu’ils en ont besoin et se contentent de créer des points de références (ou <em>stubs</em>) pour les fonctions qui n’ont pas encore été appelées. À partir de cette étape, l’arbre syntaxique abstrait est converti en une représentation intermédiaire (aussi appelée <em>bytecode</em>) qui est spécifique au moteur JavaScript.</p>
<p>En comparaison, WebAssembly n’a pas besoin de cette phase de transformation, car il s’agit déjà d’une représentation intermédiaire. Il suffit qu’il soit décodé et validé pour vérifier qu’il ne contient pas d’erreur.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-04-diagram_compare02.png" alt="Comparaison approximative entre JS et WASM pour l'analyse (parsing)" /></p>
<h2>La compilation et l’optimisation</h2>
<p>Comme nous l’avons vu dans l’article sur <a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-de-compilation-a-la-volee-(JIT)">les compilateurs à la volée (JIT)</a>, JavaScript est compilé pendant l’exécution du code. Selon les types utilisés pendant l’exécution, on peut avoir plusieurs versions du même code qui ont besoin d’être compilées.</p>
<p>Les différents navigateurs ont chacun leur approche pour compiler du code WebAssembly. Certains navigateurs lancent une compilation minimale du code WebAssembly avant de l’exécuter, d’autres utilisent une compilation à la volée.</p>
<p>Dans tous les cas, à l’état initial, WebAssembly est déjà beaucoup plus proche du code machine. Les types de données font par exemple partie du programme. Cette phase est plus rapide pour plusieurs raisons :</p>
<ul>
<li>Le compilateur n’a pas besoin de passer du temps à exécuter le code pour surveiller les types à utiliser avant de commencer à compiler un code optimisé.</li>
<li>Le compilateur n’a pas besoin de compiler différentes versions du même code selon les différents types observés.</li>
<li>Des optimisations ont déjà été appliquées en amont par LLVM. Il faut donc moins de travail pour la compilation et l’optimisation.</li>
</ul>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-05-diagram_compare03.png" alt="Comparaison approximative entre JS et WASM pour la compilation et l'optimisation" /></p>
<h2>La deuxième passe d’optimisation</h2>
<p>Il arrive parfois que le compilateur à la volée doive rejeter une version du code pour l’observer de nouveau.</p>
<p>Cela se produit lorsque les hypothèses utilisées par le compilateur à la volée selon le code exécuté s’avèrent incorrectes. C’est par exemple le cas lorsque des variables utilisées dans une boucle sont différentes par rapport aux itérations précédentes ou lorsqu’une nouvelle fonction est insérée dans la chaîne de prototypes.</p>
<p>Cela consomme du temps pour deux raisons. Premièrement, il faut repasser du code optimisé au code de base, ce qui prend du temps. Deuxièmement, si une fonction continue d’être appelée fréquemment, le compilateur à la volée peut choisir de la passer à nouveau à l’optimiseur : on a alors le coût en temps d’une deuxième compilation.</p>
<p>En WebAssembly, les paramètres tels que les types sont explicites. Le compilateur à la volée n’a donc pas besoin d’émettre des hypothèses sur les types à partir des données récupérées pendant l’exécution. Cela signifie qu’il n’est pas nécessaire de passer par ces cycles de ré-optimisation.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-06-diagram_compare04.png" alt="Comparaison approximative entre JS et WASM pour la deuxième passe d'optimisation" /></p>
<h2>L’exécution</h2>
<p>Il est tout à fait possible d’écrire du JavaScript qui est exécuté de façon performante. Pour cela, il est nécessaire de connaître les optimisations qui sont réalisées par le compilateur à la volée. Il faut par exemple savoir comment écrire du code afin que le compilateur puisse opérer une spécialisation de type (cf. <a href="https://tech.mozfr.org/post/2017/03/08/Un-petit-cours-accelere-de-compilation-a-la-volee-(JIT)">l’article sur la compilation JIT</a>).</p>
<p>Cependant, la plupart des développeurs ne connaissent pas ces détails de compilation. Et même pour les développeurs qui connaissent ces notions, obtenir le bon équilibre est parfois difficile. Certaines méthodes utilisées pour rendre le code plus lisible (comme créer des tâches abstraites génériques qui fonctionnent quel que soit le type utilisé) vont à l’encontre du compilateur lorsqu’il s’agit d’optimiser le code.</p>
<p>De plus, les optimisations utilisées par un compilateur JIT varient d’un navigateur à l’autre et développer « pour » un navigateur donné peut ne pas avoir l’effet escompté voire l’effet inverse…</p>
<p>Étant donné ces différentes raisons, l’exécution de code WebAssembly est généralement plus rapide. La plupart des optimisations réalisées par le compilateur à la volée pour JavaScript (comme la spécialisation de type) ne sont pas nécessaires pour WebAssembly.</p>
<p>En outre, WebAssembly a été conçu comme une cible de compilation. Cela signifie qu’il a été conçu pour être généré par des compilateurs et pas pour être écrit par des humains.</p>
<p>Les développeurs n’ayant pas besoin de programmer directement en WebAssembly, celui-ci peut utiliser un ensemble d’instructions plus adaptées aux machines. Selon la tâche réalisée par votre code, ces instructions peuvent s’exécuter 10 % à 800 % plus rapidement.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-07-diagram_compare05.png" alt="Comparaison approximative entre JS et WASM pour l'exécution" /></p>
<h2>La gestion du ramasse-miettes</h2>
<p>En JavaScript, le développeur n’a pas à se soucier de la mémoire utilisée par des variables devenues inutiles. C’est le moteur JavaScript qui s’occupe automatiquement de cette tâche grâce à ce qu’on appelle un ramasse-miettes.</p>
<p>Cela peut toutefois poser problème si on souhaite avoir des performances prédictibles. On ne maîtrise pas le moment où le ramasse-miettes sera actif et ça peut très bien être au mauvais moment. La plupart des navigateurs sont désormais assez affutés pour déclencher le ramasse-miettes quand il faut mais cela représente toujours une dépense de ressources et de temps qui peut ralentir l’exécution du code.</p>
<p>À l’heure actuelle, WebAssembly fonctionne sans aucun ramasse-miettes. La mémoire doit être gérée manuellement (comme c’est le cas avec des langages comme C ou C++). Bien que cela rende le développement plus complexe, cela permet également d’obtenir des performances plus stables.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/05-08-diagram_compare06.png" alt="Comparaison approximative entre JS et WASM pour la gestion du ramasse-miettes" /></p>
<h2>Conclusion</h2>
<p>À de nombreux égards, WebAssembly est plus rapide que JavaScript :</p>
<ul>
<li>La récupération des ressources WebAssembly prend moins de temps, car le code WebAssembly est plus compact que le code JavaScript même lorsque ce dernier est compressé.</li>
<li>Le décodage du code WebAssembly prend moins de temps que l’analyse syntaxique du code JavaScript.</li>
<li>La compilation et l’optimisation du code WebAssembly prend moins de temps car celui-ci est plus proche du code machine et a déjà subi certaines optimisations du générateur de code wasm (par ex. LLVM) en amont.</li>
<li>Les passes d’optimisation successives ne sont pas nécessaires en WebAssembly, car les types et les autres informations font partie du code. Le moteur JavaScript n’a donc pas besoin d’émettre des hypothèses comme il le fait pour du code JavaScript classique.</li>
<li>L’exécution est généralement plus rapide, car il y a moins d’astuces/pièges à connaître pour écrire du code qui soit cohérent et performant. De plus l’ensemble des instructions WebAssembly est plus adapté aux machines.</li>
<li>Le ramasse-miettes n’est pas utilisé avec WebAssembly, car la mémoire est gérée manuellement.</li>
</ul>
<p>C’est pour ces différentes raisons que dans de nombreux cas, WebAssembly sera plus performant que JavaScript pour réaliser une même tâche.</p>
<p>Il existe certains cas où WebAssembly n’est pas aussi performant qu’il devrait l’être. Certains changements sont également en cours pour rendre WebAssembly plus rapide. C’est ce que nous verrons <a href="https://tech.mozfr.org/post/2017/03/08/WebAssembly-aujourd-hui-et-demain">dans le prochain article</a>.</p>
<h2>À propos de <a href="http://twitter.com/linclark">Lin Clark</a></h2>
<p>Lin est ingénieure au sein de l’équipe Mozilla Developer Relations. Elle bidouille avec JavaScript, WebAssembly, Rust et Servo et crée des bandes dessinées sur le code.</p>
<p><style>
pre { white-space: pre;}
</style></p>
WebAssembly aujourd'hui et demain
urn:md5:cf9cbb2c0d5fe4495c4e9a24a6883add
2017-03-08T18:50:00+01:00
2017-03-09T20:23:26+01:00
sphinx
JavaScript
JavaScript
WASM
WebAssembly
<p><em>Cet article est le sixième d’une série de traductions d’articles écrits par <a href="http://code-cartoons.com/">Lin Clark</a> et publiés sur le blog Hacks. <a href="https://hacks.mozilla.org/2017/02/where-is-webassembly-now-and-whats-next/">La version anglaise est disponible ici</a>. Merci à dattaz et à goofy et Benjamin pour la relecture :) Si vous n’avez pas encore lu les autres articles, nous vous recommandons de commencer <a href="https://tech.mozfr.org/post/2017/03/08/Une-introduction-cartoonesque-a-WebAssembly">depuis le début</a>.</em></p>
<hr />
<p>Le 28 février, <a href="https://lists.w3.org/Archives/Public/public-webassembly/2017Feb/0002.html">les quatre navigateurs principaux ont annoncé leur consensus</a> sur le fait que WebAssembly était suffisamment avancé pour fournir un produit viable. Ceci fournit une version initiale stable que les navigateurs peuvent implémenter et mettre à disposition.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/06-01-logo_party01.png" alt="Les différents navigateurs sont d'accord pour WASM" /></p>
<p>Ceci fournit un noyau stable que les navigateurs peuvent rendre disponible. Ce noyau ne contient pas toutes les fonctionnalités prévues par le groupe communautaire, mais il en contient suffisamment pour que WebAssembly soit rapide et utilisable.</p>
<p>Avec ceci, les développeurs peuvent commencer à diffuser du code WebAssembly. Pour les versions antérieures des navigateurs, les développeurs peuvent fournir une version asm.js du code. asm.js étant un sous-ensemble de JavaScript, tout moteur JavaScript pourra exécuter ce code. Avec Emscripten, vous pouvez compiler la même application vers WebAssembly et vers asm.js</p>
<p>Même dans cette version initiale, WebAssembly sera rapide. Il devrait devenir plus rapide à l’avenir, grâce à un ensemble de corrections et de nouvelles fonctionnalités.</p>
<h2>Améliorer les performances de WebAssembly dans les navigateurs</h2>
<p>Certaines améliorations de vitesse viendront au fur et à mesure que les navigateurs amélioreront la prise en charge de WebAssembly dans leurs moteurs. Les fournisseurs de navigateurs travaillent sur ces différents problèmes de manière indépendante.</p>
<h3>Des appels de fonction plus rapides entre JavaScript et WebAssembly</h3>
<p>Actuellement, appeler une fonction WebAssembly dans du code JavaScript est plus lent que ce qu’on pourrait espérer. C’est à cause de ce qu’on appelle le « trampolinage ». Le compilateur JIT ne sait pas comment interagir directement avec WebAssembly et il doit donc rediriger le code WebAssembly vers quelque chose qui sait le faire. Le composant en question est un code lent dans le moteur et qui prépare à exécuter le code WebAssembly optimisé.</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/06-02-trampoline01.png" alt="Vers l'infini et WebAssembly (oui je sors)" /></p>
<p>Cette étape peut être jusqu’à 100 fois plus lente que ce qu’on aurait obtenu si le compilateur JIT savait comment l’interpréter directement.</p>
<p>Vous ne remarquez pas ce délai si vous passez une seule tâche conséquente au module WebAssembly. En revanche, si vous avez de nombreux allers-retours entre WebAssembly et JavaScript (comme lorsqu’on effectue de petites tâches), ce délai sera remarquable.</p>
<h3>Un temps de chargement plus rapide</h3>
<p>Les compilateurs à la volée (JIT) doivent négocier un compromis entre des temps de chargement plus rapides et des temps d’exécution plus rapides. Si on passe plus de temps à compiler et optimiser le code en amont, cela accélèrera l’exécution mais ralentira le démarrage du programme.</p>
<p>De nombreux travaux sont en cours pour améliorer cet équilibre entre la compilation en amont (qui s’assure qu’il n’y a pas de ralentissement lorsque le code a démarré son exécution) et l’hypothèse simple comme quoi la plupart du code ne sera pas exécuté suffisamment pour que l’optimisation soit rentable.</p>
<p>Puisque WebAssembly n’a pas besoin de spéculer sur les types qui seront utilisés, les moteurs n’ont pas à surveiller les types manipulés lors de l’exécution. Délestés de cette tâche, ils peuvent faire autre chose et notamment compiler et exécuter le code en parallèle.</p>
<p>De plus, de récents ajouts à l’API JavaScript permettront d’effectuer une compilation au fil de l’eau (en utilisant des <em>streams</em>). Cela signifie que le moteur pourra commencer la compilation alors que le module est toujours en cours de téléchargement.</p>
<p>Dans Firefox, nous travaillons sur un système à deux compilateurs. Un premier compilateur est exécuté en amont et effectue une première optimisation du code, plutôt efficace. Pendant que ce code est exécuté, un deuxième compilateur effectue une optimisation poussée en arrière-plan. Lorsque la version pleinement optimisée est disponible, on bascule l’exécution sur cette version.</p>
<h2>Ajouter des fonctionnalités à la spécification après cette phase initiale</h2>
<p>Un des objectifs de WebAssembly est de construire la spécification au fur et à mesure, par petits morceaux, plutôt que de concevoir tout d’un bloc en amont.</p>
<p>Cela signifie qu’on attend de nombreuses fonctionnalités mais qu’elles n’ont pas encore été complètement conçues. Elles devront passer par une phase de spécification dans laquelle interviennent tous les fournisseurs de navigateur.</p>
<p>Ces fonctionnalités sont intitulées « fonctionnalités futures ». En voici quelques-unes.</p>
<h3>Manipuler le DOM directement</h3>
<p>Actuellement, il n’existe aucun moyen qui permette d’interagir avec le DOM. Cela signifie que depuis WebAssembly, on ne peut pas utiliser un objet comme <code>element.innerHTML</code> pour mettre à jour un nœud du document.</p>
<p>En l’état, il faut passer par JavaScript pour définir la valeur. Cela signifie qu’il faut transmettre une valeur au code JavaScript qui a appelé le module WebAssembly ou qu’il faut appeler une fonction JavaScript depuis le code WebAssembly (un module WebAssembly pouvant importer des fonctions WebAssembly et des fonctions JavaScript).</p>
<p><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/WASM/06-03-dom01.png" alt="Écrire sur le DOM sans passer par le JavaScript" /></p>
<p>Dans tous les cas, utiliser JavaScript comme intermédiaire sera plus lent qu’un accès direct. Certains champs d’application de WebAssembly devront peut-être attendre que ce point soit résolu.</p>
<h3>Un accès concurrent à la mémoire partagée</h3>
<p>Une méthode pour accélérer le code consiste à exécuter différentes parties du code en même temps, en parallèle. Cette approche réserve parfois de mauvaises surprises, car la communication entre les threads peut nécessiter plus de temps qu’il n’aurait fallu pour exécuter la même tâche de façon classique.</p>
<p>Mais lorsqu’il est possible de partager la mémoire entre les threads, ce délai est réduit. Pour cela, WebAssembly utilisera les nouveaux objets <code>SharedArrayBuffer</code> de JavaScript. Une fois que ce type d’objet sera implémenté dans les navigateurs, le groupe de travail pourra définir la façon dont WebAssembly fonctionne avec <code>SharedArrayBuffer</code>.</p>
<h3>SIMD</h3>
<p>Si vous avez lu d’autres billets ou regardé des présentations sur WebAssembly, vous avez pu entendre parler de SIMD. Cet acronyme signifie <em>Single Instruction Multiple Data</em> (NDT pour « instruction unique, données multiples »). C’est une autre méthode pour exécuter des tâches en parallèle.</p>
<p>SIMD permet de traiter de grands ensembles de données (un vecteur de différents nombres par exemple) et d’appliquer en même temps la même instruction aux différentes parties de cet ensemble. Grâce à cet outil, on peut accélérer de façon drastique certains calculs complexes nécessaires pour les jeux ou la réalité virtuelle.</p>
<p>Cette avancée n’est pas primordiale pour les développeurs d’applications web classiques, mais elle est cruciale pour les développeurs qui travaillent sur des applications multimédia comme les jeux vidéos.</p>
<h3>La gestion des exceptions</h3>
<p>De nombreuses bases de code écrites dans des langages tels que C++ utilisent les exceptions. Cependant, les exceptions ne font pas encore partie de la spécification WebAssembly.
Si vous compilez votre code avec Emscripten, celui-ci émulera la gestion des exceptions pour certains niveaux d’optimisation. Cependant, cette émulation reste plutôt lente et si vous souhaitez la désactiver, vous pouvez utiliser l’option de compilation <a href="https://kripken.github.io/emscripten-site/docs/optimizing/Optimizing-Code.html#c-exceptions"><code>DISABLE_EXCEPTION_CATCHING</code></a>.</p>
<p>Lorsque les exceptions seront gérées nativement par WebAssembly, cette émulation ne sera plus nécessaire.</p>
<h3>D’autres pistes d’amélioration : simplifier le travail des développeurs.</h3>
<p>Certaines fonctionnalités à venir n’auront pas d’impact sur les performances mais faciliteront la tâche aux développeurs qui travaillent avec WebAssembly :</p>
<ul>
<li><strong>Des outils de développement de premier rang</strong> pour interagir avec le code source. À l’heure actuelle, déboguer du code WebAssembly dans le navigateur ressemble un peu à déboguer de l’assembleur brut. Très peu de développeurs peuvent faire le lien mental entre le code source et l’assembleur obtenu. Nous cherchons à améliorer les outils disponibles afin que les développeurs puissent déboguer leur code source.</li>
<li><strong>Intégration du ramasse-miettes.</strong> Si vous pouvez définir les types en amont, vous devriez pouvoir transformer votre code en WebAssembly. Du code écrit avec un langage tel que TypeScript devrait donc être compilable en WebAssembly. Le seuil écueil qui subsiste est que WebAssembly ne sait pas comment interagir avec les ramasse-miettes existants tels que ceux construits dans les moteurs JavaScript. L’idée de cette fonctionnalité est de permettre à WebAssembly d’accéder au ramasse-miettes natif grâce à un ensemble de types et d’opérations de bas niveau qui sont reliées au ramasse-miettes.</li>
<li><strong>Intégration des modules ES6.</strong> Les navigateurs sont en train d’ajouter la prise en charge du chargement des modules JavaScript grâce à la balise <code>script</code>. Une fois cette fonctionnalité ajoutée, une balise comme <code><script src=url type="module"></code> fonctionnera, même si l’URL pointe vers un module WebAssembly.</li>
</ul>
<h2>Conclusion</h2>
<p>WebAssembly est rapide aujourd’hui et avec les nouvelles fonctionnalités et améliorations des implémentations dans les navigateurs, il devrait devenir encore plus rapide</p>
<h2>À propos de <a href="http://twitter.com/linclark">Lin Clark</a></h2>
<p>Lin est ingénieure au sein de l’équipe Mozilla Developer Relations. Elle bidouille avec JavaScript, WebAssembly, Rust et Servo et crée des bandes dessinées sur le code.</p>
<p><style>
pre { white-space: pre;}
</style></p>
Les sections HTML, CSS et JavaScript de MDN sont disponibles en français
urn:md5:02b4b0e1217ae10f8738125c8fab4516
2017-01-31T19:52:00+01:00
2017-02-01T20:53:14+01:00
sphinx
Général
CSS
HTML
JavaScript
Localisation
MDN
Mozilla Developer Network
Traduction
<p><em>TL;DR : Les 1 749 pages de <a href="http://developer.mozilla.org/">MDN</a> pour les sections HTML/JS/CSS sont désormais disponibles, à jour, en français.</em></p>
<p>MDN (ou plus longuement le <a href="http://developer.mozilla.org/">« Mozilla Developer Network »</a>) est un wiki, documentant les technologies web. Depuis quelques jours, l’ensemble des sections relatives à HTML, JavaScript et CSS sont intégralement disponibles en français, à jour par rapport à leurs homologues anglaises.</p>
<div style="text-align:center"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/MDN_fr/mdn_logo-wordmark-full_color.jpg" alt="Logo de MDN"/></div>
<p>MDN est donc un wiki auquel chacun peut contribuer et la documentation qu’il contient pour les technologies web est « agnostique », autrement dit, il ne s’agit pas de documenter uniquement ce qui est pris en charge par Firefox mais bien de servir l’ensemble des développeurs web, quel que soit le navigateur.</p>
<p>Ces sections contiennent 1 749 pages qui décrivent les trois briques fondamentales du Web que sont HTML (Hypertext Markup Language), CSS (Cascading Style Sheets) et JavaScript. Voyons rapidement ce que chacune contient.</p>
<h3>HTML</h3>
<p>S’il n’en fallait qu’un, ce serait lui. <a href="https://developer.mozilla.org/fr/docs/Web/HTML/">HTML</a> permet de composer un document grâce à des éléments comme <a href="https://developer.mozilla.org/fr/docs/Web/HTML/Element/p"><code><p></code></a>, <a href="https://developer.mozilla.org/fr/docs/Web/HTML/Element/img"><code><img></code></a>, <a href="https://developer.mozilla.org/fr/docs/Web/HTML/Element/a"><code><a></code></a> (qui sert à créer les liens hypertextes qui tissent la toile du Web) et plein d’autres. MDN contient <a href="https://developer.mozilla.org/fr/docs/Web/HTML/Reference">une référence exhaustive</a> qui décrit chaque élément ainsi que les attributs qui permettent de les paramétrer.</p>
<p>Voici un exemple d’un fragment de code HTML d’une page web :</p>
<pre><code><p> Et voici une bannière intéressante :
<a href="https://soutien.laquadrature.net/">
<img src="https://tech.mozfr.org/img/support_lqdn.png"
alt="Logo de La Quadrature Du Net pour la campagne de dons"
title="Soutenez l'action de La Quadrature Du Net par un don">
</a>
</p>
</code></pre>
<p>Pour les plus nostalgiques des lecteurs, soyez sans crainte, <a href="https://developer.mozilla.org/fr/docs/Web/HTML/Element/blink"><code><blink></code></a> et <a href="https://developer.mozilla.org/fr/docs/Web/HTML/Element/marquee#Exemples"><code><marquee></code></a> sont toujours documentés.</p>
<p><a href="https://developer.mozilla.org/fr/docs/Web/HTML/">La documentation HTML sur MDN</a></p>
<h3>CSS</h3>
<p>Le fond, c’est bien, avec la bonne forme, c’est mieux. CSS permet de définir les règles de mise en forme pour les éléments grâce à de nombreuses propriétés, <a href="https://developer.mozilla.org/fr/docs/Web/CSS/Pseudo-classes">pseudo-classes</a>, <a href="https://developer.mozilla.org/fr/docs/Web/CSS/Pseudo-elements">pseudo-éléments</a> et autres boîtes et <a href="https://developer.mozilla.org/fr/docs/Web/CSS/Requ%C3%AAtes_m%C3%A9dia/Utiliser_les_Media_queries">requêtes de média</a>. Chacune des propriétés de la référence est illustrée par <a href="https://developer.mozilla.org/fr/docs/Web/CSS/animation-direction#Exemples">un exemple de mise en œuvre</a> et les dernières avancées de ce langage y sont présentées en détails (comme <a href="https://developer.mozilla.org/fr/docs/Web/CSS/CSS_Grid_Layout">le modèle de grille CSS</a> ou <a href="https://developer.mozilla.org/fr/docs/Web/CSS/Animations_CSS">les animations</a>. Il y a même <a href="https://developer.mozilla.org/fr/docs/Web/CSS/Arri%C3%A8re-plans_et_bordures_CSS/G%C3%A9n%C3%A9rateur_border-radius">quelques outils</a> destinés à compléter votre arsenal.</p>
<script async src="//jsfiddle.net/mdxymn2g/embed/css,result/"></script>
<p>Peut-être que, parmi ces 749 pages, vous pourrez trouver comment centrer verticalement une image dans un conteneur…</p>
<p><a href="https://developer.mozilla.org/fr/docs/Web/CSS/">La documentation CSS sur MDN</a></p>
<h3>JavaScript</h3>
<div style="text-align:center"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/MDN_fr/Unofficial_JavaScript_logo_2.svg.png" alt="Logo JavaScript"/></div>
<p>Avec bientôt 22 ans au compteur, JavaScript a bien évolué, y compris ces dernières années avec <a href="https://tech.mozfr.org/tag/ES6%20en%20d%C3%A9tails">ECMAScript 6 (ou 2015, c’est selon)</a>. Sur MDN, ce langage riche, qui ne se cantonne plus au navigateur, est documenté selon plusieurs axes. Il y a tout d’abord <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Guide">un guide</a> dont les 14 chapitres permettent d’appréhender progressivement les concepts de JavaScript. Ensuite, on trouvera <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/">la référence</a>, avec une page pour chaque objet, méthode, instruction ou opérateur du langage : cette référence est un outil de précision quand il s’agit de lever le doute sur une imprécision ou un comportement incompréhensible.</p>
<p>Voici un exemple d’un fragment de code JavaScript :</p>
<pre><code>var p1 = Promise.resolve(3);
var p2 = 1337;
var p3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "toto");
});
Promise.all([p1, p2, p3]).then((values) => {
console.log(values); // [3, 1337, "toto"]
});
</code></pre>
<p>La documentation JavaScript de MDN est une des pierres angulaires du wiki et elle est utilisée dans divers outils comme <a href="https://hacks.mozilla.org/2016/06/helping-web-developers-with-javascript-errors/">la console de développement</a> ou <a href="https://github.com/ternjs/tern/blob/master/defs/ecmascript.json">les aides à l’édition de code</a>.
Certes <code>undefined</code> n’est pas une fonction et <a href="https://fr.wikipedia.org/wiki/NaN#Comparaisons"><code>NaN</code> a un peu de mal à se regarder dans le miroir</a> mais JavaScript ayant conquis de nombreuses plateformes, il n’y a plus tellement d’<a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/escape">échappatoires</a>…</p>
<p><em>Note : cette section concerne uniquement le langage JavaScript en tant que tel et non <a href="https://developer.mozilla.org/fr/docs/Web/API">les API web</a> (telles que le DOM, XHR, etc.)</em></p>
<p><a href="https://developer.mozilla.org/fr/docs/Web/CSS/">La documentation JavaScript sur MDN</a></p>
<h2>Quelques chiffres</h2>
<p>Ce résultat représente environ :</p>
<ul>
<li>plus de 3 ans de travail</li>
<li>plus de mots à traduire que les trois tomes du <em>Seigneur des Anneaux</em> (ou que <em>Guerre et Paix</em>, ou que l’intégrale de <em>50 Nuances de Grey</em>)</li>
<li>plus de 13 000 révisions sur MDN (dont de nombreuses mises à jour : imaginez si Tolkien révisait son manuscrit ou ajoutait de nouveaux chapitres)</li>
<li>1 749 pages</li>
</ul>
<div style="text-align:center;"><img src="https://tech.mozfr.org/dotclear/public/bidouilleux/MDN_fr/blink.png" alt="CC-BY-SA Goofy©"/></div>
<h2>Contribuer</h2>
<p>Au-delà de ces chiffres un peu bêtes, le plus intéressant a été d’apprendre, de découvrir des gens, d’avoir la chance de voyager. Et si la documentation technique peut paraître austère, la rédiger à plusieurs est bien plus agréable. Aussi, n’hésitez pas à contribuer à ce projet. Vous pouvez (sans ordre, ni exhaustivité garantie) :</p>
<ul>
<li><a href="https://developer.mozilla.org/fr/docs/MDN/Doc_status/Learn">traduire des articles destinés aux débutants</a></li>
<li><a href="https://developer.mozilla.org/fr/docs/MDN/Doc_status/HTTP">traduire la nouvelle documentation relative à HTTP</a></li>
<li><a href="https://developer.mozilla.org/fr/docs/MDN/Doc_status/l10nPriority">traduire les articles prioritaires</a></li>
<li><a href="https://developer.mozilla.org/fr/docs/MDN/Contribute/Howto/faire_relecture_redactionnelle">relire les pages en faisant la chasse aux fautes</a></li>
<li><a href="https://pontoon.mozilla.org/fr/mdn/">traduire l’interface du site MDN lorsque de nouvelles chaînes sont disponibles</a></li>
<li><a href="https://www.meetup.com/Mozilla-Developer-Network/">participer aux MercrediDoc tous les premiers mercredis du mois</a></li>
<li>échanger sur <a href="https://lists.mozilla.org/listinfo/dev-mdc">la liste de diffusion</a> ou sur <a href="https://kiwiirc.com/client/irc.mozilla.org/#mdn">IRC</a></li>
</ul>
<p>Si la traduction vous intéresse, je vous invite à lire <a href="https://blog.mozfr.org/post/2017/01/atelier-traduction-avril-2017-Mozilla-Paris">ce billet</a> et à remplir <a href="https://framaforms.org/atelier-de-traduction-mozilla-francophone-8-et-9-avril-2017-1482344769">ce formulaire</a> pour un week-end où vous pourrez mettre le pied à l’étrier.</p>
<h2>Remerciements</h2>
<p>Si l’introduction d’un livre contient des remerciements, pourquoi n’en irait-il pas de même pour un tel projet ?
Naturellement, j’oublierai de nombreuses personnes qui ont participé et qui le méritent, <em>mea culpa</em>.</p>
<p>À ma chère et tendre qui a supporté cette étrange passion et les bruits du clavier, à Goofy et <a href="https://framasoft.org/">Framasoft</a> qui m’ont lancé sur la traduction collaborative, à Clochix et David pour m’avoir aidé à découvrir MDN, à Jérémie pour les MercrediDoc, à Florian et Jean-Yves pour leur patience, leur aide et leur soutien précieux, à toutes les personnes de la communauté pour les rencontres et les occasions créées…</p>
<p>Merci.</p>
<p><style>
pre { white-space: pre;}
</style></p>
Dans les entrailles de Git
urn:md5:fea4f27a59602e08773a8524ecafc9a2
2017-01-14T20:23:00+01:00
2017-01-15T21:30:30+01:00
Goofy
Général
arbre
branche
cloner
commit
dépôt
Git
index
répertoire
<p><em>Le billet qui suit a <a href="http://nicolas.loeuillet.org/billets/entrailles-git">d’abord été publié sur son blog</a> par Nicolas Leuillet, à qui nous devons l’excellent <a href="https://www.wallabag.it/fr" hreflang="fr">Wallabag</a>. Cette traduction collaborative à laquelle a participé <a href="https://framablog.org/framalang/" hreflang="fr">le groupe Framalang</a> nous a semblé intéressante pour notre blog « technique » et c’est l’occasion pour nous de lancer une invitation à tous ceux qui voudraient publier ici sur les technologies web. N’hésitez pas à nous contacter.</em></p>
<p> </p> <p>Ce billet est une traduction de l’excellent billet de <strong>Mary Rose Cook</strong>, <a class="external-link no-image" href="https://maryrosecook.com/blog/post/git-from-the-inside-out" rel="nofollow" target="_self">Git from the inside out</a>. On y apprend vraiment plein de choses sur le fonctionnement de Git.</p>
<p>Il se peut qu’il reste quelques coquilles, n’hésitez pas à me les signaler.</p>
<p>Je tiens à remercier Pierre Ozoux, goofy, Agnès H, Stéphane Hulard, Jums, Julien aka Sphinx et xi de m’avoir aidé à traduire ce très très long billet.</p>
<p>Cet article explique comment fonctionne <a href="https://tech.mozfr.org/tag/Git">Git</a>. Il part du principe que vous comprenez suffisamment Git pour l’utiliser en tant que système de gestion de versions pour vos projets.<br />
Cet article se concentre sur la structure de graphe sur laquelle s’appuie Git et sur la manière dont les propriétés de ce graphe dictent le comportement de Git. Revenir aux bases vous permet de visualiser les choses telles qu’elles sont réellement, et vous évite d’échafauder des hypothèses à partir du fonctionnement de l’API. Ce modèle, plus fidèle à la réalité, vous permettra de mieux comprendre ce que Git a fait, ce qu’il fait et ce qu’il fera.</p>
<p>Cet article est structuré comme une série de commandes Git exécutées sur un projet. Par moments, il y a des remarques à propos de la structure de données en graphe sur laquelle Git est construit. Ces remarques illustrent une propriété du graphe et le comportement provoquée par celle-ci.</p>
<p>Après votre lecture, si vous voulez aller plus loin avec Git, <a class="external-link no-image" href="http://gitlet.maryrosecook.com/docs/gitlet.html" rel="nofollow" target="_self">vous pouvez regarder sur le code source annoté de mon implémentation de Git en JavaScript</a>.</p>
<h2>Créer le projet</h2>
<pre>
<code>~ $ mkdir alpha
~ $ cd alpha</code></pre>
<p>L’utilisateur crée <code>alpha</code>, un répertoire pour son projet.</p>
<pre>
<code>~/alpha $ mkdir data
~/alpha $ printf 'a' > data/letter.txt</code></pre>
<p>Il se rend dans le répertoire <code>alpha</code> et crée un répertoire <code>data</code>. À l’intérieur, il crée un fichier appelé <code>letter.txt</code> qui contient le caractère <code>a</code>. Le répertoire <code>alpha</code> ressemble donc à ceci :</p>
<pre>
<code>alpha
└── data
└── letter.txt</code></pre>
<h2>Initialisation du dépôt</h2>
<pre>
<code>~/alpha $ git init
Dépôt Git vide initialisé</code></pre>
<p><code>git init</code> transforme le répertoire courant en dépôt Git. Pour faire cela, il crée un répertoire <code>.git</code> et écrit quelques fichiers dedans. Ces fichiers définissent tout ce qui concerne la configuration Git et l’historique du projet. Ce sont des fichiers classiques. Rien d’extraordinaire. L’utilisateur peut les lire et les éditer avec un éditeur de texte ou avec son terminal. Ce qui revient à dire : l’utilisateur peut lire et modifier l’historique de son projet aussi facilement que les fichiers du projet.</p>
<p>Le répertoire <code>alpha</code> ressemble maintenant à ça :</p>
<pre>
<code>alpha
├── data
| └── letter.txt
└── .git
├── objects
etc.</code></pre>
<p>Le répertoire <code>.git</code> et son contenu appartiennent à Git. Tous les autres fichiers sont considérés comme la copie de travail. Ils appartiennent à l’utilisateur.</p>
<h2>Ajouter quelques fichiers</h2>
<pre>
<code>~/alpha $ git add data/letter.txt</code></pre>
<p>L’utilisateur exécute <code>git add</code> sur le fichier <code>data/letter.txt</code>. Cela a deux effets.</p>
<p>Tout d’abord, cela crée un nouveau fichier binaire dans le répertoire <code>.git/objects/</code>.</p>
<p>Ce fichier binaire contient le contenu compressé de <code>data/letter.txt</code>. Son nom est basé sur le contenu haché du fichier. Hacher du texte veut dire qu’un programme le convertit en un texte plus petit <sup id="fnref1:1"><a class="footnote-ref" href="https://tech.mozfr.org/post/2017/01/14/Dans-les-entrailles-de-Git#fn:1">1</a></sup> qui identifie de manière unique <sup id="fnref1:2"><a class="footnote-ref" href="https://tech.mozfr.org/post/2017/01/14/Dans-les-entrailles-de-Git#fn:2">2</a></sup> le texte original. Par exemple, Git hache le caractère <code>a</code> en <code>2e65efe2a145dda7ee51d1741299f848e5bf752e</code>. Les deux premiers caractères sont utilisés comme nom de répertoire dans la base de données des objets : <code>.git/objects/2e/</code>. Le reste du hachage est utilisé comme nom pour le fichier binaire qui contient le contenu du fichier ajouté : <code>.git/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e</code>.</p>
<p>Notez que le simple fait d’ajouter un fichier à Git sauvegarde son contenu dans le répertoire <code>objects</code>. Son contenu restera intact dans Git si l’utilisateur supprime le fichier <code>data/letter.txt</code> de sa copie de travail.</p>
<p>Ensuite, <code>git add</code> ajoute le fichier à l’index. L’index est une liste qui contient chaque fichier dont Git doit conserver une trace. Il est stocké comme fichier ici : <code>.git/index</code>. Chaque ligne du fichier associe un fichier suivi au hachage de son contenu au moment où il a été ajouté. Voici l’index après l’exécution de la commande <code>git add</code> :</p>
<pre>
<code>data/letter.txt 2e65efe2a145dda7ee51d1741299f848e5bf752e</code></pre>
<p>L’utilisateur crée un fichier appelé <code>data/number.txt</code> qui contient <code>1234</code>.</p>
<pre>
<code>~/alpha $ printf '1234' > data/number.txt</code></pre>
<p>Le répertoire de travail correspond donc à ça :</p>
<pre>
<code>alpha
└── data
└── letter.txt
└── number.txt</code></pre>
<p>L’utilisateur ajoute le fichier à Git.</p>
<pre>
<code>~/alpha $ git add data</code></pre>
<p>La commande <code>git add</code> crée un fichier binaire qui contient le contenu de <code>data/number.txt</code>. Elle ajoute une entrée dans l’index pour le fichier <code>data/number.txt</code> qui pointe vers le fichier binaire. Voici l’index après l’exécution, pour la deuxième fois, de la commande <code>git add</code> :</p>
<pre>
<code>data/letter.txt 2e65efe2a145dda7ee51d1741299f848e5bf752e
data/number.txt 274c0052dd5408f8ae2bc8440029ff67d79bc5c3</code></pre>
<p>Notez que seuls les fichiers dans le répertoire data sont listés dans l’index malgré le fait que l’utilisateur ait exécuté la commande <code>git add data</code>. Le répertoire <code>data</code> n’est pas listé séparément.</p>
<pre>
<code>~/alpha $ printf '1' > data/number.txt
~/alpha $ git add data</code></pre>
<p>Quand l’utilisateur a créé <code>data/number.txt</code>, il voulait taper <code>1</code> et non <code>1234</code>. Il effectue la correction et ajoute de nouveau le fichier à l’index. La commande crée un nouveau fichier binaire avec le nouveau contenu. Et elle met à jour l’entrée dans l’index pour <code>data/number.txt</code> pour pointer vers ce nouveau fichier binaire.</p>
<h2>Faire un commit</h2>
<pre>
<code>~/alpha $ git commit -m 'a1'
[master (commit-racine) 774b54a] a1</code></pre>
<p>L’utilisateur crée le « commit » <code>a1</code>. Git affiche quelques infos à propos du commit. La signification de ces informations sera plus claire dans quelques paragraphes. La commande <code>commit</code> est faite en trois étapes. Elle crée un arbre qui représente le contenu de la version du projet à commiter. Elle crée un objet de commit. Elle pointe la branche courante sur ce nouvel objet de commit.</p>
<h3>Créer un arbre</h3>
<p>Git enregistre l’état courant du projet en créant un arbre depuis l’index. Cet arbre enregistre l’emplacement et le contenu de chaque fichier dans le projet.</p>
<p>Ce graphe est composé de deux types d’objet : des fichiers binaires et des arbres.</p>
<p>Les fichiers binaires sont stockés par la commande <code>git add</code>. Ils représentent le contenu des fichiers.</p>
<p>Les arbres sont stockés quand un commit est créé. Un arbre représente un répertoire dans la copie de travail.</p>
<p>Voici un arbre qui enregistre les contenus du répertoire <code>data</code> pour le nouveau commit :</p>
<pre>
<code>100664 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e letter.txt
100664 blob 56a6051ca2b02b04ef92d5150c9ef600403cb1de number.txt</code></pre>
<p>La première ligne enregistre tout ce qui est nécessaire pour reproduire le fichier <code>data/letter.txt</code>. La première partie correspond aux permissions du fichier. La seconde partie correspond au contenu de l’entrée, représentée par un fichier binaire plutôt que par un arbre. La troisième partie correspond à l’empreinte du fichier binaire. La quatrième partie correspond au nom du fichier.</p>
<p>La deuxième enregistre la même chose pour <code>data/number.txt</code>.</p>
<p>Voici un arbre pour le répertoire <code>alpha</code>, qui est le répertoire racine du projet :</p>
<pre>
<code>040000 tree 0eed1217a2947f4930583229987d90fe5e8e0b74 data</code></pre>
<p>La seule ligne dans cet arbre pointe vers l’arbre <code>data</code>.</p>
<p><img alt="L'arbre pour le commit a1" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/1-tree-graph.png" /></p>
<p><em>Image : L’arbre pour le commit a1</em></p>
<p>Dans le graphe ci-dessus, l’arbre <code>root</code> pointe vers l’arbre <code>data</code>. L’arbre <code>data</code> pointe vers les fichiers binaires pour <code>data/letter.txt</code> et <code>data/number.txt</code>.</p>
<h3>Créer un objet de commit</h3>
<p><code>git commit</code> crée un objet de commit après la création de l’arbre. L’objet de commit est simplement un autre fichier texte dans <code>.git/objects/</code>.</p>
<pre>
<code>tree ffe298c3ce8bb07326f888907996eaa48d266db4
author Mary Rose Cook <mary@maryrosecook.com> 1424798436 -0500
committer Mary Rose Cook <mary@maryrosecook.com> 1424798436 -0500
a1</code></pre>
<p>La première ligne pointe vers l’arbre. L’empreinte correspond à l’objet qui représente la racine de la copie de travail, c’est-à-dire le répertoire <code>alpha</code>. La dernière ligne correspond au commit.</p>
<p><img alt="L'objet pour le commit a1 qui pointe vers son arbre" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/2-commit.png" /></p>
<p><em>Image : L’objet pour le commit <code>a1</code> qui pointe vers son arbre</em></p>
<h3>Placer la branche actuelle sur le nouveau commit</h3>
<p>Finalement, la commande <code>commit</code> place la branche courante sur le nouvel objet commit. Quelle est la branche actuelle ? Git va dans le fichier <code>HEAD</code> se trouvant dans <code>.git/HEAD</code> et trouve :</p>
<pre>
<code>ref: refs/heads/master</code></pre>
<p>Cela signifie que <code>HEAD</code> pointe sur <code>master</code>. <code>master</code> est la branche actuelle. <code>HEAD</code> et <code>master</code> sont toutes les deux des références. Une référence est un libellé utilisé par Git ou l’utilisateur pour identifier un commit spécifique. Ce fichier que représente la référence <code>master</code> n’existe pas, parce que c’est le premier commit dans le dépôt. Git crée le fichier <code>.git/refs/heads/master</code> et y inscrit l’empreinte de l’objet de commit :</p>
<pre>
<code>74ac3ad9cde0b265d2b4f1c778b283a6e2ffbafd</code></pre>
<p>(Si vous exécutez ces commandes Git pendant que vous lisez, l’empreinte de votre commit <code>a1</code> sera différente de celle que j’ai obtenue ici. Les objets de contenu comme les fichiers binaires et les arbres sont toujours hachés avec la même valeur. En revanche, l’empreinte d’un commit peut varier car un commit inclut une date et le nom de son créateur.)</p>
<p>Ajoutons <code>HEAD</code> et <code>master</code> sur le graphe Git :</p>
<p><img alt="HEAD pointant sur master et master pointant sur le commit a1" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/3-refs.png" /></p>
<p><em>Image : <code>HEAD</code> pointant sur <code>master</code> et <code>master</code> pointant sur le commit <code>a1</code></em></p>
<p><code>HEAD</code> pointe sur <code>master</code>, comme elle le faisait avant le commit. Mais désormais, master existe et pointe sur le nouvel objet de commit.</p>
<h3>Créer un commit qui n’est pas le premier commit</h3>
<p>Ci-dessous, on voit le graphe Git après le commit <code>a1</code> avec le contenu de la copie de travail et l’index.</p>
<p><img alt="Le commit a1, affiché avec la copie de travail et l'index" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/4-wc-and-index.png" /></p>
<p><em>Image : Le commit <code>a1</code>, affiché avec la copie de travail et l’index</em></p>
<p>Notez que la copie de travail, l’index et le commit <code>a1</code> ont tous le même contenu pour <code>data/letter.txt</code> et <code>data/number.txt</code>. L’index et le commit <code>HEAD</code> utilisent tous les deux des empreintes pour se référer aux objets binaires, mais le contenu de la copie de travail est stockée sous forme de texte dans un autre endroit.</p>
<pre>
<code>~/alpha $ printf '2' > data/number.txt</code></pre>
<p>L’utilisateur initialise le contenu de <code>data/number.txt</code> à <code>2</code>. Cette action met à jour la copie de travail mais laisse l’index et le commit <code>HEAD</code> tels quels.</p>
<p><img alt="data/number.txt initialisé à 2 dans la copie de travail" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/5-wc-number-set-to-2.png" /></p>
<p><em>Image : <code>data/number.txt</code> initialisé à <code>2</code> dans la copie de travail</em></p>
<pre>
<code>~/alpha $ git add data/number.txt</code></pre>
<p>L’utilisateur ajoute le fichier à Git. Cela rajoute un fichier binaire qui contient <code>2</code> dans le répertoire <code>objects</code>. Il pointe sur l’entrée de l’index pour <code>data/number.txt</code> sur le nouvel objet binaire.</p>
<p><img alt="`data/number.txt` initialisé à `2` dans la copie de travail et dans l'index" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/6-wc-number-set-to-2.png" /></p>
<p><em>Image : <code>data/number.txt</code> initialisé à <code>2</code> dans la copie de travail et dans l’index</em></p>
<pre>
<code>~/alpha $ git commit -m 'a2'
[master f0af7e6] a2</code></pre>
<p>L’utilisateur ajoute un commit. Les étapes pour le commit sont les mêmes que précédemment.</p>
<p>Tout d’abord, un nouvel arbre/graphe est créé pour représenter le contenu de l’index.</p>
<p>Dans l’index, l’entrée pour <code>data/number.txt</code> a changé. L’ancien arbre <code>data</code> ne correspond plus à l’état indexé du repertoire <code>data</code>. Un nouvel arbre <code>data</code> doit être créé :</p>
<pre>
<code>100664 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e letter.txt
100664 blob d8263ee9860594d2806b0dfd1bfd17528b0ba2a4 number.txt</code>
</pre>
<p>La nouvelle empreinte (<i>hash</i>) correspondant à l’arbre <code>data</code> est différente de la précédente. Un nouvel arbre <code>root</code> doit être créé pour enregistrer cette nouvelle empreinte :</p>
<pre>
<code>040000 tree 40b0318811470aaacc577485777d7a6780e51f0b data</code></pre>
<p>Deuxièmement, un nouvel objet de commit est créé.</p>
<pre>
<code>tree ce72afb5ff229a39f6cce47b00d1b0ed60fe3556
parent 774b54a193d6cfdd081e581a007d2e11f784b9fe
author Mary Rose Cook <mary@maryrosecook.com> 1424813101 -0500
committer Mary Rose Cook <mary@maryrosecook.com> 1424813101 -0500
a2</code></pre>
<p>La première ligne de ce commit pointe vers le nouvel arbre <code>root</code>. La deuxième ligne pointe vers <code>a1</code> : le commit parent. Pour trouver le commit parent, Git est allé sur la référence <code>HEAD</code> puis a suivi sur <code>master</code> et a trouvé l’empreinte du commit <code>a1</code>.<br />
Troisièmement, Git inscrit l’empreinte du nouveau commit dans le fichier qui décrit la branche <code>master</code>.</p>
<p><img alt="Le commit `a2`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/-a2.png" /></p>
<p><em>Image : Le commit <code>a2</code></em></p>
<p><img alt="Le graphe Git, sans index ni copie de travail" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/7-just-objects-commits-and-refs.png" /></p>
<p><em>Image : Le graphe Git, sans index ni copie de travail</em></p>
<p><strong>Propriété du graphe :</strong> le contenu est stocké en tant qu’arbre d’objets. Ce qui signifie que seules les différences entre les objets sont stockées dans la base de données des objets. Observez le graphe ci-dessus. Le commit <code>a2</code> réutilise le fichier binaire qui a été créé avant le commit <code>a1</code>. Si le répertoire entier reste inchangé de commit en commit, alors son arbre et tous les blobs et arbres enfants pourront être réutilisés. En général, il n’y a que peu de changements d’un commit à un autre. Cela signifie que Git peut stocker un large historique de commits dans un très petit espace.</p>
<p><strong>Propriété du graphe :</strong> chaque commit a un parent. Ce qui signifie qu’un répertoire peut stocker l’historique du projet.</p>
<p><strong>Propriété du graphe :</strong> les références (ref) sont les points d’entrée vers une partie de l’historique de commit. Cela signifie qu’un commit peut être nommé de façon adéquate. L’utilisateur peut organiser son travail sous la forme de lignées de commits pertinentes pour un projet et les intituler avec des références concrètes, par exemple <code>fix-for-bug-376</code>. Git utilise des références symboliques telles que <code>HEAD</code>, <code>MERGE_HEAD</code> et <code>FETCH_HEAD</code> pour les commandes qui manipulent l’historique des commits.</p>
<p><strong>Propriété du graphe :</strong> les nœuds du répertoire <code>objects/</code> sont immuables. Ceci signifie que le contenu est édité, pas effacé. Tout ce qui a jamais été ajouté et tous les commits qui ont été réalisés se trouvent quelque part dans le répertoire <code>objects</code><sup id="fnref1:3"><a class="footnote-ref" href="https://tech.mozfr.org/post/2017/01/14/Dans-les-entrailles-de-Git#fn:3">3</a></sup>.</p>
<p><strong>Propriété du graphe :</strong> les références (<code>refs</code>) sont modifiables. Par conséquent, la signification d’une référence peut changer. Le commit vers lequel pointe la branche <code>master</code> peut être la meilleure version existante d’un projet, mais très vite, il sera remplacé par un meilleur commit plus récent.</p>
<p><strong>Propriété du graphe :</strong> la copie de travail et les commits vers lesquels pointent les références sont facilement accessibles mais les autres commits ne le sont pas. Ceci signifie que l’historique récent est plus facilement accessible, mais aussi qu’il change plus souvent. Autrement dit, Git a une mémoire vacillante qui doit souvent être rafraîchie.</p>
<p>La copie de travail est le point de l’historique le plus facile à accéder parce qu’elle est à la racine du répertoire. Y accéder ne nécessite même pas l’utilisation d’une commande Git. C’est aussi le point le moins fixe de l’historique. L’utilisateur peut faire une dizaine de versions d’un fichier mais Git n’en enregistrera aucune à moins qu’elles ne soient ajoutées.</p>
<p>On peut très facilement se souvenir du commit sur lequel pointe <code>HEAD</code>. Cette référence est à l’extrémité de la branche sur laquelle on travaille. Pour voir son contenu, l’utilisateur peut mettre de côté (<code>stash</code>)<sup id="fnref1:4"><a class="footnote-ref" href="https://tech.mozfr.org/post/2017/01/14/Dans-les-entrailles-de-Git#fn:4">4</a></sup> ses modifications et examiner la copie de travail. La référence <code>HEAD</code> est donc également celle qui change le plus souvent.</p>
<p>On peut facilement se souvenir du commit sur lequel pointe une référence concrète. Pour ce faire, l’utilisateur peut simplement basculer sur la branche correspondante. L’extrémité d’une branche évolue moins fréquemment que <code>HEAD</code> mais suffisamment pour que le nom de la branche traduise quelque chose qui évolue.</p>
<p>Il est plus difficile de se rappeler un commit pour lequel il n’existe aucune référence. Plus on s’écarte d’une référence, plus il est difficile de reconstituer le sens d’un commit. Cela dit, plus on s’éloigne d’une référence, moins il y a de chances que quelqu’un ait modifié l’historique depuis la dernière fois qu’on l’a consulté<sup id="fnref1:5"><a class="footnote-ref" href="https://tech.mozfr.org/post/2017/01/14/Dans-les-entrailles-de-Git#fn:5">5</a></sup>.</p>
<h2>Basculer sur un commit</h2>
<pre>
<code>~/alpha $ git checkout 37888c2
You are in 'detached HEAD' state...</code></pre>
<p>L’utilisateur bascule sur le commit <code>a2</code> en utilisant son empreinte. (Si vous lancez cette commande Git telle quelle, elle ne fonctionnera pas. Vous devez utiliser <code>git log</code> afin de trouver l’empreinte qui correspond au commit <code>a2</code>.)</p>
<p>Le basculement se fait en quatre étapes.</p>
<p>Premièrement, Git récupère le commit <code>a2</code> et le graphe vers lequel il pointe.</p>
<p>Deuxièmement, il écrit les fichiers listés dans le graphe dans la copie de travail. Ici, cela ne produit aucune modification car la copie de travail contient déjà les mêmes fichiers que le graphe car <code>HEAD</code> pointait déjà, via la branche <code>master</code>, sur le commit <code>a2</code>.</p>
<p>Troisièmement, Git écrit la liste des fichiers du graphe dans l’index. Là encore, aucune modification n’est appliquée. L’index contient déjà le contenu du commit <code>a2</code>.</p>
<p>Enfin, lors de la quatrième étape, <code>HEAD</code> est mis à jour avec l’empreinte du commit <code>a2</code> :</p>
<pre>
<code>f0af7e62679e144bb28c627ee3e8f7bdb235eee9</code></pre>
<p>Lorsque le contenu de <code>HEAD</code> est défini avec une empreinte, le dépôt est dans un état où la tête (<code>HEAD</code>) est détachée. On voit dans le schéma ci-après que <code>HEAD</code> pointe directement sur le commit <code>a2</code> plutôt que de pointer vers <code>master</code>.</p>
<p><img alt="La référence `HEAD`, détachée, sur le commit `a2`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/8-detached-head.png" /></p>
<p><em>Image : La référence <code>HEAD</code>, détachée, sur le commit <code>a2</code></em></p>
<pre>
<code>~/alpha $ printf '3' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m 'a3'
[HEAD détachée 3645a0e] a3</code></pre>
<p>L’utilisateur écrit <code>3</code> dans le fichier <code>data/number.txt</code> puis ajoute un commit pour cette modification. Git utilise la référence <code>HEAD</code> pour obtenir le parent du commit <code>a3</code>. Plutôt que de trouver une référence de branche, Git trouve et renvoie l’empreinte du commit <code>a2</code>.</p>
<p>Git met à jour la référence <code>HEAD</code> pour qu’elle pointe directement sur l’empreinte du commit <code>a3</code>. Le depôt est alors toujours dans un état où <code>HEAD</code> est détachée. Elle n’est pas sur une branche car aucune référence de branche ne pointe sur le commit <code>a3</code> ou l’un de ses descendants. Cela signifie qu’on peut facilement perdre le travail en cours.</p>
<p>À partir de maintenant, la plupart des schémas n’incluront plus les arbres et les blobs.</p>
<p><img alt="Le commit `a3` qui n'est pas sur une branche" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/a3-detached-head.png" /></p>
<p><em>Image : Le commit <code>a3</code> qui n’est pas sur une branche</em></p>
<h2>Créer une branche</h2>
<pre>
<code>~/alpha $ git branch deputy</code></pre>
<p>Avec cette commande, l’utilisateur crée une nouvelle branche intitulée <code>deputy</code>. Cette opération crée simplement un nouveau fichier situé sous <code>.git/refs/heads/deputy</code> et qui contient l’empreinte vers laquelle <code>HEAD</code> pointe, c’est-à-dire l’empreinte du commit <code>a3</code>.</p>
<p><strong>Propriété du graphe :</strong> les branches sont simplement des références (<i>refs</i>) et les références sont simplement des fichiers. Cela signifie que les branches Git sont légères.</p>
<p>La création de la branche <code>deputy</code> permet d’enregistrer le commit <code>a3</code> de façon sûre, sur une branche. <code>HEAD</code> est toujours détachée car elle pointe toujours directement vers un commit.</p>
<p><img alt="Le commit `a3`, désormais sur la branche `deputy`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/9-on-deputy.png" /></p>
<p><em>Image : Le commit <code>a3</code>, désormais sur la branche <code>deputy</code></em></p>
<h2>Basculer sur une branche</h2>
<pre>
<code>~/alpha $ git checkout master
Basculement sur la branche 'master'</code></pre>
<p>L’utilisateur bascule sur la branche <code>master</code>.</p>
<p>Pour commencer, Git récupère le commit <code>a2</code> vers lequel pointe la branche <code>master</code> puis il récupère le graphe sur lequel pointe le commit.</p>
<p>Ensuite, dans la copie de travail, Git écrit le contenu des fichiers qui sont listés dans le graphe. Ainsi, il écrit <code>2</code> dans le fichier <code>data/number.txt</code>.</p>
<p>Lors d’une troisième étape, Git écrit la liste des fichiers du graphe dans l’index. Dans l’index, l’élément pour le fichier <code>data/number.txt</code> pointe désormais vers l’empreinte du blob <code>2</code>.</p>
<p>Enfin, Git fait pointer la référence <code>HEAD</code> sur <code>master</code> en modifiant son contenu :</p>
<pre>
<code>ref: refs/heads/master</code></pre>
<p><img alt="Basculement sur `master` et pointage vers le commit `a2`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/10-on-master-on-a2.png" /></p>
<p><em>Image : Basculement sur <code>master</code> et pointage vers le commit <code>a2</code></em></p>
<h2>Basculer sur une branche incompatible avec la copie de travail</h2>
<pre>
<code>~/alpha $ printf '789' > data/number.txt
~/alpha $ git checkout deputy
Your local changes to the following files would be overwritten
by checkout:
data/number.txt
Commit your changes or stash them before you
switch branches.</code></pre>
<p>L’utilisateur écrit par erreur <code>789</code> dans le fichier dans <code>data/number.txt</code> puis essaie de basculer sur la branche <code>deputy</code>. Git empêche le basculement.</p>
<p>La référence <code>HEAD</code> pointe sur la branche <code>master</code> qui pointe vers le commit <code>a2</code> où <code>data/number.txt</code> contient <code>2</code>. La branche <code>deputy</code> pointe vers le commit <code>a3</code> où <code>data/number.txt</code> contient <code>3</code>. Dans la copie de travail, <code>data/number.txt</code> contient <code>789</code>. Toutes ces versions sont différentes et ces différences doivent être résolues.</p>
<p>Git pourrait remplacer la version du fichier <code>data/number.txt</code> de la copie de travail par celle qui correspond au commit sur lequel on bascule mais il empêche à tout prix de perdre des données.</p>
<p>Git pourrait fusionner la version de la copie de travail avec la version sur laquelle on bascule… mais c’est compliqué.</p>
<p>C’est pour ça que Git interrompt le basculement.</p>
<pre>
<code>~/alpha $ printf '2' > data/number.txt
~/alpha $ git checkout deputy
Basculement sur la branche 'deputy'</code></pre>
<p>L’utilisateur remarque qu’il a édité <code>data/number.txt</code> par accident puis réécrit <code>2</code> dans le fichier. Il peut alors basculer sans problème.</p>
<p><img alt="Basculement sur `deputy`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/11-ondeputy.png" /></p>
<p><em>Image : Basculement sur <code>deputy</code></em></p>
<h2>Fusionner un commit qui est un ancêtre</h2>
<pre>
<code>~/alpha $ git merge master
Already up-to-date.</code></pre>
<p>L’utilisateur fusionne la branche <code>master</code> sur la branche <code>deputy</code>. Fusionner deux branches signifie qu’on fusionne deux commits. Le premier commit est celui sur lequel pointe la branche <code>deputy</code> : c’est le commit receveur. Le deuxième commit est celui sur lequel point la branche <code>master</code> : c’est le commit donneur. Pour cette fusion, Git n’a rien à faire et c’est ce qu’il indique : <code>Already up-to-date</code> (déjà à jour).</p>
<p><strong>Propriété du graphe :</strong> la succession de commits du graphe est interprétée comme une succession de modifications à appliquer au contenu du dépôt. Cela signifie que, lors d’une fusion, si le commit donneur est un ancêtre du commit receveur, Git n’a rien à faire : les modifications concernées ont déjà été intégrées au dépôt.</p>
<h2>Fusionner un commit qui est un descendant</h2>
<pre>
<code>~/alpha $ git checkout master
Basculement sur la branche 'master'</code></pre>
<p>L’utilisateur bascule sur la branche <code>master</code>.</p>
<p><img alt="Basculement sur `master` qui pointe sur le commit `a2`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/12-on-master-on-a2.png" /></p>
<p><em>Image : Basculement sur <code>master</code> qui pointe sur le commit <code>a2</code></em></p>
<pre>
<code>~/alpha $ git merge deputy
Fast-forward</code></pre>
<p>Grâce à cette commande, l’utilisateur fusionne la branche <code>deputy</code> avec la branche <code>master</code>. Git analyse et comprend que le commit receveur, <code>a2</code>, est un ancêtre du commit donneur, <code>a3</code>. Il peut donc appliquer une fusion en avance rapide (<i>fast-forward merge</i>).</p>
<p>Git récupère le commit donneur et l’arbre correspondant. Il écrit les fichiers du graphe dans la copie de travail et dans l’index puis effectue une avance rapide pour pointer sur le commit <code>a3</code>.</p>
<p><img alt="Le commit `a3` de la branche `deputy`, fusionné en avance rapide sur la branche `master`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/13-on-master.png" /></p>
<p><em>Image : Le commit <code>a3</code> de la branche <code>deputy</code>, fusionné en avance rapide sur la branche <code>master</code></em></p>
<p><strong>Propriété du graphe :</strong> les suites de commits du graphe sont interprétées comme une suite de modifications appliquées sur le contenu du dépôt. Cela signifie que pendant une fusion, si le commit donneur est un descendant du commit receveur, l’historique n’est pas modifié. Il existe déjà une succession de commits qui décrit les modifications à réaliser : ce sont les commits situés entre le commit receveur et le commit donneur. Toutefois, si l’historique Git ne change pas, le graphe Git, lui, change. La référence concrète vers laquelle pointe <code>HEAD</code> est mise à jour pour correspondre au commit donneur.</p>
<h2>Fusionner deux commits ayant des historiques différents</h2>
<pre>
<code>~/alpha $ printf '4' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m 'a4'
[master 7b7bd9a] a4</code></pre>
<p>L’utilisateur écrit <code>4</code> dans le fichier <code>number.txt</code> puis ajoute un commit pour cette modification sur la branche <code>master</code>.</p>
<pre>
<code>~/alpha $ git checkout deputy
Basculement sur la branche 'deputy'
~/alpha $ printf 'b' > data/letter.txt
~/alpha $ git add data/letter.txt
~/alpha $ git commit -m 'b3'
[deputy 982dffb] b3</code></pre>
<p>Avec ces instructions, l’utilisateur bascule sur la branche <code>deputy</code> puis écrit <code>b</code> dans le fichier <code>data/letter.txt</code> et ajoute un commit pour cette modification sur la branche <code>deputy</code>.</p>
<p><img alt="Le commit `a4` appliqué sur `master`, le commit `b3` ajouté sur `deputy` et le basculement sur `deputy`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/14-on-deputy.png" /></p>
<p><em>Image : Le commit <code>a4</code> appliqué sur <code>master</code>, le commit <code>b3</code> ajouté sur <code>deputy</code> et le basculement sur <code>deputy</code>.</em></p>
<p><strong>Propriété du graphe :</strong> les commits peuvent partager un même parent. Cela signifie que de nouveaux historiques peuvent être créés.</p>
<p><strong>Propriété du graphe :</strong> un commit peut avoir plusieurs parents. Cela signifie que deux historiques peuvent être fusionnés par un commit qui possède deux parents, c’est ce qu’on appelle un commit de fusion.</p>
<pre>
<code>~/alpha $ git merge master -m 'b4'
Merge made by the 'recursive' strategy.</code></pre>
<p>Ici, l’utilisateur fusionne la branche <code>master</code> avec la branche <code>deputy</code>.</p>
<p>Git découvre que le commit receveur, <code>b3</code> et que le commit donneur, <code>a4</code>, ont chacun un historique différent. Il crée un commit de fusion. Ce processus se déroule selon huit étapes.</p>
<p>Tout d’abord, Git écrit l’empreinte du commit donneur dans le fichier <code>alpha/.git/MERGE_HEAD</code>. Ce fichier, lorsqu’il existe, indique que Git est en train d’effectuer une fusion.</p>
<p>Ensuite, Git détermine le commit de base : c’est le plus proche ancêtre commun au commit donneur et au commit receveur.</p>
<p><img alt="`a3`, le commit de base pour `a4` et `b3`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/15-on-deputy.png" /></p>
<p><em>Image : <code>a3</code>, le commit de base pour <code>a4</code> et <code>b3</code></em></p>
<p><strong>Propriété du graphe :</strong> les commits ont des parents. Cela signifie qu’on peut déterminer le point à partir duquel deux historiques ont divergé. Git remonte l’historique de <code>b3</code> pour trouver ses ancêtres et fait de même avec <code>a4</code> pour trouver les ancêtres de <code>a4</code>. Il trouve alors l’ancêtre le plus récent, présent dans les deux historiques : <code>a3</code>. Ce commit est le commit de base.</p>
<p>Dans un troisième temps, Git génère trois index pour le commit de base, le commit donneur et le commit receveur grâce à leurs graphes respectifs.</p>
<p>Lors de la quatrième étape, Git génère une différence qui combine les modifications appliquées à la base par le commit receveur d’une part et le commit donneur d’autre part. Cette différence est une liste de chemins de fichiers qui identifient un changement : un ajout, une suppression, une modification ou un conflit.</p>
<p>Pour la construire, Git dresse la liste de tous les fichiers qui apparaissent dans les index du commit de base, du commit receveur et du commit donneur. Pour chacune, il compare les éléments de l’index pour choisir le changement à appliquer au fichier et il écrit l’élément correspondant dans la différence. Dans cet exemple, la différence possède deux éléments.</p>
<p>Le premier élément concerne <code>data/letter.txt</code>. Le contenu de ce fichier vaut <code>a</code> pour le commit de base, <code>b</code> pour le commit receveur et <code>a</code> pour le commit donneur. Le contenu est donc différent entre la base et le receveur mais est le même entre la base et le donneur. Git détecte que le contenu a été modifié par le receveur mais pas par le donneur. Aussi, l’élément de la liste des différences pour <code>data/letter.txt</code> est une modification (ce n’est pas un conflit).</p>
<p>Le deuxième élément se rapporte à <code>data/number.txt</code>. Dans ce cas, le contenu est le même entre la base et le receveur et il est différent dans le donneur. L’élément de la liste des différences pour le fichier <code>data/letter.txt</code> est également une modification.</p>
<p><strong>Propriété du graphe :</strong> il est possible de déterminer le commit de base d’une fusion. Cela signifie que si un fichier a évolué depuis le commit de base mais uniquement sur le receveur ou sur le donneur, Git peut automatiquement résoudre la fusion pour ce fichier. Cela diminue le travail laissé à l’utilisateur.</p>
<p>Cinquièmement, les modifications indiquées dans la liste des différences (<code>diff</code>) sont appliquées sur la copie de travail. Le contenu de <code>data/letter.txt</code> vaut désormais <code>b</code> et le contenu de <code>data/number.txt</code> vaut désormais <code>4</code>.</p>
<p>Sixièmement, les modifications indiquées dans la liste des différences sont appliquées à l’index. L’entrée pour <code>data/letter.txt</code> pointe désormais vers le blob <code>b</code> et l’entrée pour <code>data/number.txt</code> pointe désormais vers le blob <code>4</code>.</p>
<p>Septièmement, l’index mis à jour est commité :</p>
<pre>
<code>tree 20294508aea3fb6f05fcc49adaecc2e6d60f7e7d
parent 982dffb20f8d6a25a8554cc8d765fb9f3ff1333b
parent 7b7bd9a5253f47360d5787095afc5ba56591bfe7
author Mary Rose Cook <mary@maryrosecook.com> 1425596551 -0500
committer Mary Rose Cook <mary@maryrosecook.com> 1425596551 -0500
b4</code></pre>
<p>On peut remarquer ici que le commit possède deux parents.</p>
<p>À la huitième et dernière étape, Git fait pointer la branche courante, <code>deputy</code>, sur le nouveau commit.</p>
<p><img alt="`b4`, le commit de fusion obtenu suite à la fusion récursive de `a4` sur `b3`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/16-on-deputy.png" /></p>
<p><em>Image : <code>b4</code>, le commit de fusion obtenu suite à la fusion récursive de <code>a4</code> sur <code>b3</code></em></p>
<h2>Fusionner deux commits ayant un historique différent et qui modifient le même fichier</h2>
<pre>
<code>~/alpha $ git checkout master
Basculement sur la branche 'master'
~/alpha $ git merge deputy
Fast-forward</code></pre>
<p>L’utilisateur bascule sur la branche <code>master</code> puis fusionne la branche <code>master</code>. Cela propage la branche master en avance rapide jusqu’au commit <code>b4</code>. Les branches <code>master</code> et <code>deputy</code> pointent désormais vers le même commit.</p>
<p><img alt="La branche `deputy`, fusionnée avec `master` permet d'amener `master` au dernier commit, `b4`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/17-master-deputy-on-b4.png" /></p>
<p><em>Image : La branche <code>deputy</code>, fusionnée avec <code>master</code> permet d’amener <code>master</code> au dernier commit, <code>b4</code>.</em></p>
<pre>
<code>~/alpha $ git checkout deputy
Basculement sur la branche 'deputy'
~/alpha $ printf '5' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m 'b5'
[deputy bd797c2] b5</code></pre>
<p>L’utilisateur bascule sur la branche <code>deputy</code> puis ajoute un fichier <code>data/number.txt</code> qui contient <code>5</code> et ajoute un commit sur <code>deputy</code> pour enregistrer cette modification.</p>
<pre>
<code>~/alpha $ git checkout master
Basculement sur la branche 'master'
~/alpha $ printf '6' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m 'b6'
[master 4c3ce18] b6</code></pre>
<p>L’utilisateur bascule sur <code>master</code>. Il définit un fichier <code>data/number.txt</code> qui contient <code>6</code> et ajoute un commit sur <code>master</code> pour cette modification.</p>
<p><img alt="Le commit `b5` sur `deputy` et le commit `b6` sur `master`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/18-on-deputy-b6-on-master.png" /></p>
<p><em>Image : Le commit <code>b5</code> sur <code>deputy</code> et le commit <code>b6</code> sur <code>master</code>.</em></p>
<pre>
<code>~/alpha $ git merge deputy
CONFLIT (contenu) : Conflit de fusion dans data/number.txt
La fusion automatique a échoué ; réglez les conflits et validez le résultat.</code></pre>
<p>L’utilisateur fusionne <code>deputy</code> et <code>master</code>. Il y a un conflit et la fusion est donc interrompue. Lorsqu’un conflit se produit, les 6 premières étapes sont les mêmes que lors d’une fusion sans conflit : on définit <code>.git/MERGE_HEAD</code>, on trouve le commit de base, on génère les index de la base, les commits donneurs et receveurs, on crée un diff, on met à jour la copie de travail et l’index. Étant donné le conflit, la septième étape de commit et la huitième qui met à jour la référence n’ont jamais lieu. Revoyons les étapes pour voir ce qui se passe exactement.</p>
<p>Pour commencer, Git écrit l’empreinte du commit donneur dans un fichier <code>.git/MERGE_HEAD</code>.</p>
<p><img alt="L'écriture de `MERGE_HEAD` lors de la fusion de `b5` sur `b6`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/19-on-master-with-merge-head.png" /></p>
<p>_Image : L’écriture de <code>MERGE_HEAD</code> lors de la fusion de <code>b5</code> sur <code>b6</code>_</p>
<p>Ensuite, Git identifie le commit de base : <code>b4</code>.</p>
<p>Lors de la troisième étape, Git génère les index pour le commit de base, le commit donneur et le commit receveur.</p>
<p>À la quatrième étape, Git génère une différence (<i>diff</i>) qui combine les modifications appliquées à la base par le commit receveur d’une part et par le commit donner d’autre part. Cette différence est une liste de chemins de fichiers qui traduisent un changement : un ajout, une suppression, une modification ou un conflit.</p>
<p>Dans notre cas, la différence ne contient qu’un seul élément : <code>data/number.txt</code>. Cet élément est identifié comme un conflit car le contenu de <code>data/number.txt</code> est différent entre le receveur, le donneur et la base.</p>
<p>Ensuite, à la cinquième étape, les modifications indiquées pour chacun des éléments de la différence sont appliquées à la copie de travail. Lorsqu’il s’agit d’un conflit, Git écrit les deux versions du fichier dans la copie de travail. Le contenu de <code>data/number.txt</code> vaut alors :</p>
<pre>
<code><<<<<<< HEAD
6
=======
5
>>>>>>> deputy</code></pre>
<p>À la sixième étape, les modifications listées pour les éléments de la différence sont appliquées à l’index. Les éléments de l’index sont identifiés de façon unique grâce à une combinaison de leur chemin et de leur niveau. Le niveau correspondant à un fichier sans conflit est <code>0</code>. Avant cette fusion, l’index ressemblait à ceci, les <code>0</code> correspondant aux niveaux :</p>
<pre>
<code>0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748
0 data/number.txt 62f9457511f879886bb7728c986fe10b0ece6bcb</code></pre>
<p>Après l’écriture de la différence relative à la fusion dans l’index, l’index ressemble à ceci :</p>
<pre>
<code>0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748
1 data/number.txt bf0d87ab1b2b0ec1a11a3973d2845b42413d9767
2 data/number.txt 62f9457511f879886bb7728c986fe10b0ece6bcb
3 data/number.txt 7813681f5b41c028345ca62a2be376bae70b7f61</code></pre>
<p>L’élément pour <code>data/letter.txt</code> de niveau <code>0</code> est le même qu’avant la fusion. Il n’y a plus d’élément <code>data/number.txt</code> de niveau <code>0</code> mais trois nouveaux éléments à la place. L’élément de niveau <code>1</code> contient l’empreinte du contenu de <code>data/number.txt</code> pour le commit de base. L’élément de niveau <code>2</code> contient l’empreinte du contenu de <code>data/number.txt</code> pour le commit receveur. Celui de niveau trois contient l’empreinte du contenu de <code>data/number.txt</code> pour le commit donneur. La présence de ces trois éléments permet à Git d’identifier un conflit pour le fichier <code>data/number.txt</code>.</p>
<p>Le processus de merge s’arrête.</p>
<pre>
<code>~/alpha $ printf '11' > data/number.txt
~/alpha $ git add data/number.txt</code></pre>
<p>Ici l’utilisateur intègre le contenu des deux versions conflictuelles en écrivant <code>11</code> dans <code>data/number.txt</code>. Ensuite, il ajoute le fichier à l’index. Git ajoute un blob qui contient <code>11</code>. L’ajout d’un fichier en conflit indique à Git que le conflit est résolu. Aussi, Git retire les éléments de niveaux <code>1</code>, <code>2</code> et <code>3</code> dans l’index qu’il remplace par une nouvelle ligne de niveau <code>0</code> contenant l’empreinte du nouveau blob. L’index contient désormais les lignes suivantes :</p>
<pre>
<code>0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748
0 data/number.txt 9d607966b721abde8931ddd052181fae905db503
~/alpha $ git commit -m 'b11'
[master 251a513] b11</code></pre>
<p>Lors de la septième étape, l’utilisateur ajoute un commit. Git voit le fichier <code>.git/MERGE_HEAD</code> dans le dépôt et sait donc qu’une fusion est en cours. Il vérifie l’index et ne trouve aucun conflit, il crée alors un nouveau commit, <code>b11</code>, pour enregistrer le contenu de la fusion pour laquelle le conflit a été résolu. Il supprime le fichier <code>.git/MERGE_HEAD</code>. La fusion est alors terminée.</p>
<p>Enfin, à la huitième étape, Git fait pointer la branche courante, <code>master</code>, sur le dernier commit.</p>
<p><img alt="`b11`, le commit de fusion provenant de la fusion récursive conflictuelle entre `b5` et `b6`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/20-on-master.png" /></p>
<p><em>Image : <code>b11</code>, le commit de fusion provenant de la fusion récursive conflictuelle entre <code>b5</code> et <code>b6</code></em></p>
<h2>Supprimer un fichier</h2>
<p>Le diagramme de ce graphe Git contient l’historique des commits, les arbres et les blobs du dernier commit, ainsi que la copie de travail et l’index :</p>
<p><img alt="La copie de travail, l'index, le commit `b11` et son graphe" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/21-with-objects-wc-and-index.png" /></p>
<p><em>Image : La copie de travail, l’index, le commit <code>b11</code> et son graphe</em></p>
<pre>
<code>~/alpha $ git rm data/letter.txt
rm 'data/letter.txt'</code></pre>
<p>L’utilisateur indique à Git de supprimer <code>data/letter.txt</code>. Le fichier est supprimé de la copie de travail et l’entrée est supprimée de l’index.</p>
<p><img alt="L'état après la suppression de `data/letter.txt` de la copie de travail et de l'index." src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/22-letter-removed-from-wc-and-index.png" /></p>
<p><em>Image : L’état après la suppression de <code>data/letter.txt</code> de la copie de travail et de l’index.</em></p>
<pre>
<code>~/alpha $ git commit -m '11'
[master d14c7d2] 11</code></pre>
<p>L’utilisateur ajoute un commit. Comme toujours avec le commit, Git construit l’arbre qui représente le contenu de l’index. <code>data/letter.txt</code> n’est pas inclus dans cet arbre car il n’est pas dans l’index.</p>
<p><img alt="Le commit `11` réalisé après la suppression de `data/letter.txt`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/23.png" /></p>
<p><em>Image : Le commit <code>11</code> réalisé après la suppression de <code>data/letter.txt</code></em></p>
<h2>Copier un dépôt</h2>
<pre>
<code>~/alpha $ cd ..
~ $ cp -R alpha bravo</code></pre>
<p>L’utilisateur copie le contenu du répertoire <code>alpha/</code> dans le répertoire <code>bravo/</code>. On obtient alors l’arborescence de fichiers suivante :</p>
<pre>
<code>~
├── alpha
| └── data
| └── number.txt
└── bravo
└── data
└── number.txt</code></pre>
<p>Il y a désormais un autre graphe Git dans le répertoire <code>bravo</code> :</p>
<p><img alt="Le nouveau graphe créé lorsqu’`alpha` est copié en `bravo` avec `cp`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/24-cp-alpha-to-bravo.png" /></p>
<p><em>Image : Le nouveau graphe créé lorsqu’<code>alpha</code> est copié en <code>bravo</code> avec <code>cp</code></em></p>
<h2>Lier un dépôt à un autre dépôt</h2>
<pre>
<code>~ $ cd alpha
~/alpha $ git remote add bravo ../bravo</code></pre>
<p>L’utilisateur retourne dans le dépôt <code>alpha</code>. Ensuite, il définit <code>bravo</code>, un dépôt distant de <code>alpha</code>. Cette opération ajoute les lignes suivantes au fichier <code>alpha/.git/config</code> :</p>
<pre>
<code>[remote "bravo"]
url = ../bravo/</code></pre>
<p>Ces lignes signifient qu’il y a un dépôt distant appelé <code>bravo</code> et que celui-ci se situe à l’emplacement <code>../bravo</code>.</p>
<h2>Récupérer une branche depuis un dépôt distant</h2>
<pre>
<code>~/alpha $ cd ../bravo
~/bravo $ printf '12' > data/number.txt
~/bravo $ git add data/number.txt
~/bravo $ git commit -m '12'
[master 94cd04d] 12</code></pre>
<p>L’utilisateur se déplace dans le dépôt <code>bravo</code> puis crée un fichier <code>data/number.txt</code> qui contient <code>12</code> puis ajoute un commit pour cette modification sur la branche <code>master</code> de <code>bravo</code>.</p>
<p><img alt="Le commit `12` sur le dépôt `bravo`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/26-bravo.png" /></p>
<p><em>Image : Le commit <code>12</code> sur le dépôt <code>bravo</code></em></p>
<pre>
<code>~/bravo $ cd ../alpha
~/alpha $ git fetch bravo master
Dépaquetage des objets : 100%
Depuis ../bravo
* branch master -> FETCH_HEAD</code></pre>
<p>L’utilisateur se déplace dans le dépôt <code>alpha</code>. Il récupère ensuite la branche <code>master</code> depuis le dépôt <code>bravo</code> vers <code>alpha</code>. Cette récupération se fait en quatre étapes.</p>
<p>Pour commencer, Git récupère l’empreinte (<i>hash</i>) du commit sur lequel pointe la branche <code>master</code> du dépôt <code>bravo</code>. C’est l’empreinte du commit <code>12</code>.</p>
<p>Ensuite, Git dresse la liste de tous les objets dont dépend le commit <code>12</code> : l’objet même qui décrit le commit, les objets qui sont sur son graphe, les commits ancêtres du commit <code>12</code> et les objets qui sont dans leurs graphes. Il retire alors de cette liste tous les objets déjà contenus dans la base de données des objets de <code>alpha</code>. Ce qui reste est copié dans <code>alpha/.git/objects/</code>.</p>
<p>La troisième étape consiste à modifier le fichier de référence <code>alpha/.git/refs/remotes/bravo/master</code> afin qu’il contienne l’empreinte du commit <code>12</code>.</p>
<p>Enfin, le contenu de <code>alpha/.git/FETCH_HEAD</code> est défini avec :</p>
<pre>
<code>94cd04d93ae88a1f53a4646532b1e8cdfbc0977f branch 'master' of ../bravo</code></pre>
<p>Cette ligne indique que la commande <code>fetch</code> la plus récente a récupéré le commit <code>12</code> de la branche <code>master</code> depuis <code>bravo</code>.</p>
<p><img alt="`alpha` après avoir récupéré `bravo/master`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/28-fetched-to-alpha.png" /></p>
<p><em>Image : <code>alpha</code> après avoir récupéré <code>bravo/master</code></em></p>
<p><strong>Propriété de graphe :</strong> les objets peuvent être copiés. Cela veut dire que l’historique peut être partagé entre les dépôts.</p>
<p><strong>Propriété de graphe :</strong> un dépôt peut stocker des références à des branches distantes comme <code>alpha/.git/refs/remotes/bravo/master</code>. Cela veut dire qu’un dépôt peut enregistrer localement l’état d’une branche ou d’un dépôt distant. C’est valable au moment où on le récupère mais ça sera périmé quand la branche distante changera.</p>
<h2>Fusionner FETCH_HEAD</h2>
<pre>
<code>~/alpha $ git merge FETCH_HEAD
Mise à jour d14c7d2..94cd04d
Fast-forward</code></pre>
<p>L’utilisateur fusionne <code>FETCH_HEAD</code>. <code>FETCH_HEAD</code> est simplement une autre référence. Elle correspond ici au commit <code>12</code>, celui qui est donné. La référence <code>HEAD</code> correspond au commit <code>11</code>, celui qui reçoit. Git fait une fusion en avance rapide (<code>fast-forward merge</code>) et fait pointer la branche <code>master</code> sur le commit <code>12</code>.</p>
<p><img alt="Le dépôt `alpha` après la fusion avec `FETCH_HEAD`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/30-merged-to-alpha.png" /></p>
<p>_Image : Le dépôt <code>alpha</code> après la fusion avec <code>FETCH_HEAD</code>_</p>
<h2>Tirer une branche depuis un dépôt distant</h2>
<pre>
<code>~/alpha $ git pull bravo master
Already up-to-date.</code></pre>
<p>Ici, l’utilisateur tire la branche <code>master</code> depuis le dépôt <code>bravo</code> vers le dépôt <code>alpha</code>. <code>pull</code> est un raccourci pour « récupère puis fusionne <code>FETCH_HEAD</code> » (<code>fetch</code>/<code>merge</code>). Git applique ces deux commandes puis indique que la branche <code>master</code> est déjà à jour.</p>
<h2>Cloner un dépôt</h2>
<pre>
<code>~/alpha $ cd ..
~ $ git clone alpha charlie
Clonage dans 'charlie'</code></pre>
<p>Là, l’utilisateur remonte dans le dossier parent puis clone le dédpôt <code>alpha</code> vers un dépôt <code>charlie</code>. Cloner le dépôt vers <code>charlie</code> permet d’obtenir des résultats similaires à la copie (via <code>cp</code>) utilisée pour créer le dépôt <code>bravo</code>. Ici, Git crée un nouveau répertoire appelé <code>charlie</code>. Il initialise ce dossier en tant que dépôt Git puis ajoute <code>alpha</code> comme un dépôt distant, appelé <code>origin</code>. Il récupère (<i>fetch</i>) le contenu de <code>origin</code> puis fusionne <code>FETCH_HEAD</code>.</p>
<h2>Pousser une branche vers une branche distante utilisée</h2>
<pre>
<code>~ $ cd alpha
~/alpha $ printf '13' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m '13'
[master 3238468] 13</code></pre>
<p>L’utilisateur retourne dans le dépôt <code>alpha</code>, ajoute un fichier <code>data/number.txt</code> qui contient <code>13</code> puis ajoute un commit pour cette modification sur la branche <code>master</code> sur le dépôt <code>alpha</code>.</p>
<pre>
<code>~/alpha $ git remote add charlie ../charlie</code></pre>
<p>Ensuite, il ajoute un dépôt <code>charlie</code> qui est un dépôt distant d’<code>alpha</code>.</p>
<pre>
<code>~/alpha $ git push charlie master
Écriture des objets : 100%
remote error: refusing to update checked out
branch: refs/heads/master because it will make
the index and work tree inconsistent</code></pre>
<p>Enfin, il pousse la branche <code>master</code> vers <code>charlie</code>.</p>
<p>Tous les objets nécessaires au commit <code>13</code> sont copiés dans <code>charlie</code>.</p>
<p>À cette étape, le push s’arrête. Git, comme à son habitude, indique à l’utilisateur qu’il y a un problème. Il refuse de pousser vers une branche utilisée sur le dépôt distant. C’est plutôt logique, une opération push mettrait à jour l’index du dépôt distant et son commit <code>HEAD</code>. Cette modification serait source de confusion si quelqu’un éditait la version de travail sur le dépôt distant.</p>
<p>Ici, l’utilisateur, pourrait créer une nouvelle branche, fusionner ce commit <code>13</code> sur cette branche puis pousser cette branche vers le dépôt <code>charlie</code>. En fait, ce qu’il veut, c’est un dépôt vers lequel pousser à tout moment, un dépôt central vers lequel on peut pousser ou depuis lequel on peut tirer des branches, sans personne qui n’y ajoute de commits directement. Bref, il veut quelque chose qui se comporte comme GitHub : c’est ce qu’on appelle un dépôt nu (<code>bare</code>).</p>
<h2>Cloner un dépôt nu</h2>
<pre>
<code>~/alpha $ cd ..
~ $ git clone alpha delta --bare
Clonage dans le dépôt nu 'delta'</code></pre>
<p>Ici, l’utilisateur se déplace dans le répertoire parent. Ensuite, il clone le dépôt <code>alpha</code> dans un dépôt <code>delta</code>, indiqué comme un dépôt nu (<i>bare repository</i>). C’est un clone classique avec deux différences notables : le fichier de configuration indique que le dépôt est un dépôt nu et les fichiers normalement stockés dans le dossier <code>.git</code> sont ici stocké à la racine du répertoire :</p>
<pre>
<code>delta
├── HEAD
├── config
├── objects
└── refs</code></pre>
<p><img alt="Les graphes d’`alpha` et de `delta` après le clonage dans `delta`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/32-alpha-cloned-to-delta-bare.png" /></p>
<p><em>Image : Les graphes d’<code>alpha</code> et de <code>delta</code> après le clonage dans <code>delta</code></em></p>
<h2>Pousser une branche vers un dépôt nu</h2>
<pre>
<code>~ $ cd alpha
~/alpha $ git remote add delta ../delta</code></pre>
<p>L’utilisateur retourne dans le dépôt <code>alpha</code> puis définit <code>delta</code> comme un dépôt distant sur <code>alpha</code>.</p>
<pre>
<code>~/alpha $ printf '14' > data/number.txt
~/alpha $ git add data/number.txt
~/alpha $ git commit -m '14'
[master cb51da8] 14</code></pre>
<p>Ces instructions ajoutent un fichier <code>data/number.txt</code> (dont le contenu est <code>14</code>) puis ajoutent un commit sur la branche <code>master</code> du dépôt <code>alpha</code>.</p>
<p><img alt="Le commit `14` sur `alpha`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/34-alpha.png" /></p>
<p><em>Image : Le commit <code>14</code> sur <code>alpha</code></em></p>
<pre>
<code>~/alpha $ git push delta master
Écriture des objets : 100%
To ../delta
3238468..cb51da8 master -> master</code></pre>
<p>Ici, on pousse les données de <code>master</code> vers <code>delta</code>. Cela se fait en trois étapes.</p>
<p>Tout d’abord, tous les objets nécessaires au commit <code>14</code> de la branche <code>master</code> sont copiés de <code>alpha/.git/objects/</code> vers <code>delta/objects</code>.</p>
<p>Ensuite, <code>delta/refs/heads/master</code> est mis à jour pour pointer au commit <code>14</code>.</p>
<p>Troisièmement, <code>alpha/.git/refs/remotes/delta/master</code> pointe vers le commit <code>14</code>. <code>alpha</code> a ainsi un état à jour de <code>delta</code>.</p>
<p><img alt="Le commit `14` poussé depuis `alpha` vers `delta`" src="https://tech.mozfr.org/dotclear/public/bidouilleux/Git/36-pushed-to-delta.png" /></p>
<p><em>Image : Le commit <code>14</code> poussé depuis <code>alpha</code> vers <code>delta</code></em></p>
<h2>Résumé</h2>
<p>Git est construit sur un graphe. Presque toutes les commandes Git manipulent ce graphe. Pour comprendre Git en profondeur, il faut se concentrer sur les propriétés de ce graphe et non sur des <i>workflows</i> ou sur des commandes.</p>
<p>Pour en savoir plus sur Git, examinez le répertoire <code>.git</code>. Ce n’est pas forcément effrayant. Regardez à l’intérieur. Changez le contenu des fichiers et regardez ce qui se passe. Créez un commit à la main. Essayez et regardez comment vous pouvez mettre un dépôt sens dessus dessous. Ensuite, réparez-le.</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn:1">
<p>Dans ce cas, l’empreinte est plus longue que le contenu. Mais tous les morceaux de contenu plus longs que le nombre de caractères dans l’empreinte seront exprimés de façon plus concise que l’original. <a class="footnote-backref" href="https://tech.mozfr.org/post/2017/01/14/Dans-les-entrailles-de-Git#fnref1:1" rev="footnote">↩</a></p>
</li>
<li id="fn:2">
<p>Il y a une chance que deux contenus différents aient la même empreinte mais les probabilités sont faibles. <a class="footnote-backref" href="https://tech.mozfr.org/post/2017/01/14/Dans-les-entrailles-de-Git#fnref1:2" rev="footnote">↩</a></p>
</li>
<li id="fn:3">
<p><code>git prune</code> supprime tous les objets qui ne peuvent êtres atteints depuis une référence. Si un utilisateur utilise cette commande, il peut perdre du contenu. <a class="footnote-backref" href="https://tech.mozfr.org/post/2017/01/14/Dans-les-entrailles-de-Git#fnref1:3" rev="footnote">↩</a></p>
</li>
<li id="fn:4">
<p><code>git stash</code> sauvegarde toutes les différences entre la copie de travail et le commit <code>HEAD</code> dans un endroit sûr. Elles peuvent être retrouvées plus tard. <a class="footnote-backref" href="https://tech.mozfr.org/post/2017/01/14/Dans-les-entrailles-de-Git#fnref1:4" rev="footnote">↩</a></p>
</li>
<li id="fn:5">
<p>La commande <code>rebase</code> peut être utilisée afin d’ajouter, éditer ou supprimer des commits dans l’historique du dépôt. <a class="footnote-backref" href="https://tech.mozfr.org/post/2017/01/14/Dans-les-entrailles-de-Git#fnref1:5" rev="footnote">↩</a></p>
</li>
</ol>
</div>
<style type="text/css">pre { white-space: pre;}
</style>
Le projet Quantum de Mozilla, des ambitions et des précisions
urn:md5:a2101a8a6d00aab78661ce4808fc26eb
2016-10-29T09:09:00+02:00
2017-12-19T10:47:21+01:00
Goofy
Développement Web
DOM
Firefox
JavaScript
Quantum
thread
<p>Après un premier billet qui traçait les grandes lignes du projet et sa philosophie (<a href="https://tech.mozfr.org/post/2016/10/27/Un-saut-quantique-pour-le-Web" hreflang="fr">Un saut quantique pour le Web</a>) nous disposons maintenant d’un peu plus de précisions techniques grâce à <a href="https://billmccloskey.wordpress.com/2016/10/27/mozillas-quantum-project/" hreflang="en">cet article de Bill Mc Closkey </a>que nous vous traduisons ci-dessous : après avoir mentionné les quatre projets distincts mais associés sous la bannière de Quantum, Bill nous expose l’avancement du projet Quantum Code et <strong>invite déjà à contribuer</strong>. Bill travaille notamment sur les processus séparés chez Mozilla, il n’est donc guère surprenant de le retrouver ici impliqué sur le projet Quantum.</p>
<p><em>Traduction MozFr : Julien / Sphinx, Jérémie et Goofy</em></p> <figure style="margin: 0 auto; display: table;"><a class="media-link" href="https://tech.mozfr.org/dotclear/public/Capture_du_2016-10-28_23-43-53.png"><img alt="Capture_du_2016-10-28_23-43-53.png" class="media" src="https://tech.mozfr.org/dotclear/public/Capture_du_2016-10-28_23-43-53.png" /></a>
<figcaption> </figcaption>
</figure>
<h2>Quantum comporte quatre projets distincts…</h2>
<p>Le projet <strong>Quantum CSS </strong>remplacera le moteur CSS de Gecko par celui de Servo. Le moteur de Servo permet un parallélisme massif, pas celui de Gecko.</p>
<p>Le projet <strong>Quantum DOM</strong> va rendre Gecko plus adaptatif, particulièrement dans le cas où de très nombreux onglets sont ouverts en arrière-plan. Lorsque le Quantum DOM sera achevé, le code JS de différents onglets (et peut-être de différents <em>iframes</em>) tournera de façon coordonnée et planifiée entre différents <em>threads</em> de sorte que le code de plusieurs onglets en arrière-plan ne s’interrompe jamais.</p>
<p>Le <strong>Compositor</strong> de Quantum remplacera celui de Gecko par le sien propre. Comme l’instabilité des <em>drivers</em> graphiques est une cause principale des plantages de Firefox, nous espérons que transférer le code qui interagit avec le <abbr lang="en" title="Graphics Processing Unit">GPU</abbr> rendra Firefox beaucoup plus stable.</p>
<p>Enfin,<strong> Quantum Rendering</strong> remplacera le sous-système de rendu graphique de Gecko par celui de Servo, appelé WebRender. Servo utilise le GPU de façon plus efficace que Gecko, il le pilote comme le ferait un jeu plutôt que comme un navigateur a l’habitude de le faire.</p>
<p>Ces projets en sont à des stades d’avancement divers. Le projet Quantum Compositor est déjà bien avancé alors que Quantum Rendering n’en est qu’à ses débuts. Il existe encore pas mal d’incertitudes sur ces projets. Toutefois je veux dire quelques mots à propos de Quantum DOM, le projet sur lequel je travaille.</p>
<h2>Quantum DOM</h2>
<p>Quantum DOM est essentiellement conçu pour que le contenu web fonctionne mieux et pour que les « accrocs » soient plus rares (il s’agit des pénibles petits sursauts et hoquets qui dégradent l’expérience de navigation). Un grand nombre de ces accrocs viennent des onglets en arrière-plan et des publicités. Voyez leur évaluation <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1296486" hreflang="en">sur le bug 1296486</a>. Un moyen pratique de réduire ces nuisances consiste à faire tourner chaque onglet et autre cadre de rendu de l’onglet dans son propre processus. Dans la mesure où les systèmes d’exploitation organisent les processus de manière préemptive, les cadres en arrière-plan peuvent toujours êtres interrompus pour favoriser le travail d’avant-plan.</p>
<p>Malheureusement, si l’on augmente le nombre de processus de contenu on augmente aussi la consommation de mémoire. D’après les expériences préliminaires que nous avons menées, il semble peu probable que nous puissions augmenter le nombre de processus de contenu au-delà de 8 dans un futur proche ; tout processus supplémentaire augmenterait de façon inacceptable la consommation de mémoire. Il vaut certainement mieux avoir 8 processus au lieu d’un, mais de nombreux utilisateurs ont plus de 8 onglets ouverts simultanément. Les développeurs de Mozilla travaillent à réduire la consommation excessive de mémoire des processus de contenu, mais nous ne pourrons jamais réduire cet excès à zéro. Nous explorons donc d’autres pistes, comme l’ordonnancement coopératif pour les onglets et le gel des onglets.</p>
<h3>L’ordonnancement coopératif</h3>
<p>Quantum DOM est une approche alternative qui vise à réduire les accrocs de performance sans augmenter la quantité de mémoire utilisée. Plutôt que de placer les différents cadres de rendu dans différents processus, nous les placerons dans des <em>threads</em> séparés. Plutôt que d’utiliser les <em>threads</em> du système d’exploitation, nous utiliserons des <em>threads</em> ordonnancés de façon coopérative dans la sphère de l’utilisateur (<abbr title="Note du traducteur">NDT</abbr> : <em>user space</em> : tout le code qui n’est pas lié au cœur du système d’exploitation). L’avantage des <em>threads</em> est qu’ils permettent de partager un même espace d’adressage ce qui facilite le partage de données. L’inconvénient c’est que ces données partagées doivent être protégées par des verrous. Les <em>threads</em> ordonnancés de façon coopérative nous permettent de passer d’un thread à l’autre de manière « sûre » à des endroits où l’état partagé est censément consistant, rendant de fait les verrous inutiles (ou à minima, moins nécessaires).</p>
<p>Firefox dispose d’ores et déjà d’un ensemble de points de transition sûrs. Le moteur <a href="https://tech.mozfr.org/tag/JavaScript">JavaScript</a> est capable de se mettre en pause lors d’un appel de fonction ou d’un tour de boucle afin d’exécuter un autre code. Nous utilisons d’ailleurs déjà cette capacité pour mettre en œuvre la fenêtre de dialogue « script lent » qui permet à un utilisateur d’arrêter une boucle infinie. Cependant, le même mécanisme peut être utilisé pour passer d’un thread à l’autre et commencer à exécuter du code issu d’un nouveau cadre de rendu.</p>
<p>Nous avons déjà commencé certaines expérimentations autour de ces concepts. <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1279086" hreflang="en">Le bug 1279086</a>, déployé dans Firefox 51, nous permet de mettre en pause l’exécution de JavaScript afin de rafraîchir l’affichage lors d’un changement d’onglet. Nos mesures de télémétrie nous indiquent que la conséquence directe de ceci est une diminution de moitié du nombre de changements d’onglets lents (> 300 ms). Nous espérons utiliser le même genre de démarche dans un futur proche pour pouvoir rafraîchir l’affichage des pages pendant le <em>scrolling</em>, même si une page d’arrière-plan est en train de faire tourner du JavaScript.</p>
<p>Notre objectif ultime cependant, c’est d’exécuter tous les cadres de rendu dans leur propre thread ordonnancé de manière coopérative. Cela nous permettra de mettre en pause un onglet d’arrière-plan afin de prioriser un certain nombre de tâches (i.e. les évènements d’entrée ou les animations) dans un onglet d’avant-plan. Avant d’en arriver là, il nous faut « étiqueter » toutes les tâches de notre pile d’évènements pour pouvoir les faire correspondre au cadre auquel elles appartiennent. De cette façon nous pourrons exécuter chaque tâche dans le <a href="https://tech.mozfr.org/tag/thread">thread</a> qui lui correspond.</p>
<p>Étiqueter les tâches et les prioriser est un énorme travail. Michael Layzell est en train de construire une abstraction de regroupement (<em>“DocGroup”</em>) de manière à ce que les cadres de même origine (ceux qui peuvent dialoguer via <code>window.parent</code> ou <code>window.opener</code>) s’exécutent dans le même thread (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1303196" hreflang="en">bug 1303196</a>). Andreas Farre est en train de mettre en place un mécanisme permettant d’organiser les tâches à basse priorités comme le GC (bug <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1198381" hreflang="en">1198381</a>). Olli Pettay et Thinker Li sont, eux, en train de créer un mécanisme similaire pour les tâches à haute priorité comme les évènements d’entrée (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1306591">bug 1306591</a>). Au fur et à mesure de l’amélioration de notre infrastructure d’étiquetage dans les prochaines semaines, cet étiquetage des tâches sera<strong> un point d’entrée idéal pour les contributeurs qui veulent rejoindre le projet</strong>. L’état d’avancement du projet sera régulièrement publié sur <a href="https://lists.mozilla.org/listinfo/dev-platform" hreflang="en">la liste de diffusion dev-platform</a>.</p>
<p>Finalement, nous pourrons envisager d’exécuter nos cadres de rendu dans leur propre <em>thread</em> géré par le système d’exploitation de façon préemptive. L’avantage des <em>threads</em> du système d’exploitation par rapport aux threads « utilisateur » c’est leur capacité à utiliser plusieurs cœurs du processeur. Cependant nos <em>threads</em> d’utilisateur seront déjà répartis sur 4 à 8 processus et la plupart des utilisateurs n’ont pas vraiment plus de cœurs de processeurs disponibles. Au fur et à mesure de l’avancée du projet nous évaluerons à nouveau si exploiter les <em>threads</em> du système d’exploitation nous semble pertinent.</p>
<p>Enfin, ce serait négligence de ma part de ne pas indiquer qu’Opera a été le premier à travailler sur une architecture d’ordonnancement coopératif. Ce sont eux qui ont accompli tout ça en premier.</p>
<h3>Geler les onglets</h3>
<p>Lors de nos discussions sur les méthodes d’ordonnancement du travail d’arrière-plan, nous nous sommes demandés : pourquoi exécuter quoi que ce soit ? <a href="https://tech.mozfr.org/tag/Firefox">Firefox</a> réduit déjà la fréquence de certaines actions d’arrière-plan telles que les tâches <code>setTimeout</code> qui sont exécutées au plus une fois par seconde. Il est très probable que nous continuerons dans cette direction et que nous ralentirons encore plus. Pourrait-on envisager d’être encore plus agressifs et de geler complètement certains des onglets d’arrière-plan ?</p>
<figure style="margin: 0 auto; display: table;"><a class="media-link" href="https://tech.mozfr.org/dotclear/public/bidouilleux/freeze.png"><img alt="freeze.png" class="media" src="https://tech.mozfr.org/dotclear/public/bidouilleux/freeze.png" /></a>
<figcaption> </figcaption>
</figure>
<p>Il n’est pas toujours possible de geler des onglets d’arrière-plan car certains doivent continuer à tourner. Par exemple, vous voulez que Pandora continue de lire votre musique même lorsque vous changez d’onglet. Vous voulez également que Gmail vous notifie de l’arrivée d’un e-mail même lorsque l’onglet Gmail n’est pas sélectionné. Cependant, si le navigateur avait la capacité d’identifier ces onglets « importants », nous pourrions geler le reste. Nous sommes toujours en train de définir les heuristiques qui nous permettront d’identifier les onglets d’arrière-plan importants. Ehsan Akhgari explore certaines pistes de travail que nous espérons pouvoir tester dans Firefox Nightly prochainement.</p>
<figure style="margin: 0 auto; display: table;"><a href="https://nightly.mozfr.org/" hreflang="fr"><img alt="Capture_du_2016-10-29_00-06-02.png" class="media" src="https://tech.mozfr.org/dotclear/public/Capture_du_2016-10-29_00-06-02.png" /></a>
<figcaption><em>Cliquez sur la bannière pour adopter Firefox Nightly en français</em></figcaption>
</figure>
Un saut quantique pour le Web
urn:md5:9db50da58194f0d4f556c4422be3b135
2016-10-27T21:34:00+02:00
2016-10-29T14:48:41+02:00
Goofy
Développement Web
Electrolysis
Quantum
Web
<p>Cet article est la traduction du billet <em><a href="https://medium.com/mozilla-tech/a-quantum-leap-for-the-web-a3b7174b3c12#.6ii3kdttp" hreflang="en">A Quantum Leap for the Web</a></em> écrit par <a href="https://medium.com/@david_bryant" hreflang="en">David Bryant</a>. David est à la tête de l’équipe <em>Platform Engineering</em> de Mozilla et il annonce ici le lancement et le futur proche du projet Quantum, qui vise à renouveler entièrement plusieurs composants majeurs sur lesquels reposent les technologies web actuelles.</p>
<p><em>Traduction MozFr : Sphinx, TheGennok et Goofy.</em></p> <figure style="float: left; margin: 0 1em 1em 0;"><a class="media-link" href="https://tech.mozfr.org/dotclear/public/bidouilleux/davidBryant.jpeg"><img alt="davidBryant.jpeg" class="media" height="75" src="https://tech.mozfr.org/dotclear/public/bidouilleux/davidBryant.jpeg" width="75" /></a>
<figcaption>David Bryant</figcaption>
</figure>
<p>Au cours de cette année, le projet <a href="https://tech.mozfr.org/tag/Electrolysis">Electrolysis</a> a été la plus haute priorité pour <a href="https://www.mozilla.org/fr/firefox/products/">Firefox</a> afin que les utilisateurs puissent naviguer en bénéficiant de plusieurs processus. Utiliser plusieurs processus pour Firefox permet d’améliorer considérablement la sécurité et les performances. C’est la plus grande modification ayant jamais été appliquée à Firefox et pendant les prochains mois, nous déploierons la première partie d’Electrolysis pour 100 % des utilisateurs de Firefox sur ordinateur.</p>
<p>Ceci étant dit, nous ne manquons pas d’idées pour améliorer les performances et la sécurité. En fait, Electrolysis nous a simplement permis d’accomplir quelque chose d’encore plus grand.</p>
<p>Nous l’appelons le Projet <a href="https://tech.mozfr.org/tag/Quantum">Quantum</a>.</p>
<p>Quantum représente notre effort afin de développer le moteur web de prochaine génération pour Mozilla et commencer à fournir aux utilisateurs des améliorations majeures d’ici la fin 2017. Si vous n’êtes pas familier avec le concept d’un moteur web, il s’agit du cœur de navigateur qui exécute l’ensemble du contenu que vous recevez lorsque vous naviguez sur le <a href="https://tech.mozfr.org/tag/Web">Web</a>. L’objectif de Quantum est de tirer le meilleur parti du parallélisme et d’exploiter pleinement les composants matériels modernes. Quantum contient différents composants dont plusieurs ont été adoptés à partir du projet Servo.</p>
<p>Le moteur obtenu permettra une navigation rapide et fluide tant sur les ordinateurs que sur les mobiles, créant ainsi un « saut quantique » (<abbr title=Note du traducteur”>NDT</abbr> : <em>quantum leap</em> en anglais, ce nom est inspiré d’<a href="https://fr.wikipedia.org/wiki/Code_Quantum">une série TV de science-fiction</a>) en termes de performance. Qu’est-ce que cela signifie ? Grâce à Quantum, nous visons des gains de performance qui soient tellement remarquables que toute votre navigation sur le Web s’en trouvera changée : les pages chargeront plus rapidement, le défilement sera fluide ; les animations et les applications interactives répondront instantanément et pourront gérer des contenus encore plus intensifs tout en conservant des taux de rafraîchissement constants. Enfin, le contenu qui vous est le plus important bénéficiera automatiquement de la priorité la plus haute, concentrant la puissance de calcul là où vous en avez besoin.</p>
<p>Comment parviendrons-nous à tout cela ?</p>
<p>Les navigateurs web sont apparus à l’ère des ordinateurs de bureau. Ces premiers ordinateurs étaient simplement équipés de processeurs dotés d’un seul cœur qui ne pouvaient traiter les commandes que dans un seul flux. Ils ne réalisaient qu’une seule chose à la fois. Encore aujourd’hui, dans la plupart des navigateurs, une page web est principalement exécutée dans un seul <em>thread</em>, au sein d’un seul cœur.</p>
<p>Mais de nos jours, on navigue sur Internet depuis un téléphone, une tablette ou un ordinateur portable, qui ont tous des processeurs bien plus sophistiqués, souvent avec deux, quatre cœurs ou plus. De plus, il est maintenant commun d’incorporer un ou plusieurs processeurs graphiques (<abbr title="Graphics processing Unit">GPU)</abbr> extrêmement puissants, qui peuvent accélérer le temps de rendu ou d’autres types de calcul.</p>
<p>Un autre changement majeur s’est produit ces quinze dernières années sur le Web : ce dernier est passé d’un ensemble de documents statiques associés par des hyperliens à une constellation d’applications interactives. Les développeurs souhaitent construire des parcours sans aucune latence, des animations foisonnantes et des interactions en temps réel, autant de choses également attendues par les consommateurs. Pour que cela soit possible, il nous faut une plateforme web qui permettent aux développeurs de bénéficier de la puissance sous-jacente de l’appareil, sans qu’ils aient à se prendre la tête sur la complexité liée au parallélisme et aux spécificités du matériel.</p>
<figure style="float: right; margin: 0 0 1em 1em;"><a class="media-link" href="https://tech.mozfr.org/dotclear/public/bidouilleux/SERVO.png"><img alt="SERVO.png" class="media" src="https://tech.mozfr.org/dotclear/public/bidouilleux/SERVO.png" /></a>
<figcaption>Logo de Servo</figcaption>
</figure>
<p>Ainsi, le projet Quantum consiste à développer un moteur de nouvelle génération afin de répondre à la demande du Web de demain grâce à la puissance de calculs de nos appareils modernes. Quantum démarrera à partir de Gecko et remplacera des composants principaux du moteur qui bénéficieront le plus de la parallélisation ou d’une migration vers le <abbr title="Graphics processing Unit">GPU</abbr>. Un axe majeur de notre stratégie consiste à intégrer des composants révolutionnaires de <a href="http://servo.org/">Servo</a>, un projet indépendant et communautaire de moteur web, parrainé par Mozilla. Dans un premier temps, Quantum partagera quelques composants avec Servo mais au fur et à mesure de l’évolution du projet, nous ferons des essais pour en intégrer encore plus.</p>
<p>Un certain nombre de composants de Quantum sont écrits en <a href="https://www.rust-lang.org/fr-FR/" hreflang="fr">Rust</a>. Si Rust ne vous est pas familier, il s’agit d’un langage de programmation système qui fonctionne à la vitesse de l’éclair et simplifie le développement de programmes parallélisés, en garantissant la sûreté de la mémoire et l’isolation entre les <em>threads</em>. Dans la plupart des cas, le code écrit en Rust refusera même de compiler s’il n’est pas fiable.</p>
<figure style="float: left; margin: 0 1em 1em 0;"><a class="media-link" href="https://tech.mozfr.org/dotclear/public/bidouilleux/rust-logo-blk.svg"><img alt="rust-logo-blk.svg" class="media" src="https://tech.mozfr.org/dotclear/public/bidouilleux/rust-logo-blk.svg" /></a>
<figcaption>Logo de Rust</figcaption>
</figure>
<p>Nous prenons un grand nombre d’initiatives distinctes mais associées dans le cadre de Quantum, et nous allons réexaminer de nombreuses hypothèses et implémentations anciennes. L’approche de haut niveau consiste à repenser de nombreux aspects fondamentaux du mode de fonctionnement d’un moteur de navigateur. Nous allons renouveler la conception des briques fondamentales, par exemple la façon d’appliquer les styles CSS, d’exécuter les opérations sur le DOM, et d’obtenir le rendu graphique sur un écran.</p>
<p>Quantum est un projet ambitieux, mais les utilisateurs n’auront pas à attendre longtemps pour en voir les premiers progrès, qui seront rendus publics. Nous allons rendre disponibles les avancées majeures l’année prochaine, à la suite de quoi nous publierons les versions successives. Une première version de notre nouveau moteur fonctionnera sur Android, Windows, Mac et GNU-Linux. Nous espérons aussi qu’un jour nous pourrons le proposer sous iOS.</p>
<p>Nous sommes persuadés que le projet Quantum produira des améliorations significatives de performances. Si vous êtes développeur et souhaitez vous investir, vous pouvez en apprendre davantage sur <a href="http://wiki.mozilla.org/quantum">Quantum sur le wiki de Mozilla</a>, et parcourir les diverses manières d’y contribuer. Nous souhaitons franchir avec vous ce pas décisif, le saut… quantique !</p>
<figure style="margin: 0 auto; display: table;"><a class="media-link" href="https://tech.mozfr.org/dotclear/public/bidouilleux/learnRustNow.png"><img alt="learnRustNow.png" class="media" src="https://tech.mozfr.org/dotclear/public/bidouilleux/.learnRustNow_m.png" /></a>
<figcaption>Image de <a href="https://framalab.org/gknd-creator/">Gégé</a></figcaption>
</figure>
Les onglets contextuels dans Firefox Nightly
urn:md5:42109e8590e3e20fbe81f821d15dfcce
2016-09-18T08:14:00+02:00
2016-09-18T07:17:39+02:00
sphinx
Général
Nightly
Onglet contextuel
Vie privée
<p><strong>Attention : cette fonctionnalité est encore expérimentale et peut être amenée à évoluer :)</strong></p>
<h2>Pourquoi faire ?</h2>
<h3>« Mais dis-moi plutôt qui tu es ? », Rafiki, Le Roi Lion</h3>
<p>Lorsque nous naviguons sur le Web, nous utilisons différents sites pour différents besoins. Notre identité numérique se compose parfois de différentes identités selon le contexte : personnel, professionnel, consommation, etc. En revanche, il était jusqu’à présent difficile d’organiser cette compartimentation via le navigateur web (utiliser différents navigateurs, la navigation privée, différents profils, etc.).</p>
<p>La fonctionnalité d’« <em>onglet contextuel</em> », implémentée dans Firefox Nightly, a été conçue pour répondre à ce besoin de séparation. Ainsi, au sein d’une même session et d’un même profil, on pourra se connecter, simultanément, sur un compte personnel et, par exemple, sur un compte professionnel (ou autre) d’un site donné (Twitter, Google, Facebook, Trello…).</p>
<p>Cette séparation permet également de limiter le pistage des différents sites en isolant les données créées par chacun. Nul besoin que les publicités (disons Facebook) laissent des traces à destination des autres sites (disons Amazon). En utilisant ces sites dans des contextes séparés, les cookies, entre autres, seront compartimentés. En plus de ces aspects relatifs à la vie privée, cela permet d’obtenir une meilleure sécurité : cela améliore la protection contre le vol de cookies de session d’un site bancaire par exemple.</p>
<p><em>Note : les onglets contextuels ne sont pas la réponse à toutes les questions, les <a href="https://support.mozilla.org/fr/kb/utiliser-gestionnaire-profils-creer-supprimer-profils">profils</a> ou <a href="https://www.torproject.org/projects/torbrowser.html">Tor Browser</a> ont toujours leurs raisons d’être.</em></p>
<h3>Les différents contextes</h3>
<p>La compartimentation ne se limite pas nécessairement au caractère personnel/professionnel. Pour cette fonctionnalité, Mozilla a prévu un premier ensemble de contextes :</p>
<ul>
<li>Personnel
<ul>
<li>exemple d’utilisation : le compte Twitter personnel, la page Gmail sur une adresse électronique privée,</li>
</ul></li>
<li>Professionnel
<ul>
<li>exemple d’utilisation : les documents Google des clients de l’entreprise (:]]), la page Facebook de la société maintenue à jour, </li>
</ul></li>
<li>Bancaire
<ul>
<li>exemple d’utilisation : suivre mon compte bancaire, accéder à des documents privés sur le portail de mon assurance, </li>
</ul></li>
<li>Achats en ligne
<ul>
<li>exemple d’utilisation : RedBubble, Amazon…</li>
</ul></li>
</ul>
<p>Par la suite, en plus de ces catégories prédéfinies, il est prévu de pouvoir ajouter des contextes personnalisés.
Enfin, il y a un contexte par défaut, le contexte « normal » (ce qui vous permet d’utiliser Firefox sans nécessairement avoir recours aux contextes).</p>
<p>Lorsque vous cliquez sur un lien depuis un contexte « Personnel », le nouvel onglet sera automatiquement rattaché au contexte « Personnel » (idem pour les autres).</p>
<h2>Comment les utiliser ?</h2>
<p>Tout est expliqué dans cette vidéo :</p>
<iframe width="100%" height="480" src="https://www.youtube.com/embed/JqS-XJf6tTA" frameborder="0" allowfullscreen></iframe>
<p>Et si vous préférez lire, voici la suite :</p>
<p><strong>Prérequis :</strong> À l’heure actuelle, cette fonctionnalité est uniquement disponible sur le canal Nightly de Firefox. Pour l’utiliser, vous pouvez télécharger cette version sur <a href="https://nightly.mozfr.org/">ce site de la communauté francophone</a> ou encore sur <a href="https://www.mozilla.org/en-US/firefox/nightly/all/#fr">ce nouveau site mozilla</a> (NB : vous pouvez tout à fait utiliser une version « classique » de Firefox en parallèle d’une « Nightly » afin de tester :) ). Si vous souhaitez désactiver cette fonctionnalité, vous pouvez utiliser la préférence <code>privacy.userContext.enabled</code> avec la valeur <code>false</code> via sur la page <code>about:config</code>.</p>
<p>Pour ouvrir un nouvel onglet contextuel, vous disposez de différentes options.</p>
<h3>1. Avec le menu <em>hamburger</em></h3>
<ol>
<li>Cliquer sur l’icône en haut à droite <img src="https://tech.mozfr.org/dotclear/public/images_blog/onglets_conteneurs/hamburger-icon.png" alt="icône menu hamburger" /></li>
<li>Cliquer sur « (+) Personnaliser » en bas</li>
<li>Ensuite, vous pouvez glisser le bouton « Onglet contextuel » dans le menu (à droite) ou dans votre barre d’outils</li>
</ol>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/onglets_conteneurs/personnalisation" alt="Aperçu du bouton dans l'écran de personnalisation" title="Aperçu du bouton dans l'écran de personnalisation" /></p>
<p><em>L’écran de personnalisation</em></p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/onglets_conteneurs/dans-menu.png" alt="Aperçu du bouton d'onglet contextuel dans le menu" title="Ajouter le bouton dans le menu" /></p>
<p><em>Accéder au bouton lorsqu’il est dans le menu</em></p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/onglets_conteneurs/boutons.png" alt="Aperçu du bouton dans la barre principale" title="Ajouter le bouton dans la barre d'outils principale" /></p>
<p><em>Accéder au bouton lorsqu’il est dans la barre d’outils</em></p>
<h3>2. Avec le menu « Fichier » de la barre de menus</h3>
<p>Si vous utilisez la barre de menu, cette option est disponible dans le menu « Fichier » :
<img src="https://tech.mozfr.org/dotclear/public/images_blog/onglets_conteneurs/menu-fichier.png" alt="Aperçu du menu via la barre de menus" /></p>
<h3>2. Grâce à la liste d’onglets</h3>
<p>Si vous naviguez avec de nombreux onglets, vous pourrez utiliser la liste d’onglets pour ajouter un nouvel onglet contextuel :
<img src="https://tech.mozfr.org/dotclear/public/images_blog/onglets_conteneurs/liste-onglets.png" alt="Aperçu du menu via la liste d'onglets" /></p>
<p><em>Note : Cette option apparaît uniquement lorsque le navigateur n’a plus suffisamment d’espace pour afficher chacun des onglets.</em></p>
<h3>3. Via les hyperliens</h3>
<p>Une fois que vous avez ouvert un nouvel onglet contextuel, tout lien sur lequel vous cliquerez ouvrira un onglet rattaché (ex. je suis sur mon Twitter avec le contexte « Personnel », si je clique sur un lien vers lesjours.fr, celui-ci sera ouvert dans le contexte « Personnel »). Mais que se passe-t-il si je suis sur Twitter (<em>perso</em>) et que je vois un lien vers Amazon (<em>achats</em>) ?
Lorsqu’on clique droit sur un lien, on dispose d’une nouvelle option permettant de choisir le contexte dans lequel on veut ouvrir le nouvel onglet :</p>
<p><img src="https://tech.mozfr.org/dotclear/public/images_blog/onglets_conteneurs/link.png" alt="Nouvelle option du menu contextuel via les liens" title="Nouvelle option du menu contextuel via les liens" /></p>
<h3>Quelques illustrations</h3>
<p>Pour s’y retrouver, on dispose de deux indicateurs :</p>
<ul>
<li>Une ligne colorée au-dessus de l’onglet
<ul>
<li>Bleue : contexte personnel</li>
<li>Orange : contexte professionnel</li>
<li>Verte : contexte bancaire</li>
<li>Rose : contexte pour les achats en ligne</li>
<li>Rien : le contexte par défaut
<img src="https://tech.mozfr.org/dotclear/public/images_blog/onglets_conteneurs/indicateurs.png" alt="Aperçu des indicateurs visuels" /></li>
</ul></li>
<li>Pour l’onglet actif, un symbole à la fin de la barre d’adresse</li>
</ul>
<h2>Sous le capot</h2>
<h3>La technique</h3>
<p>Les onglets contextuels ne sont pas des profils séparés. D’ailleurs, ils fonctionnent au sein d’un même profil (autrement dit, chaque profil embarque son propre ensemble de contextes).</p>
<p><em>Note : Pour en savoir plus, comme pour chaque fonctionnalité de Firefox, vous pouvez consulter Bugzilla. Les onglets contextuels sont poétiquement décrits dans <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1191418">le bug 1191418</a>.</em></p>
<h4>À l’origine</h4>
<p>En fait, les contextes reposent principalement sur la notion d’<a href="https://developer.mozilla.org/fr/docs/Glossaire/Origine">origine</a>. Une origine se définit par un schéma (ex. http/https), un hôte (le domaine) et le port. Cette origine est déjà employée par le navigateur pour décider si telle ressource provenant de telle origine est sécurisée et peut être chargée (cf. <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Same_origin_policy_for_JavaScript">MDN</a>). En ajoutant des informations à l’origine d’une page, on peut enrichir cette fonctionnalité (c’est ce que font déjà la navigation privée, le <a href="https://www.torproject.org/projects/torbrowser/design/#identifier-linkability">Tor Browser</a>, etc.). Gecko utilise donc des informations complémentaires, appelées <code>OriginAttributes</code> pour déterminer si deux origines sont équivalentes.</p>
<p>Pour les onglets contextuels, un nouvel attribut : <code>userContextId</code> a été ajouté à ces informations. Chaque contexte aura son propre <code>userContextId</code> et les informations stockées par les sites seront compartimentées en tenant compte de cette information.</p>
<p>Les onglets contextuels ajoutent ainsi un nouvel élément de sécurité contre les attaques de type <a href="https://fr.wikipedia.org/wiki/Cross-site_scripting">XSS</a> et <a href="https://fr.wikipedia.org/wiki/Cross-Site_Request_Forgery">CSRF</a>.</p>
<h4>Pas touche à mes cookies</h4>
<p>On vient de le voir, lorsque je navigue sur Twitter via un onglet contextuel « personnel », Firefox utilise l’origine combinée avec l’attribut <code>userContextId</code> afin de déterminer les informations auxquelles cet onglet Twitter aura accès. Il ne pourra pas, par exemple, accéder aux informations d’un autre onglet supersecurebanking.net, qui, elles, sont stockées avec un autre attribut <code>userContextId</code>. Ouf.</p>
<p>Mais qu’entend-on par donnée personnelle ? Qu’est-ce qui est compartimenté ? Qu’est-ce qui ne l’est pas ?</p>
<p>Ce qui est isolé :</p>
<ul>
<li><a href="https://www.trainline.fr/cookies">Les cookies</a></li>
<li>Le stockage local (ou <em><a href="https://developer.mozilla.org/fr/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API">localStorage</a></em>)</li>
<li>La base de données <em><a href="https://developer.mozilla.org/fr/docs/Web/API/API_IndexedDB/Basic_Concepts_Behind_IndexedDB">indexedDB</a></em></li>
<li>Le cache (HTTP, images, etc.)</li>
</ul>
<p>Ce qui n’est pas isolé :</p>
<ul>
<li>L’historique</li>
<li>Les marque-pages</li>
<li>Les mots de passe enregistrés</li>
<li>L’historique des recherches et des formulaires</li>
<li>Les permissions accordées aux sites web</li>
<li>Les exceptions de certificats de sécurité</li>
<li>Des trucs ésotériques comme les marqueurs <a href="https://fr.wikipedia.org/wiki/HTTP_Strict_Transport_Security">HSTS</a> et les réponses <a href="https://fr.wikipedia.org/wiki/Online_Certificate_Status_Protocol">OCSP</a></li>
</ul>
<h3>Les limites</h3>
<p>Les onglets contextuels augmentent la sécurité des données via cette compartimentation. Mais celle-ci n’est pas absolue, votre ordinateur aura toujours la même IP, le même agent utilisateur, le même système d’exploitation, etc. Si la séparation des cookies protège contre un pistage basique, il faut être conscient que le <em>fingerprinting</em> avancé est toujours possible, même avec les onglets contextuels. Selon les cas d’usage, le navigateur [Tor Browser] peut être une alternative pertinente. Ce dernier maximise l’anonymat en ligne quitte à casser certaines fonctionnalités des sites visités, les onglets contextuels visent à améliorer la vie privée tout en conservant les fonctionnalités des différents sites.</p>
<h2>La suite</h2>
<p>Voici quelques pistes pour l’avenir de cette fonctionnalité :</p>
<ul>
<li>L’amélioration de l’ergonomie et de l’interface utilisateur : ce n’est pas parce que cette fonctionnalité concerne la sécurité et la vie privée, qu’elle ne doit pas être claire, accessible et attirante. Elle doit pouvoir bénéficier aux personnes qui ne maîtrisent pas ces enjeux.</li>
<li>Le nettoyage des données relatives à chaque contexte (qui a dit qu’il fallait passer en navigation privée pour acheter un cadeau ?)</li>
<li>Ouvrir un onglet contextuel en appuyant longtemps sur (+) lorsqu’on veut ouvrir un nouvel onglet</li>
<li>Les contextes personnalisés ou limités à un site</li>
<li>Régler quelques bugs relatifs</li>
<li><strong>Recevoir votre avis grâce à <a href="https://docs.google.com/forms/d/1oQN14TUnqj-MDErp8MKxH_v7YttOASVdER4lwCgbGKY/viewform?c=0&w=1">ce sondage</a> ou via <a href="mailto://containers@mozilla.com">containers@mozilla.com</a></strong></li>
</ul>
<h2>Liens & sources</h2>
<ul>
<li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1191418">Le <strong>meta-bug</strong> 1191418</a></li>
<li><a href="https://wiki.mozilla.org/Security/Contextual_Identity_Project/Containers">La page de « WikiMo »</a></li>
<li><a href="https://blog.mozilla.org/tanvi/2016/06/16/contextual-identities-on-the-web/">Le billet de Tanvi Vyas</a></li>
<li><a href="https://www.youtube.com/watch?v=JqS-XJf6tTA">La vidéo de MozFr sur les onglets contextuels</a></li>
</ul>