// closest/matches support
if (!Element.prototype.matches)
    Element.prototype.matches = Element.prototype.msMatchesSelector ||
        Element.prototype.webkitMatchesSelector;

if (!Element.prototype.closest) {
    Element.prototype.closest = function (s) {
        var el = this;
        if (!document.documentElement.contains(el)) return null;
        do {
            if (el.matches(s)) return el;
            el = el.parentElement || el.parentNode;
        } while (el !== null && el.nodeType == 1);
        return null;
    };
}

// :scope support in querySelector on IE 8/11
try {
    // test for scope support
    document.querySelector(':scope *');
} catch (error) {
    (function (ElementPrototype) {
        // scope regex
        var scope = /:scope(?![\w-])/gi;

        // polyfill Element#querySelector
        var querySelectorWithScope = polyfill(ElementPrototype.querySelector);

        ElementPrototype.querySelector = function querySelector(selectors) {
            return querySelectorWithScope.apply(this, arguments);
        };

        // polyfill Element#querySelectorAll
        var querySelectorAllWithScope = polyfill(ElementPrototype.querySelectorAll);

        ElementPrototype.querySelectorAll = function querySelectorAll(selectors) {
            return querySelectorAllWithScope.apply(this, arguments);
        };

        // polyfill Element#matches
        if (ElementPrototype.matches) {
            var matchesWithScope = polyfill(ElementPrototype.matches);

            ElementPrototype.matches = function matches(selectors) {
                return matchesWithScope.apply(this, arguments);
            };
        }

        // polyfill Element#closest
        if (ElementPrototype.closest) {
            var closestWithScope = polyfill(ElementPrototype.closest);

            ElementPrototype.closest = function closest(selectors) {
                return closestWithScope.apply(this, arguments);
            };
        }

        function polyfill(qsa) {
            return function (selectors) {
                // whether the selectors contain :scope
                var hasScope = selectors && scope.test(selectors);

                if (hasScope) {
                    // fallback attribute
                    var attr = 'q' + Math.floor(Math.random() * 9000000) + 1000000;

                    // replace :scope with the fallback attribute
                    arguments[0] = selectors.replace(scope, '[' + attr + ']');

                    // add the fallback attribute
                    this.setAttribute(attr, '');

                    // results of the qsa
                    var elementOrNodeList = qsa.apply(this, arguments);

                    // remove the fallback attribute
                    this.removeAttribute(attr);

                    // return the results of the qsa
                    return elementOrNodeList;
                } else {
                    // return the results of the qsa
                    return qsa.apply(this, arguments);
                }
            };
        }
    })(Element.prototype);
}