michael@0: var scrollbarWidth = 17, scrollbarHeight = 17; michael@0: michael@0: function testElements(baseid, callback) michael@0: { michael@0: scrollbarWidth = scrollbarHeight = gcs($("scrollbox-test"), "width"); michael@0: michael@0: var elements = $(baseid).getElementsByTagName("*"); michael@0: for (var t = 0; t < elements.length; t++) { michael@0: var element = elements[t]; michael@0: testElement(element); michael@0: } michael@0: michael@0: var nonappended = document.createElement("div"); michael@0: nonappended.id = "nonappended"; michael@0: nonappended.setAttribute("_offsetParent", "null"); michael@0: testElement(nonappended); michael@0: michael@0: checkScrolledElement($("scrollbox"), $("scrollchild")); michael@0: michael@0: var div = $("noscroll"); michael@0: div.scrollLeft = 10; michael@0: div.scrollTop = 10; michael@0: is(element.scrollLeft, 0, element.id + " scrollLeft after nonscroll"); michael@0: is(element.scrollTop, 0, element.id + " scrollTop after nonscroll"); michael@0: michael@0: callback(); michael@0: } michael@0: michael@0: function toNearestAppunit(v) michael@0: { michael@0: // 60 appunits per CSS pixel; round result to the nearest appunit michael@0: return Math.round(v*60)/60; michael@0: } michael@0: michael@0: function isEqualAppunits(a, b, msg) michael@0: { michael@0: is(toNearestAppunit(a), toNearestAppunit(b), msg); michael@0: } michael@0: michael@0: function testElement(element) michael@0: { michael@0: var offsetParent = element.getAttribute("_offsetParent"); michael@0: offsetParent = $(offsetParent == "null" ? null: (offsetParent ? offsetParent : "body")); michael@0: michael@0: var borderLeft = gcs(element, "borderLeftWidth"); michael@0: var borderTop = gcs(element, "borderTopWidth"); michael@0: var borderRight = gcs(element, "borderRightWidth"); michael@0: var borderBottom = gcs(element, "borderBottomWidth"); michael@0: var paddingLeft = gcs(element, "paddingLeft"); michael@0: var paddingTop = gcs(element, "paddingTop"); michael@0: var paddingRight = gcs(element, "paddingRight"); michael@0: var paddingBottom = gcs(element, "paddingBottom"); michael@0: var width = gcs(element, "width"); michael@0: var height = gcs(element, "height"); michael@0: michael@0: if (element instanceof HTMLElement) michael@0: checkOffsetState(element, -10000, -10000, michael@0: borderLeft + paddingLeft + width + paddingRight + borderRight, michael@0: borderTop + paddingTop + height + paddingBottom + borderBottom, michael@0: offsetParent, element.id); michael@0: michael@0: var scrollWidth, scrollHeight, clientWidth, clientHeight; michael@0: var doScrollCheck = true; michael@0: if (element.id == "scrollbox") { michael@0: var lastchild = $("lastline"); michael@0: scrollWidth = lastchild.getBoundingClientRect().width + paddingLeft + paddingRight; michael@0: var top = element.firstChild.getBoundingClientRect().top; michael@0: var bottom = element.lastChild.getBoundingClientRect().bottom; michael@0: var contentsHeight = bottom - top; michael@0: scrollHeight = contentsHeight + paddingTop + paddingBottom; michael@0: clientWidth = paddingLeft + width + paddingRight - scrollbarWidth; michael@0: clientHeight = paddingTop + height + paddingBottom - scrollbarHeight; michael@0: } else { michael@0: clientWidth = paddingLeft + width + paddingRight; michael@0: clientHeight = paddingTop + height + paddingBottom; michael@0: if (element.id == "overflow-visible") { michael@0: scrollWidth = 200; michael@0: scrollHeight = 201; michael@0: } else if (element.scrollWidth > clientWidth || michael@0: element.scrollHeight > clientHeight) { michael@0: // The element overflows. Don't check scrollWidth/scrollHeight since the michael@0: // above calculation is not correct. michael@0: doScrollCheck = false; michael@0: } else { michael@0: scrollWidth = clientWidth; michael@0: scrollHeight = clientHeight; michael@0: } michael@0: } michael@0: michael@0: if (doScrollCheck) { michael@0: if (element instanceof SVGElement) michael@0: checkScrollState(element, 0, 0, 0, 0, element.id); michael@0: else michael@0: checkScrollState(element, 0, 0, scrollWidth, scrollHeight, element.id); michael@0: } michael@0: michael@0: if (element instanceof SVGElement) michael@0: checkClientState(element, 0, 0, 0, 0, element.id); michael@0: else michael@0: checkClientState(element, borderLeft, borderTop, clientWidth, clientHeight, element.id); michael@0: michael@0: var boundingrect = element.getBoundingClientRect(); michael@0: isEqualAppunits(boundingrect.width, borderLeft + paddingLeft + width + paddingRight + borderRight, michael@0: element.id + " bounding rect width"); michael@0: isEqualAppunits(boundingrect.height, borderTop + paddingTop + height + paddingBottom + borderBottom, michael@0: element.id + " bounding rect height"); michael@0: isEqualAppunits(boundingrect.right - boundingrect.left, boundingrect.width, michael@0: element.id + " bounding rect right"); michael@0: isEqualAppunits(boundingrect.bottom - boundingrect.top, boundingrect.height, michael@0: element.id + " bounding rect bottom"); michael@0: michael@0: var rects = element.getClientRects(); michael@0: if (element.id == "div-displaynone" || element.id == "nonappended") { michael@0: is(rects.length, 0, element.id + " getClientRects empty"); michael@0: } michael@0: else { michael@0: is(rects[0].left, boundingrect.left, element.id + " getClientRects left"); michael@0: is(rects[0].top, boundingrect.top, element.id + " getClientRects top"); michael@0: is(rects[0].right, boundingrect.right, element.id + " getClientRects right"); michael@0: is(rects[0].bottom, boundingrect.bottom, element.id + " getClientRects bottom"); michael@0: } michael@0: } michael@0: michael@0: function checkScrolledElement(element, child) michael@0: { michael@0: var elemrect = element.getBoundingClientRect(); michael@0: var childrect = child.getBoundingClientRect(); michael@0: michael@0: var topdiff = childrect.top - elemrect.top; michael@0: michael@0: element.scrollTop = 20; michael@0: is(element.scrollLeft, 0, element.id + " scrollLeft after vertical scroll"); michael@0: is(element.scrollTop, 20, element.id + " scrollTop after vertical scroll"); michael@0: // If the viewport has been transformed, then we might have scrolled to a subpixel value michael@0: // that's slightly different from what we requested. After rounding, however, it should michael@0: // be the same. michael@0: is(Math.round(childrect.top - child.getBoundingClientRect().top), 20, "child position after vertical scroll"); michael@0: michael@0: element.scrollTop = 0; michael@0: is(element.scrollLeft, 0, element.id + " scrollLeft after vertical scroll reset"); michael@0: is(element.scrollTop, 0, element.id + " scrollTop after vertical scroll reset"); michael@0: // Scrolling back to the top should work precisely. michael@0: is(child.getBoundingClientRect().top, childrect.top, "child position after vertical scroll reset"); michael@0: michael@0: element.scrollTop = 10; michael@0: element.scrollTop = -30; michael@0: is(element.scrollLeft, 0, element.id + " scrollLeft after vertical scroll negative"); michael@0: is(element.scrollTop, 0, element.id + " scrollTop after vertical scroll negative"); michael@0: is(child.getBoundingClientRect().top, childrect.top, "child position after vertical scroll negative"); michael@0: michael@0: element.scrollLeft = 18; michael@0: is(element.scrollLeft, 18, element.id + " scrollLeft after horizontal scroll"); michael@0: is(element.scrollTop, 0, element.id + " scrollTop after horizontal scroll"); michael@0: is(Math.round(childrect.left - child.getBoundingClientRect().left), 18, "child position after horizontal scroll"); michael@0: michael@0: element.scrollLeft = -30; michael@0: is(element.scrollLeft, 0, element.id + " scrollLeft after horizontal scroll reset"); michael@0: is(element.scrollTop, 0, element.id + " scrollTop after horizontal scroll reset"); michael@0: is(child.getBoundingClientRect().left, childrect.left, "child position after horizontal scroll reset"); michael@0: } michael@0: michael@0: function checkOffsetState(element, left, top, width, height, parent, testname) michael@0: { michael@0: checkCoords(element, "offset", left, top, width, height, testname); michael@0: is(element.offsetParent, parent, testname + " offsetParent"); michael@0: } michael@0: michael@0: function checkScrollState(element, left, top, width, height, testname) michael@0: { michael@0: checkCoords(element, "scroll", left, top, width, height, testname); michael@0: } michael@0: michael@0: function checkClientState(element, left, top, width, height, testname) michael@0: { michael@0: checkCoords(element, "client", left, top, width, height, testname); michael@0: } michael@0: michael@0: function checkCoord(element, type, val, testname) michael@0: { michael@0: if (val != -10000) michael@0: is(element[type], Math.round(val), testname + " " + type); michael@0: } michael@0: michael@0: function checkCoords(element, type, left, top, width, height, testname) michael@0: { michael@0: checkCoord(element, type + "Left", left, testname); michael@0: checkCoord(element, type + "Top", top, testname); michael@0: checkCoord(element, type + "Width", width, testname); michael@0: checkCoord(element, type + "Height", height, testname); michael@0: michael@0: if (element instanceof SVGElement) michael@0: return; michael@0: michael@0: if (element.id == "outerpopup" && !element.parentNode.open) // closed popup michael@0: return; michael@0: michael@0: if (element.id == "div-displaynone" || element.id == "nonappended") // hidden elements michael@0: ok(element[type + "Width"] == 0 && element[type + "Height"] == 0, michael@0: element.id + " has zero " + type + " width and height"); michael@0: } michael@0: michael@0: function gcs(element, prop) michael@0: { michael@0: var propVal = (element instanceof SVGElement && (prop == "width" || prop == "height")) ? michael@0: element.getAttribute(prop) : getComputedStyle(element, "")[prop]; michael@0: if (propVal == "auto") michael@0: return 0; michael@0: return parseFloat(propVal); michael@0: }