Jsdom: agregar soporte para MutationObserver

Creado en 8 jun. 2013  ·  53Comentarios  ·  Fuente: jsdom/jsdom

Comentario más útil

Todos 53 comentarios

También necesito esto. Estaría dispuesto a trabajar en una solicitud de extracción para esto si alguien me puede indicar la dirección correcta.

@ SegFaultx64 La URL proporcionada por schettino72 en la primera publicación sobre este tema es la "dirección correcta".

Muy bien, ni siquiera estoy seguro de cómo registraríamos a los observadores en un proyecto tan grande. ¿Hay algún lugar donde se almacenen cosas así?

@ SegFaultx64 No me queda claro cuál sería el mejor método porque no he mirado esa parte de jsdom. Cuando trabajé en arreglar los métodos NS (ver # 727; nominalmente, es una corrección de errores pero requirió cambios sustanciales en las entrañas de jsdom) busqué estructuras analógicas y fui con eso para decidir dónde agregar nuevas clases / datos / etc. Seguí adelante con lo que pensé que era mejor, Domenic hizo algunos comentarios, ajusté lo que necesitaba ajuste, y eso fue todo.

@lddubeau Está bien, gracias por los consejos. De hecho, encontré un calce atractivo para MutationObserver https://github.com/megawac/MutationObserver.js/blob/master/MutationObserver.js. Parece un buen punto de partida. Comenzaré con esto más tarde hoy.

Mi único consejo sería leer https://dom.spec.whatwg.org/ y ver los detalles de dónde se pone en cola un registro de mutación. Encontrar las contrapartes adecuadas en jsdom puede ser complicado ya que todavía estamos en transición al nuevo estándar DOM, pero al menos la especificación le dará una guía sobre lo que debe implementarse.

alguna actualización sobre esto?

@kresli pull request ¡bienvenido!

No tengo idea de cómo hacerlo, pero he decidido hacer algunos experimentos. En el peor de los casos, aprendo un poco, en el mejor de los casos, podría contribuir.

Actualmente estoy tratando de entender cómo está estructurado jsdom y cómo funciona webidl (y se usa en jsdom)

¿Sería posible partir de este webidl en MutationObserver: https://dxr.mozilla.org/mozilla-central/source/dom/webidl/MutationObserver.webidl~~~

Ahora lo entiendo un poco mejor: las interfaces a usar deben ser las que se describen aquí: https://dom.spec.whatwg.org/#interface -mutationobserver - ¿verdad?

@henrikkorsgaard ¡correcto! Para que funcione en jsdom, debe comenzar con un par de cosas:

  • Agregue el MutationObserver.idl apropiado a https://github.com/tmpvar/jsdom/tree/master/lib/jsdom/living/nodes. (En el futuro, probablemente debería ir en una carpeta mejor que esa. Pero nuestra configuración actual lo hace un poco molesto, por lo que puede quedarse con los nodos hasta que algo funcione).
  • Agregue un archivo MutationObserver-impl.js a esa carpeta también. Aquí es donde continúa el trabajo de implementación real. Por ejemplo, la especificación dice "Cada objeto MutationObserver tiene estos conceptos asociados". Esas probablemente serán variables de instancia en la clase impl (= implementación).

Entonces "sólo" necesita implementar los métodos observe, desconectar y takeRecords, más el constructor, en la clase impl. En general, debe intentar seguir las especificaciones tanto como sea posible. ( @Sebmaster , ¿puedes explicar cómo se hace el constructor?)

En este caso, a diferencia de algo simple como CharacterData, muchos de sus cambios requerirán modificar otros archivos impl de jsdom. Por ejemplo, la especificación dice "Cada nodo tiene una lista asociada de observadores registrados". Esto requerirá agregar una variable de instancia al Node impl.

La especificación también dice: "Cada unidad de contextos de exploración de origen similar relacionados tiene un indicador en cola de microtask compuesto de observador de mutación, que inicialmente no está configurado, y una lista asociada de objetos MutationObserver, que inicialmente está vacía". Esto es un poco más complicado, ya que "la unidad de contextos de navegación de origen similar relacionados" no es obvia en jsdom. Lo que haría para esto es comenzar colocándolos en los objetos de la ventana. Los objetos de ventana todavía no usan IDL, por lo que solo agregará una propiedad con prefijo de subrayado en Window.js. En el futuro, podemos intentar preocuparnos por asegurarnos de que las cosas se rastrean en múltiples ventanas (es decir, cuidar de los iframes). Pero por ahora, la ventana es un buen lugar para colocarlos.

Finalmente, gran parte de la implementación se encargará de encontrar lugares apropiados para "poner en cola un registro de mutación". Si va a https://dom.spec.whatwg.org/#queue -a-mutation-record y hace clic en "poner en cola un registro de mutación", puede ver todos los lugares en la especificación que ocurren. Esto será un poco complicado porque jsdom no tiene exactamente los mismos ganchos que tiene la especificación para cuando sucedan las cosas. Pero debería ser factible, usando los ganchos _attrModified, _descendantRemoved y _descendantAdded de jsdom. https://github.com/tmpvar/jsdom/blob/master/lib/jsdom/living/attributes.js también tiene "cosas de observador de mutación TODO" en todas partes.

¡Espero que ayude!

Excelente :)

