Jsdom: ajouter la prise en charge de MutationObserver

Créé le 8 juin 2013  ·  53Commentaires  ·  Source: jsdom/jsdom

Commentaire le plus utile

Tous les 53 commentaires

J'ai aussi besoin de ça. Je serais prêt à travailler sur une pull request pour cela si quelqu'un peut m'orienter dans la bonne direction.

@SegFaultx64 L'URL fournie par schettino72 dans le premier message sur ce problème est la "bonne direction".

Assez juste, je ne sais même pas comment nous pourrions inscrire les observateurs dans un projet de cette envergure. Y a-t-il un endroit où des trucs comme ça sont stockés?

@ SegFaultx64 Je ne sais pas quelle serait la meilleure méthode car je n'ai pas examiné cette partie de jsdom. Lorsque j'ai travaillé sur la correction des méthodes NS (voir #727 ; nominalement, c'est une correction de bogue mais cela a nécessité des changements substantiels dans les entrailles de jsdom), j'ai recherché des structures analogiques et je suis parti avec cela pour décider où ajouter de nouvelles classes/données/ etc. J'ai à peu près avancé avec ce que je pensais être le mieux, Domenic a fait quelques commentaires, j'ai ajusté ce qui nécessitait des ajustements, et c'est tout.

@lddubeau D'accord, merci pour les pointeurs. En fait, j'ai trouvé une belle cale pour MutationObserver https://github.com/megawac/MutationObserver.js/blob/master/MutationObserver.js. Cela ressemble à un bon point de départ. Je vais commencer plus tard dans la journée.

Mon seul conseil serait de parcourir https://dom.spec.whatwg.org/ et de rechercher les détails de l'endroit où un enregistrement de mutation est mis en file d'attente. Trouver les équivalents appropriés dans jsdom peut être délicat car nous sommes toujours en train de passer à la nouvelle norme DOM, mais au moins la spécification vous donnera des conseils sur ce qui doit être implémenté.

des mises à jour à ce sujet ?

@kresli pull request bienvenue !

Je n'ai aucune idée de comment le faire, mais j'ai décidé de faire quelques expériences. Dans le pire des cas, j'apprends un peu, dans le meilleur des cas, je pourrais peut-être contribuer.

J'essaye actuellement de comprendre comment jsdom est structuré et comment webidl fonctionne (et est utilisé dans jsdom)

Serait-il possible de s'écarter de ce webidl sur MutationObserver : https://dxr.mozilla.org/mozilla-central/source/dom/webidl/MutationObserver.webidl~~~

Je le comprends un peu mieux maintenant - les interfaces à utiliser devraient être celles décrites ici : https://dom.spec.whatwg.org/#interface -mutationobserver - n'est-ce pas ?

@henrikkorsgaard a raison ! Pour le faire fonctionner dans jsdom, vous devez commencer par quelques éléments :

  • Ajoutez le MutationObserver.idl approprié à https://github.com/tmpvar/jsdom/tree/master/lib/jsdom/living/nodes. (À l'avenir, il devrait probablement aller dans un meilleur dossier que celui-ci. Mais notre configuration actuelle rend cela un peu ennuyeux, vous pouvez donc vous en tenir aux nœuds jusqu'à ce que quelque chose fonctionne.)
  • Ajoutez également un fichier MutationObserver-impl.js à ce dossier. C'est là que se déroule le travail de mise en œuvre proprement dit. Par exemple, la spécification dit "Chaque objet MutationObserver a ces concepts associés." Ce seront probablement des variables d'instance dans la classe impl (=implémentation).

Il vous suffit ensuite d'implémenter les méthodes observe, disconnect et takeRecords, ainsi que le constructeur, dans la classe impl. En général, vous devriez essayer de suivre les spécifications autant que possible. ( @Sebmaster , pouvez-vous expliquer comment faire le constructeur ?)

Dans ce cas, contrairement à quelque chose de simple comme CharacterData, beaucoup de vos modifications nécessiteront de modifier d'autres fichiers impl jsdom. Par exemple, la spécification dit "Chaque nœud a une liste associée d'observateurs enregistrés." Cela nécessitera l'ajout d'une variable d'instance au Node impl.

La spécification indique également "Chaque unité de contextes de navigation d'origine similaire a un indicateur de file d'attente de microtâche composé d'observateur de mutation, qui est initialement non défini, et une liste associée d'objets MutationObserver, qui est initialement vide." C'est un peu plus délicat, car "l'unité de contextes de navigation d'origine similaire" n'est pas évidente dans jsdom. Ce que je ferais pour cela, c'est de commencer par les mettre simplement sur des objets Window. Les objets Window n'utilisent pas encore IDL, vous allez donc simplement ajouter une propriété avec préfixe de soulignement dans Window.js. À l'avenir, nous pouvons essayer de nous assurer que les choses sont suivies sur plusieurs fenêtres (c'est-à-dire prendre soin des iframes). Mais pour l'instant, la fenêtre est un bon endroit pour les mettre.

