Change innerHTML set on the fly

[亡魂溺海] 提交于 2019-12-30 13:39:06

问题


I need to change on the fly the value set on every node using the innerHTML.

The closest solution I found is:

...   
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
    }
}
...

But obviously it's an infinite loop. Is there a way to call the original innerHTML set?

I also try the Proxy way but i could not make it work.

More details:

I am working on an experimental project which uses a reverse proxy to generate and add CSP policies to a website, so:

  • the owner of the website will be aware of these "overwrites"
  • i needed to handle any js code client generated which could trigger the policy
  • i need to modify it before the Content Security Policy engine evalution! (this is the main problem which requires this "non so good" solution)

回答1:


Obligatory warning: Overriding the setter and getter for any property of Element.prototype is bound to be bad idea in any production-level code. If you use any libraries that rely on innerHTML to work as it should or if there are other developers in the project that don't know of these changes, things might get weird. You will also loose the ability to use innerHTML "normally" in other parts of the app.

That said, as you haven't provided any information about why you would want to do this, I'm just going to assume that you know about the caveats and you still want to override the browser's own functionality, perhaps for development purposes.


Solution: You are overriding the browser's native setter for the Element.prototype.innerHTML, but you also need the original setter to achieve your goal. This can be done using Object.getOwnPropertyDescriptor, which is sort of the "counterpart" of Object.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>



回答2:


There's no straightforward way to do this with an arbitrary HTML string, no.

A problem is you're using an arbitrary HTML string. The only way currently to set arbitrary HTML on an element is with innerHTML. You'd have to find a different way to set arbitrary HTML on an element, for example appending the HTML to a temporary node and grabbing its contents:

    // 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 ] );
    }

However this suffers the same problem, div.innerHTML = new_value; will recurse forever because you're modifying the only entry point to arbitrary HTML setting.

The only solution I can think of is to implement a true, complete HTML parser that can take an arbitrary HTML string and turn it into DOM nodes with things like document.createElement('p') etc, which you could then append to your current element with appendChild. However that would be a terrible, overengineered solution.

All that aside, you shouldn't do this. This code will ruin someone's day. It violates several principles we've come to appreciate in front end development:

  • Don't modify default Object prototypes. Anyone else who happens to run this code, or even run code on the same page (like third party tracking libraries) will have the rug pulled out from under them. Tracing what is going wrong would be nearly impossible - no one would think to look for innerHTML hijacking.
  • Setters are generally for computed properties or properties with side effects. You're hijacking a value and changing it. You face a sanitization problem - what happens if someone sets a value a second time that was already hijacked?
  • Don't write tricky code. This code is unquestionably a "tricky" solution.

The cleanest solution is probably just using my_function wherever you need to. It's readable, short, simple, vanilla programming:

someElement.innerHTML = my_function(value);

You could alternatively define a method (I would do method over property since it clobbers the value from the user), like:

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

This way when a developer comes across setUpdatedHTML it will be obviously non-standard, and they can go looking for someone hijacking the Element prototype more easily.



来源:https://stackoverflow.com/questions/39560085/change-innerhtml-set-on-the-fly

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!