Jsdom: 添加对 MutationObserver 的支持

创建于 2013-06-08  ·  53评论  ·  资料来源: jsdom/jsdom

最有用的评论

所有53条评论

我也需要这个。 如果有人能指出我正确的方向,我愿意为此处理拉取请求。

@SegFaultx64 schettino72 在关于这个问题的第一篇文章中提供的 URL 是“正确的方向”。

公平地说,我什至不确定我们如何在这么大的项目中注册观察者。 有没有地方可以存放这样的东西?

@SegFaultx64我不清楚最好的方法是什么,因为我没有看过 jsdom 的那部分。 当我修复 NS 方法时(见 #727;名义上,它是一个错误修复,但它需要对 jsdom 的内脏进行实质性更改)我寻找类比结构并决定在哪里添加新类/数据/等等。 我几乎按照我认为最好的方式进行,Domenic 发表了一些意见,我调整了需要调整的部分,仅此而已。

@lddubeau好的,感谢您的指点。 我实际上为 MutationObserver https://github.com/megawac/MutationObserver.js/blob/master/MutationObserver.js找到了一个漂亮的垫片

我唯一的建议是仔细阅读https://dom.spec.whatwg.org/并查看突变记录排队位置的详细信息。 在 jsdom 中找到合适的对应物可能会很棘手,因为我们仍在过渡到新的 DOM 标准,但至少规范会为您提供需要实施的指导。

有任何更新吗?

欢迎@kresli拉取请求!

我不知道该怎么做,但我决定做一些实验。 最坏的情况我学到了一点,最好的情况我可以做出贡献。

我目前正在尝试了解 jsdom 的结构以及 webidl 的工作方式(并在 jsdom 中使用)

有没有可能在MutationObserver上脱离这个webidl: https ://dxr.mozilla.org/mozilla-central/source/dom/webidl/MutationObserver.webidl~~~

我现在更好理解了 - 要使用的接口应该是这里描述的那些: https: //dom.spec.whatwg.org/#interface -mutationobserver - 对吗?

@henrikkorsgaard正确! 为了让它在 jsdom 中工作,你需要从几件事开始:

  • 将适当的 MutationObserver.idl 添加到https://github.com/tmpvar/jsdom/tree/master/lib/jsdom/living/nodes。 (将来它可能应该放在比那个更好的文件夹中。但我们当前的设置让这有点烦人,所以你可以坚持使用节点,直到你开始工作。)
  • 还将 MutationObserver-impl.js 文件添加到该文件夹​​中。 这是实际实施工作进行的地方。 例如,规范说“每个 MutationObserver 对象都有这些相关的概念。” 这些可能是 impl (= implementation) 类中的实例变量。

然后,您“只需要”在 impl 类中实现observe、disconnect 和 takeRecords 方法以及构造函数。 一般来说,您应该尽可能地遵循规范。 ( @Sebmaster ,你能解释一下如何做构造函数吗?)

在这种情况下,与 CharacterData 之类的简单内容不同,您的许多更改都需要修改其他 jsdom impl 文件。 例如,规范说“每个节点都有一个关联的注册观察者列表”。 这将需要向 Node impl 添加一个实例变量。

该规范还说“相关的同源浏览上下文的每个单元都有一个变异观察者复合微任务排队标志,它最初是未设置的,以及一个相关联的 MutationObserver 对象列表,它最初是空的。” 这有点棘手,因为“相关的同源浏览上下文单元”在 jsdom 中并不明显。 我会为此做的是首先将它们放在 Window 对象上。 Window 对象尚未使用 IDL,因此您只需在 Window.js 中添加一个下划线前缀的属性。 将来,我们可以尝试确保跨多个窗口跟踪事物(即处理 iframe)。 但是现在窗口是放置它们的好地方。

最后,将通过找到合适的位置来“排队突变记录”来处理很多实现。 如果你去https://dom.spec.whatwg.org/#queue -a-mutation-record 并点击“queue a mutation record”,你可以看到规范中发生的所有地方。 这将有点棘手,因为 jsdom 没有规范在事情发生时所做的所有完全相同的钩子。 但它应该是可行的,使用 jsdom 的 _attrModified、_descendantRemoved 和 _descendantAdded 钩子。 https://github.com/tmpvar/jsdom/blob/master/lib/jsdom/living/attributes.js也有“TODO 突变观察者的东西”。

希望有帮助!

伟大的 :)

