NSI Première • Programme français, lycées de Montréal

Exercices corrigés de NSI : les interactions homme-machine sur le Web

Voici une série d'exercices corrigés de NSI pour la classe de Première, sur les interactions homme-machine sur le Web du programme français. Elle s'adresse aux élèves des lycées français de Montréal, le Lycée Marie de France et le Collège Stanislas.

Le fil de la série : une page web est un ARBRE de balises, le style le met en forme, le script le modifie, et la seule question qui décide vraiment d'une conception est de savoir si un traitement se fait chez le client ou chez le serveur. Les trois quarts des questions du baccalauréat sur ce chapitre se ramènent à cette question-là.

Trois pièges sont désignés nommément dans le corrigé : croire qu'un document mal formé provoque une erreur, croire qu'une largeur déclarée est la largeur affichée, et croire qu'une vérification côté client protège quoi que ce soit.

Série autocorrigée Tape tes réponses sous chaque question : la page te dit juste ou faux avant d'ouvrir la correction. Avec un compte, chaque bonne réponse du premier coup rapporte des points.

Ce chapitre fait partie de NSI en Première
Avant de commencer Fiche de révision : les pièges et la méthode de ce chapitre

Avant ce chapitre

Ces notions sont supposées acquises ici. Si le premier exercice résiste, le blocage vient presque toujours de l'une d'elles, pas du chapitre lui-même.

Rappel de cours

  • Un document HTML est un ARBRE. Le navigateur ne refuse jamais un document mal formé : il en devine un, souvent différent de celui voulu.
  • Deux boutons radio sont exclusifs si et seulement s'ils portent le MÊME attribut name. Un champ sans name n'est pas envoyé.
  • Modèle de boîte : marge, bordure, remplissage, contenu. La largeur déclarée est celle du contenu seul.
  • Spécificité croissante : balise, puis classe, puis identifiant. À spécificité égale, la dernière règle lue l'emporte.
  • GET met les données dans l'URL, POST dans le corps. GET pour lire, POST pour modifier. POST ne chiffre rien.
  • Tout ce qui vient de l'utilisateur doit être encodé avant d'entrer dans une URL.
  • addEventListener('click', f) enregistre la fonction ; addEventListener('click', f()) l'appelle tout de suite et enregistre son résultat.
  • Un script placé dans l'en-tête s'exécute avant que le corps existe : le placer à la fin du corps, ou utiliser defer.
  • textContent écrit du texte, innerHTML interprète du HTML. Du contenu utilisateur ne passe jamais par innerHTML.
  • Toute donnée reçue par le serveur doit être vérifiée par le serveur. Le contrôle côté client est un confort, jamais une protection.

Partie A : Les bases (/50)

Exercice 1 : Le document est un arbre

Une page web n'est pas un texte : c'est un ARBRE de balises, que le navigateur construit à la lecture et que le script manipule ensuite. Tout le chapitre découle de cette structure.

htmlheadbodytitleh1forminputbutton
  • a) Écrivez le document HTML minimal qui correspond exactement à cet arbre. Respectez l'imbrication.
  • b) Donnez la racine de l'arbre, les feuilles, la profondeur de l'élément button, et le nombre de frères et soeurs de l'élément form.
  • c) Un élément est dit descendant d'un autre s'il en est fils, petit-fils, et ainsi de suite. Citez tous les descendants de body, puis tous les ancêtres de input.
  • d) Expliquez pourquoi une balise mal fermée ne provoque pas un message d'erreur mais un arbre différent de celui attendu. Donnez un exemple court.
  • e) Un document comporte 3 niveaux d'imbrication et chaque élément a exactement 4 fils, sauf les feuilles. Combien d'éléments l'arbre compte-t-il en tout ?

Tape tes réponses, la page te dit juste ou faux 0/8

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) html, head et title ; body, h1 et form
  • b) 4 feuilles, profondeur 3, un frère
  • c) 4 descendants de body, 3 ancêtres de input
  • d) Récupération silencieuse : arbre différent
  • e) 1+4+16+64=851 + 4 + 16 + 64 = 85 éléments

a) Le document s'écrit ainsi : une balise html contenant une balise head, elle-même contenant une balise title, puis une balise body contenant une balise h1 et une balise form, cette dernière contenant une balise input et une balise button. En code : la balise ouvrante html, puis head, puis title avec son texte et sa fermeture, la fermeture de head, puis body, h1 avec son texte et sa fermeture, form contenant input et button avec leurs fermetures, puis la fermeture de form, celle de body et celle de html. L'indentation du code n'a aucun effet sur l'arbre : elle ne sert qu'au lecteur humain, et c'est l'ordre des balises ouvrantes et fermantes qui décide seul de la structure.

b) La racine est html. Les feuilles, c'est-à-dire les éléments sans fils, sont title, h1, input et button. La profondeur de button est 3 si l'on compte la racine au niveau 0 : html, puis body, puis form, puis button. L'élément form a un seul frère, h1, puisque body a deux fils.

c) Les descendants de body sont h1, form, input et button, soit quatre éléments. Les ancêtres de input sont form, body et html, soit trois éléments, en remontant jusqu'à la racine. Un noeud n'est ni son propre ancêtre ni son propre descendant.

d) Parce que le navigateur ne refuse jamais un document : il applique une procédure de récupération qui devine une structure. Une balise laissée ouverte est fermée automatiquement à un endroit choisi par le navigateur, et cet endroit n'est pas forcément celui que l'auteur imaginait. Exemple : si l'on ouvre un paragraphe, puis un second paragraphe sans fermer le premier, le navigateur ferme le premier tout seul, si bien que les deux paragraphes sont FRÈRES alors que le code laissait croire qu'ils étaient imbriqués. Toute règle de style visant le paragraphe intérieur cesse alors de s'appliquer, sans le moindre message.

e) Avec la racine au niveau 0 et 3 niveaux d'imbrication, il y a 1 élément au niveau 0, 44 au niveau 1, 42=164^{2} = 16 au niveau 2 et 43=644^{3} = 64 au niveau 3. Le total vaut 1+4+16+64=851 + 4 + 16 + 64 = 85 éléments. La formule générale, pour un arbre complet de degré dd et de hauteur hh, est (dh+11)/(d1)(d^{h+1} - 1) / (d - 1), ce qui donne ici (441)/3=255/3=85(4^{4} - 1) / 3 = 255 / 3 = 85 et confirme le calcul.

Exercice 2 : HTML : corriger un document mal formé

Voici un extrait de page contenant plusieurs défauts. Certains empêchent l'affichage voulu, d'autres ne se voient qu'à l'usage.

<div class='carte'>
  <h2>Sondage de la classe<h2>
  <p>Choisissez une réponse :
  <form action='/vote'>
    <input type='text' name='reponse'>
    <input type='radio' value='oui'>Oui
    <input type='radio' value='non'>Non
    <button>Envoyer</button>
  </form>
  <img src='logo.png'>
</div>
  • a) Relevez cinq défauts de ce document et dites, pour chacun, ce qu'il provoque.
  • b) Les deux boutons radio devraient être exclusifs : cocher l'un doit décocher l'autre. Qu'est-ce qui manque, et pourquoi cet attribut est-il indispensable ?
  • c) Réécrivez les lignes fautives.
  • d) Expliquez à quoi servent les attributs name et value d'un champ de formulaire, et lequel des deux le serveur reçoit.
  • e) L'attribut alt manque à l'image. Citez trois raisons de le remplir, dont une qui n'a rien à voir avec l'accessibilité.

Tape tes réponses, la page te dit juste ou faux 0/5

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) h2 non fermé, p ouvert, radios sans name, sans label, sans alt
  • b) name commun : groupe exclusif
  • c) Fermer, grouper, étiqueter
  • d) Le serveur reçoit name=value
  • e) Accessibilité, robustesse, référencement

