siesta-lite
Version:
Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers
316 lines (224 loc) • 10.3 kB
JavaScript
/*
Siesta 5.6.1
Copyright(c) 2009-2022 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 : {
// Returns document or shadowRoot for a child element of a nested context (iframe / web component) which provides API for querySelector and elementFromPoint
getQueryableContainer : function (el) {
return el ? (el.getRootNode ? el.getRootNode() : el.ownerDocument) : this.global.document;
},
// Returns the queryable root of an iframe or web component
getQueryableContainerForNestedContext : function (el) {
return el.shadowRoot || el.contentWindow.document;
},
// Returns the root element of an element, the document (<HTML>) node or a shadowroot
getRootElement : function (el) {
if (el.getRootNode) {
var rootNode = el.getRootNode();
if (rootNode.constructor.name === 'ShadowRoot' || rootNode.nodeType === 11) {
return rootNode;
}
}
return el.ownerDocument.documentElement;
},
// Returns top most parent node of the visible DOM of an element, the body (<BODY>) node for non-web components
getBodyElement : function (el) {
var doc = this.getQueryableContainer(el);
if (doc.constructor.name === 'ShadowRoot' || doc.nodeType === 11) {
return doc;
}
return doc.body;
},
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 rootEl = this.getRootElement(elem);
// Get closest match
for (var i = 0; i < maxLevels && elem && elem !== rootEl; 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 = this.getQueryableContainer(el).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) {
if (el.constructor.name === 'ShadowRoot' || el.nodeType === 11) {
el = el.host;
}
var docOrHtmlFragment = this.getRootElement(el)
// Detached doc fragment ?
if (docOrHtmlFragment && docOrHtmlFragment.nodeType === 11) {
return !docOrHtmlFragment.isConnected;
}
return !docOrHtmlFragment || !docOrHtmlFragment.contains(el);
},
getNodeParents : function (node) {
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 ];
}
}
}
}
})