2

简而言之,我需要知道页面上的某些元素是否在页面上,因为某个脚本通过父元素上的 InnerHtml 属性插入了它们,或者它们是否是下载的原始 HTML 文档的一部分。这两种可能性在这个(荒谬的)应用程序中意味着非常不同的东西。

实际用例:

第 3 方脚本通过设置元素的 InnerHtml 属性来更新页面上的随机节点元素。我可以完全控制浏览器(WPF / GeckoFx / XulRunner),并且可以随意注入和修改(新)JS,但没有洞察力或修改严重混淆的第 3 方脚本的能力。

获取我需要的数据的唯一方法是在页面加载后确定屏幕上的某些元素(如果存在)是否由第三方脚本(innerHtml)加载,或者它们之前是否是原始 Html 文档的一部分第 3 方脚本运行。


简单地将页面的原始 html 内容源与其最终状态进行比较是很困难的,因为原始页面上有很多内联脚本。

有没有人有任何想法?

4

3 回答 3

1

如果脚本依赖 jQuery,这很容易,您可以使用$.holdReady()延迟触发 ready 事件,直到您的观察者正在侦听。

HTML:

<h1>Sample title</h1>
<p>Sample paragraph</p>

JS:

$(function() {
    $('body').append("<p>Foo</p>").append("<p>Bar</p>");
});

(function() {
    $.holdReady(true);
    var observer = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            console.log(mutation.type);
        });
    });
    var target = document.querySelector('html');
    var config = {
        childList: true,
        attributes: true,
        subtree: true,
        characterData: true
    };
    setTimeout(function() {
        observer.observe(target, config);
        $.holdReady(false);
    }, 1);
}());

如上所示,无论其他脚本在何处绑定到 ready 事件,这都将起作用。


然而,不用说,假设其他脚本依赖于 jQuery 远非我们总能指望的事​​情。如果我们正在寻找一种不管它如何都有效的解决方案,我们将不得不变得棘手。

HTML 和以前一样。正文末尾的
js :

$(function() {
    $('body').append("<p>Foo</p>").append("<p>Bar</p>");
});

(function() {
    var observer = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            console.log(mutation.type);
        });
    });
    var target = document.querySelector('html');
    var config = {
        childList: true,
        attributes: true,
        subtree: true,
        characterData: true
    };
    observer.observe(target, config);
}());

要获得预期的功能,请确保此脚本块是正文底部的绝对最后一个脚本块。这确保了所有静态 DOM 都已经存在,并且我们可以在正确的时间开始监听。
我们假设所有其他脚本在 load 或 ready 事件触发后开始修改 DOM。如果不是这种情况,请相应地移动脚本块,以便此脚本在 DOM 解析结束时触发,而其他脚本在此之后触发。

我尚未对此进行彻底测试,但这应该可以帮助您入门。

于 2015-01-09T00:25:08.353 回答
1

突变观察者应该(主要)基于以下假设工作​​:

  • HTML 解析器仅沿树的最底部分支附加节点。即它们都应该按树顺序到达。任何不是脚本生成的
  • 在突变观察者批次之间跟踪最后插入的节点很简单
  • .innerHTML 不仅添加节点,还删除当前子节点,尤其是经常出现的空白文本节点或注释,html 解析器 otoh 不应生成任何删除
  • dom 就绪事件之后的任何突变显然都是由 javascript 执行的
  • 如果有疑问,任何子树都可以通过比较最近的唯一可识别祖先节点的内容与从 html 源生成的文档对象而不执行脚本来双重检查(XMLHttpRequest 可以以文档形式而不是文本返回内容)
  • 在加载第 3 方脚本之前,您还可以忽略任何受信任脚本所做的任何修改,这至少可以避免一些误报。在那之后,尽管您显然无法区分哪个脚本负责修改。

因此,应该可以为突变事件构建一个分类器,以准确区分脚本生成的节点和解析器生成的节点。会有一些边缘情况,你不能确定和改进它的方法,但在不知道更多细节的情况下,我认为这可能已经足够好了。

由于您可以完全控制您的浏览器,您可以通过特权代码和/或框架脚本中的DOMWindowCreated事件尽早执行您自己的脚本。

于 2015-01-09T21:19:53.780 回答
1

不幸的是,使用突变观察者的建议不适用于这种情况。突变观察者不知道将 dom 节点添加到页面的原因他们只报告有一个。这意味着无法确定是否添加了 DOM 的一部分是因为页面仍在加载,还是因为脚本已触发并动态添加内容。

然而

本文解释了如何覆盖 dom 中每个元素的 InnerHTML getter/setter 属性:http : //msdn.microsoft.com/en-us/library/dd229916 (v=vs.85).aspx 由于 InnerHTML总是由 javascript 调用,我知道是否使用此函数调用加载了 dom 的某个部分变得微不足道。

虽然这对于大多数应用程序来说几乎肯定是矫枉过正并且不是一个好主意,但对于像这种奇怪的情况以及 js 框架的构建,它可能很有意义。

如果该文章在某个时候下线,我的初始代码类似于以下内容:

var elem = isInIE() ? HTMLElement : Element;    // IE and FF have different inheritance models, behind the scenes.
var proxiedInnerHTML = Object.getOwnPropertyDescriptor(elem.prototype, "innerHTML");

Object.defineProperty(elem.prototype, "innerHTML", {
    set: function ( htmlContent )
    {
        // custom code goes here

        proxiedInnerHTML.set.call(this, htmlContent);
    }); 

在较旧的浏览器中应该警告一个人,或者如果您使用了错误的元素(HTMLElement vs Element),调用将在innerHTML调用上失败,而不是在属性定义上。

在浏览器中处理原型:

我在 FF 和 IE 中测试了这个块,但在 Chrome 中没有。更重要的是,我发现帖子指出 w3c 规范中没有保证指定浏览器如何处理其元素类型的继承,因此不能保证 HtmlDivElement 将来或过去会调用 InnerHTML 的 HtmlElement 或 Element 基方法任何给定浏览器的版本。

也就是说,使用所有保留的 html 关键字创建一个网页非常简单,并测试这种技术是否适用于它们。对于 IE 和 FF,截至 2015 年 1 月,此技术适用于所有领域。

旧浏览器支持:

虽然我没有使用它,但在较旧的浏览器中,您可以使用

document.__defineGetter__("test", /* getter function */ );
document.__defineSetter__("test", /* setter function */ );
document.__lookupGetter__("test");
document.__lookupSetter__("test");

感谢 RobG 让我走上这条路

于 2015-01-12T17:05:22.383 回答