a) Cinq défauts. Un, la balise h2 est ouverte deux fois au lieu d'être fermée : il faudrait une balise fermante, et le navigateur va imbriquer un titre dans l'autre, ce qui décale tout le reste. Deux, le paragraphe n'est jamais fermé : le navigateur le refermera tout seul, probablement juste avant le formulaire, mais rien ne le garantit d'une version à l'autre. Trois, les deux boutons radio n'ont pas d'attribut name commun, donc ils ne forment pas un groupe. Quatre, les textes Oui et Non ne sont pas dans une balise label, donc cliquer sur le mot ne coche pas le bouton. Cinq, l'image n'a pas d'attribut alt. On peut ajouter un sixième défaut : le formulaire n'a pas de méthode explicite, il utilisera donc GET par défaut, ce qui n'est pas toujours voulu.

b) Il manque l'attribut name, identique sur les deux boutons, par exemple name='choix'. C'est le name, et lui seul, qui constitue le GROUPE : deux boutons radio portant le même name sont exclusifs, deux boutons radio portant des noms différents sont indépendants et peuvent être cochés tous les deux. C'est une source d'erreur fréquente, parce que la page a l'air correcte tant qu'on ne clique que sur un seul bouton.

c) La ligne du titre devient une balise h2 ouvrante, le texte, puis la balise h2 FERMANTE. La ligne du paragraphe reçoit sa balise fermante à la fin du texte. Les deux lignes radio deviennent : une balise label contenant un input de type radio avec name='choix' et value='oui', suivi du texte Oui, puis la fermeture du label ; et de même pour non. La ligne de l'image reçoit un attribut alt décrivant l'image, par exemple alt='Logo du lycée'. Enfin, la balise form reçoit method='post' si l'on veut enregistrer un vote.

d) L'attribut name est le NOM du champ : c'est lui qui devient la clé transmise au serveur. L'attribut value est la VALEUR envoyée quand le champ est sélectionné, pour un bouton radio ou une case à cocher ; pour un champ texte, la valeur envoyée est ce que l'utilisateur a saisi. Le serveur reçoit les DEUX, sous la forme d'un couple nom égale valeur, et c'est le name qui lui permet de savoir de quel champ il s'agit. Un champ sans name n'est tout simplement pas envoyé : il est invisible pour le serveur, ce qui explique que le défaut passe souvent inaperçu à la relecture du code.

e) Trois raisons. L'ACCESSIBILITÉ : un lecteur d'écran énonce le texte alternatif, sans lequel une personne aveugle n'apprend rien de l'image. La ROBUSTESSE : si l'image ne se charge pas, réseau coupé, chemin faux, format refusé, le texte s'affiche à sa place et la page reste compréhensible. Et le RÉFÉRENCEMENT, qui n'a rien à voir avec l'accessibilité : un moteur de recherche ne voit pas le contenu d'une image, il lit le texte alternatif, et c'est par lui qu'une image est indexée et trouvée.

Exercice 3 : CSS : sélecteurs, cascade et modèle de boîte

Le schéma rappelle les quatre couches d'une boîte CSS. La question qui coûte le plus de points n'est pourtant pas là : c'est celle de savoir quelle règle l'emporte quand deux règles se contredisent.

margebordureremplissagecontenu
  • a) Nommez les quatre couches du schéma, de l'extérieur vers l'intérieur, et dites laquelle sépare deux boîtes voisines et laquelle sépare le texte de sa bordure.
  • b) Un élément a une largeur de contenu de 300 pixels, un remplissage de 20 pixels de chaque côté, une bordure de 2 pixels et une marge de 15 pixels. Quelle largeur occupe-t-il à l'écran, bordure comprise ? Et espace réservé compris ?
  • c) Trois règles s'appliquent au même paragraphe : une règle sur la balise p, une règle sur la classe .avis, une règle sur l'identifiant #principal. Classez-les par priorité croissante et expliquez le principe.
  • d) Deux règles de même priorité se contredisent. Laquelle l'emporte ? Que se passe-t-il si la seconde est dans un fichier chargé avant la première ?
  • e) Expliquez pourquoi il vaut mieux styler par classe que par identifiant ou par style en ligne. Donnez l'argument de maintenance et l'argument de priorité.

Tape tes réponses, la page te dit juste ou faux 0/7

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Marge, bordure, remplissage, contenu
  • b) 344 px, puis 374 px
  • c) p, puis .avis, puis #principal
  • d) À égalité, la dernière règle lue
  • e) Classes : réutilisables et surchargeables

a) De l'extérieur vers l'intérieur : la MARGE, la BORDURE, le REMPLISSAGE et le CONTENU. C'est la marge qui sépare deux boîtes voisines : elle est à l'extérieur de la bordure et n'appartient visuellement à personne. C'est le remplissage qui sépare le texte de sa bordure : il est à l'intérieur, il prend la couleur de fond de l'élément, ce qui se voit immédiatement dès qu'on donne un fond coloré à la boîte.

b) Bordure comprise, la largeur vaut 300+2×20+2×2=300+40+4=344300 + 2 \times 20 + 2 \times 2 = 300 + 40 + 4 = 344 pixels. Espace réservé compris, il faut ajouter les deux marges : 344+2×15=374344 + 2 \times 15 = 374 pixels. Le piège classique consiste à croire qu'une largeur déclarée de 300 pixels donne une boîte de 300 pixels : dans le modèle par défaut, la largeur déclarée est celle du CONTENU seul, et tout ce qu'on ajoute autour élargit la boîte. C'est ce qui fait déborder une mise en page dont les colonnes semblaient pourtant totaliser cent pour cent.

c) Par priorité croissante : la règle sur la balise p, puis la règle sur la classe .avis, puis la règle sur l'identifiant #principal. Le principe est celui de la SPÉCIFICITÉ : plus un sélecteur désigne un ensemble restreint d'éléments, plus il l'emporte. Une balise désigne potentiellement des centaines d'éléments, une classe quelques-uns, un identifiant un seul. Le raisonnement est le même que pour une exception qui prime sur une règle générale.

d) À spécificité égale, c'est la règle écrite en DERNIER qui l'emporte : le navigateur applique les règles dans l'ordre où il les rencontre, et la dernière écrase la précédente. Si la seconde règle se trouve dans un fichier chargé AVANT, alors elle est rencontrée en premier, donc elle est écrasée par la première : l'ordre qui compte est l'ordre de lecture, pas l'ordre du raisonnement de l'auteur. C'est pourquoi l'ordre des balises link dans l'en-tête d'une page est un vrai choix technique, et non un détail.

e) Argument de MAINTENANCE : une classe se réutilise sur autant d'éléments qu'on veut, donc une modification faite à un seul endroit se propage partout ; un identifiant ne sert qu'une fois, et un style en ligne doit être recopié sur chaque élément, ce qui garantit qu'un jour l'un d'eux sera oublié. Argument de PRIORITÉ : un style en ligne est presque impossible à surcharger, et un identifiant l'est difficilement, si bien qu'on finit par empiler des règles de plus en plus spécifiques pour corriger les précédentes. Styler par classe garde la marge de manoeuvre intacte, et c'est cette marge qui permet à un thème sombre ou à une version imprimable d'exister.

Exercice 4 : Formulaires : GET, POST et ce que reçoit le serveur

Un formulaire est le seul moyen, en HTML pur, d'envoyer des données au serveur. Le choix de la méthode n'est pas une préférence de style : les deux ne font pas la même chose.

<form action='/recherche' method='get'>
  <input type='text' name='q' value='arbre binaire'>
  <input type='number' name='page' value='2'>
  <button>Chercher</button>
</form>
  • a) Écrivez l'URL exacte que le navigateur demande quand on clique sur le bouton. Nommez les deux caractères de séparation employés.
  • b) On remplace get par post. Qu'est-ce qui change dans la requête ? Où se trouvent alors les données ?
  • c) Dressez un tableau comparant GET et POST sur cinq points : visibilité des données, possibilité de mettre en signet, limite de taille, effet d'un rechargement, et usage recommandé.
  • d) Un formulaire de connexion utilise GET. Citez trois endroits où le mot de passe se retrouve écrit en clair, indépendamment de tout chiffrement de la liaison.
  • e) Un champ de recherche contient le texte « arbre & feuille ». Que se passe-t-il si ce texte est inséré tel quel dans l'URL ? Comment s'appelle l'opération qui règle le problème, et que devient le texte ?