Enfin, une grande partie de la mise en œuvre sera prise en charge en trouvant des endroits appropriés pour « mettre en file d'attente un enregistrement de mutation ». Si vous allez sur https://dom.spec.whatwg.org/#queue -a-mutation-record et cliquez sur "mettre en file d'attente un enregistrement de mutation", vous pouvez voir tous les endroits de la spécification qui se produisent. Ce sera un peu délicat car jsdom n'a pas exactement les mêmes crochets que la spécification pour quand les choses se produisent. Mais cela devrait être faisable, en utilisant les crochets _attrModified, _descendantRemoved et _descendantAdded de jsdom. https://github.com/tmpvar/jsdom/blob/master/lib/jsdom/living/attributes.js contient également des "trucs d'observateur de mutation TODO" partout.

J'espère que cela pourra aider!

Super :)

Encore une fois, je ne suis pas très expérimenté avec webidl et les bases de code plus grandes. Je suis aussi un peu gâché par quelques échéances ;)

Je vais faire de mon mieux et essayer au cours des prochaines semaines. Je vais aussi essayer de faire un cas de test ou deux.

D'accord,

Je suis arrivé jusqu'à présent à créer l'idl et l'impl et je l'ai requis dans window.js.

Maintenant, j'essaie de créer un fichier de test pour cela et je peux appeler new MutationObserver() avec le préfixe de fenêtre.

const window = jsdom("<html><body><div id='mutation'></div></body></html>").defaultView;

let observer = new window.MutationObserver(function(mutations){
      console.log(mutations);
});

Ai-je raté une astuce ici ou y a-t-il quelque part dont j'ai besoin d'exposer le MutationObserver en tant qu'objet global (appelé sans l'appeler sur l'objet window).

J'ai ajouté la ligne suivante dans Window.js

const MutationObserver = require("../living/generated/MutationObserver");

Désolé pour toutes ces demandes. J'essaie juste d'établir des points d'entrée pour pouvoir commencer à tester et suivre l'architecture/le modèle jsdom.

Ah, désolé, j'ai oublié cette partie. Vous devrez ajouter une ligne comme https://github.com/tmpvar/jsdom/blob/28f00b30236d540df1777ca6c2c0ee9e5e19fe5b/lib/jsdom/living/index.js#L28

J'ai une question concernant la mise en attente de la tâche de mutation . La spécification semble indiquer un endroit central où plusieurs micro-tâches sont mises en file d'attente et exécutées dans l'ordre. Je peux trouver un tel endroit dans jsdom et je pense que l'idée de @domenic d'avoir cette responsabilité dans Window.js fonctionnera ( webkit ajoute des enregistrements de mutation à un fil/file d'attente dédié ). Mais cela nécessiterait également une forme de mise en file d'attente et, plus important encore, une logique qui exécute ce qui se trouve dans la file d'attente. Cela m'amène à deux questions :

