Jsdom: Unterstützung für MutationObserver hinzufügen

Erstellt am 8. Juni 2013  ·  53Kommentare  ·  Quelle: jsdom/jsdom

Hilfreichster Kommentar

Alle 53 Kommentare

Das brauche ich auch. Ich wäre bereit, an einem Pull-Request dafür zu arbeiten, wenn mir jemand die richtige Richtung weisen kann.

@SegFaultx64 Die von schettino72 im ersten Beitrag zu diesem Thema bereitgestellte URL ist die "richtige Richtung".

Fair genug, ich bin mir nicht einmal sicher, wie wir die Beobachter in einem so großen Projekt registrieren würden. Gibt es einen Ort, an dem so etwas gelagert wird?

@SegFaultx64 Es ist mir unklar, was die beste Methode wäre, da ich mir diesen Teil von jsdom nicht angesehen habe. Als ich an der Korrektur der NS-Methoden arbeitete (siehe #727; nominell ist es ein Bugfix, aber es erforderte erhebliche Änderungen an den Eingeweiden von jsdom) suchte ich nach analogen Strukturen und entschied mich, wo neue Klassen/Daten/ hinzugefügt werden sollten. usw. Ich ging so ziemlich weiter mit dem, was ich für das Beste hielt, Domenic machte einige Kommentare, ich passte an, was Anpassungen brauchten, und das war's.

@lddubeau Okay, danke für die https://github.com/megawac/MutationObserver.js/blob/master/MutationObserver.js. Es sieht aus wie ein schöner Absprungpunkt. Ich fange heute später damit an.

Mein einziger Rat wäre, https://dom.spec.whatwg.org/ zu lesen und die Details zu prüfen, wo ein Mutationsdatensatz eingereiht wird. Das Finden der geeigneten Gegenstücke in jsdom kann schwierig sein, da wir immer noch auf den neuen DOM-Standard umstellen, aber zumindest gibt Ihnen die Spezifikation eine Anleitung, was implementiert werden muss.

irgendwelche Updates dazu?

@kresli Pull-Request willkommen!

Ich habe keine Ahnung, wie es geht, aber ich habe beschlossen, einige Experimente durchzuführen. Im schlimmsten Fall lerne ich ein wenig, im besten Fall kann ich vielleicht etwas beitragen.

Ich versuche derzeit zu verstehen, wie jsdom strukturiert ist und wie Webidl funktioniert (und in jsdom verwendet wird).

Wäre es möglich, von diesem Webidl auf MutationObserver abzuweichen: https://dxr.mozilla.org/mozilla-central/source/dom/webidl/MutationObserver.webidl~~~

Ich verstehe es jetzt etwas besser - die zu verwendenden Schnittstellen sollten die hier beschriebenen sein: https://dom.spec.whatwg.org/#interface -mutationobserver - richtig?

@henrikkorsgaard richtig! Damit es in jsdom funktioniert, müssen Sie mit ein paar Dingen beginnen:

  • Fügen Sie die entsprechende MutationObserver.idl zu https://github.com/tmpvar/jsdom/tree/master/lib/jsdom/living/nodes hinzu. (In Zukunft sollte es wahrscheinlich in einem besseren Ordner abgelegt werden. Aber unser aktuelles Setup macht das etwas nervig, sodass Sie bei den Knoten bleiben können, bis etwas funktioniert.)
  • Fügen Sie diesem Ordner auch eine MutationObserver-impl.js-Datei hinzu. Hier geht die eigentliche Umsetzungsarbeit weiter. Die Spezifikation sagt beispielsweise "Jedes MutationObserver-Objekt hat diese zugehörigen Konzepte". Dies werden wahrscheinlich Instanzvariablen in der Klasse impl (= Implementation) sein.

Sie müssen dann "nur" die Methoden Observe, Disconnect und TakeRecords sowie den Konstruktor in der Impl-Klasse implementieren. Im Allgemeinen sollten Sie versuchen, die Spezifikation so weit wie möglich zu befolgen. ( @Sebmaster , können Sie erklären, wie der Konstruktor ausgeführt wird?)

In diesem Fall erfordern viele Ihrer Änderungen im Gegensatz zu etwas Einfachem wie CharacterData das Ändern anderer jsdom-impl-Dateien. Beispielsweise sagt die Spezifikation "Jeder Knoten hat eine zugeordnete Liste von registrierten Beobachtern". Dies erfordert das Hinzufügen einer Instanzvariable zum Node-Impl.

In der Spezifikation heißt es auch: "Jede Einheit verwandter Browsing-Kontexte ähnlichen Ursprungs hat ein Warteschlangen-Flag für Mutation Observer Compound Microtask, das anfangs nicht gesetzt ist, und eine zugehörige Liste von MutationObserver-Objekten, die anfangs leer ist." Dies ist etwas kniffliger, da die "Einheit verwandter Browsing-Kontexte ähnlicher Herkunft" in jsdom nicht offensichtlich ist. Was ich dafür tun würde, ist, sie einfach auf Window-Objekte zu setzen. Window-Objekte verwenden noch keine IDL, daher fügen Sie einfach eine Eigenschaft mit einem Unterstrich-Präfix in Window.js hinzu. In Zukunft können wir versuchen, sicherzustellen, dass die Dinge über mehrere Fenster hinweg verfolgt werden (dh wir kümmern uns um iframes). Aber im Moment ist das Fenster ein guter Ort, um sie zu platzieren.

Schließlich wird ein Großteil der Implementierung erledigt, indem geeignete Orte gefunden werden, um einen Mutationsdatensatz in die Warteschlange zu stellen. Wenn Sie zu https://dom.spec.whatwg.org/#queue -a-mutation-record gehen und auf "Mutationsdatensatz in die Warteschlange stellen" klicken, können Sie alle Orte in der Spezifikation sehen, die passieren. Dies wird ein bisschen schwierig sein, da jsdom nicht genau die gleichen Hooks hat, die die Spezifikation für den Fall hat, dass etwas passiert. Mit den Hooks _attrModified, _descendantRemoved und _descendantAdded von jsdom sollte es jedoch machbar sein. https://github.com/tmpvar/jsdom/blob/master/lib/jsdom/living/attributes.js enthält auch durchgehend "TODO-Mutationsbeobachter-Zeug".

Ich hoffe, das hilft!

Groß :)

