(function() {
    // inspired by Eli Grey's shim @ http://eligrey.com/blog/post/textcontent-in-ie8
    // heavily modified to better match the spec:
    // http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Node3-textContent
    if (Object.defineProperty && Object.getOwnPropertyDescriptor &&
        Object.getOwnPropertyDescriptor(Element.prototype, 'textContent') &&
        !Object.getOwnPropertyDescriptor(Element.prototype, 'textContent').get) {

        // NOTE: Neither of these "drop-in" patterns would work:
        // Object.defineProperty(..., ..., descriptor); // nope!
        // Object.defineProperty(..., ..., { get: descriptor.get, set: descriptor.set }); // nope!
        // So must use function-wrapped descriptor.fn.call pattern.

        // "Normal" Elements
        // NOTE: textContent is different from innerText, so its use would be incorrect:
        //var innerText = Object.getOwnPropertyDescriptor(Element.prototype, "innerText"); // nope!
        var getTextContent = function(x) {
            if (this.tagName === 'BR') { return '\n'; }
            var c = this.firstChild;
            var tc=[];
            // append the textContent of its children
            while(!!c) {
                if (c.nodeType !== 8 && c.nodeType !== 7) { // skip comments
                    tc.push(c.textContent);
                }
                c = c.nextSibling;
            }
            // a <br> Element should show as a newline
            c = null;
            tc = tc.join('');
            return tc;
        };
        var setTextContent = function(x) {
            var c;
            while(!!(c=this.lastChild)) {
                this.removeChild(c);
            }
            if (x!==null) {
                c=document.createTextNode(x);
                this.appendChild(c);
            }
        };
        Object.defineProperty(Element.prototype, 'textContent', {
            get: function() {
                // return innerText.get.call(this); // not good enough!
                return getTextContent.call(this);
            },
            set: function(x) {
                // return innerText.set.call(this, x); // not good enough!
                setTextContent.call(this, x);
            },
            configurable: true
        });
        // <script> Elements
        var scriptText = Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype, 'text');
        Object.defineProperty(HTMLScriptElement.prototype, 'textContent', {
            get: function() {
                return scriptText.get.call(this);
            },
            set: function(x) {
                scriptText.set.call(this, x);
            },
            configurable: true
        });
        // <style> Elements
        var cssText = Object.getOwnPropertyDescriptor(CSSStyleSheet.prototype, 'cssText');
        Object.defineProperty(HTMLStyleElement.prototype, 'textContent', {
            get: function() {
                return cssText.get.call(this.styleSheet);
            },
            set: function(x) {
                cssText.set.call(this.styleSheet, x);
            },
            configurable: true
        });
        // <title> Elements
        var titleText = Object.getOwnPropertyDescriptor(HTMLTitleElement.prototype, 'text');
        Object.defineProperty(HTMLTitleElement.prototype, 'textContent', {
            get: function() {
                return titleText.get.call(this);
            },
            set: function(x) {
                titleText.set.call(this, x);
            },
            configurable: true
        });
        // Text nodes
        var textNodeValue = Object.getOwnPropertyDescriptor(Text.prototype, 'nodeValue');
        Object.defineProperty(Text.prototype, 'textContent', {
            get: function() {
                return textNodeValue.get.call(this);
            },
            set: function(x) {
                textNodeValue.set.call(this, x);
            },
            configurable: true
        });
        // Comments (and possibly other weird Node types that are treated as comments in IE)
        var elementNodeValue = Object.getOwnPropertyDescriptor(Element.prototype, 'nodeValue');
        Object.defineProperty(HTMLCommentElement.prototype, 'textContent', {
            get: function() {
                return elementNodeValue.get.call(this);
            },
            set: function(x) {
                elementNodeValue.set.call(this, x);
            },
            configurable: true
        });
        // Document and DocumentFragment Nodes
        // NOTE: IE8 seems to reuse HTMLDocument for both, so have to check nodeType explicitly
        var documentNodeValue = Object.getOwnPropertyDescriptor(HTMLDocument.prototype, 'nodeValue');
        Object.defineProperty(HTMLDocument.prototype, 'textContent', {
            get: function() {
                // document fragments have textContent
                if (this.nodeType === 11) {
                    return getTextContent.call(this);
                }
                // a true Document's textContent is always null
                return null; // === documentNodeValue.get.call(this);
            },
            set: function(x) {
                if (this.nodeType === 11) {
                    setTextContent.call(this, x);
                }
                // setting a Document's textContent has no side effects
                // else { documentNodeValue.set.call(this, x); }
            },
            configurable: true
        });
        // other Node types are either deprecated or don't matter in HTML5/IE standards mode
    }
})();