Jsdom: adicionar suporte para MutationObserver

Criado em 8 jun. 2013  ·  53Comentários  ·  Fonte: jsdom/jsdom

Comentários muito úteis

Todos 53 comentários

Eu também preciso disso. Eu estaria disposto a trabalhar em uma solicitação de pull para isso, se alguém pudesse me indicar a direção certa.

@ SegFaultx64 O URL fornecido por schettino72 na primeira postagem sobre esse problema é a "direção certa".

Muito justo, nem tenho certeza de como registraríamos os observadores em um projeto tão grande. Existe um lugar onde coisas assim estão sendo armazenadas?

@ SegFaultx64 Não está claro para mim qual seria o melhor método porque não olhei para essa parte do jsdom. Quando trabalhei na correção dos métodos NS (ver # 727; nominalmente, é uma correção de bug, mas exigiu mudanças substanciais na essência do jsdom), procurei estruturas analógicas e decidi onde adicionar novas classes / dados / etc. Eu praticamente fiz o que achei melhor, o Domenic fez alguns comentários, ajustei o que precisava de ajuste e pronto.

@lddubeau Ok, obrigado pelas dicas. Na verdade, encontrei um shim bonito para MutationObserver https://github.com/megawac/MutationObserver.js/blob/master/MutationObserver.js. Parece um bom ponto de partida. Vou começar mais tarde hoje.

Meu único conselho seria examinar https://dom.spec.whatwg.org/ e examinar os detalhes de onde um registro de mutação está enfileirado. Encontrar as contrapartes apropriadas em jsdom pode ser complicado, pois ainda estamos fazendo a transição para o novo padrão DOM, mas pelo menos a especificação fornecerá orientação sobre o que precisa ser implementado.

alguma atualização sobre isso?

@kresli pull request bem-vindo!

Não tenho ideia de como fazer, mas decidi fazer alguns experimentos. Na pior das hipóteses, aprendo um pouco, na melhor das hipóteses posso contribuir.

Atualmente estou tentando entender como jsdom está estruturado e como webidl está funcionando (e é usado em jsdom)

Seria possível sair deste webidl no MutationObserver: https://dxr.mozilla.org/mozilla-central/source/dom/webidl/MutationObserver.webidl~~~

Eu entendo um pouco melhor agora - as interfaces a serem usadas devem ser as descritas aqui: https://dom.spec.whatwg.org/#interface -mutationobserver - certo?

@henrikkorsgaard correto! Para fazê-lo funcionar no jsdom, você precisa começar com algumas coisas:

  • Adicione o MutationObserver.idl apropriado a https://github.com/tmpvar/jsdom/tree/master/lib/jsdom/living/nodes. (No futuro, provavelmente, ele deve ir para uma pasta melhor do que essa. Mas nossa configuração atual torna isso um pouco chato, então você pode ficar com os nós até que algo funcione.)
  • Adicione um arquivo MutationObserver-impl.js a essa pasta também. É aqui que o trabalho de implementação real continua. Por exemplo, a especificação diz "Cada objeto MutationObserver tem esses conceitos associados." Essas provavelmente serão variáveis ​​de instância na classe impl (= implementação).

Você então "apenas" precisa implementar os métodos observe, disconnect e takeRecords, mais o construtor, na classe impl. Em geral, você deve tentar seguir as especificações o máximo possível. ( @Sebmaster , você pode explicar como fazer o construtor?)

Neste caso, ao contrário de algo simples como CharacterData, muitas das suas alterações exigirão a modificação de outros arquivos jsdom impl. Por exemplo, a especificação diz "Cada nó tem uma lista associada de observadores registrados." Isso exigirá a adição de uma variável de instância ao Node impl.

A especificação também diz: "Cada unidade de contextos de navegação de origem semelhante relacionados tem um sinalizador de microtarefa composto de observador de mutação, que é inicialmente indefinido, e uma lista associada de objetos MutationObserver, que está inicialmente vazia." Isso é um pouco mais complicado, já que "unidade de contextos de navegação de origem semelhante relacionados" não é óbvia no jsdom. O que eu faria para isso é começar apenas colocando-os em objetos Window. Objetos de janela ainda não usam IDL, portanto, você apenas adicionará uma propriedade com prefixo de sublinhado em Window.js. No futuro, podemos tentar nos preocupar em garantir que as coisas sejam rastreadas em várias janelas (ou seja, cuidar dos iframes). Mas, por enquanto, a janela é um bom lugar para colocá-los.

Finalmente, grande parte da implementação será feita encontrando locais apropriados para "enfileirar um registro de mutação". Se você acessar https://dom.spec.whatwg.org/#queue -a-mutation-record e clicar em "enfileirar um registro de mutação", poderá ver todos os locais na especificação que acontecem. Isso será um pouco complicado porque jsdom não tem exatamente os mesmos ganchos que a especificação tem para quando as coisas acontecem. Mas deve ser possível, usando os ganchos _attrModified, _descendantRemoved e _descendantAdded de jsdom. https://github.com/tmpvar/jsdom/blob/master/lib/jsdom/living/attributes.js também tem "TODO mutation observer stuff" por toda parte.

Espero que ajude!

Excelente :)