Tape tes réponses, la page te dit juste ou faux 0/6

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) /recherche?q=arbre%20binaire&page=2
  • b) POST : données dans le corps
  • c) GET lit, POST modifie
  • d) Historique, journaux, provenance
  • e) Encodage : arbre%20%26%20feuille

a) L'URL demandée est /recherche?q=arbre%20binaire&page=2. Les deux caractères de séparation sont le point d'interrogation, qui sépare le chemin de la chaîne de paramètres, et l'esperluette, qui sépare les paramètres entre eux. À l'intérieur d'un paramètre, le signe égale sépare le nom de la valeur. L'espace ne pouvant pas figurer tel quel dans une URL, il est encodé, souvent en %20 et parfois par le signe plus.

b) Avec POST, l'URL demandée est simplement /recherche, sans aucun paramètre visible. Les données sont placées dans le CORPS de la requête, après les en-têtes, dans le même format nom égale valeur séparé par des esperluettes. Le serveur reçoit exactement la même information ; c'est la façon de la transporter qui change, et avec elle tout ce que la question suivante énumère.

c) Visibilité : GET affiche tout dans la barre d'adresse, POST ne montre rien. Signet et partage : une requête GET se met en signet et se partage par copier-coller, une requête POST non. Taille : GET est limité par la longueur maximale d'une URL, souvent quelques milliers de caractères, POST n'a pas de limite pratique et transporte des fichiers. Rechargement : recharger une page obtenue par GET refait simplement la même demande, alors que recharger une page obtenue par POST renvoie les données une seconde fois, d'où l'avertissement du navigateur et le risque de double enregistrement. Usage : GET pour toute demande qui LIT, POST pour toute demande qui MODIFIE.

d) Le mot de passe se retrouve en clair, au minimum, dans trois endroits. L'HISTORIQUE du navigateur, qui conserve l'URL complète et que n'importe qui ayant accès à la machine peut lire. Les JOURNAUX du serveur, qui enregistrent classiquement l'URL demandée de chaque requête, si bien que le mot de passe finit dans un fichier lu par des administrateurs et sauvegardé pendant des mois. Et l'en-tête de PROVENANCE envoyé au site suivant, qui transmet l'URL de la page d'où l'on vient, exportant le mot de passe vers un tiers. Aucun de ces trois problèmes n'est corrigé par le chiffrement de la liaison, qui protège le transport et non le stockage.

e) L'esperluette serait interprétée comme un séparateur de paramètres : le serveur croirait recevoir un paramètre q valant « arbre » et un paramètre nommé « feuille » sans valeur. L'opération qui règle le problème s'appelle l'ENCODAGE d'URL, ou encodage pour cent. Le texte devient arbre%20%26%20feuille, chaque caractère réservé étant remplacé par un signe pour cent suivi de son code en hexadécimal, l'espace donnant 20 et l'esperluette 26. La règle générale à retenir : tout ce qui vient de l'utilisateur doit être encodé avant d'être inséré dans une URL, sans exception.

Exercice 5 : Événements : quand le code s'exécute-t-il ?

Un script de page web ne s'exécute presque jamais dans l'ordre où il est écrit. Il installe des gestionnaires, puis rend la main ; le reste se produit plus tard, quand l'utilisateur agit.

let compteur = 0

function incremente() {
    compteur = compteur + 1
    document.getElementById('total').textContent = compteur
}

document.getElementById('bouton').addEventListener('click', incremente)
console.log(compteur)
  • a) Qu'affiche la console au chargement de la page ? Pourquoi ?
  • b) L'utilisateur clique trois fois. Qu'affiche l'élément d'identifiant total après chaque clic ?
  • c) Un élève écrit addEventListener('click', incremente()) avec des parenthèses. Décrivez le comportement obtenu et expliquez la différence.
  • d) Le script est placé dans l'en-tête du document, avant le corps. Que se passe-t-il et pourquoi ? Donnez deux corrections.
  • e) Citez quatre événements autres que le clic, et pour chacun un usage concret dans une interface.

Tape tes réponses, la page te dit juste ou faux 0/5

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 0 : enregistrer n'est pas exécuter
  • b) 1, 2, 3
  • c) incremente() : appel immédiat
  • d) Script en fin de corps ou defer
  • e) input, submit, change, keydown

a) La console affiche 0. La dernière ligne s'exécute immédiatement au chargement, alors que la fonction incremente n'a encore jamais été appelée : l'appel à addEventListener ne fait qu'ENREGISTRER la fonction pour plus tard, il ne l'exécute pas. La variable compteur vaut donc toujours sa valeur initiale.

b) Après le premier clic, l'élément affiche 1 ; après le deuxième, 2 ; après le troisième, 3. La variable compteur est déclarée à l'extérieur de la fonction, donc elle survit d'un appel à l'autre : c'est ce qui permet au compte de s'accumuler. Si elle était déclarée à l'intérieur, elle serait recréée à zéro à chaque clic et l'affichage resterait bloqué à 1.

c) Avec des parenthèses, la fonction est APPELÉE immédiatement, au chargement de la page : le compteur passe à 1 et l'affichage se met à jour une fois, sans aucun clic. Sa valeur de retour, qui est indéfinie ici, est ensuite enregistrée comme gestionnaire, si bien que plus aucun clic ne produit d'effet. La différence tient en une phrase : incremente désigne la fonction elle-même, incremente() désigne le RÉSULTAT de son exécution. Un gestionnaire attend la fonction, pas son résultat.

d) Placé dans l'en-tête, le script s'exécute avant que le corps du document ait été construit : l'appel à getElementById('bouton') ne trouve rien et renvoie une valeur nulle, sur laquelle addEventListener provoque une erreur. La page se charge, mais le bouton ne fait rien. Deux corrections : placer la balise script à la FIN du corps, après les éléments qu'elle manipule ; ou lui ajouter l'attribut defer, qui demande au navigateur de retarder l'exécution jusqu'à la fin de la construction de l'arbre. On peut aussi envelopper le code dans un gestionnaire de l'événement de fin de chargement du document, ce qui revient au même.

e) Quatre événements. L'événement de SAISIE sur un champ de texte, pour afficher en direct le nombre de caractères restants ou filtrer une liste au fur et à mesure de la frappe. L'événement de SOUMISSION d'un formulaire, pour valider les champs et empêcher l'envoi si quelque chose manque. L'événement de CHANGEMENT sur une liste déroulante, pour recharger le contenu dépendant du choix. Et l'événement de TOUCHE ENFONCÉE, pour offrir des raccourcis clavier, par exemple la touche Échap qui referme une fenêtre modale.

Partie B : Problèmes et raisonnement (/50)

Exercice 6 : Client ou serveur : où se fait le calcul

Le chronogramme décrit un échange complet entre un navigateur et un serveur, pour une page de sondage. C'est la question la plus fréquente au baccalauréat : quel programme s'exécute où, et sur quelles données.

navigateurserveurGET /sondagepage HTML, CSS et scriptPOST /voteconfirmationle script vérifie la saisie sans réseau
  • a) Décrivez les quatre échanges du schéma, en précisant à chaque fois qui parle et ce qui circule.
  • b) Classez les six traitements suivants en côté client ou côté serveur : vérifier qu'un champ n'est pas vide, enregistrer un vote, calculer le pourcentage de chaque réponse à partir de la base, afficher ou masquer un bloc, chiffrer le mot de passe stocké, mettre en forme un tableau.
  • c) Expliquez pourquoi un traitement côté client est instantané, et quel prix on paie pour cette rapidité.
  • d) La page pèse 12 kio de HTML, 40 kio de CSS et 95 kio de script. Sur une liaison à 8 Mbit/s, combien de temps prend le premier chargement ? Combien pour la deuxième visite si le CSS et le script sont en cache ?
  • e) Le serveur reçoit 3 000 votes en une heure et chaque enregistrement lui coûte 12 millisecondes de calcul. Quelle proportion d'une heure de processeur cela représente-t-il ? Que conclure sur le dimensionnement ?

