    /*

    Siesta 5.3.0
    Copyright(c) 2009-2019 Bryntum AB
    https://bryntum.com/contact
    https://bryntum.com/products/siesta/license

    */
    Role('Siesta.Util.Role.Dom', {

        does        : [
            Siesta.Util.Role.CanCalculatePageScroll
        ],

        has : {
            doesNotIncludeMarginInBodyOffset : false
        },

        methods : {

            isCrossOriginWindow : function (win) {
                try {
                    var doc     = win.document;
                } catch (e) {
                    return true
                }

                // Safari doesn't throw exception when trying to access x-domain frames
                return !doc
            },

            closest : function (elem, selector, maxLevels) {
                maxLevels = maxLevels || Number.MAX_VALUE;

                var docEl = elem.ownerDocument.documentElement;

                // Get closest match
                for (var i = 0; i < maxLevels && elem && elem !== docEl; elem = elem.parentNode) {
                    if (Siesta.Sizzle.matchesSelector(elem, selector)) {
                        return elem;
                    }

                    i++;
                }

                return false;
            },

            contains : function (parentEl, childEl) {
                if (!parentEl) return false

                if (parentEl.contains)  return parentEl.contains(childEl)

                // SVG elements in IE does not have "contains" method
                if (parentEl.compareDocumentPosition)
                    return parentEl === childEl || Boolean(parentEl.compareDocumentPosition(childEl) & 16)

                throw new Error("Can't determine `contains` status")
            },

            matches : function (node, selector) {
                return Siesta.Sizzle.matchesSelector(node, selector);
            },

            // returns { left : Number, top : Number } object in page coordinates
            offset : function (elem) {
                if (!elem) return null

                var doc     = elem.ownerDocument
                if (!doc) return null

                if (elem === doc.body) return this.bodyOffset(elem);

                var box = this.getBoundingClientRect(elem)

                var win     = doc.defaultView || doc.parentWindow

                return box ? {
                    left    : this.viewportXtoPageX(Math.floor(box.left), win),
                    top     : this.viewportYtoPageY(Math.floor(box.top), win)
                } : {
                    left    : 0,
                    top     : 0
                }
            },

            bodyOffset: function (body) {
                var top     = body.offsetTop,
                    left    = body.offsetLeft;

                this.initializeOffset();

                if (this.doesNotIncludeMarginInBodyOffset) {
                    var style = getComputedStyle(body);

                    top     += parseFloat(style.marginTop) || 0;
                    left    += parseFloat(style.marginLeft) || 0;
                }

                return { top: top, left: left };
            },

            initializeOffset: function () {
                var body        = document.body,
                    container   = document.createElement("div"),
                    bodyMarginTop = parseFloat(getComputedStyle(body).marginTop) || 0,
                    html        = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";

                var styles      = { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" };

                for (var o in styles) {
                    container.style[ o ] = styles[ o ];
                }

                container.innerHTML     = html;

                body.insertBefore(container, body.firstChild);

                var innerDiv            = container.firstChild;
                var checkDiv            = innerDiv.firstChild;
                var td                  = innerDiv.nextSibling.firstChild.firstChild;

                checkDiv.style.position = "fixed";
                checkDiv.style.top      = "20px";
                checkDiv.style.position = checkDiv.style.top = "";

                innerDiv.style.overflow = "hidden";
                innerDiv.style.position = "relative";

                this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop);

                body.removeChild(container);

                this.initializeOffset   = function () {};
            },

            getElementWidth : function (el) {
                return this.getBoundingClientRect(el).width;
            },

            getElementHeight : function (el) {
                return this.getBoundingClientRect(el).height;
            },

            getWindowSize : function (win) {
                var doc         = win.document

                return {
                    width       : win.innerWidth || doc.documentElement.clientWidth || doc.body.clientWidth,
                    height      : win.innerHeight || doc.documentElement.clientHeight || doc.body.clientHeight
                }
            },

            isPointWithinElement : function (x, y, el) {
                var rect = this.getBoundingClientRect(el);

                return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
            },

            isElementReachableAt : function (el, pageX, pageY, allowChild) {
                allowChild      = allowChild !== false

                var doc         = el.ownerDocument
                var win         = doc.defaultView || doc.parentWindow
                var foundEl     = doc.elementFromPoint(this.pageXtoViewportX(pageX, win), this.pageYtoViewportY(pageY, win))

                return foundEl && (foundEl === el || allowChild && this.contains(el, foundEl))
            },

            isElementReachableAtCenter : function (el, allowChild) {
                allowChild      = allowChild !== false

                var offsets     = this.offset(el);

                return this.isElementReachableAt(
                    el,
                    offsets.left + (this.getElementWidth(el) / 2),
                    offsets.top + (this.getElementHeight(el) / 2),
                    allowChild
                );
            },

            // patched version to support SVG in IE11/Edge
            getBoundingClientRect : function (el) {
                var svgEl = el.ownerSVGElement;

                if (svgEl && (bowser.msie || bowser.msedge || bowser.gecko)) {
                    var elBox   = el.getBBox(),
                        svgRect = svgEl.getBoundingClientRect(),
                        left    = svgRect.left + elBox.x,
                        top     = svgRect.top + elBox.y,
                        right   = left + elBox.width,
                        bottom  = top + elBox.height;

                    return {
                        x      : left,
                        y      : top,
                        left   : left,
                        top    : top,
                        bottom : bottom,
                        height : elBox.height,
                        width  : elBox.width
                    };
                } else {
                    return el.getBoundingClientRect();
                }
            },

            nodeIsUnloaded : function (el) {
                try {
                    // throws if accessed when element belonged to an iframe that's no longer in DOM
                    el && el.tagName

                    var doc = el.ownerDocument
                    var win = doc && (doc.defaultView || doc.parentWindow)

                    return !Boolean(win)
                } catch (e) {
                    // exception here probably means the "lastOverEl" is from freed context (unloaded page)
                    // access to such elements throws exceptions in IE
                    el          = null

                    return true
                }
            },

            nodeIsOrphan : function (el) {
                var doc = el.ownerDocument

                return !doc || !doc.body || !el.parentNode || !(el === doc.body || $.contains(doc.body, el));
            },

            getNodeParents : function (node) {
                var doc   = node.ownerDocument;
                var nodes = [];

                for (; node && node.parentNode; node = node.parentNode) {
                    nodes.unshift(node);
                }

                return nodes;
            },

            getCommonAncestor : function (node1, node2) {
                var parents1 = this.getNodeParents(node1);
                var parents2 = this.getNodeParents(node2);

                // Make sure both nodes are part of same DOM tree
                if (parents1[ 0 ] != parents2[ 0 ]) return null;

                for (var i = 0; i < parents1.length; i++) {
                    if (parents1[ i ] !== parents2[ i ]) {
                        return parents1[ i - 1 ];
                    }
                }
            }
        }
    })