Nuevamente, no tengo mucha experiencia con webidl y bases de código más grandes. También estoy un poco invadido por algunos plazos;)

Haré todo lo posible y lo intentaré durante las próximas semanas. También intentaré hacer uno o dos casos de prueba.

OK,

He llegado tan lejos para crear el idl y Impl y lo requiero en window.js.

Ahora estoy tratando de crear un archivo de prueba para él y puedo llamar a new MutationObserver () con el prefijo de ventana.

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

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

¿Me estoy perdiendo algún truco aquí o hay algún lugar en el que necesito exponer el MutationObserver como un objeto global (llamado sin llamarlo en el objeto de la ventana)?

He agregado la siguiente línea en Window.js

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

Perdón por toda esta pregunta. Solo estoy tratando de establecer algunos puntos de entrada para poder comenzar a probar y seguir la arquitectura / patrón jsdom.

Tengo una pregunta relacionada con la puesta en @domenic de tener esa responsabilidad en Window.js funcionará ( webkit agrega registros de mutación a un hilo / cola dedicado ). Pero esto también requeriría algún tipo de cola y, lo que es más importante, alguna lógica que ejecute lo que está en la cola. Esto me lleva a dos preguntas:

¿Hay otros componentes en jsdom que podrían beneficiarse de dicha cola? ¿Hay algo que deba considerar aquí en términos de arquitectura e integración (futura)?

¿Sería más inteligente ejecutar tareas en la cola en función de un temporizador (¿hay un tic global en jsdom?) O simplemente en función de una estructura de devolución de llamada de promesas / hechas?

Me estoy enfocando en hacer la implementación muy básica y luego escribir casos de prueba extensos para guiar la implementación futura.

Entonces, una microtarea es básicamente solo process.nextTick(fn) . Esto es un poco más complicado para los observadores de mutaciones debido al negocio de microtareas compuestas y la "ejecución de una subtarea de microtareas compuestas". Creo que la mayoría de las veces puede simplemente ignorar eso; jsdom no necesita preocuparse por las cosas que está configurando.

Entonces: cuando la especificación dice "Poner en cola una microtarea compuesta para notificar a los observadores de mutaciones", haces process.nextTick(notifyMutationObservers) . Luego, dentro de notifyMutationObservers , creo que el paso 3 puede ser simplemente un bucle sobre los observadores de mutación, que realiza los subpasos de forma sincrónica.

Para las pruebas, asegúrese de consultar https://github.com/tmpvar/jsdom/blob/master/Contributing.md. Es posible que haya algunas pruebas de plataforma web existentes que pueda usar (aunque no siempre es tan fácil aprobarlas para una implementación desde cero). Y cualquier prueba que cree debe estar en la carpeta de flujo ascendente, siguiendo ese formato.