Tape tes réponses, la page te dit juste ou faux 0/8

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) GET, page, POST, confirmation
  • b) Client : affichage ; serveur : données partagées
  • c) Instantané mais visible et modifiable
  • d) 0,1510{,}151 s puis 12,312{,}3 ms
  • e) 1 % : le calcul n'est pas limitant

a) Premier échange, le NAVIGATEUR demande la page par une requête GET sur l'adresse du sondage. Deuxième échange, le SERVEUR renvoie le document HTML accompagné de sa feuille de style et de son script : ce sont trois fichiers distincts, donc en réalité trois demandes et trois réponses, résumées ici en une flèche. Troisième échange, le NAVIGATEUR envoie le vote par une requête POST, dont les données voyagent dans le corps. Quatrième échange, le SERVEUR renvoie une confirmation, en pratique une nouvelle page ou une réponse courte que le script affiche.

b) Côté CLIENT : vérifier qu'un champ n'est pas vide, afficher ou masquer un bloc, mettre en forme un tableau. Ce sont trois opérations qui ne portent que sur ce que le navigateur possède déjà. Côté SERVEUR : enregistrer un vote, calculer le pourcentage de chaque réponse à partir de la base, chiffrer le mot de passe stocké. Ce sont trois opérations qui touchent des données partagées ou secrètes, auxquelles le client n'a et ne doit avoir aucun accès. Le critère de tri est toujours le même : le traitement a-t-il besoin de données que le client ne détient pas, ou produit-il un effet que les autres utilisateurs doivent voir ?

c) Il est instantané parce qu'il ne traverse pas le réseau : le navigateur possède déjà les données et le code, il n'attend personne. Un aller-retour vers un serveur coûte au minimum quelques dizaines de millisecondes, et bien davantage sur un réseau mobile, alors qu'un calcul local se compte en microsecondes. Le prix payé est double. D'abord, tout ce qui est côté client est VISIBLE et MODIFIABLE par l'utilisateur, qui peut lire le script et changer son comportement. Ensuite, le résultat n'engage que ce navigateur : il n'est ni partagé, ni conservé, ni fiable pour un tiers.

d) Le premier chargement transporte 12+40+95=14712 + 40 + 95 = 147 kio, soit 147×1 024=150 528147 \times 1\ 024 = 150\ 528 octets, c'est-à-dire 150 528×8=1 204 224150\ 528 \times 8 = 1\ 204\ 224 bits. À 8×1068 \times 10^{6} bit/s, cela prend 1 204 224/(8×106)0,1511\ 204\ 224 / (8 \times 10^{6}) \approx 0{,}151 s, environ un sixième de seconde. À la deuxième visite, seuls les 12 kio de HTML circulent, soit 12×1 024×8=98 30412 \times 1\ 024 \times 8 = 98\ 304 bits, donc 98 304/(8×106)0,012398\ 304 / (8 \times 10^{6}) \approx 0{,}0123 s, soit environ 12 millisecondes. Le cache divise le temps par plus de douze, ce qui explique qu'une page paraisse lente la première fois et instantanée ensuite.

e) Trois mille votes à 12 millisecondes font 3 000×0,012=363\ 000 \times 0{,}012 = 36 secondes de calcul. Une heure en compte 3 6003\ 600, donc la proportion vaut 36/3 600=0,0136 / 3\ 600 = 0{,}01, soit 1 pour cent d'un processeur. La conclusion est que le calcul n'est pas le facteur limitant : un seul serveur absorberait cent fois ce trafic. Ce qui limite réellement, ce sont les accès disque de la base, la mémoire consommée par les connexions simultanées et les pics de charge, jamais la moyenne. Dimensionner sur une moyenne horaire est d'ailleurs l'erreur classique : trois mille votes également répartis ne posent aucun problème, trois mille votes en dix secondes en posent un.

Exercice 7 : Modifier le document par script

Le script ne réécrit pas la page : il modifie l'arbre construit par le navigateur, et l'affichage suit. Voici une fonction qui remplit une liste à partir d'un tableau de données.

const villes = ['Montréal', 'Québec', 'Sherbrooke']

function remplir() {
    const liste = document.getElementById('villes')
    liste.innerHTML = ''
    for (const v of villes) {
        const item = document.createElement('li')
        item.textContent = v
        liste.appendChild(item)
    }
}
  • a) Décrivez l'état de l'arbre du document après un appel à remplir(). Combien d'éléments ont été créés ?
  • b) À quoi sert la ligne qui vide la liste ? Que se passerait-il si on l'oubliait et qu'on appelait la fonction trois fois ?
  • c) On remplace item.textContent par item.innerHTML. Une ville s'appelle « Saint-Jean ». Décrivez la différence de résultat, puis expliquez pourquoi cette différence est un problème de sécurité et pas seulement d'affichage.
  • d) On veut ajouter les villes une par une sans effacer les précédentes, tout en évitant les doublons. Décrivez l'algorithme et la structure de données à employer.
  • e) La liste contient 5 000 villes. Expliquer pourquoi ajouter les éléments un par un au document est lent, et donner la technique qui le rend rapide.

Tape tes réponses, la page te dit juste ou faux 0/6

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 3 li et 3 nœuds de texte
  • b) Vider : fonction idempotente
  • c) innerHTML : injection de script
  • d) Ensemble des villes présentes
  • e) Fragment inséré une seule fois

a) L'élément d'identifiant villes contient trois éléments li, dans l'ordre du tableau, chacun portant un noeud de texte contenant le nom de la ville. Trois éléments li ont été créés, plus trois noeuds de texte, soit six noeuds au total : textContent crée en effet un noeud de texte fils, il ne pose pas le texte sur l'élément lui-même. Le tableau villes, lui, n'a pas changé : le script a construit une représentation, il n'a pas déplacé les données.

b) Elle sert à repartir d'une liste vide, pour que la fonction soit IDEMPOTENTE : l'appeler deux fois donne le même résultat que l'appeler une fois. Sans elle, un second appel ajouterait trois nouveaux li aux trois précédents, et trois appels afficheraient neuf villes, chacune trois fois. Ce défaut est particulièrement pénible parce qu'il ne se voit pas au premier essai : il n'apparaît qu'à la deuxième interaction de l'utilisateur.

c) Avec textContent, la chaîne s'affiche telle quelle, balises comprises : le lecteur voit Saint-Jean écrit littéralement. Avec innerHTML, la chaîne est INTERPRÉTÉE comme du HTML, l'arbre reçoit un élément b, et le lecteur voit Saint-Jean avec Jean en gras. Ce n'est pas seulement une différence d'affichage, parce que la chaîne peut venir d'un utilisateur : si un visiteur inscrit comme nom une balise de script, innerHTML l'insère dans l'arbre et le navigateur l'exécute, avec tous les droits de la page. Cette faille porte un nom, l'injection de script, et elle permet de voler les identifiants de session des autres visiteurs. La règle est donc absolue : on écrit du texte venu de l'utilisateur avec textContent, jamais avec innerHTML.

d) On conserve les villes déjà présentes dans un ENSEMBLE, structure dont le test d'appartenance coûte un temps constant. L'algorithme : à l'arrivée d'une ville, on teste si elle appartient à l'ensemble ; si oui, on ne fait rien ; sinon, on l'ajoute à l'ensemble, on crée l'élément li correspondant et on l'ajoute à la liste du document. Utiliser un tableau à la place de l'ensemble donnerait le même résultat, mais chaque test coûterait un parcours complet, donc un coût total quadratique en le nombre de villes.

e) Ajouter un élément au document déclenche potentiellement un recalcul de la mise en page par le navigateur : positions, tailles, retours à la ligne de tout ce qui suit. Répété 5 000 fois, ce recalcul domine largement le coût de création des éléments eux-mêmes. La technique consiste à construire tous les éléments dans un CONTENEUR HORS DOCUMENT, un fragment, qui n'est pas affiché et ne déclenche donc aucun recalcul, puis à insérer ce fragment en une seule opération. Le navigateur ne recalcule alors la mise en page qu'une fois au lieu de cinq mille. C'est exactement le même principe que l'indexation de la fusion : on paie une seule fois ce qu'on payait à chaque tour.

