À la conquête du Shadow DOM pour une meilleure expérience de remplissage automatique avec Dashlane

Par :
Dashlane

mar, 22/02/2022 - 13:31

L'utilisation de Shadow DOM sur une page Web donnée a toujours posé des problèmes aux gestionnaires de mots de passe car les champs « shadow » sont masqués pour les appels DOM normaux, ce qui signifie que le remplissage automatique ne peut pas marcher pour un identifiant de connexion, un formulaire d'adresse, ou toute autre donnée qui pourrait être pertinente pour la page donnée. Grâce aux ingénieurs de Dashlane, cette limitation a été surmontée, ouvrant la voie à une expérience encore meilleure sur un plus large éventail de sites Web qu'auparavant.

  Définitions : Shadow DOM

Le Shadow DOM est une technologie Web standard mise en œuvre par tous les navigateurs modernes (Chrome, Firefox, Edge, Opera, Safari, sur ordinateur et sur mobile) qui vous permet de compartimenter le markup et les comportements Web. En bref, chaque composant (avec son balisage interne, ses styles et ses comportements basés sur JavaScript) est attaché à un élément hôte (l'hôte fantôme) qui "cache" tout le contenu interne (le DOM fantôme) des appels DOM réguliers. Par exemple, les appels à document.querySelectorAll().

Le problème

Comment fonctionne un gestionnaire de mots de passe comme Dashlane dans une page Web ?

  • Nous analysons vos pages Web et détectons les formulaires et leurs champs de formulaire via des appels DOM. Nous avons récemment basculé notre moteur d'analyse vers l'apprentissage automatique et nous sommes fiers de la vitesse incroyable de ce nouveau noyau.
  • Nous ajoutons notre icône Dashlane aux champs que nous comprenons et pouvons remplir automatiquement avec des données (si vous avez des données pertinentes, bien sûr) ou dans lesquels vous pouvez générer un mot de passe.
  • Lorsque vous placez le curseur à l'intérieur d'un tel champ, nous affichons une interface proposant différents choix : remplissage automatique à partir des données de votre coffre-fort (identifiants, identité, adresses, informations de paiement, passeport, etc.), génération d'un mot de passe sécurisé, etc.
  • Nous détectons l’utilisation de nouveaux identifiants sur le site ou leur mise à jour et nous affichons une autre interface permettant aux utilisateurs de stocker les données dans leur coffre-fort de données.

Tout cela est basé sur la plate-forme Web et en particulier sur les requêtes DOM (Document Object Model) qui nous permettent de parcourir une arborescence de documents, d'interroger des éléments dans cette arborescence, etc.

Page Web avec une interface Dashlane permettant le remplissage automatique pour un mot de passe généré à l'intérieur d'un champ de formulaire

Comme nous l'avons évoqué en introduction, le contenu interne d'un Shadow DOM est complètement "caché" des appels DOM normaux. Si on prend un Shadow DOM d'hôte <div id="host"> et contenant par exemple un <input id="username" class="loginField"> ,

  • document.querySelector(".loginField") répondra null
  • document.getElementById("username") répondra null
  • document.getElementById("host").firstElementChild répondra null

Vous pouvez facilement voir le Shadow DOM en action en utilisant l'inspecteur de votre navigateur préféré. Dans la capture d'écran ci-dessous (Chrome), vous pouvez voir le formulaire de connexion d'une banque bien connue et une vue de son balisage. Celui-ci utilise un Shadow DOM qui est visible dans la capture d'écran de l'inspecteur.

Cette page Web fait un usage intensif des arborescences Shadow imbriquées. Tout le formulaire est inclus dans un Shadow DOM et on ne peut pas faire des requêtes à partir du document via des appels DOM réguliers.

C'est pourquoi tous les gestionnaires de mots de passe sur le marché (jusqu'à récemment), y compris Dashlane, échouent silencieusement mais totalement à analyser et remplir automatiquement les pages Web utilisant un Shadow DOM pour leurs pages de connexion ou d'inscription. Nous ne voyons tout simplement pas ces formulaires via des appels DOM réguliers.

/deep/ quelqu'un ?

Il y a longtemps, CSS Selectors disposait d'un "shadow-piercing combinator”.

