Bloquer ou monitorer AJAX à partir d’un script GreaseMonkey (ou Userscript)

Facebook a récemment ajouté une fonctionnalité qui m’est très désagréable: la notification de la lecture des messages à l’expéditeur. Impossible dès lors d’ignorer un message instantané sans que la personne le sache, or c’est un droit immuable qui devrait être inscrit au burin dans la roche des droits de l’homme.

Modification de XMLHttpRequest

Pour empêcher ces notifications, il faut empêcher l’envoi de données vers http(s)://www.facebook.com/ajax/mercury/change_read_status.php à partir d’un script Greasemonkey. Le principe du blocage consiste en la modification de l’objet XMLHttpRequest pour que la méthode open n’autorise pas l’ouverture du socket vers la page en question:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// On sauvegarde la méthode open originale
window.XMLHttpRequest.prototype.trueOpen = window.XMLHttpRequest.prototype.open;

// On modifie la méthode open avec la notre
window.XMLHttpRequest.prototype.open = function() {
   
  // Du code pour bloquer ou logger ce qui se passe
  // On peut notamment regarder ce qu'il y a dans arguments
  // pour bloquer: return;

  // Si on ne souhaite pas bloquer l'ouverture du socket,
  // on fait appel à la méthode originale
  this.trueOpen.apply(this, arguments);
};

Vous pouvez ainsi intercepter chaque appel aux méthodes de XMLHttpRequest, et les bloquer ou les rerouter à votre guise. Pour la liste complète des méthodes en question, je vous invite à consulter sa documentation W3C.

Dans le cas d’un blocage (pas d’appel à trueOpen), voici ce qu’il va se passer du côté de la page monitorée:

1
2
3
4
5
6
// Instanciation normale de notre objet
var xhr = new XMLHttpRequest();
// Rien ne se passe, l'instance de xhr n'est pas modifiée et le socket n'est pas ouvert
xhr.open("get", "page_bloquee.html?param=1", true);
// Le send échoue car le socket n'est pas ouvert
xhr.send()

L’appel à xhr.send ne provoque aucune erreur, du moins sous Chrome.

Le script Greasemonkey

Le script abordé précédemment suppose que vous soyez dans le scope de la page dont vous souhaitez monitorer les appels AJAX. Ce n’est pas le cas pour les scripts GreaseMonkey. Comme expliqué très judicieusement dans ce post sur StackOverflow, Firefox et Chrome exécutent ces scripts dans une sandbox qui ne permet pas d’accéder à l’environnement javascript de la page concernée.

Néanmoins, il existe une méthode assez simple pour sortir de cette sandbox: injecter un élément script dans la page en question qui exécutera le code de notre choix.

1
2
3
4
5
6
7
8
9
function main() {
  // Notre code précédent
}

// On crée un élément <script></script> et on l'injecte dans la page à contrôler
// Dans cet élément, on copie la fonction main afin qu'elle soit exécutée
var script = document.createElement("script");
script.textContent = "(" + main.toString() + ")();";
document.body.appendChild(script);

Le code final

1
2
3
4
5
6
7
8
9
10
11
12
function main() {
  window.XMLHttpRequest.prototype.trueOpen = window.XMLHttpRequest.prototype.open;

  window.XMLHttpRequest.prototype.open = function() {
    // ...
    // this.trueOpen.apply(this, arguments);
  };
}

var script = document.createElement("script");
script.textContent = "(" + main.toString() + ")();";
document.body.appendChild(script);

En ce qui concerne le blocage de la notification facebook, voici facebook-read-status-disabler. Il est également à noter que ce monitoring ne fonctionnera pas avec Internet Explorer car celui-ci utilise un autre objet pour les requêtes AJAX. Un script plus complet est disponible sur github: AJAX Calls Intercepter, avec notamment la prise en compte d’Internet Explorer.