Y a-t-il d'autres composants dans jsdom qui pourraient bénéficier d'une telle file d'attente ? Y a-t-il quelque chose que je devrais considérer ici en termes d'architecture et d'intégration (future) ?

Serait-il plus intelligent d'exécuter des tâches dans la file d'attente en fonction d'une minuterie (y a-t-il une coche globale dans jsdom ?)

Je me concentre sur la réalisation de l'implémentation très basique, puis sur l'écriture de cas de test approfondis pour guider la future implémentation.

Ainsi, une microtâche est fondamentalement juste process.nextTick(fn) . C'est un peu plus délicat pour les observateurs de mutations en raison de l'activité de microtâche composée et de la "sous-tâche d'exécution d'une microtâche composée". Je pense que vous pouvez presque tout simplement ignorer cela ; jsdom n'a pas à se soucier des éléments qu'il configure.

Donc : lorsque la spécification dit "Mettre en file d'attente une microtâche composée pour notifier les observateurs de mutations", vous faites process.nextTick(notifyMutationObservers) . Ensuite, à l'intérieur de notifyMutationObservers , je pense que l'étape 3 peut simplement être une boucle sur les observateurs de mutation, qui exécute les sous-étapes de manière synchrone.

Pour les tests, assurez-vous de consulter https://github.com/tmpvar/jsdom/blob/master/Contributing.md. Il peut exister des tests de plate-forme Web que vous pouvez utiliser (bien qu'ils ne soient pas toujours aussi faciles à faire passer pour une implémentation à partir de zéro). Et tous les tests que vous créez doivent se trouver dans le dossier to-upstream, en suivant ce format.

Ce n'est peut-être pas le bon endroit pour demander, mais j'ai du mal à comprendre comment passer des objets de schéma webidl (par exemple /generated/MutationRecord.js) entre les fichiers impl (Node-impl -> MutationObserver-Impl) .

Je suis tenté de construire les objets en tant qu'objets JSON purs qui reflètent le schéma de MutationRecords et de le transmettre simplement de Node-impl à MutationObserver lorsqu'une mutation se produit. Je suppose que cela briserait l'ambition d'implémenter tous les aspects de MutationObservers comme décrit dans la spécification.

Je soupçonne que c'est parce que je ne suis pas familier avec l'architecture/le modèle d'objet/les interfaces jsdom webidl. Un exemple dans le code m'aiderait également à comprendre le travail avec les objets /generated/ dans les fichiers impl.

Nous devrions probablement documenter cela quelque part, mais voici :

En général, tous les arguments passant par l'API générée seront automatiquement déballés/reboxés, vous n'avez donc jamais à vous soucier des objets générés - seuls les objets d'implémentation sont importants. Sauf - pas vraiment. Ce serait le cas si nous avions déjà _tout_ basculé sur l'IDL, ce qui n'est pas le cas. Les arguments et les valeurs de retour fonctionnent, mais chaque fois que vous interagissez avec une classe non-IDL (comme Window), vous devrez effectuer le déballage/reboxing lorsque vous leur fournirez vous-même des arguments (voir par exemple https://github. com/tmpvar/jsdom/blob/master/lib/jsdom/living/events/EventTarget-impl.js#L103 , où nous devons déballer une fenêtre, car Window n'est pas encore idl'd, mais EventTarget (dont Window hérite de) est). Vous faites cette boxe manuelle avec idlUtils.wrapperForImpl / idlUtils.implForWrapper si nécessaire.

Donc, en général, vous voulez construire un objet Impl. Pour ce faire, vous avez besoin du fichier généré et appelez createImpl lors de ses exportations. Cela prendra un tableau d'arguments (pour les arguments du constructeur public) et un objet d'arguments privés (les deux sont fournis à la classe impl). Voir https://github.com/tmpvar/jsdom/blob/9dd9069354e36c077032f4cbcb1616a7d9e6f0c4/lib/jsdom/living/nodes/Document-impl.js#L549 pour un exemple pour cela.