Exercice 8 : Cinq affirmations à corriger

Chacune des cinq affirmations suivantes est FAUSSE. Dites pourquoi et donnez l'énoncé correct.

  • 1) « Un document HTML mal formé provoque une erreur et la page ne s'affiche pas. »
  • 2) « Une largeur de 300 pixels donne une boîte de 300 pixels à l'écran. »
  • 3) « Avec la méthode POST, les données sont chiffrées. »
  • 4) « addEventListener('click', f()) installe la fonction f comme gestionnaire du clic. »
  • 5) « Si le script vérifie que le champ n'est pas vide, le serveur peut se dispenser de le vérifier. »

Tape tes réponses, la page te dit juste ou faux 0/5

1)
2)
3)
4)
5)
Voir la correction

Réponses

  • 1) Arbre différent, pas d'erreur
  • 2) 344 px avec remplissage et bordure
  • 3) POST ne chiffre pas
  • 4) Passer f, pas f()
  • 5) Le serveur vérifie toujours

1) FAUX. Le navigateur ne refuse jamais un document : il applique une procédure de récupération et construit l'arbre qu'il peut. La page s'affiche, mais sa STRUCTURE n'est pas celle que l'auteur croyait, et les règles de style comme les scripts qui s'appuient sur cette structure cessent de fonctionner. Énoncé correct : un document mal formé s'affiche quand même, avec un arbre différent de celui attendu, ce qui rend le défaut plus difficile à trouver qu'une erreur franche.

2) FAUX dans le modèle par défaut. La largeur déclarée est celle du CONTENU : le remplissage, la bordure et la marge s'y ajoutent. Avec 20 pixels de remplissage et 2 de bordure de chaque côté, la boîte occupe 344 pixels. Énoncé correct : la largeur affichée vaut la largeur déclarée plus les remplissages plus les bordures, sauf si l'on demande explicitement le modèle où la largeur inclut ces couches.

3) FAUX. POST place les données dans le corps de la requête au lieu de l'URL, ce qui les rend moins VISIBLES, pas chiffrées. Sur une liaison non chiffrée, un observateur du réseau lit le corps aussi facilement que l'URL. Énoncé correct : seul le protocole sécurisé chiffre la requête, en-têtes et corps compris ; POST ne fait que déplacer les données hors de l'URL, ce qui reste utile pour l'historique et les journaux.

4) FAUX. Les parenthèses APPELLENT la fonction immédiatement et enregistrent son résultat, presque toujours indéfini, comme gestionnaire. Le clic ne produit alors plus rien, alors que l'effet de la fonction a eu lieu une fois au chargement. Énoncé correct : il faut passer la fonction elle-même, sans parenthèses, addEventListener('click', f).

5) FAUX, et c'est l'erreur la plus grave du chapitre. Le contrôle côté client est un service rendu à l'utilisateur, pas une garantie : n'importe qui peut désactiver le script, modifier la page, ou envoyer la requête directement sans passer par elle. Énoncé correct : toute donnée reçue par le serveur doit être vérifiée par le serveur, quelle que soit la vérification déjà faite côté client ; celle-ci sert seulement à donner un retour immédiat et à éviter des allers-retours inutiles.

Exercice 9 : Pourquoi un contrôle côté client ne protège rien

Le schéma montre les deux chemins par lesquels des données peuvent atteindre le serveur. Le premier passe par la page et ses contrôles ; le second ne passe par rien du tout.

page + contrôlerequête forgéeserveurdonnées validéesdonnées quelconques
  • a) Décrivez les deux chemins du schéma. Qu'est-ce qui, dans l'architecture du Web, rend le second possible ?
  • b) Un formulaire limite un champ à 30 caractères par un attribut HTML et par une vérification en script. Donnez trois façons dont un utilisateur envoie malgré tout 5 000 caractères.
  • c) Le serveur enregistre le champ sans vérifier. Décrivez trois conséquences possibles, de la plus bénigne à la plus grave.
  • d) Un prix est calculé par le script à partir de la quantité, puis envoyé au serveur dans un champ caché. Expliquez précisément la faille et corrigez la conception.
  • e) Énoncez la règle générale en une phrase, puis dites ce à quoi sert malgré tout la validation côté client, en trois arguments.

Tape tes réponses, la page te dit juste ou faux 0/5

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Requête forgée : le serveur parle HTTP
  • b) Inspecteur, scripts désactivés, requête directe
  • c) Affichage, saturation, injection
  • d) Prix recalculé par le serveur
  • e) Retour immédiat, économie, guidage

a) Le premier chemin part de la page, qui contient le formulaire et son contrôle en script : les données arrivent au serveur après avoir été filtrées. Le second chemin part d'une requête FORGÉE, construite à la main avec un outil en ligne de commande ou l'inspecteur du navigateur, et atteint le serveur sans jamais passer par la page. Ce qui rend ce second chemin possible, c'est que le serveur ne parle pas à une page : il parle à un protocole. Il reçoit une requête HTTP et n'a aucun moyen de savoir si elle a été produite par sa propre page, par un autre programme ou par une commande tapée à la main. La page n'est pas une porte d'entrée, c'est une commodité.

b) Trois façons. Modifier la page dans l'inspecteur du navigateur, en supprimant l'attribut de longueur maximale et le gestionnaire de vérification, puis soumettre normalement. Désactiver l'exécution des scripts, ce qui neutralise toute vérification en JavaScript, l'attribut HTML restant contournable par la première méthode. Ou envoyer directement la requête avec un outil en ligne de commande, sans ouvrir la page du tout. Aucune de ces trois façons ne demande de compétence particulière, et la troisième est même la plus simple.

c) De la plus bénigne à la plus grave. Un AFFICHAGE cassé : le champ trop long déborde de sa colonne et déforme la page pour tous les visiteurs. Une SATURATION : répétée automatiquement, l'opération remplit la base de données et fait tomber le service, faute d'espace ou de mémoire. Une INJECTION : si le contenu est ensuite réaffiché sans échappement, il peut porter un script exécuté chez les autres visiteurs, ou, s'il est inséré dans une requête à la base sans précaution, modifier cette requête et permettre la lecture ou l'effacement de données qui ne le concernaient pas.

d) La faille est que le prix devient une donnée d'ENTRÉE fournie par le client, alors qu'il devrait être une donnée calculée par le serveur. Un utilisateur qui modifie le champ caché commande à un prix qu'il choisit lui-même, et le serveur enregistre docilement une vente à un centime. La correction n'est pas de mieux cacher le champ, ni de le chiffrer, ni de vérifier une signature : c'est de ne PAS l'envoyer. Le client transmet la quantité et la référence du produit ; le serveur relit le prix dans sa propre base et calcule le total. La règle générale s'entend ici très bien : le client transmet des intentions, jamais des conclusions.

e) La règle générale : toute donnée venue du client est hostile jusqu'à preuve du contraire, donc elle doit être validée par le serveur, sans exception et quelle que soit la validation déjà faite ailleurs. La validation côté client garde malgré tout trois utilités. Elle donne un RETOUR IMMÉDIAT, ce qui améliore franchement l'expérience : signaler un champ vide sans attendre un aller-retour réseau. Elle ÉCONOMISE des requêtes inutiles, donc de la bande passante et de la charge serveur, ce qui compte à grande échelle. Et elle GUIDE la saisie, en indiquant le format attendu avant que l'erreur ne soit commise. Aucune de ces trois utilités n'est de la sécurité, et c'est exactement le point à retenir.

Exercice 10 : Problème : construire une page de sondage