同样,我对 webidl 和更大的代码库不是很有经验。 我也有点忙于几个最后期限;)

我会尽我所能,并在接下来的几周内试一试。 我也会尝试制作一两个测试用例。

好的,

到目前为止,我已经创建了 idl 和 Impl 并将其添加到 window.js 中。

现在我正在尝试为它创建一个测试文件,我可以使用窗口前缀调用 new MutationObserver() 。

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

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

我是在这里遗漏了一些技巧,还是在某处我需要将 MutationObserver 作为全局对象公开(在不调用 window 对象的情况下调用它)。

我在 Window.js 中添加了以下行

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

很抱歉所有这些问题。 我只是想建立一些入口点,这样我就可以开始测试并遵循 jsdom 架构/模式。

啊,对不起,我忘了那部分。 您需要添加像https://github.com/tmpvar/jsdom/blob/28f00b30236d540df1777ca6c2c0ee9e5e19fe5b/lib/jsdom/living/index.js#L28这样的一行

我有一个与排队突变任务相关的问题。 该规范似乎表明多个微任务按顺序排队和执行的中心位置。 我可以在 jsdom 中找到这样的地方,我认为@domenic在 Window.js 中承担这个责任的想法会起作用( webkit 将突变记录添加到专用线程/队列)。 但这也需要某种形式的排队,更重要的是一些执行队列中内容的逻辑。 这让我想到两个问题:

jsdom 中是否还有其他组件可以从这样的队列中受益? 在架构和(未来)集成方面,我应该考虑什么?

基于计时器(jsdom 中是否有全局刻度?)或仅基于 promise/done 回调结构执行队列中的任务是最聪明的吗?

我专注于完成非常基本的实现,然后编写大量的测试用例来指导未来的实现。

所以,一个微任务基本上就是process.nextTick(fn) 。 由于复合微任务业务和“执行复合微任务子任务”,这对于变异观察者来说有点棘手。 我_认为_你基本上可以忽略它; jsdom 不需要担心它正在设置的东西。

所以:当规范说“排队一个复合微任务以通知突变观察者”时,你做process.nextTick(notifyMutationObservers) 。 然后,在notifyMutationObservers ,我认为第 3 步可以只是对突变观察者的循环,它同步执行子步骤。

对于测试,请务必查看https://github.com/tmpvar/jsdom/blob/master/Contributing.md。 可能有一些现有的 web-platform-tests 您可以使用(尽管从头开始实现这些测试并不总是那么容易)。 并且您编写的任何测试都应该在 to-upstream 文件夹中,遵循该格式。

这可能不是问的正确地方,但我在理解如何在 impl 文件(Node-impl -> MutationObserver-Impl)之间来回传递 webidl 方案对象(例如 /generated/MutationRecord.js)时遇到了一些麻烦.

我很想将对象构造为反映 MutationRecords 模式的纯 JSON 对象,并在发生突变时将其从 Node-impl 传递到 MutationObserver。 我想这会打破实现规范中描述的 MutationObservers 的所有方面的雄心。

我怀疑这是因为我不熟悉 jsdom webidl 架构/对象模型/接口。 代码中的一个示例也将帮助我理解在 impl 文件中使用 /generated/ 对象。

我们可能应该在某处记录下来,但这里是:

通常,通过生成的 API 的任何参数都将自动取消装箱/重新装箱,因此您无需关心生成的对象 - 只有实现对象很重要。 除了 - 不是真的。 如果我们已经将_一切_都切换到基于 IDL 的,情况就会如此,但事实并非如此。 参数和返回值确实有效,但是每次与非 IDL 类(如 Window)交互时,您都必须在自己提供任何参数时进行拆箱/重新装箱(例如参见https://github.com)。 com/tmpvar/jsdom/blob/master/lib/jsdom/living/events/EventTarget-impl.js#L103 ,我们必须在那里解开一个 Window,因为 Window 还没有被 idl'd,但是 EventTarget(其中 Window 继承了从) 是)。 如有必要,您可以使用idlUtils.wrapperForImpl / idlUtils.implForWrapper手动拳击。

所以一般来说,你要构造一个 Impl 对象。 为此,您需要生成的文件并对其导出调用createImpl 。 它将接受一个 args 数组(用于公共构造函数参数)和一个私有 args 对象(这两个都提供给 impl 类)。 有关示例,请参见https://github.com/tmpvar/jsdom/blob/9dd9069354e36c077032f4cbcb1616a7d9e6f0c4/lib/jsdom/living/nodes/Document-impl.js#L549

我意识到这还不够深入,无法掌握整个事情(我认为),所以如果你有更具体的东西,我很乐意解释更多。

谢谢@Sebmaster ,它帮助了很多。

是的,用一些关于如何集成和使用 API/对象的例子来记录这一点会很有帮助,但我想我对接下来的几个步骤已经足够了解了。

快速更新!
如果 MutationRecord 遵循规范(我认为), W3C 测试将失败。 我在他们的存储库中发表了评论。

我会出于我的目的在本地更新它们,但我不确定我是否会将它们提交给 W3C/jsdom。 也就是说,除非我也有时间完成那个任务。

但是属性突变现在通过了大部分测试,我将在周末或下周初的某个时间提出拉取请求。

真是太刺激了! 你能更详细地解释一下是什么问题吗? 我不太能够遵循https://github.com/w3c/web-platform-tests/issues/2482所以也许只是:测试期望 MutationRecord 的属性值是什么,以及您认为规范需要什么?

问题是测试没有指定完整的 mutationRecord 来比较。 因此,在第一个测试用例中,测试将更改 id 值,因此返回的突变记录将具有旧值属性。 oldValue 属性未在预期对象中定义,测试将失败。 据我了解,突变记录应始终包含所有属性,即使它们为空。 在这种情况下,它们应该是 DOMString null。 当测试用例中未设置属性时,测试最终将比较 null (typeof object) 和 null (typeof string) 并失败。

测试是惰性构建的,例如未定义的字段会自动设置为空(对象)。 这将导致测试在以下情况下失败:a)mutationRecord 返回未在预期记录对象中设置的属性(例如 oldValue)和 b)当变异记录属性为 DOMstring null(例如 attributteNamespace)时。

说得通?

如果我是对的,这很容易解决,但需要一一处理所有案例。 作为 jsdom 和 w3c 测试的局外人,这可能有点困难:)

对不起,你能简化吗? 我更喜欢以下形式的答案:测试期望 oldValue 为空或未定义,但规范给出了一个值“空”。 或者类似的。

上面引用的测试隐含地期望 oldValue 为空。 应该是“n”

该文件中的所有测试用例都期望 attributeNamespace 为 null(对象),根据规范它们应该是 DOMString null。

attributeNamespace 的类型是 DOMString?,所以它允许为空(不是“空”,只是空)。

感谢您澄清 oldValue 案例:)。

好的,感谢您的澄清 - 我想我错过了规范的重要方面。

在我的 fork 中添加了一个分支