Novamente, não tenho muita experiência com webidl e bases de código maiores. Também estou um pouco sobrecarregado com alguns prazos;)

Vou fazer o meu melhor e dar uma olhada nas próximas semanas. Também tentarei fazer um ou dois casos de teste.

OK,

Eu cheguei até agora para criar o idl e Impl e exigi-lo no window.js.

Agora estou tentando criar um arquivo de teste para ele e posso chamar o novo MutationObserver () com o prefixo da janela.

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

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

Estou perdendo algum truque aqui ou há algum lugar em que preciso expor o MutationObserver como um objeto global (chamado sem chamá-lo no objeto janela).

Eu adicionei a seguinte linha em Window.js

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

Desculpe por tudo isso perguntando. Estou apenas tentando estabelecer alguns pontos de entrada para que possa começar a testar e seguir a arquitetura / padrão jsdom.

Ah, desculpe, esqueci essa parte. Você precisará adicionar uma linha como https://github.com/tmpvar/jsdom/blob/28f00b30236d540df1777ca6c2c0ee9e5e19fe5b/lib/jsdom/living/index.js#L28

Tenho uma pergunta relacionada ao enfileiramento da tarefa de mutação . A especificação parece indicar um lugar central onde várias micro tarefas são enfileiradas e executadas em ordem. Posso encontrar esse lugar no jsdom e acho que a ideia de @domenic de ter essa responsabilidade no Window.js funcionará (o webkit adiciona registros de mutação a um thread / fila dedicado ). Mas isso também exigiria alguma forma de enfileiramento e, mais importante, alguma lógica que execute o que está na fila. Isso me leva a duas perguntas:

Existe algum outro componente no jsdom que pode se beneficiar dessa fila? Há algo que devo considerar aqui em termos de arquitetura e integração (futura)?

Seria mais inteligente executar tarefas na fila com base em um cronômetro (há um tique global no jsdom?) Ou apenas com base em uma estrutura de retorno de chamada feita / promessas?

Estou me concentrando em fazer a implementação básica e, em seguida, escrever casos de teste extensos para orientar a implementação futura.

Portanto, uma microtarefa é basicamente apenas process.nextTick(fn) . Isso é um pouco mais complicado para observadores de mutação por causa do negócio de microtarefa composta e da "execução de uma subtarefa de microtarefa composta". _Acho_ que você pode simplesmente ignorar isso; jsdom não precisa se preocupar com as coisas que está configurando.

Portanto: quando a especificação diz "Enfileirar uma microtarefa composta para notificar os observadores de mutação", você faz process.nextTick(notifyMutationObservers) . Então, dentro de notifyMutationObservers , acho que a etapa 3 pode ser apenas um loop sobre os observadores de mutação, que executa as subetapas de forma síncrona.

