Tous les exemples de code sont testables sur une github page qui est dans les sources 😉

Introduction à la magie noire

Un lien de base serait en général sous la forme http://example.com. Concrètement le navigateur voit quoi lui ? D'abord on a le schéma (http), puis le caractère deux points (:), suivi de la cible (//example.com). Jusque-là je ne dois pas vraiment vous surprendre.

La magie noire dont je veux parler s'invoque comme ça : javascript:alert('je suis un lien !'). Je vous vois venir : pour vous ce n'est pas un lien, désolé de casser vos certitudes mais pour un navigateur c'est bien un lien ! On retrouve un schema (javascript), le caractère deux points (:) puis une cible (alert('je suis un lien !')).

Vous pouvez tester chez vous : en cliquant ici.

Comme on a un lien clickable, qu'est-ce qui nous empêche de mettre ce lien dans un marque-page et de cliquer sur ce dernier pour appeler le code ? Rien, c'est pour ça qu'on parle de bookmarklet (contraction de bookmark et applet). Et si on range notre bookmarklet dans la barre de marque-page, on a quasiment un bouton de plus dans notre navigateur 🤯

Plusieurs lignes ? Aucun souci !

Mon exemple au-dessus est très sympa mais dans la vraie vie, on veut généralement plus qu'un seul appel de fonction ou instruction.

Est-ce qu'on pourrait écrire quelque chose du genre :

const style = document.createElement('style');
style.innerText = 'a,a:hover,a:focus,a:visited{color: yellow;}';
document.head.append(style);

Eh bien c'est parfaitement possible ! Il suffit de démarrer par javascript: et ça ne pose aucun souci. Généralement on préférera quand même utiliser une IIFE (ou Immediately-invoked Function Expression) :

javascript:(function () {
    const style = document.createElement('style');
    style.innerText = 'a,a:hover,a:focus,a:visited{color: yellow;}';
    document.head.append(style);
})()

Je vous mets dans les sources un lien qui détail comment fonctionne une IIFE, mais dans les grandes lignes c'est juste une manière d'écrire une fonction de sorte à l'appeler dès qu'elle a été créée.

Quelques exemples d'usages

Du style "à l'arache"

La semaine dernière j'ai vu un collègue qui mécaniquement ajoutait via l'outil d'inspection de DOM un height: 100% sur un élément parce qu'on attendait une autre configuration pour une vraie gestion du style et ça à chaque rafraîchissement de la page. J'ai pu lui éviter quelques clics via ce bookmarklet :

javascript:(function() { document.querySelector('#an-element').style = 'height: 100%' })()

undefined class

Quand on fait du React (potentiellement avec d'autres frameworks aussi), on se retrouve régulièrement avec des undefined dans nos class. En théorie ce n'est pas gênant mais ce n'est pas très propre donc voici un petit bookmarklet qui permet de repérer tous les éléments qui ont une classe undefined.

javascript:(() => {
    const style = document.createElement('style');
    style.innerText = '.undefined,.null{border: 1px solid red;}';
    document.head.append(style);
})()

Un peu d'aide pour l'accessibilité ?

On peut faire pas mal de chose pour l'accessibilité avec des bookmarklets. Par exemple on peut mettre en évidence les images qui n'ont pas d'attribut alt ou un alt vide.

javascript:(() => {
    const style = document.createElement('style');
    style.innerText = 'img:not([alt]),img[alt=""]{border: 2px dotted red;}';
    document.head.append(style);
})()

Note : avoir un attribut alt est censé être obligatoire pour avoir une balise img valide selon la norme W3C.

Mettre en évidence les sélecteurs de test

Quand on fait de l'automatisation de test (E2E principalement), on va parfois se demander ce qui a été correctement taggé avec un sélecteur stable du type data-e2e. Ce bookmarklet vous permettra de tous les voir d'un coup :

javascript:(() => {
    const style = document.createElement('style');
    style.innerText = `
    [data-e2e]{border: 3px dashed green;}
    [data-auto]{border: 3px dashed orange;}
    [e2e-id]{border: 3px dashed blue;}
    `;
    document.head.append(style);
})()

Un moteur de recherche en un clic

Parfois on aimerait bien rechercher dans le contenu d'un site, mais il n'y a pas forcément de fonction de recherche. Une solution peut être un bookmarklet qui va automatiquement ouvrir une recherche avec une restriction d'url :

javascript:(() => {
    const currentUrl = document.location.href;
    const googleSearchUrl = `https://www.google.fr/search?q=url%3A${currentUrl}%2F`;
    document.location.replace(googleSearchUrl);
})()

Un peu de React ?

Et pourquoi pas afficher un compte à rebours à base de composant React, parce que pourquoi pas ?

javascript:(async () => {
    const importScript = (url) => {
        const script = document.createElement('script');
        script.src = url;
        document.head.append(script);
    };
    importScript('https://unpkg.com/react@17/umd/react.development.js');
    importScript('https://unpkg.com/react-dom@17/umd/react-dom.development.js');
    const div = document.createElement('div');
    div.id = 'react-countdown';
    div.style = 'position: absolute;top:0; right:0;';
    document.body.append(div);
    setTimeout(() => {
        const Counter = () => {
            const [remaining,setRemaining] = React.useState(10);
            React.useEffect(() => {
                if (remaining > 0) {
                    setTimeout(() => setRemaining(remaining-1), 1000);
                } else if (remaining === 0) {
                    setRemaining('👻')
                }
            }, [remaining, setRemaining]);
            return remaining;
        };
        ReactDOM.render(React.createElement(Counter, null), document.getElementById('react-countdown'));
    }, 1000);
})()

Limitations

J'ai beau chanter les louanges des bookmarklets, il y a quand même quelques limites.

Déjà on ne peut pas vraiment les mettre à jour, en effet ce n'est jamais qu'un lien stocké dans un marque-page, ce n'est pas prévu pour être mis à jour, en tout cas pas automatiquement. Manuellement on pourra toujours recréer le bookmarklet ou l'éditer pour changer le lien qu'il contient.

Ensuite de plus en plus de site utilise les headers de CSP (Content Security Policy) qui permette de protéger une page web contre certains types d'attaques, dont certaines attaques de XSS. Et injecter des scripts externes, c'est exactement ce qu'on est en train de faire via les bookmarklets, donc au fond c'est normal que ça ne passe pas.

On est assez limité en termes de taille de code du fait des limitations en terme création de fonction, avoir tout le code dans un seul morceau de code (pas de fichier séparé typiquement), etc.

Conclusion

Sans pour autant dire que les WebExtensions sont inutiles, ce n'est clairement pas le cas, on peut tout de même réaliser des choses très pratiques, très simplement avec les bookmarklets. Juste, pensez-y si vous avez un besoin avant de sortir le bazooka que sont parfois les WebExtensions 😉

Sources :

Crédit photo : https://pixabay.com/photos/cross-stitch-bookmarks-tassel-blue-1249653/