Puede que este no sea el lugar adecuado para preguntar, pero tengo problemas para entender cómo pasar objetos de esquema webidl (por ejemplo, /generated/MutationRecord.js) de un lado a otro entre los archivos impl (Node-impl -> MutationObserver-Impl) .

Me siento tentado a construir los objetos como objetos JSON puros que reflejan el esquema de MutationRecords y simplemente lo pasan de Node-impl a MutationObserver cuando ocurre una mutación. Supongo que esto rompería la ambición de implementar todos los aspectos de MutationObservers como se describe en la especificación.

Sospecho que esto se debe a que no estoy familiarizado con la arquitectura jsdom webidl / modelo de objeto / interfaces. Un ejemplo dentro del código también me ayudaría a entender cómo trabajar con / generate / objects en los archivos impl.

Probablemente deberíamos documentar esto en alguna parte, pero aquí va:

En general, todos los argumentos que pasan por la API generada se desempaquetarán / volverán a empaquetar automáticamente, por lo que nunca tendrá que preocuparse por los objetos generados; solo los objetos de implementación son importantes. Excepto, no realmente. Este sería el caso, si ya hubiéramos _todo_ cambiado a IDL, que no es el caso. Los argumentos y los valores de retorno funcionan, sin embargo, cada vez que interactúa con una clase que no es IDL (como Window), tendrá que hacer el desempaquetado / cambio de caja cuando les proporcione los argumentos usted mismo (consulte, por ejemplo, https: // github. com / tmpvar / jsdom / blob / master / lib / jsdom / living / events / EventTarget-impl.js # L103 , donde tenemos que desenvolver una ventana, ya que Window aún no está idl, pero EventTarget (de la cual Window hereda de es). Realiza este boxeo manual con idlUtils.wrapperForImpl / idlUtils.implForWrapper si es necesario.

Entonces, en general, desea construir un objeto Impl. Para hacerlo, necesita el archivo generado y llama a createImpl en sus exportaciones. Tomará una matriz de argumentos (para argumentos de constructor público) y un objeto de argumentos privados (ambos se proporcionan a la clase impl). Consulte https://github.com/tmpvar/jsdom/blob/9dd9069354e36c077032f4cbcb1616a7d9e6f0c4/lib/jsdom/living/nodes/Document-impl.js#L549 para ver un ejemplo.

Me doy cuenta de que esto no es lo suficientemente profundo como para comprender todo (creo), por lo que si tiene algo más específico, me complacerá explicar más.

Gracias @Sebmaster , ayudó mucho.

Sí, sería útil documentar esto con algunos ejemplos sobre cómo se deben integrar y usar API / objetos, pero creo que sé lo suficiente para los siguientes pasos.

¡Actualización rápida!
Las pruebas del W3C fallarán si MutationRecord sigue las especificaciones (creo). He hecho un comentario en su repositorio.

Los actualizaré localmente para mi propósito, pero no estoy seguro de si los enviaré a W3C / jsdom. Es decir, a menos que yo también tenga tiempo para terminar esa tarea.

Pero las mutaciones de atributos ahora pasan la mayoría de las pruebas y haré una solicitud de extracción en algún momento durante el fin de semana o al comienzo de la próxima semana.

¡Eso es súper emocionante! ¿Puede explicar con un poco más de detalle cuál es el problema? No pude seguir https://github.com/w3c/web-platform-tests/issues/2482, así que tal vez solo: qué valor de propiedad de un MutationRecord esperan las pruebas y qué crees que requiere la especificación ?

El problema es que la prueba no especifica un mutationRecord completo para comparar. Entonces, en el primer caso de prueba, la prueba cambiará el valor de id y, como consecuencia, el registro de mutación devuelto tendrá la propiedad del valor anterior. La propiedad oldValue no está definida en el objeto esperado y la prueba fallará. Según tengo entendido, un registro de mutación siempre debe contener todas las propiedades, incluso cuando son nulas. En ese caso, deberían ser DOMString nulo. Cuando las propiedades no se establecen en el caso de prueba, la prueba terminará comparando nulo (tipo de objeto) con nulo (tipo de cadena) y fallará.

Las pruebas se construyen de forma perezosa, por ejemplo, los campos indefinidos se establecen automáticamente en nulo (objeto). Esto hará que las pruebas fallen cuando a) mutationRecord devuelve una propiedad que no está establecida en el objeto de registro esperado (por ejemplo, oldValue) yb) cuando la propiedad del registro de mutación es DOMstring null (por ejemplo, attributteNamespace).