Auch hier bin ich nicht sehr erfahren mit Webidl und größeren Codebasen. Ich bin auch ein bisschen überschwärmt von ein paar Deadlines ;)

Ich werde mein Bestes geben und es in den nächsten Wochen ausprobieren. Ich werde auch versuchen, ein oder zwei Testfälle zu erstellen.

Okay,

Ich bin so weit gekommen, die IDL und Impl zu erstellen und sie in window.js zu benötigen.

Jetzt versuche ich eine Testdatei dafür zu erstellen und kann new MutationObserver() mit dem Window-Präfix aufrufen.

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

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

Übersehe ich hier irgendwelche Tricks oder muss ich den MutationObserver irgendwo als globales Objekt verfügbar machen (aufgerufen, ohne es im Fensterobjekt aufzurufen).

Ich habe die folgende Zeile in Window.js hinzugefügt

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

Tut mir leid wegen der ganzen Nachfrage. Ich versuche nur, einige Einstiegspunkte einzurichten, damit ich mit dem Testen beginnen und der jsdom-Architektur/dem -Muster folgen kann.

Ah, Entschuldigung, den Teil habe ich vergessen. Sie müssen eine Zeile wie https://github.com/tmpvar/jsdom/blob/28f00b30236d540df1777ca6c2c0ee9e5e19fe5b/lib/jsdom/living/index.js#L28 hinzufügen