Para testes, certifique-se de verificar https://github.com/tmpvar/jsdom/blob/master/Contributing.md. Pode haver alguns testes de plataforma da web existentes que você pode usar (embora nem sempre seja fácil passar por uma implementação do zero). E todos os testes que você criar devem estar na pasta upstream, seguindo esse formato.

Este pode não ser o lugar certo para perguntar, mas estou tendo alguns problemas para entender como passar objetos de esquema webidl (por exemplo, /generated/MutationRecord.js) entre os arquivos impl (Node-impl -> MutationObserver-Impl) .

Estou tentado a construir os objetos como objetos JSON puros que espelham o esquema de MutationRecords e apenas passam isso de Node-impl para MutationObserver quando ocorre uma mutação. Acho que isso quebraria a ambição de implementar todos os aspectos de MutationObservers conforme descrito na especificação.

Suspeito que isso seja porque não estou familiarizado com a arquitetura / modelo de objeto / interfaces jsdom webidl. Um exemplo dentro do código também me ajudaria a entender como trabalhar com objetos / gerados / nos arquivos impl.

Provavelmente deveríamos documentar isso em algum lugar, mas aqui vai:

Em geral, quaisquer argumentos que passam pela API gerada serão automaticamente desempacotados / reembalados, então você nunca precisa se preocupar com os objetos gerados - apenas os objetos de implementação são importantes. Exceto - não realmente. Esse seria o caso, se já tivéssemos _tudo_ alterado para baseado em IDL, o que não é o caso. Argumentos e valores de retorno funcionam, no entanto, toda vez que você interagir com uma classe não IDL (como Window), você terá que fazer o unboxing / reboxing quando fornecer a eles quaisquer argumentos você mesmo (consulte por exemplo https: // github. com / tmpvar / jsdom / blob / master / lib / jsdom / living / events / EventTarget-impl.js # L103 , onde temos que desembrulhar uma janela, uma vez que a janela ainda não está idl'd, mas EventTarget (do qual a janela herda De é). Você faz este boxe manual com idlUtils.wrapperForImpl / idlUtils.implForWrapper se necessário.

Portanto, em geral, você deseja construir um objeto Impl. Para fazer isso, você precisa do arquivo gerado e chama createImpl em suas exportações. Vai levar um array de args (para argumentos do construtor público) e um objeto de args privados (ambos são fornecidos para a classe impl). Consulte https://github.com/tmpvar/jsdom/blob/9dd9069354e36c077032f4cbcb1616a7d9e6f0c4/lib/jsdom/living/nodes/Document-impl.js#L549 para um exemplo disso.

Sei que isso não é profundo o suficiente para compreender a coisa toda (eu acho), então se você tiver algo mais específico, ficarei feliz em explicar mais.

Obrigado @Sebmaster , ajudou muito.

Sim, documentar isso com alguns exemplos de como integrar e usar API / objetos seria útil, mas acho que sei o suficiente para as próximas etapas.

Rápida atualização!
Os testes W3C falharão se MutationRecord seguir as especificações (eu acho). Eu fiz um comentário em seu repositório.

Vou atualizá-los localmente para o meu propósito, mas não tenho certeza se vou enviá-los para W3C / jsdom. Isto é, a menos que eu tenha tempo para terminar essa tarefa também.

Mas as mutações de attribute agora passam na maioria dos testes e farei uma solicitação de pull em algum momento durante o fim de semana ou no início da próxima semana.

Isso é super empolgante! Você pode explicar com mais detalhes qual é o problema? Não consegui seguir https://github.com/w3c/web-platform-tests/issues/2482, então talvez apenas: qual valor de propriedade de um MutationRecord os testes esperam e o que você acha que a especificação requer ?

O problema é que o teste não especifica um mutationRecord completo para comparação. Portanto, no primeiro caso de teste, o teste irá alterar o valor de id e, como consequência, o registro de mutação retornado terá a propriedade de valor antigo. A propriedade oldValue não está definida no objeto esperado e o teste irá falhar. Pelo que entendi, um registro de mutação deve sempre conter todas as propriedades, mesmo quando elas são nulas. Nesse caso, eles devem ser DOMString null. Quando as propriedades não são definidas no caso de teste, o teste vai acabar comparando nulo (objeto typeof) com nulo (string typeof) e falhará.

Os testes são construídos lentamente, por exemplo, campos indefinidos são automaticamente definidos como nulos (objeto). Isso fará com que os testes falhem quando a) o mutationRecord retornar uma propriedade que não está definida no objeto de registro esperado (por exemplo, oldValue) e b) quando a propriedade do registro de mutação for DOMstring nula (por exemplo, attributeutteNamespace).