On veut publier un sondage à trois réponses, afficher les résultats en direct et empêcher les envois répétés. Le serveur expose une adresse de vote et une adresse de résultats. Cet exercice demande de la conception, pas seulement du code.

  • a) Écrivez la structure HTML de la page : le formulaire, ses trois réponses exclusives, le bouton et la zone d'affichage des résultats. Précisez les attributs indispensables.
  • b) Écrivez, en français, l'algorithme du gestionnaire de soumission : ce qu'il vérifie, ce qu'il envoie, ce qu'il fait de la réponse. Précisez pourquoi il doit empêcher le comportement par défaut du formulaire.
  • c) Le serveur renvoie les trois compteurs 148, 92 et 47. Calculez les trois pourcentages au dixième près, et la largeur en pixels de chaque barre si la zone fait 480 pixels de large.
  • d) On veut empêcher un même visiteur de voter deux fois. Proposez une protection côté client et une protection côté serveur, puis dites laquelle protège réellement et pourquoi aucune des deux n'est parfaite.
  • e) Le sondage est prévu pour 2 000 votants sur une journée, mais un message viral amène 2 000 votes en 4 minutes. Comparez la charge moyenne et la charge de pointe, puis citez deux mesures qui absorbent le pic.

Tape tes réponses, la page te dit juste ou faux 0/9

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Radios de même name, value distinctes
  • b) preventDefault : pas de rechargement
  • c) 51,651{,}6 %, 32,132{,}1 %, 16,416{,}4 % ; 248, 154, 79 px
  • d) Serveur, mais sans certitude
  • e) 8,38{,}3 votes/s : cache et file

a) La page contient un formulaire portant une méthode post et une adresse d'action. À l'intérieur, trois éléments input de type radio partageant le MÊME attribut name, par exemple name='choix', et portant chacun une value distincte ; chaque bouton est enveloppé dans un élément label avec son texte, pour que cliquer sur le mot coche le bouton. Vient ensuite un bouton de soumission. Après le formulaire, un élément conteneur portant un identifiant, par exemple id='resultats', vide au départ : c'est le script qui le remplira. Les attributs indispensables sont le name commun aux trois radios, sans lequel ils ne sont pas exclusifs, les value distinctes, sans lesquelles le serveur ne saurait pas lequel a été choisi, et l'identifiant de la zone de résultats, sans lequel le script ne saurait pas où écrire.

b) L'algorithme. Un, empêcher le comportement par défaut du formulaire. Deux, lire la valeur du bouton radio coché ; si aucun ne l'est, afficher un message et s'arrêter. Trois, désactiver le bouton d'envoi pour éviter les doubles clics. Quatre, envoyer la valeur au serveur par une requête POST asynchrone. Cinq, à la réception de la réponse, lire les trois compteurs et remplir la zone de résultats. Six, en cas d'échec réseau, réactiver le bouton et afficher un message d'erreur. Il faut empêcher le comportement par défaut parce que, sans cela, le navigateur soumet le formulaire à sa manière et RECHARGE la page : le script est interrompu, la zone de résultats disparaît avec l'ancienne page, et l'utilisateur perd le contexte. On ne peut afficher un résultat en direct qu'à condition d'avoir repris la main sur l'envoi.

c) Le total vaut 148+92+47=287148 + 92 + 47 = 287 votes. Pourcentages : 148/2870,5157148 / 287 \approx 0{,}5157, soit 51,651{,}6 pour cent ; 92/2870,320692 / 287 \approx 0{,}3206, soit 32,132{,}1 pour cent ; 47/2870,163847 / 287 \approx 0{,}1638, soit 16,416{,}4 pour cent. La somme des trois valeurs arrondies vaut 100,1100{,}1, ce qui est normal et doit être assumé plutôt que corrigé silencieusement. Largeurs sur 480 pixels : 0,5157×4802480{,}5157 \times 480 \approx 248 pixels, 0,3206×4801540{,}3206 \times 480 \approx 154 pixels, 0,1638×480790{,}1638 \times 480 \approx 79 pixels ; leur somme vaut 481, l'écart d'un pixel venant des arrondis.

d) Côté client, on peut enregistrer un indicateur dans le stockage local du navigateur et masquer le formulaire s'il est présent. Côté serveur, on peut enregistrer un identifiant de session, ou l'adresse réseau, avec l'horodatage, et refuser un second vote de la même origine. C'est la protection côté SERVEUR qui protège réellement, pour la raison du schéma de l'exercice 9 : le stockage local est effacé en deux clics, et une requête forgée ne le consulte même pas. Aucune des deux n'est parfaite pour autant : côté serveur, une adresse réseau est partagée par tout un établissement scolaire, si bien que refuser sur ce critère bloquerait des votants légitimes, tandis qu'un même votant peut changer d'adresse en passant sur le réseau mobile. Un sondage ouvert et anonyme ne peut pas être infalsifiable ; il faut choisir entre l'anonymat et la certitude, et le dire.

e) Charge moyenne sur une journée : 2 000/86 4000,0232\ 000 / 86\ 400 \approx 0{,}023 vote par seconde. Charge de pointe : 2 000/2408,32\ 000 / 240 \approx 8{,}3 votes par seconde. Le rapport vaut 8,3/0,023=3608{,}3 / 0{,}023 = 360, ce qui est exactement le rapport des durées, 86 400/24086\ 400 / 240. Dimensionner sur la moyenne conduirait donc à une panne certaine. Deux mesures qui absorbent le pic : METTRE EN CACHE la page de résultats et ne la recalculer qu'une fois par seconde, ce qui borne le coût de lecture quel que soit le nombre de visiteurs ; et METTRE EN FILE les écritures, en acceptant le vote immédiatement et en l'enregistrant en arrière-plan, ce qui découple la vitesse d'arrivée de la vitesse d'écriture en base. On peut ajouter une troisième mesure, servir les fichiers statiques depuis un cache réparti, afin que seul le vote lui-même atteigne le serveur applicatif.

Partie C : les classiques (/50)

Exercice 11 : Les cookies et la mémoire d'un site

Le protocole HTTP est SANS ÉTAT : chaque requête est traitée indépendamment des précédentes. Pour reconnaître un visiteur d'une page à l'autre, le serveur lui confie un cookie, par un en-tête de réponse Set-Cookie ; le navigateur le renvoie ensuite dans l'en-tête Cookie de chaque requête vers le même site.

Le serveur d'une boutique répond : Set-Cookie: session=7f3a9c; Max-Age=1800. La durée Max-Age est exprimée en secondes.

  • a) Sans cookie, pourquoi le serveur ne peut-il pas savoir que deux requêtes successives viennent du même visiteur ?
  • b) Au bout de combien de minutes le cookie expire-t-il ? Le visiteur revient 25 minutes plus tard, puis 40 minutes après sa première visite : le cookie est-il envoyé à chaque fois ?
  • c) Une page du site nécessite 1 fichier HTML, 2 feuilles de style, 3 scripts et 14 images, tous servis par le même domaine. L'en-tête Cookie pèse 120 octets. Combien de requêtes, et combien d'octets de cookie, pour afficher une page ? Pour une visite de 50 pages ?
  • d) Un développeur propose de stocker directement role=admin dans un cookie plutôt qu'un identifiant de session. Pourquoi est-ce une faille ?
  • e) Une régie publicitaire place le même script sur trois sites différents. Expliquez comment son cookie lui permet de suivre un internaute d'un site à l'autre.

Tape tes réponses, la page te dit juste ou faux 0/9

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) HTTP sans état : le cookie identifie
  • b) 30 min : envoyé à 25 min, pas à 40
  • c) 20 requêtes, 2 400 octets, 120 000 pour 50 pages
  • d) Cookie modifiable : jamais de droits dedans
  • e) Cookie tiers : suivi entre sites

a) Chaque requête HTTP arrive seule, sans aucun lien avec la précédente : le serveur voit une adresse, un chemin et des en-têtes, rien qui dise « c'est encore moi ». Deux visiteurs derrière la même box partagent même la même adresse réseau. Le cookie ajoute l'information qui manque : un identifiant que le navigateur renvoie à chaque fois.

b) 1 8001\ 800 s =30= 30 min. Au retour après 25 minutes, le cookie est encore valide et il est envoyé : le serveur retrouve la session, par exemple le panier. Après 40 minutes, il a expiré et le navigateur l'a supprimé : il n'est pas envoyé, et le serveur voit un nouveau visiteur.