Ich habe eine Frage zum Einreihen der Mutationsaufgabe . Die Spezifikation scheint einen zentralen Ort anzugeben, an dem mehrere Mikroaufgaben in die Warteschlange gestellt und der Reihe nach ausgeführt werden. Ich kann einen solchen Platz in jsdom finden und ich denke, @domenics Idee, diese Verantwortung in Window.js zu übernehmen, wird funktionieren ( Webkit fügt Mutationsdatensätze zu einem dedizierten Thread/Warteschlange hinzu ). Dies würde jedoch auch eine Form von Warteschlange und vor allem eine Logik erfordern, die das ausführt, was sich in der Warteschlange befindet. Dies führt mich zu zwei Fragen:

Gibt es andere Komponenten in jsdom, die von einer solchen Warteschlange profitieren könnten? Gibt es hier etwas, was ich in Bezug auf Architektur und (zukünftige) Integration beachten sollte?

Wäre es am klügsten, Aufgaben in der Warteschlange basierend auf einem Timer auszuführen (gibt es einen globalen Tick in jsdom?)

Ich konzentriere mich darauf, die sehr grundlegende Implementierung durchzuführen und dann umfangreiche Testfälle zu schreiben, um die zukünftige Implementierung zu leiten.

Eine Mikroaufgabe ist also im Grunde nur process.nextTick(fn) . Dies ist für Mutationsbeobachter aufgrund des zusammengesetzten Mikrotask-Geschäfts und der "Ausführen einer zusammengesetzten Mikrotask-Unteraufgabe" etwas schwieriger. Ich glaube, das kann man meistens einfach ignorieren; jsdom muss sich nicht um die Dinge kümmern, die es einrichtet.

Also: Wenn die Spezifikation sagt "Stellen Sie eine zusammengesetzte Mikroaufgabe in die Warteschlange, um Mutationsbeobachter zu benachrichtigen", tun Sie process.nextTick(notifyMutationObservers) . Dann denke ich, dass Schritt 3 innerhalb von notifyMutationObservers einfach eine Schleife über die Mutationsbeobachter sein kann, die die Unterschritte synchron ausführt.

Besuchen Sie für Tests unbedingt https://github.com/tmpvar/jsdom/blob/master/Contributing.md. Möglicherweise gibt es einige existierende Web-Plattform-Tests, die Sie verwenden können (obwohl diese nicht immer so einfach für eine von Grund auf neu implementierte Implementierung zu erhalten sind). Und alle Tests, die Sie erstellen, sollten sich im Ordner "to-upstream" befinden und diesem Format folgen.

Dies ist vielleicht nicht der richtige Ort, um zu fragen, aber ich habe einige Probleme zu verstehen, wie Webidl-Schemaobjekte (zB /generated/MutationRecord.js) zwischen den Impl-Dateien hin und her übergeben werden (Node-impl -> MutationObserver-Impl) .

Ich bin versucht, die Objekte als reine JSON-Objekte zu konstruieren, die das Schema von MutationRecords spiegeln und dieses einfach von Node-impl an MutationObserver übergeben, wenn eine Mutation auftritt. Ich denke, dies würde den Ehrgeiz brechen, alle Aspekte von MutationObservers wie in der Spezifikation beschrieben zu implementieren.

Ich vermute, dass dies daran liegt, dass ich mit der jsdom-Webidl-Architektur / dem Objektmodell / den Schnittstellen nicht vertraut bin. Ein Beispiel im Code würde mir auch helfen, die Arbeit mit /generated/ -Objekten in den Impl-Dateien zu verstehen.

Wir sollten dies wahrscheinlich irgendwo dokumentieren, aber hier geht es:

Im Allgemeinen werden alle Argumente, die die generierte API durchlaufen, automatisch entpackt/reboxed, sodass Sie sich nie um die generierten Objekte kümmern müssen - nur die Implementierungsobjekte sind wichtig. Außer - nicht wirklich. Dies wäre der Fall, wenn wir bereits _alles_ auf IDL-basiert umgestellt hätten, was nicht der Fall ist. Argumente und Rückgabewerte funktionieren, aber jedes Mal, wenn Sie mit einer Nicht-IDL-Klasse (wie Window) interagieren, müssen Sie das Unboxing/Reboxing durchführen, wenn Sie ihnen selbst Argumente bereitstellen (siehe zum Beispiel https://github. com/tmpvar/jsdom/blob/master/lib/jsdom/living/events/EventTarget-impl.js#L103 , wo wir ein Window auspacken müssen, da Window noch nicht im Leerlauf ist, aber EventTarget (von dem Window erbt von) ist). Du machst dieses manuelle Boxen mit idlUtils.wrapperForImpl / idlUtils.implForWrapper falls nötig.

Im Allgemeinen möchten Sie also ein Impl-Objekt erstellen. Dazu benötigen Sie die generierte Datei und rufen createImpl für die Exporte auf. Es braucht ein Array von Argumenten (für Argumente des öffentlichen Konstruktors) und ein Objekt von privaten Argumenten (beide werden der Impl-Klasse bereitgestellt). Ein Beispiel dafür finden Sie unter https://github.com/tmpvar/jsdom/blob/9dd9069354e36c077032f4cbcb1616a7d9e6f0c4/lib/jsdom/living/nodes/Document-impl.js#L549 .

Mir ist klar, dass dies nicht annähernd tief genug ist, um das Ganze zu verstehen (glaube ich), also wenn Sie etwas spezifischeres haben, würde ich gerne mehr erklären.

Danke @Sebmaster , es hat sehr geholfen.

Ja, es wäre hilfreich, dies mit ein paar Beispielen zu dokumentieren, wie man APIs/Objekte integrieren und verwenden sollte, aber ich denke, ich weiß genug für die nächsten Schritte.

Schnelles Update!
Die W3C-Tests werden fehlschlagen, wenn MutationRecord der Spezifikation folgt (glaube ich). Ich habe einen Kommentar in ihrem Repository gemacht.

Ich werde sie für meinen Zweck lokal aktualisieren, aber ich bin mir nicht sicher, ob ich diese an W3C/jsdom übergeben werde. Das heißt, es sei denn, ich habe die Zeit, diese Aufgabe ebenfalls zu erledigen.

Aber Attributmutationen bestehen jetzt die meisten Tests und ich werde irgendwann am Wochenende oder Anfang nächster Woche eine Pull-Anfrage stellen.

Das ist superspannend! Kannst du etwas genauer erklären, woran es liegt? Ich konnte https://github.com/w3c/web-platform-tests/issues/2482 nicht ganz folgen, also vielleicht nur: Welchen Eigenschaftswert eines MutationRecord erwarten die Tests und was denkst du, erfordert die Spezifikation? ?

Das Problem ist, dass der Test keinen vollständigen Mutationsdatensatz zum Vergleich angibt. Im ersten Testfall ändert der Test also den id-Wert und folglich hat der zurückgegebene Mutationsdatensatz die alte value-Eigenschaft. Die Eigenschaft oldValue ist im erwarteten Objekt nicht definiert und der Test schlägt fehl. Nach meinem Verständnis sollte ein Mutationsdatensatz immer alle Eigenschaften enthalten, auch wenn sie null sind. In diesem Fall sollten sie DOMString null sein. Wenn im Testfall keine Eigenschaften festgelegt sind, vergleicht der Test null (Objekttyp) mit null (Zeichenfolgetyp) und schlägt fehl.

Die Tests sind faul aufgebaut, zB werden undefinierte Felder automatisch auf null (Objekt) gesetzt. Dies führt dazu, dass Tests fehlschlagen, wenn a) der mutationRecord eine Eigenschaft zurückgibt, die nicht im erwarteten Datensatzobjekt festgelegt ist (zB oldValue) und b) wenn die Mutationsdatensatzeigenschaft DOMstring null ist (zB attributteNamespace).

Macht Sinn?

Wenn ich richtig liege, ist es ziemlich einfach zu beheben, erfordert aber, alle Fälle einzeln durchzugehen. Dies kann als Außenstehender sowohl für jsdom- als auch für w3c-Tests etwas schwierig sein :)

Entschuldigung, können Sie vereinfachen? Ich würde eine Antwort in der Form bevorzugen: Die Tests erwarten, dass oldValue null oder undefiniert ist, aber die Spezifikation gibt einen Wert "null" an. Oder ähnliches.