Faz sentido?

Se eu estiver certo, é bastante fácil de consertar, mas requer que você passe por todos os casos, um por um. Isso pode ser um pouco difícil para alguém de fora dos testes jsdom e w3c :)

Me desculpe, você pode simplificar? Eu prefiro uma resposta na forma: os testes esperam que oldValue seja nulo ou indefinido, mas a especificação fornece um valor "nulo". Ou similar.

O teste mencionado acima espera implicitamente que oldValue seja nulo. Deve ser "n"

Todos os casos de teste nesse arquivo esperam que attributeNamespace seja nulo (objeto), eles devem ser DOMString nulo de acordo com a especificação.

O tipo de attributeNamespace é DOMString ?, portanto, pode ser nulo (não "nulo", apenas nulo).

Obrigado por esclarecer sobre o caso oldValue :).

Ok, obrigado por esclarecer - acho que esqueci aspectos importantes das especificações.

Eu adicionei um branch MutationObserver em meu fork e fiz um push nele. Atualmente, as mutações Attribute e CharacterData estão passando nos testes w3c mais importantes.

Onde posso documentar os testes que não foram aprovados por motivos / problemas conhecidos (falta de suporte de intervalo nº 317 etc.)?

O que fazemos é adicioná-los ao arquivo index.js para testes de plataforma da web, mas comentados, com um comentário sobre o motivo. Veja, por exemplo, o que fazemos para o modelo .

Isso está longe de ser finalizado?

Parece que os eventos de mutação foram removidos em 8.5 . Existe alguma maneira de testar elementos personalizados (requer Mutation Observers) sem voltar para 8.5 e depender de um polyfill? DOMParser não foi implementado em 8.5 e eu exijo isso para nosso polyfill Shadow DOM, então estamos meio que bloqueados por podermos executar testes em JSDOM no momento. Qualquer orientação seria útil aqui. Não tenho capacidade no momento para mergulhar e ajudar a implementar isso agora, infelizmente.