¿Tiene sentido?

Si estoy en lo cierto, es bastante fácil de arreglar, pero requiere revisar todos los casos uno por uno. Esto puede ser un poco difícil como un extraño para las pruebas de jsdom y w3c :)

Lo siento, ¿puedes simplificar? Preferiría una respuesta en la forma: las pruebas esperan que oldValue sea nulo o indefinido, pero la especificación da un valor "nulo". O similar.

La prueba mencionada anteriormente espera implícitamente que oldValue sea nulo. Debería ser "n"

Todos los casos de prueba en ese archivo esperan que attributeNamespace sea nulo (objeto), deben ser DOMString nulo de acuerdo con la especificación.

El tipo de attributeNamespace es DOMString ?, por lo que se permite que sea nulo (no "nulo", solo nulo).

Gracias por aclarar el caso de oldValue :).

Ok, gracias por aclararlo, creo que me he perdido aspectos importantes de la especificación.

Agregué una rama MutationObserver en mi bifurcación y la empujé. Actualmente, las mutaciones Attribute y CharacterData están pasando las pruebas w3c más importantes.

¿Dónde puedo documentar las pruebas que no pasan por razones / problemas conocidos (falta el soporte de Range # 317, etc.)?

Lo que hacemos es agregarlos al archivo index.js para las pruebas de la plataforma web, pero comentamos, con un comentario sobre el motivo. Vea, por ejemplo, lo que hacemos para la plantilla .

¿Está esto lejos de estar finalizado?

Parece que los eventos de mutación se eliminaron en 8.5 . ¿Hay alguna forma de probar elementos personalizados (requiere observadores de mutaciones) sin volver a 8.5 y depender de un polyfill? DOMParser no se implementó en 8.5 y lo requiero para nuestro polyfill Shadow DOM, por lo que estamos un poco bloqueados al poder ejecutar pruebas en JSDOM en este momento. Cualquier orientación sería útil aquí. Desafortunadamente, no tengo la capacidad en este momento para intentar sumergirme y ayudar a implementar esto.

No tengo conocimiento de ninguna forma de hacerlo, lo siento :(.

¿Puedo ayudar a mover este? Parece que se empezó a trabajar aquí: https://github.com/henrikkorsgaard/jsdom/commits/MutationObserver

Pero no estoy seguro de qué se necesita todavía para hacer avanzar este. Se siente como una gran brecha para que esto no exista dado que MutationObserver se admite básicamente en todas partes: http://caniuse.com/#feat = mutationobserver

¿Podríamos aprovechar el polyfill de webcomponentsjs? https://github.com/webcomponents/webcomponentsjs

Una solicitud de extracción siempre es bienvenida. El trabajo de @henrikkorsgaard es definitivamente un buen lugar para comenzar. Sin embargo, no creo que el polyfill tenga sentido.

Por lo que puedo decir, los eventos de mutación se eliminaron debido a consideraciones de rendimiento; la misma razón por la que Mutation Observer fue especificado e implementado por los navegadores. Sin embargo, los navegadores mantuvieron esa funcionalidad al menos hasta que se implementó la compatibilidad con MO. El enfoque pragmático aquí podría ser volver a agregar soporte para que, al menos, los MO se puedan rellenar hasta que esto se implemente. Me doy cuenta de que no es ideal para el mantenimiento de JSDOM y la base de código puede haber divergido desde entonces. Dicho esto, creo que vale la pena considerarlo. Esto deja una gran brecha, especialmente en esta etapa en la que los componentes web son una solución viable para las aplicaciones de producción y requieren que el MO se rellene con polietileno. Sería bueno poder ejecutar pruebas en JSDOM.

si ayuda; I jsdom MO en la parte superior rellena de polietileno; aquí y aquí . No diría que es una buena solución, pero funciona y también sin temporizadores.

¿Hace 4 años y todavía está en proceso? este problema se menciona en el registro de cambios desde la versión 9.0.0 . ¿Tienen alguna noticia sobre esto?

@MeirionHughes el archivo funcionó sin problemas, ¡gracias!

¿Alguna actualización sobre este tema?

Solo una extensión del enlace de @domenic :
Intente poner ese archivo que sugirió @MeirionHughes , funcionó bien hasta ahora.

@mtrabelsi @MeirionHughes Hola, no logré hacerlo funcionar en Jest, recibí este error https://stackoverflow.com/questions/43190171/jsdom-cannot-read-property-location-of-null
¿Podría explicar cómo se las arregló para usar MO en JSDOM?

EDITAR:
Ok, parece que me las arreglo para que funcione https://gist.github.com/romuleald/1b9272fce11d344e257d0bdfd3a984b0

@romuleald hay un error en su esencia, está configurando this.expando y this.counter en el constructor pero luego accediendo a él estáticamente. Esto causará problemas graves en _findMutations . Observará que en los archivos mecanografiados que intentaba convertir, ambas propiedades eran estáticas (no es posible con ES6). Sugiero agregar estas líneas debajo de la clase Util:

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

Y luego puede deshacerse del constructor por completo (de todos modos, la clase nunca se crea una instancia).

Me tomó vergonzosamente mucho tiempo detectar esto, ya que he estado depurando un problema causado por él durante el último día y medio.

Además, la fuente de usted, basado en la cuña no admite cambios en el .data propiedad de CharacterData nodos. Puede ver mi problema vinculado arriba de este comentario para una solución simple.

He podido probar esto usando https://github.com/megawac/MutationObserver.js polyfill por el momento.

Yo uso AVA. Y 'm' es un módulo my que contiene 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');
});