Der oben referenzierte Test erwartet implizit, dass oldValue null ist. Es sollte "n" sein

Alle Testfälle in dieser Datei erwarten, dass attributeNamespace null (Objekt) ist, sie sollten gemäß der Spezifikation DOMString null sein.

Der Typ von attributeNamespace ist DOMString?, also darf er null sein (nicht "null", nur null).

Vielen Dank für die Klärung des oldValue-Falls :).

Ok, danke für die Klarstellung - ich glaube, ich habe wichtige Aspekte der Spezifikation übersehen.

Ich habe einen MutationObserver- Zweig

Wo dokumentiere ich die Tests, die aus bekannten Gründen/Problemen nicht bestehen (fehlende Reichweitenunterstützung #317 usw.)?

Wir fügen sie der Datei index.js für Webplattformtests hinzu, aber auskommentiert mit einem Kommentar zum Grund. Sehen Sie zB, was wir für Vorlage tun .

Ist das noch lange nicht abgeschlossen?

Scheint, dass Mutationsereignisse in 8.5 entfernt wurden . Gibt es eine Möglichkeit, benutzerdefinierte Elemente zu testen (erfordert Mutation Observer), ohne zu 8.5 zurückzukehren und sich auf ein Polyfill zu verlassen? DOMParser wurde in 8.5 nicht implementiert und ich benötige das für unser Shadow DOM Polyfill, daher sind wir im Moment irgendwie blockiert, weil wir Tests in JSDOM ausführen können. Hier wäre jede Anleitung hilfreich. Ich habe im Moment leider nicht die Kapazitäten, einzutauchen und bei der Umsetzung zu helfen.

Mir ist keine Möglichkeit bekannt, sorry :(.

Kann ich helfen, dieses weiterzubewegen? Es sieht so aus, als ob hier mit einigen Arbeiten begonnen wurde: https://github.com/henrikkorsgaard/jsdom/commits/MutationObserver

Aber ich bin mir nicht sicher, was noch erforderlich ist, um diesen Schritt voranzutreiben. Es fühlt sich wie eine große Lücke an, dass dies nicht existiert, da MutationObserver im Grunde überall unterstützt wird: http://caniuse.com/#feat =mutationobserver

Könnten wir das polyfill von webcomponentsjs nutzen? https://github.com/webcomponents/webcomponentsjs

Ein Pull-Request ist immer willkommen. Die Arbeit von @henrikkorsgaard ist definitiv ein guter Anfang. Ich glaube aber nicht, dass das Polyfill sinnvoll ist.

Soweit ich das beurteilen kann, wurden Mutationsereignisse aus Leistungsgründen entfernt; aus dem gleichen Grund wurde Mutation Observer von Browsern spezifiziert und implementiert. Browser behielten diese Funktionalität jedoch zumindest bis zur Implementierung der MO-Unterstützung bei. Der pragmatische Ansatz hier könnte darin bestehen, die Unterstützung dafür erneut hinzuzufügen, damit MOs zumindest bis zur Implementierung mehrfach gefüllt werden können. Mir ist klar, dass es nicht ideal für die Wartung von JSDOM ist und die Codebasis seitdem möglicherweise auseinandergegangen ist. Das heißt, ich denke, es ist eine Überlegung wert. Dies hinterlässt eine große Lücke, insbesondere in diesem Stadium, in dem Webkomponenten eine praktikable Lösung für Produktions-Apps sind und eine Mehrfachfüllung von MO erfordern. Es wäre schön, Tests in JSDOM ausführen zu können.

wenn es hilft; I polygefülltes MO on-top jsdom; hier und hier . Ich würde nicht sagen, dass es eine gute Lösung ist, aber

Vor 4 Jahren und noch in Arbeit? dieses Problem wird seit Version 9.0.0 im Changelog erwähnt. Habt ihr Neuigkeiten dazu?

@MeirionHughes die Datei hat reibungslos funktioniert, danke!

Irgendein Update zu diesem Thema?

Nur eine Erweiterung des Links von
Versuchen Sie, die Datei zu platzieren, die @MeirionHughes vorgeschlagen hat.

@mtrabelsi @MeirionHughes Hallo, ich habe es nicht geschafft, es in Jest zum https://stackoverflow.com/questions/43190171/jsdom-cannot-read-property-location-of-null
Könnten Sie erklären, wie Sie es geschafft haben, MO in JSDOM zu verwenden?

BEARBEITEN:
Ok, anscheinend schaffe ich es, dass es funktioniert https://gist.github.com/romuleald/1b9272fce11d344e257d0bdfd3a984b0

@romuleald es gibt einen Fehler in Ihrem Kern, Sie setzen this.expando und this.counter im Konstruktor, greifen dann aber statisch darauf zu. Dies führt zu schwerwiegenden Problemen in _findMutations . Sie werden feststellen, dass in den Typoskriptdateien, die Sie konvertieren wollten, beide Eigenschaften statisch waren (mit ES6 nicht möglich). Ich schlage vor, diese Zeilen unter der Util-Klasse hinzuzufügen:

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

Und dann können Sie den Konstruktor vollständig loswerden (die Klasse wird sowieso nie instanziiert).

Ich habe peinlich lange gebraucht, um dies zu erkennen, da ich in den letzten anderthalb Tagen ein Problem behoben habe, das dadurch verursacht wurde.

Außerdem unterstützt die Quelle, auf der das Shim basiert, keine Änderungen an der .data Eigenschaft von CharacterData Knoten. Sie können mein verlinktes Problem über diesem Kommentar sehen, um eine einfache Lösung zu finden.

Ich konnte dies vorerst mit https://github.com/megawac/MutationObserver.js polyfill testen.

Ich benutze AVA. Und 'm' ist ein my-Modul, das MutationObserver enthält.

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

Da es sich um Polyfill handelt, gibt es einige Dinge, die sich von der Standardschnittstelle unterscheiden. Aber dieses Problem scheint keinen Fortschritt zu haben, also bitte als eine Option bezeichnen.

Eine andere Möglichkeit, es als Polyfill mit jsdom und jest zu verwenden, besteht darin, das Shim manuell als Skript zu laden.

npm install mutationobserver-shim

und zum Beispiel in der beforeAll-Funktion:

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

Dieser "Hack" funktioniert bei mir, vielleicht auch bei anderen ;)

IIRC Ich fand es gut, das Shim als Teil von setupFiles for Jest zu laden (ich habe eine optimierte Version des Codes von pal-nodejs verwendet, bin mir nicht sicher, worauf das obige npm-Paket basiert). Ich könnte genauere Informationen geben, sobald ich an meinem anderen Laptop bin, wenn jemand interessiert ist.

Das ist also seit fast 5 Jahren offen, irgendwelche Statusaktualisierungen?

@mendrik spöttisch, es hat bei mir funktioniert https://github.com/benitogf/corsarial/blob/master/test/specs/utils.js#L29 und wie oben erwähnt gibt es auch die Polyfill-Lösung, guten Tag :)

@benitogf Ich habe das versucht, aber irgendwie haben die Aufzeichnungen für mich nicht ausgelöst .
@treshugart wie kann ich das verwenden? :)

@mendrik in Ihren Tests, importieren Sie dies https://github.com/aurelia/pal-nodejs/blob/master/src/polyfills/mutation-observer.ts
und setze es auf die globale Variable. das ist es!

@mendrik, wenn Sie sich von JSDOM abmelden möchten https://github.com/skatejs/skatejs/tree/master/packages/ssr#usage. Wenn nicht, können Sie diese Datei direkt importieren und die Exporte (https://github.com/skatejs/skatejs/blob/master/packages/ssr/register/MutationObserver.js#L109) zu Ihrem globalen hinzufügen.

Dies war genug, um dieses Problem für mich zu beheben, als ich versuchte, eine Komponente zu testen, die den Reakquill erweitert:

import 'mutationobserver-shim';

document.getSelection = () => null;
War diese Seite hilfreich?
0 / 5 - 0 Bewertungen