我在哪里记录由于已知原因/问题(缺少 Range 支持 #317 等)而未通过的测试?

我们所做的是将它们添加到用于 Web 平台测试的 index.js 文件中,但已注释掉,并附有关于原因的注释。 参见例如我们为模板所做的工作

这离最终确定还远吗?

似乎在 8.5 中删除了 Mutation Events 。 有没有办法测试自定义元素(需要 Mutation Observers)而不回到 8.5 并依赖 polyfill? DOMParser没有在 8.5 中实现,我需要在我们的 Shadow DOM polyfill 中实现,所以我们目前无法在 JSDOM 中运行测试。 任何指导在这里都会有所帮助。 不幸的是,我目前没有能力尝试深入研究并帮助实现这一点。

我不知道这样做的任何方式,对不起:(。

我可以帮忙搬这个吗? 看起来这里开始了一些工作: https :

但我不确定推动这一进程还需要什么。 鉴于基本上到处都支持MutationObserver ,这感觉就像一个很大的差距: http :

我们可以利用 webcomponentsjs polyfill 吗? https://github.com/webcomponents/webcomponentsjs

拉取请求总是受欢迎的。 @henrikkorsgaard的工作绝对是一个很好的起点。 我认为 polyfill 没有意义。

据我所知,出于性能考虑,Mutation Events 已被删除; 与 Mutation Observer 被浏览器规范和实现的原因相同。 但是,浏览器至少会在实现 MO 支持之前保留该功能。 这里的务实方法可能是重新添加对此的支持,至少,在实现之前,可以对 MO 进行填充。 我意识到这对于 JSDOM 的维护来说并不理想,而且代码库从那时起可能已经发生了分歧。 话虽如此,我认为值得考虑。 这留下了巨大的差距,特别是在这个阶段,Web 组件是生产应用程序的可行解决方案,并且它们需要 MO 被 polyfill。 能够在 JSDOM 中运行测试会很好。

如果有帮助; 我在顶部 jsdom 上填充了 MO; 这里这里。 我不会说它是一个好的解决方案,但它也可以工作并且没有计时器。

4 年前,仍在进行中? 自版本9.0.0以来的日志中提到了此问题。 大家有这方面的消息吗?

@MeirionHughes文件运行顺利,谢谢!

关于这个问题的任何更新?

只是@domenic链接的扩展:
尝试放置@MeirionHughes建议的文件,

@mtrabelsi @MeirionHughes你好,我没有设法让它在 Jest 中工作,我收到了这个错误https://stackoverflow.com/questions/43190171/jsdom-cannot-read-property-location-of-null
你能解释一下你是如何在 JSDOM 中使用 MO 的吗?

编辑:
好的,看来我设法让它工作了https://gist.github.com/romuleald/1b9272fce11d344e257d0bdfd3a984b0

@romuleald您的要点中有错误,您在构造函数中设置了this.expandothis.counter ,然后静态访问它。 这将导致_findMutations出现严重问题。 您会注意到,在您尝试转换的打字稿文件中,两个属性都是静态的(ES6 不可能)。 我建议在 Util 类下面添加这些行:

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

然后你可以完全摆脱构造函数(无论如何都不会实例化该类)。

我花了很长时间才发现这一点,因为过去一天半我一直在调试由它引起的问题。

此外,您基于 shim 的源不支持对CharacterData节点的.data属性的更改。 您可以在此评论上方看到我的链接问题,以进行简单的修复。

我暂时可以使用https://github.com/megawac/MutationObserver.js polyfill 对此进行测试。

我用AVA。 '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');
});

因为是Polyfill,所以和标准接口有些不同。 但是这个问题似乎没有任何进展,所以请作为一种选择。

另一种将其用作 jsdom 和 jest 的 polyfill 的方法是手动加载 shim 作为脚本。

npm install mutationobserver-shim

例如在 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);

这个“hack”对我有用,也许对其他人也有用;)

IIRC 我发现它可以很好地加载 shim(我使用了来自 pal-nodejs 的代码的调整版本,不确定上面的 npm 包基于什么)作为 Jest 的 setupFiles 的一部分。 如果有人感兴趣,我可以在我的另一台笔记本电脑上提供更具体的信息。

所以这已经开放了将近 5 年了,有任何状态更新吗?

@mendrik嘲笑它对我你有美好的一天:)

我们在这里有一个部分实现: https :

@benitogf我试过了,但不知何故,记录并没有为我触发:( 它只是没有抛出任何错误。
@treshugart我该如何使用它? :)

@mendrik在你的测试中,导入这个https://github.com/aurelia/pal-nodejs/blob/master/src/polyfills/mutation-observer.ts
并将其设置为全局变量。 就是这样!

@mendrik如果您愿意选择退出 JSDOM https://github.com/skatejs/skatejs/tree/master/packages/ssr#usage。 如果没有,那么您可以直接导入该文件并将导出(https://github.com/skatejs/skatejs/blob/master/packages/ssr/register/MutationObserver.js#L109)添加到您的全局。

这足以为我尝试测试扩展 react-quill 的组件解决这个问题:

import 'mutationobserver-shim';

document.getSelection = () => null;
此页面是否有帮助?
0 / 5 - 0 等级