Quelle était cette bête ?

Dans une page Web normale, c'est-à-dire une page Web qui n'utilise pas du tout Shadow DOM, vous pouvez sélectionner tous les champs de saisie de texte à l'intérieur d'un élément div via le sélecteur CSS suivant :

div input[type="text"]

Mais, comme nous l'avons vu ci-dessus, si votre document a un <div> contenant un shadow host lui-même contenant un shadow <input type="text">, le sélecteur ci-dessus ne le verra pas. Le combinateur /deep/ corrigeait ce manque:

div /deep/ input[type="text"]

Ce sélecteur "perce l'ombre" grâce au combinateur /deep/. Les Shadow boundaries, c'est-à-dire les murs de visibilité entre les Shadow hosts et leur Shadow DOM interne sont traversés par /deep/.

/deep/ était alors parfait pour trouver des formulaires et des champs de formulaire dans toutes les pages Web, y compris celles utilisant Shadow DOM. Malheureusement, /deep/ a été officiellement abandonné et supprimé des implémentations (navigateurs Web) en 2017, invoquant des raisons de performances et, je cite, des "violations d'encapsulation".

Cette dépréciation a laissé les fournisseurs d'applications tiers comme Dashlane sans option native pour interroger les éléments à l'intérieur d'un Shadow DOM.

 Mais…

Mais Shadow DOM est à la mode ces jours-ci car il est assez pratique pour architecturer un site web en termes de composants réutilisables.

Le nombre de sites Web populaires utilisant Shadow DOM augmente lentement mais régulièrement, ce qui signifie qu'il y avait de plus en plus de pages Web que Dashlane ne pouvait pas remplir automatiquement. Parce que nous nous soucions de l’expérience de nos clients et parce que nous devons faire face au Web dans toute sa diversité glorieuse (et parfois étrange), nous avons dû, à Dashlane, essayer à nouveau de traiter avec des sites Web basés sur Shadow DOM.

Si les requêtes natives de perçage d'ombre ne sont plus possibles, y a-t-il quelque chose que nous puissions faire via JavaScript et d'autres appels réguliers ? En résumé, la réponse est oui.

PoC #1

Nous avons alors commencé à écrire du code simulant le combinateur /deep/ mentionné ci-dessus pour interroger des éléments même s'ils sont contenus dans un Shadow DOM, avec quelques restrictions mineures et inoffensives. L'algorithme de base était simple :

  • Etant donné sel un sélecteur CSS valide dans la portée de querySelectorAll() et root un Document, DocumentFragment ou Element, soit retArray un tableau de root.querySelectorAll(sel)
  • Soit hostArray un tableau de tous les Shadow hosts dans le sous-arbre fixé à root, sans traverser aucune Shadow boundary.
  • Pour chaque shadowHost dans hostArray, on concatène le résultat de l'algorithme actuel sur sel et shadowHost.shadowRoot à retArray
  • On retourne retArray

De même, nous avons dû écrire notre propre code pour faire d'autres appels qui traversent les Shadow DOM comme Node.parentNode ou Node.firstChild.