Não tenho conhecimento de nenhuma maneira de fazer isso, desculpe :(.

Posso ajudar a mover este? Parece que algum trabalho foi iniciado aqui: https://github.com/henrikkorsgaard/jsdom/commits/MutationObserver

Mas não tenho certeza do que ainda é necessário para levar este adiante. Parece que há uma grande lacuna para que isso não exista, já que MutationObserver é suportado basicamente em todos os lugares: http://caniuse.com/#feat = mutationobserver

Podemos aproveitar o polyfill de componentes da web? https://github.com/webcomponents/webcomponentsjs

Uma solicitação de pull é sempre bem-vinda. O trabalho de @henrikkorsgaard é definitivamente um bom lugar para começar. Eu não acho que o polyfill faça sentido.

Pelo que posso dizer, os eventos de mutação foram removidos devido a considerações de desempenho; o mesmo motivo pelo qual o Mutation Observer foi especificado e implementado pelos navegadores. No entanto, os navegadores mantiveram essa funcionalidade pelo menos até que o suporte a MO fosse implementado. A abordagem pragmática aqui pode ser adicionar suporte novamente para que, pelo menos, os MOs possam ser polyfilled até que seja implementado. Percebi que não é ideal para a manutenção do JSDOM e a base de código pode ter divergido desde então. Dito isso, acho que vale a pena considerar. Isso deixa uma grande lacuna, especialmente neste estágio em que os componentes da web são uma solução viável para aplicativos de produção e exigem que o MO seja polyfilled. Seria bom poder executar testes em JSDOM.

se ajudar; I poli-preenchido MO no topo jsdom; aqui e aqui . Não diria que é uma boa solução, mas funciona e também sem temporizadores.

4 anos atrás e ainda em andamento? este problema é mencionado no changelog desde a versão 9.0.0 . Vocês têm novidades sobre isso?

@MeirionHughes o arquivo funcionou bem, obrigado!

Alguma atualização sobre este problema?

Apenas uma extensão para o link de @domenic :
tente colocar aquele arquivo que o @MeirionHughes sugeriu, funcionou bem até agora.

@mtrabelsi @MeirionHughes Olá, não consegui fazer funcionar no Jest, recebi este erro https://stackoverflow.com/questions/43190171/jsdom-cannot-read-property-location-of-null
Você poderia explicar como você conseguiu usar o MO no JSDOM?

EDITAR:
Ok, parece que consigo fazer funcionar https://gist.github.com/romuleald/1b9272fce11d344e257d0bdfd3a984b0

@romuleald há um erro em sua essência, você está definindo this.expando e this.counter no construtor, mas acessando-o estaticamente. Isso causará problemas graves em _findMutations . Você notará que nos arquivos datilografados que você estava tentando converter, ambas as propriedades eram estáticas (não é possível com ES6). Eu sugiro adicionar estas linhas abaixo da classe Util:

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

E então você pode se livrar totalmente do construtor (a classe nunca é instanciada de qualquer maneira).

Levei um tempo embaraçosamente longo para detectar isso, pois estive depurando um problema causado por ele no último dia e meio.

Além disso, a fonte em que você baseou o shim não suporta alterações na propriedade .data de CharacterData nodes. Você pode ver meu problema vinculado acima deste comentário para uma solução simples.

Pude testar isso usando https://github.com/megawac/MutationObserver.js polyfill por enquanto.

Eu uso AVA. E 'm' é um módulo meu que contém 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');
});

Por ser Polyfill, existem algumas coisas diferentes da interface padrão. Mas este problema parece não ter progredido, portanto, consulte como uma opção.

Outra maneira de usá-lo como polyfill com jsdom e jest é carregar o shim como script manualmente.

npm install mutationobserver-shim

e, por exemplo, dentro da função 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);

Este "hack" funciona para mim, talvez para outros também;)

IIRC Achei que funcionou bem para carregar o shim (usei uma versão ajustada do código de pal-nodejs, não tenho certeza em que o pacote npm acima é baseado) como parte de setupFiles for Jest. Eu poderia fornecer informações mais específicas quando estiver em meu outro laptop, se houver alguém interessado.

então está aberto há quase 5 anos, alguma atualização de status?

@mendrik simular funcionou para mim https://github.com/benitogf/corsarial/blob/master/test/specs/utils.js#L29 e como mencionado acima, há também a solução polyfill, tenha um bom dia :)

@benitogf Tentei fazer isso, mas de alguma forma os registros não dispararam para mim :( simplesmente não gerou nenhum erro.
@treshugart como posso usar isso? :)

@mendrik em seus testes, importe este https://github.com/aurelia/pal-nodejs/blob/master/src/polyfills/mutation-observer.ts
e defina-o como a variável global. é isso!

@mendrik se você deseja cancelar o JSDOM https://github.com/skatejs/skatejs/tree/master/packages/ssr#usage. Caso contrário, você pode importar esse arquivo diretamente e adicionar as exportações (https://github.com/skatejs/skatejs/blob/master/packages/ssr/register/MutationObserver.js#L109) ao seu global.

Isso foi o suficiente para corrigir esse problema para mim ao tentar testar um componente que estendia a pena de reação:

import 'mutationobserver-shim';

document.getSelection = () => null;
Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

vsemozhetbyt picture vsemozhetbyt  ·  4Comentários

khalyomede picture khalyomede  ·  3Comentários

potapovDim picture potapovDim  ·  4Comentários

domenic picture domenic  ·  3Comentários

mitar picture mitar  ·  4Comentários