动态更改innerHTML集

时间:2016-09-18 16:36:34

标签: javascript hook metaprogramming innerhtml content-security-policy

我需要使用innerHTML动态更改每个节点上设置的值。

我找到的最接近的解决方案是:

...   
Object.defineProperty(Element.prototype, 'innerHTML', {
    set: function () {
        // get value (ok)
        var value = arguments[0];
        // change it (ok)
        var new_value = my_function(value);
        // set it (problem)
        this.innerHTML = new_value;              // LOOP
    }
}
...

但显然它是一个无限循环。 有没有办法调用原始的innerHTML集?

我也尝试Proxy way,但我无法使其发挥作用。

更多详情:

我正在开发一个实验项目,该项目使用反向代理生成CSP策略并将其添加到网站,所以:

  • 网站所有者将了解这些" 覆盖"
  • 我需要处理任何可能触发的js代码客户端 政策
  • 我需要在内容安全策略引擎评估之前修改它! (这是需要这个问题的主要问题" 非常好"解决方案)

2 个答案:

答案 0 :(得分:5)

强制警告: 在任何生产级代码中,覆盖Element.prototype的任何属性的setter和getter都是坏主意。如果您使用任何依赖innerHTML的库来工作,或者项目中有其他开发人员不知道这些更改,那么事情可能会变得奇怪。您还将失去在应用程序的其他部分使用innerHTML“正常”的能力。

那就是说,由于你没有提供任何关于为什么你想要这样做的信息,我只是假设你知道这些警告,你仍然想要覆盖浏览器自身的功能,可能用于开发目的。

解决方案:您正在覆盖浏览器Element.prototype.innerHTML的原生设置器,但您还需要原始设置器才能实现目标。这可以使用Object.getOwnPropertyDescriptor来完成,http://thbecker.net/articles/auto_and_decltype/section_07.htmlObject.defineProperty的“对应”。

(function() {
  
  //Store the original "hidden" getter and setter functions from Element.prototype
  //using Object.getOwnPropertyDescriptor
  var originalSet = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML').set;
  
  Object.defineProperty(Element.prototype, 'innerHTML', {
    set: function (value) {
        // change it (ok)
        var new_value = my_function(value);
        
        //Call the original setter
        return originalSet.call(this, new_value);
    }
  });
                        
  function my_function(value) {
    //Do whatever you want here
    return value + ' World!';
  }
                        
})();


//Test
document.getElementById('test').innerHTML = 'Hello';
<div id="test"></div>

答案 1 :(得分:1)

使用任意HTML字符串没有直接的方法,没有。

问题是您使用任意HTML字符串。目前在元素上设置任意HTML的唯一方法是使用innerHTML。您必须找到一种在元素上设置任意HTML的不同方法,例如将HTML附加到临时节点并抓取其内容:

    // Attempt: build a temporary element, append the HTML to it,
    // then grab the contents
    var div = document.createElement( 'div' );
    div.innerHTML = new_value;
    var elements = div.childNodes;

    for( var i = 0; i < elements.length; i++ ) {
        this.appendChild( elements[ i ] );
    }

然而,这会遇到同样的问题,div.innerHTML = new_value;会永远递归,因为您正在修改任意HTML设置的唯一入口点。

我能想到的唯一解决方案是实现一个真正的,完整的HTML解析器,它可以使用任意HTML字符串并将其转换为带有document.createElement('p')等内容的DOM节点,然后您可以附加到当前元素appendChild。然而,这将是一个可怕的,过度设计的解决方案。

除此之外,你不应该这样做。这段代码将毁掉某人的一天。它违反了我们在前端开发中所欣赏的几个原则:

  • 不要修改默认的对象原型。其他任何碰巧运行此代码,甚至在同一页面上运行代码的人(如第三方跟踪库)都会将地毯拉出来从它们下面。追踪出现问题几乎是不可能的 - 没有人会想到innerHTML劫持。
  • Setter通常用于计算属性或具有副作用的属性。您正在劫持某个值并进行更改。你面临着一个消毒问题 - 如果有人第二次设置了一个已被劫持的值,会发生什么?
  • 不要编写棘手的代码。这段代码毫无疑问是一个棘手的问题&#34;溶液

最干净的解决方案可能就是在您需要的任何地方使用my_function。它的可读性,简短,简单,香草编程:

someElement.innerHTML = my_function(value);

您也可以定义一个方法(我会对属性进行方法,因为它会破坏用户的值),例如:

Element.prototype.setUpdatedHTML = function(html) {
    this.innerHTML = my_function(html);
}

这样当开发人员遇到setUpdatedHTML时,它显然是非标准的,他们可以更容易地寻找劫持Element原型的人。