Ça a marché. Assez bien, en fait. Mais les performances de cette tentative étaient sous-optimales sur les pages Web avec un balisage très complexe ou utilisant des DOM Shadow imbriqués (composants à l'intérieur de composants) car la détection de tous les hôtes Shadow à l'intérieur d'un document n'est pas facile. Il n'y a pas de pseudo-classe CSS qui pourrait nous aider à détecter les Shadow hosts à la vitesse native, par exemple. Un NodeIterator récupérant tous les nœuds de type Element et ayant un attribut shadowRoot non nul est alors le meilleur choix et ce choix peut être coûteux, très coûteux.

Shadow DOM a donc continué d’être un problème pour nous, et pour nos clients.

PoC #2

Quelques mois ont passé et une nouvelle idée a émergé pour soulager la douleur. Nous avons considérablement optimisé l'algorithme ci-dessus pour le rendre beaucoup plus rapide tout en ayant un impact négligeable sur les pages Web n'utilisant pas Shadow DOM. Pour décrire notre paysage actuel, notre moteur de Machine Learning analyse et classe habituellement un formulaire Web ordinaire en 20 millisecondes en moyenne et en 5 millisecondes en pointe… La classification d'un champ de formulaire unique est effectuée après analyse en 160 microsecondes en moyenne… Nous sommes rapides, très rapides.

  • Les mutations à l'intérieur d'un Shadow DOM sont assez difficiles à observer depuis l'extérieur de ce Shadow DOM car les changements dans le DOM ne traversent pas les Shadow boundaries… Un changement d'attribut, un changement de sous-arborescence, par exemple la modification de la visibilité d'un formulaire Web ou l’ajout d’un champ à un formulaire Web à l'intérieur d'un Shadow DOM sont difficiles à détecter au niveau du document, et c’est à ce niveau que le code des WebExtensions se trouve. Nous avons réussi à contourner ce problème.
  • Sur les pages Web qui utilisent Shadow DOM, la surcharge est bien maîtrisée et inférieure à 5% (et souvent bien plus faible) dans tous les cas que nous avons pu trouver. Cela signifie qu'un formulaire Web composé d'éléments DOM réguliers et classé en 20 millisecondes serait classé en 21 millisecondes maximum s'il utilisait Shadow DOM.
  • Sur les pages Web qui n'utilisent pas Shadow DOM, la surcharge est négligeable (bien inférieure à 1 milliseconde).

Au total, cette nouvelle implémentation a atteint un niveau de performances qui la rendait très acceptable du seul point de vue qui compte vraiment, celui du client : impact négligeable sur les pages Web classiques et vitesse plus que raisonnable sur les pages utilisant Shadow DOM.

  Ce que nous en avons tiré

Quelques conclusions importantes doivent être tirées de cette exploration en deux étapes :

  1. Il manque quelques extras aux spécifications de Shadow DOM permettant aux fournisseurs tiers d'extensions Web comme Dashlane d'interagir avec toutes les pages Web :
    1. Le combinateur shadow-piercing, permettant de trouver tous les éléments correspondant à un sélecteur CSS donné à la vitesse native où qu'ils se trouvent (dans l'arborescence DOM normale ou à l'intérieur d'un Shadow DOM), est le manquement le plus important, pour nous, dans le Standard W3C. Le problème de performance, cité par les éditeurs de navigateurs comme l'une des deux raisons de la dépréciation de cette fonctionnalité, n'existe pas vraiment, du moins du côté de l'API DOM (querySelector, querySelectorAll, matches, closest). Les implémentations dans matching.rs de Gecko et selector_checker.cc de Blink semblent facilement faisables sans impact sur les performances par rapport à un appel régulier sans shadow-piercing.
    2. La deuxième raison donnée pour cette dépréciation (violation de l'encapsulation), n'est pas vraiment valable. Il est encore relativement facile, mais coûteux, d'implémenter une version JavaScript  shadow-piercing de querySelectorAll(), closest() ou matches().
    3. Il nous manque clairement une pseudo-classe CSS sélectionnant des éléments qui sont des Shadow hosts, même sans traverser les Shadow boundaries. La disponibilité d'une telle pseudo-classe plutôt facile à implémenter augmenterait considérablement nos performances.
  2. Une valeur de configuration pour Mutation Observers nous permettant de détecter les mutations du DOM à l'intérieur des Shadow DOM (éventuellement imbriqués) a été proposée et abandonnée il y a longtemps. La leçon étant : les fournisseurs de navigateurs ne peuvent pas penser à tous les cas d'utilisation possibles d'une nouvelle technologie. Seuls les utilisateurs (lire : les implémenteurs qui utiliseront cette nouvelle technologie) découvriront ce qui est bien ou mal conçu, ce qui manque ou ce qui doit être amélioré. En particulier lorsque cette nouvelle technologie modifie la façon dont les pages Web sont créées, la proactivité est cruciale. Les fournisseurs d'extensions Web doivent être beaucoup plus actifs dans la normalisation Web.

Maintenant disponible dans votre navigateur

Nous sommes ravis de vous annoncer que Dashlane va désormais encore plus loin dans les pages Web, en trouvant et en reconnaissant les formulaires Web dans les Web components et le Shadow DOM pour vous offrir une expérience de remplissage automatique encore meilleure !

A propos de l'auteur

Dashlane