Debido a que es Polyfill, hay algunas cosas diferentes a la interfaz estándar. Pero este problema parece no tener ningún progreso, por lo que debe referirse como una opción.

Otra forma de usarlo como polyfill con jsdom y jest es cargar el shim como script manualmente.

npm install mutationobserver-shim

y por ejemplo dentro de beforeAll función:

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);

Este "truco" funciona para mí, tal vez también para otros;)

IIRC Encontré que funcionó bien para cargar la corrección (utilicé una versión modificada del código de pal-nodejs, no estoy seguro de en qué se basa el paquete npm anterior) como parte de setupFiles for Jest. Podría dar información más específica una vez que esté en mi otra computadora portátil si alguien está interesado.

así que esto ha estado abierto durante casi 5 años, ¿alguna actualización de estado?

@mendrik burlándose de que funcionó para mí https://github.com/benitogf/corsarial/blob/master/test/specs/utils.js#L29 y como se mencionó anteriormente también está la solución polyfill, que tengas un buen día :)

@benitogf Intenté eso, pero de alguna manera los registros no se
@treshugart ¿cómo puedo usar eso? :)

@mendrik en sus pruebas, importe este https://github.com/aurelia/pal-nodejs/blob/master/src/polyfills/mutation-observer.ts
y configúrelo en la variable global. ¡eso es todo!

@mendrik si está dispuesto a darse de baja de JSDOM https://github.com/skatejs/skatejs/tree/master/packages/ssr#usage. De lo contrario, puede importar ese archivo directamente y agregar las exportaciones (https://github.com/skatejs/skatejs/blob/master/packages/ssr/register/MutationObserver.js#L109) a su archivo global.

Esto fue suficiente para solucionar este problema para mí al intentar probar un componente que extendió react-quill:

import 'mutationobserver-shim';

document.getSelection = () => null;
¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

lehni picture lehni  ·  4Comentarios

domenic picture domenic  ·  3Comentarios

kilianc picture kilianc  ·  4Comentarios

jhegedus42 picture jhegedus42  ·  4Comentarios

khalyomede picture khalyomede  ·  3Comentarios