c) 1+2+3+14=201 + 2 + 3 + 14 = 20 requêtes, chacune portant l'en-tête Cookie : 20×120=2 40020 \times 120 = 2\ 400 octets pour une page. Pour 50 pages : 50×2 400=120 00050 \times 2\ 400 = 120\ 000 octets, soit environ 117 kio envoyés uniquement pour répéter le même identifiant. C'est pourquoi les images et les scripts sont souvent servis depuis un autre domaine, qui ne reçoit pas le cookie.

d) Un cookie est stocké chez le CLIENT : l'utilisateur peut le lire et le modifier dans son navigateur en quelques clics. S'il contient role=admin, n'importe qui peut écrire cette valeur et se déclarer administrateur. Un identifiant de session, long et aléatoire, ne donne aucun droit par lui-même : le serveur garde chez lui le rôle associé à cet identifiant, et l'utilisateur ne peut pas le changer. C'est la règle de l'exercice 9 appliquée aux cookies.

e) Le script est chargé depuis le domaine de la régie, qui dépose SON cookie, le même quel que soit le site visité. À chaque page qui contient le script, le navigateur envoie ce cookie à la régie, accompagné de l'adresse de la page d'origine. La régie relie ainsi les trois visites au même identifiant et reconstitue le parcours de l'internaute. On parle de cookies tiers, et c'est pour limiter ce suivi que les navigateurs les bloquent de plus en plus et que le consentement est exigé.

Exercice 12 : Sélecteurs CSS : quels éléments sont visés ?

Un sélecteur CSS désigne un ENSEMBLE d'éléments du document. Un espace entre deux sélecteurs signifie « descendant », le signe > signifie « enfant direct », et deux classes collées, comme .a.b, désignent les éléments qui portent les deux classes à la fois. On travaille sur le document ci-dessous.

html
<article class='fiche'>
  <h2>Titre</h2>
  <p class='intro'>Premier</p>
  <section>
    <p>Deuxième</p>
    <p class='intro note'>Troisième</p>
  </section>
  <p>Quatrième</p>
</article>
<p class='intro'>Cinquième</p>
  • a) Combien d'éléments sont visés par p ? Par .intro ?
  • b) Combien d'éléments sont visés par article p ? Par article > p ? Expliquez la différence.
  • c) Combien d'éléments sont visés par .intro.note ? Par .intro .note ? Pourquoi un seul espace change-t-il tout ?
  • d) Combien d'éléments sont visés par p.intro ? Par .intro p ?
  • e) Trois règles s'appliquent : p { color: black }, .intro { color: blue } et article p { color: green }. Quelle est la couleur du paragraphe Premier ? Du paragraphe Quatrième ? Du paragraphe Cinquième ?

Tape tes réponses, la page te dit juste ou faux 0/11

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 5 et 3
  • b) 4 descendants, 2 enfants directs
  • c) 1 élément contre 0
  • d) 3 contre 0
  • e) Bleu, vert, bleu

a) p vise les 5 paragraphes, de Premier à Cinquième. .intro vise les éléments portant la classe intro : Premier, Troisième et Cinquième, soit 3.

b) article p vise tous les paragraphes situés quelque part dans l'article, à n'importe quelle profondeur : Premier, Deuxième, Troisième et Quatrième, soit 4. article > p ne vise que les ENFANTS directs de l'article : Premier et Quatrième, soit 2, car Deuxième et Troisième sont les enfants de section, donc les petits-enfants de l'article.

c) .intro.note, sans espace, vise un élément qui porte les DEUX classes : Troisième, soit 1. .intro .note, avec espace, vise un élément de classe note situé À L'INTÉRIEUR d'un élément de classe intro : aucun paragraphe de classe intro ne contient d'élément, donc 0. L'espace est un opérateur : il transforme « et » en « à l'intérieur de ».

d) p.intro vise les paragraphes de classe intro : Premier, Troisième et Cinquième, soit 3. .intro p vise les paragraphes contenus dans un élément de classe intro : il n'y en a aucun, soit 0. L'ordre et l'espace décident de qui contient qui.

e) Premier est visé par les trois règles. .intro a une spécificité d'une classe, qui l'emporte sur article p, formé de deux balises, et sur p : Premier est BLEU. Quatrième n'a pas de classe : article p, deux balises, l'emporte sur p, une seule : il est VERT. Cinquième est hors de l'article : seules p et .intro s'appliquent, et .intro l'emporte : il est BLEU. Le piège est de croire que article p, plus long à écrire, est plus précis qu'une classe.

Exercice 13 : La propagation d'un événement

Quand on clique sur un élément, l'événement ne concerne pas que lui : il REMONTE ensuite vers son parent, puis vers le parent de son parent, jusqu'à la racine du document. Chaque ancêtre qui possède un gestionnaire pour cet événement l'exécute à son tour.

html
<body>
  <div id='carte'>
    <button id='ok'>OK</button>
  </div>
</body>

<script>
document.getElementById('ok').addEventListener('click', () => console.log('bouton'))
document.getElementById('carte').addEventListener('click', () => console.log('carte'))
document.body.addEventListener('click', () => console.log('page'))
</script>
  • a) L'utilisateur clique sur le bouton OK. Qu'affiche la console, et dans quel ordre ?
  • b) L'utilisateur clique dans la carte, à côté du bouton. Qu'affiche la console ?
  • c) On ajoute event.stopPropagation() dans le gestionnaire du bouton, qui reçoit alors l'événement en paramètre. Qu'affiche un clic sur le bouton ?
  • d) Un menu s'ouvre quand on clique sur son bouton, et un gestionnaire placé sur la page le referme à chaque clic ailleurs. En pratique, le menu ne s'ouvre jamais. Expliquez, et corrigez.
  • e) Une liste contient 200 éléments cliquables. Plutôt que 200 gestionnaires, on en place un seul sur la liste. Combien de gestionnaires économise-t-on, et comment le gestionnaire unique sait-il quel élément a été cliqué ?

Tape tes réponses, la page te dit juste ou faux 0/6

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) bouton, carte, page
  • b) carte, page
  • c) bouton seulement
  • d) Remontée : stopPropagation
  • e) 199 économisés : délégation par event.target

a) La console affiche bouton, puis carte, puis page. L'événement part de la CIBLE, le bouton, dont le gestionnaire s'exécute en premier ; il remonte à la div carte, puis au corps de la page. L'ordre d'écriture des trois lignes du script n'y est pour rien : c'est l'arbre du document qui décide.

b) La console affiche carte, puis page. La cible est cette fois la div : le gestionnaire du bouton, qui est un DESCENDANT de la cible et non un ancêtre, n'est pas concerné.

c) La console n'affiche plus que bouton. stopPropagation arrête la remontée après le gestionnaire courant : ni la carte ni la page ne reçoivent l'événement. On l'emploie avec parcimonie, car d'autres parties de la page peuvent compter sur cet événement.

d) Le clic sur le bouton ouvre le menu, puis l'événement REMONTE jusqu'à la page, dont le gestionnaire referme aussitôt le menu. Les deux actions se succèdent en une fraction de milliseconde : l'utilisateur ne voit rien. Correction : appeler event.stopPropagation() dans le gestionnaire du bouton, ou, dans le gestionnaire de la page, ne fermer le menu que si la cible de l'événement n'est pas le bouton ni un élément du menu.

e) On économise 2001=199200 - 1 = 199 gestionnaires. Grâce à la remontée, un clic sur n'importe quel élément atteint la liste. Le gestionnaire unique lit event.target, qui désigne l'élément réellement cliqué, et agit en conséquence. Cette technique, la DÉLÉGATION, fonctionne aussi pour les éléments ajoutés plus tard par script, qui n'ont jamais reçu de gestionnaire propre.

Exercice 14 : Problème : un chronomètre dans la page