Je me rends compte que ce n'est pas assez approfondi pour saisir le tout (je pense), donc si vous avez quelque chose de plus spécifique, je serais heureux de vous expliquer davantage.

Merci @Sebmaster , ça

Oui, documenter cela avec quelques exemples sur la façon dont on devrait intégrer et utiliser des API/objets serait utile, mais je pense que j'en sais assez pour les prochaines étapes.

Mise à jour rapide !
Les tests W3C échoueront si MutationRecord suit les spécifications (je pense). J'ai fait un commentaire dans leur référentiel.

Je les mettrai à jour localement pour mon objectif, mais je ne suis pas sûr de les valider dans W3C/jsdom. C'est-à-dire, à moins que j'aie le temps de terminer cette tâche aussi.

Mais les mutations d'attributs réussissent maintenant la plupart des tests et je ferai une demande d'extraction au cours du week-end ou au début de la semaine prochaine.

C'est super excitant ! Pouvez-vous expliquer un peu plus en détail quel est le problème? Je n'étais pas tout à fait capable de suivre https://github.com/w3c/web-platform-tests/issues/2482 alors peut-être juste: quelle valeur de propriété d'un MutationRecord les tests attendent-ils, et que pensez-vous que la spécification exige ?

Le problème est que le test ne spécifie pas un enregistrement de mutation complet avec lequel comparer. Ainsi, dans le premier cas de test, le test modifiera la valeur id et, par conséquent, l'enregistrement de mutation renvoyé aura l'ancienne propriété value. La propriété oldValue n'est pas définie dans l' objet attendu et le test échouera. Si je comprends bien, un enregistrement de mutation doit toujours contenir toutes les propriétés, même lorsqu'elles sont nulles. Dans ce cas, ils doivent être DOMString null. Lorsque les propriétés ne sont pas définies dans le scénario de test, le test finira par comparer null (type d'objet) avec null (type de chaîne) et échouera.

Les tests sont construits paresseusement, par exemple les champs non définis sont automatiquement définis sur null (objet). Cela entraînera l'échec des tests lorsque a) la mutationRecord renvoie une propriété qui n'est pas définie dans l'objet d'enregistrement attendu (par exemple oldValue) et b) lorsque la propriété d'enregistrement de mutation est DOMstring null (par exemple, attributteNamespace).

Logique?

Si j'ai raison, c'est assez facile à corriger, mais nécessite de parcourir tous les cas un par un. Cela peut être un peu difficile en tant qu'étranger aux tests jsdom et w3c :)

Je suis désolé, pouvez-vous simplifier? Je préférerais une réponse sous la forme : les tests s'attendent à ce que oldValue soit nul ou non défini, mais la spécification donne une valeur "null". Ou similaire.

Le test référencé ci-dessus s'attend implicitement à ce que oldValue soit nul. Il devrait être "n"

Tous les cas de test de ce fichier s'attendent à ce que l'attribut attributeNamespace soit null (objet), ils doivent être null DOMString selon la spécification.

Le type d'attributNamespace est DOMString?, il est donc autorisé à être null (pas "null", juste null).

Merci d'avoir clarifié le cas oldValue :).

Ok, merci pour la clarification - je pense que j'ai raté des aspects importants de la spécification.

J'ai ajouté une branche MutationObserver dans mon fork et j'y ai poussé. Actuellement, les mutations Attribute et CharacterData passent les tests w3c les plus importants.

Où puis-je documenter les tests qui ne réussissent pas pour des raisons/problèmes connus (prise en charge de la gamme n°317 manquante, etc.) ?

Ce que nous faisons, c'est les ajouter au fichier index.js pour les tests de plate-forme Web, mais commentés, avec un commentaire expliquant pourquoi. Voir par exemple ce que nous faisons pour le modèle .