La fonction setInterval(f, d) appelle f toutes les d millisecondes, et renvoie un numéro qui permet de l'arrêter avec clearInterval. La fonction setTimeout(f, d) appelle f une seule fois, au plus tôt d millisecondes plus tard. Voici un chronomètre au dixième de seconde ; affichage, demarrer et arreter désignent des éléments de la page.

javascript
let dixiemes = 0
let id = null

function tic() {
    dixiemes = dixiemes + 1
    affichage.textContent = (dixiemes / 10).toFixed(1)
}

demarrer.addEventListener('click', () => { id = setInterval(tic, 100) })
arreter.addEventListener('click', () => { clearInterval(id) })
  • a) On clique sur Démarrer. Que vaut dixiemes au bout de 3,0 secondes, en théorie, et qu'affiche la page ?
  • b) Un utilisateur impatient clique deux fois sur Démarrer. Qu'affiche la page au bout de 3,0 secondes ? Que se passe-t-il ensuite quand il clique sur Arrêter ? Corrigez les deux gestionnaires.
  • c) Qu'affiche la console, et dans quel ordre, pour : console.log('A') ; setTimeout(() => console.log('B'), 0) ; console.log('C') ?
  • d) Les appels de setInterval ne sont pas parfaitement ponctuels. Après 10 minutes réelles, le chronomètre affiche 598.2. Calculez l'erreur relative. Comment mesurer le temps de façon fiable ?
  • e) Quand l'onglet passe en arrière-plan, les navigateurs n'exécutent plus un intervalle qu'une fois par seconde au plus. L'onglet reste caché pendant 60 secondes : de combien le chronomètre avance-t-il pendant ce temps ?

Tape tes réponses, la page te dit juste ou faux 0/7

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 30 dixièmes : 3.0
  • b) Double intervalle : 6.0 ; tester id === null
  • c) A, C, B
  • d) 0,30{,}3 % : mesurer avec Date.now()
  • e) 6 s au lieu de 60

a) Un appel toutes les 100 ms pendant 3 000 ms : 30 appels, donc dixiemes vaut 30 et la page affiche 3.0. La division par 10 convertit les dixièmes en secondes, et toFixed(1) garde un chiffre après la virgule.

b) Chaque clic crée un NOUVEL intervalle : deux intervalles appellent tic, chacun toutes les 100 ms, et le compteur avance deux fois trop vite. Au bout de 3,0 secondes : 60 appels, la page affiche 6.0. La variable id ne garde que le numéro du second intervalle : Arrêter arrête celui-là, et le premier continue de tourner, sans plus aucun moyen de l'arrêter. Correction : dans Démarrer, ne créer l'intervalle que si id === null ; dans Arrêter, appeler clearInterval(id) puis remettre id = null.

c) A, puis C, puis B. Même avec un délai de 0, setTimeout ne fait qu'INSCRIRE la fonction dans la file des tâches : elle ne s'exécute qu'une fois le script en cours terminé. C'est le même principe que les gestionnaires d'événements : enregistrer n'est pas exécuter.

d) L'écart vaut 600598,2=1,8600 - 598{,}2 = 1{,}8 s, soit 1,8600=0,003\frac{1{,}8}{600} = 0{,}003, c'est-à-dire 0,30{,}3 pour cent. Chaque appel peut être un peu retardé, et les retards s'accumulent parce qu'on compte des appels. La mesure fiable consiste à relever l'heure au démarrage avec Date.now(), puis à afficher, à chaque appel, la différence entre l'heure courante et l'heure de départ : l'intervalle ne sert plus qu'à rafraîchir l'affichage, et un appel en retard ne fausse plus rien.

e) Pendant 60 secondes, tic n'est appelée qu'une fois par seconde au plus : 60 appels au plus, soit 60 dixièmes, et le chronomètre n'avance que de 6,0 secondes au lieu de 60. Il retarde de 54 secondes. La méthode de la question d, fondée sur l'heure réelle, corrige ce défaut aussi : au retour sur l'onglet, la différence d'heures est juste, quel que soit le nombre d'appels.

javascript
demarrer.addEventListener('click', () => {
    if (id === null) {                 // un seul intervalle a la fois
        id = setInterval(tic, 100)
    }
})

arreter.addEventListener('click', () => {
    clearInterval(id)
    id = null
})

Exercice 15 : Problème : paginer les résultats d'une recherche

Un moteur de recherche interne affiche ses résultats par pages. L'adresse /recherche?q=graphe&page=3&parpage=20 demande la page 3, à raison de 20 résultats par page. La recherche trouve 237 résultats, rangés dans une liste Python resultats, numérotés à partir de l'indice 0.

  • a) Combien de pages faut-il ? Combien de résultats la dernière page contient-elle ?
  • b) Quels indices de la liste la page 3 affiche-t-elle ? Donnez la tranche Python, puis la formule générale de l'indice de début en fonction de page et parpage.
  • c) Un utilisateur tape page=13 dans l'adresse. Que renvoie la tranche ? Que devrait répondre le serveur ?
  • d) Un utilisateur tape parpage=1000000. Que se passe-t-il sur une base de deux millions de résultats ? Quelle protection le serveur doit-il appliquer ?
  • e) Le serveur découpe la chaîne 'q=graphe&page=3&parpage=20' en couples nom et valeur. Quel est le type des valeurs obtenues ? Pourquoi une recherche utilise-t-elle GET, alors qu'un vote utilise POST ?

Tape tes réponses, la page te dit juste ou faux 0/8

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 12 pages, 17 sur la dernière
  • b) resultats[40:60] ; debut = (page - 1) * parpage
  • c) Tranche vide : 404 ou redirection
  • d) Borner parpage
  • e) Chaînes à convertir ; GET pour lire

a) 23720=11,85\frac{237}{20} = 11{,}85 : 11 pages pleines ne suffisent pas, il en faut 12. Le nombre de pages est l'arrondi à l'entier SUPÉRIEUR, ce qui s'écrit (237 + 20 - 1) // 20 en Python. Les 11 premières pages contiennent 220 résultats : la dernière en contient 237220=17237 - 220 = 17.

b) La page 3 commence après les pages 1 et 2, soit après 2×20=402 \times 20 = 40 résultats : elle affiche les indices 40 à 59, par la tranche resultats[40:60]. En général, debut = (page - 1) * parpage et la tranche est resultats[debut:debut + parpage]. Le piège est d'écrire page * parpage, qui décale tout d'une page : la page 1 commencerait à l'indice 20.

c) debut vaut 12×20=24012 \times 20 = 240, au-delà de la fin de la liste : la tranche resultats[240:260] renvoie une liste VIDE, sans erreur, car les tranches sont tolérantes. Le serveur ne doit pas afficher une page vide sans explication : il répond par une erreur 404, ou redirige vers la dernière page, la 12.

d) Le serveur tente de construire et d'envoyer d'un coup deux millions de résultats : mémoire saturée, réponse de plusieurs centaines de mégaoctets, service ralenti pour tout le monde. Un seul utilisateur, sans aucune compétence, dégrade le service en modifiant un nombre dans l'adresse. Le serveur doit BORNER la valeur reçue, par exemple parpage = min(parpage, 100), et refuser les valeurs non numériques ou négatives. Avec 100 résultats par page, les 237 résultats tiennent en 3 pages.

e) Les valeurs sont des CHAÎNES : 'graphe', '3' et '20'. Il faut les convertir avec int avant tout calcul, sinon (page - 1) lève une TypeError, et refuser proprement une valeur comme page=trois. Une recherche utilise GET parce qu'elle ne fait que LIRE : l'adresse complète peut être mise en signet, partagée ou rechargée sans effet de bord. Un vote MODIFIE des données : avec GET, un simple rechargement ou un lien partagé voterait à nouveau.

Chapitre précédent Traitement de données en tables et types construits Chapitre suivant Architectures matérielles, systèmes et réseaux

Voir aussi

Vous cherchez un tuteur en NSI à Montréal ?

Contactez-moi pour une première séance. Ce chapitre paraît facile parce que le vocabulaire est familier, et c'est précisément pour cela qu'il fait perdre des points : les questions portent sur l'architecture, pas sur la syntaxe.

Site par Studio Squalli