Est-ce loin d'être finalisé ?

Apparemment, les événements de mutation ont été supprimés dans la version 8.5 . Existe-t-il un moyen de tester des éléments personnalisés (nécessite des observateurs de mutation) sans revenir à la version 8.5 et s'appuyer sur un polyfill ? DOMParser n'a pas été implémenté dans 8.5 et j'en ai besoin pour notre polyfill Shadow DOM donc nous sommes un peu bloqués en pouvant exécuter des tests dans JSDOM pour le moment. Toute orientation serait utile ici. Je n'ai pas la capacité pour le moment d'essayer de plonger et d'aider à mettre cela en œuvre dès maintenant, malheureusement.

Je ne connais aucun moyen de le faire, désolé :(.

Puis-je aider à faire avancer celui-ci ? Il semble que des travaux aient commencé ici : https://github.com/henrikkorsgaard/jsdom/commits/MutationObserver

Mais je ne suis pas sûr de ce qui est encore nécessaire pour faire avancer celui-ci. Cela ressemble à un grand écart pour que cela n'existe pas étant donné que MutationObserver est pris en charge pratiquement partout : http://caniuse.com/#feat =mutationobserver

Pourrions-nous tirer parti du polyfill webcomponentsjs ? https://github.com/webcomponents/webcomponentsjs

Une pull request est toujours la bienvenue. Le travail de @henrikkorsgaard est définitivement un bon point de départ. Je ne pense pas que le polyfill ait du sens cependant.

D'après ce que je peux dire, les événements de mutation ont été supprimés pour des raisons de performances ; la même raison pour laquelle Mutation Observer a été spécifié et implémenté par les navigateurs. Cependant, les navigateurs ont conservé cette fonctionnalité au moins jusqu'à ce que la prise en charge de MO soit implémentée. L'approche pragmatique ici pourrait consister à rajouter la prise en charge pour que, au moins, les MO puissent être polyremplis jusqu'à ce que cela soit mis en œuvre. Je me rends compte que ce n'est pas idéal pour la maintenance de JSDOM et que la base de code a peut-être divergé depuis lors. Cela dit, je pense que cela vaut la peine d'être considéré. Cela laisse un énorme vide, en particulier à ce stade où les composants Web sont une solution viable pour les applications de production et ils nécessitent que MO soit rempli. Ce serait bien de pouvoir exécuter des tests dans JSDOM.

si ça aide; Je poly-rempli MO on-top jsdom; ici et ici . Je ne dirais pas que c'est une bonne solution, mais cela fonctionne et sans minuterie aussi.

il y a 4 ans et toujours en cours ? ce problème est mentionné dans le changelog depuis la version 9.0.0 . Avez-vous des nouvelles à ce sujet les gars?

@MeirionHughes, le fichier a fonctionné sans problème, merci !

Une mise à jour sur ce problème?

Juste une extension du lien de
essayez de mettre ce fichier suggéré par @MeirionHughes , cela a bien fonctionné jusqu'à présent.

@mtrabelsi @MeirionHughes Bonjour, je n'ai pas réussi à le faire fonctionner dans Jest, j'ai eu cette erreur https://stackoverflow.com/questions/43190171/jsdom-cannot-read-property-location-of-null
Pourriez-vous expliquer comment avez-vous réussi à utiliser MO dans JSDOM ?

ÉDITER:
Ok, il semble que j'arrive à le faire fonctionner https://gist.github.com/romuleald/1b9272fce11d344e257d0bdfd3a984b0

@romuleald il y a une erreur dans votre message, vous définissez this.expando et this.counter dans le constructeur mais vous y accédez ensuite de manière statique. Cela causera de graves problèmes dans _findMutations . Vous remarquerez que dans les fichiers tapuscrits que vous essayiez de convertir, les deux propriétés étaient statiques (pas possible avec ES6). Je suggère d'ajouter ces lignes sous la classe Util :

Util.counter = 1;
Util.expando = 'mo_id';

Et puis vous pouvez vous débarrasser complètement du constructeur (la classe n'est de toute façon jamais instanciée).

Il m'a fallu un temps embarrassant pour le repérer, car je débogue un problème causé par celui-ci depuis un jour et demi.

De plus, la source que vous basé sur la cale ne prend pas en charge les modifications au .data propriété de CharacterData nœuds. Vous pouvez voir mon problème lié au-dessus de ce commentaire pour une solution simple.

J'ai pu tester cela en utilisant https://github.com/megawac/MutationObserver.js polyfill pour le moment.

J'utilise AVA. Et 'm' est un module my contenant MutationObserver.

import test from 'ava';
import delay from 'delay';
import jsdom from 'jsdom';
import m from '.';

const dom = new jsdom.JSDOM();
global.window = dom.window;
global.document = dom.window.document;

require('mutationobserver-shim');

global.MutationObserver = window.MutationObserver;

test('MutationObserver test', async t => {
    delay(500).then(() => {
        const el = document.createElement('div');
        el.id = 'late';
        document.body.appendChild(el);
    });

    const checkEl = await m('#late');
    t.is(checkEl.id, 'late');
});

Parce qu'il s'agit de Polyfill, certaines choses sont différentes de l'interface standard. Mais ce problème ne semble avoir aucun progrès, veuillez donc vous référer à une option.

Une autre façon de l'utiliser comme polyfill avec jsdom et jest consiste à charger manuellement le shim en tant que script.

npm install mutationobserver-shim

et par exemple dans la fonction beforeAll :

const mo = fs.readFileSync(
  path.resolve('node_modules', 'mutationobserver-shim', 'dist', 'mutationobserver.min.js'),
  { encoding: 'utf-8' },
);
const moScript = win.document.createElement('script');
moScript.textContent = mo;

win.document.body.appendChild(moScript);

Ce "hack" fonctionne pour moi, peut-être pour d'autres aussi ;)

IIRC J'ai trouvé que cela fonctionnait bien pour charger la cale (j'ai utilisé une version modifiée du code de pal-nodejs, je ne sais pas sur quoi est basé le package npm ci-dessus) dans le cadre de setupFiles for Jest. Je pourrais donner des informations plus spécifiques une fois que je serai sur mon autre ordinateur portable si quelqu'un est intéressé.

donc c'est ouvert depuis presque 5 ans, des mises à jour de statut ?

@mendrik moqueur ça a marché pour moi https://github.com/benitogf/corsarial/blob/master/test/specs/utils.js#L29 et comme mentionné ci-dessus il y a aussi la solution polyfill, bonne journée :)

@benitogf J'ai essayé cela mais d'une manière ou d'une autre, les enregistrements ne se sont pas déclenchés pour moi :( cela n'a tout simplement
@treshugart comment puis-je l'utiliser? :)

@mendrik dans vos tests, importez ceci https://github.com/aurelia/pal-nodejs/blob/master/src/polyfills/mutation-observer.ts
et définissez-le sur la variable globale. c'est ça!

@mendrik si vous souhaitez vous désinscrire de JSDOM https://github.com/skatejs/skatejs/tree/master/packages/ssr#usage. Sinon, vous pouvez importer ce fichier directement et ajouter les exportations (https://github.com/skatejs/skatejs/blob/master/packages/ssr/register/MutationObserver.js#L109) à votre fichier global.

C'était suffisant pour résoudre ce problème pour moi en essayant de tester un composant qui étendait la réactance :

import 'mutationobserver-shim';

document.getSelection = () => null;
Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

Progyan1997 picture Progyan1997  ·  3Commentaires

domenic picture domenic  ·  3Commentaires

potapovDim picture potapovDim  ·  4Commentaires

cg433n picture cg433n  ·  3Commentaires

tolmasky picture tolmasky  ·  4Commentaires