test_offsets.js (9368B)
1 var scrollbarWidth = 17, 2 scrollbarHeight = 17; 3 4 function testElements(baseid, callback) { 5 scrollbarWidth = scrollbarHeight = gcs($("scrollbox-test"), "width"); 6 7 var elements = $(baseid).getElementsByTagName("*"); 8 for (var t = 0; t < elements.length; t++) { 9 var element = elements[t]; 10 11 // Ignore presentational content inside menus 12 if ( 13 element.closest("menu, menuitem") && 14 element.closest("[aria-hidden=true]") 15 ) { 16 continue; 17 } 18 19 // Ignore content inside a <button> This can be removed if/when 20 // button switches to use shadow DOM. 21 let buttonParent = element.closest("button"); 22 if (buttonParent && buttonParent !== element) { 23 continue; 24 } 25 26 testElement(element); 27 } 28 29 var nonappended = document.createElementNS( 30 "http://www.w3.org/1999/xhtml", 31 "div" 32 ); 33 nonappended.id = "nonappended"; 34 nonappended.setAttribute("_offsetParent", "null"); 35 testElement(nonappended); 36 37 checkScrolledElement($("scrollbox"), $("scrollchild")); 38 39 var div = $("noscroll"); 40 div.scrollLeft = 10; 41 div.scrollTop = 10; 42 is(element.scrollLeft, 0, element.id + " scrollLeft after nonscroll"); 43 is(element.scrollTop, 0, element.id + " scrollTop after nonscroll"); 44 45 callback(); 46 } 47 48 function usesSVGLayout(e) { 49 return e instanceof SVGElement && !(e instanceof SVGSVGElement); 50 } 51 52 function toNearestAppunit(v) { 53 // 60 appunits per CSS pixel; round result to the nearest appunit 54 return Math.round(v * 60) / 60; 55 } 56 57 function isEqualAppunits(a, b, msg) { 58 is(toNearestAppunit(a), toNearestAppunit(b), msg); 59 } 60 61 function testElement(element) { 62 var offsetParent = element.getAttribute("_offsetParent"); 63 offsetParent = $( 64 offsetParent == "null" ? null : offsetParent ? offsetParent : "body" 65 ); 66 67 var borderLeft = gcs(element, "borderLeftWidth"); 68 var borderTop = gcs(element, "borderTopWidth"); 69 var borderRight = gcs(element, "borderRightWidth"); 70 var borderBottom = gcs(element, "borderBottomWidth"); 71 var paddingLeft = gcs(element, "paddingLeft"); 72 var paddingTop = gcs(element, "paddingTop"); 73 var paddingRight = gcs(element, "paddingRight"); 74 var paddingBottom = gcs(element, "paddingBottom"); 75 var width = gcs(element, "width"); 76 var height = gcs(element, "height"); 77 78 if (element instanceof HTMLElement) { 79 checkOffsetState( 80 element, 81 -10000, 82 -10000, 83 borderLeft + paddingLeft + width + paddingRight + borderRight, 84 borderTop + paddingTop + height + paddingBottom + borderBottom, 85 offsetParent, 86 element.id 87 ); 88 } 89 90 var scrollWidth, scrollHeight, clientWidth, clientHeight; 91 var doScrollCheck = true; 92 { 93 if (element.id == "scrollbox") { 94 clientWidth = paddingLeft + width + paddingRight - scrollbarWidth; 95 clientHeight = paddingTop + height + paddingBottom - scrollbarHeight; 96 } else { 97 clientWidth = paddingLeft + width + paddingRight; 98 clientHeight = paddingTop + height + paddingBottom; 99 } 100 if (element.id == "overflow-visible") { 101 scrollWidth = 200; 102 scrollHeight = 201; 103 } else if ( 104 element.scrollWidth > clientWidth || 105 element.scrollHeight > clientHeight 106 ) { 107 // The element overflows. Don't check scrollWidth/scrollHeight since the 108 // above calculation is not correct. 109 doScrollCheck = false; 110 } else { 111 scrollWidth = clientWidth; 112 scrollHeight = clientHeight; 113 } 114 } 115 116 if (doScrollCheck) { 117 if (usesSVGLayout(element)) { 118 checkScrollState(element, 0, 0, 0, 0, element.id); 119 } else { 120 checkScrollState(element, 0, 0, scrollWidth, scrollHeight, element.id); 121 } 122 } 123 124 if (usesSVGLayout(element)) { 125 checkClientState(element, 0, 0, 0, 0, element.id); 126 } else { 127 checkClientState( 128 element, 129 borderLeft, 130 borderTop, 131 clientWidth, 132 clientHeight, 133 element.id 134 ); 135 } 136 137 var boundingrect = element.getBoundingClientRect(); 138 isEqualAppunits( 139 boundingrect.width, 140 borderLeft + paddingLeft + width + paddingRight + borderRight, 141 element.id + " bounding rect width" 142 ); 143 isEqualAppunits( 144 boundingrect.height, 145 borderTop + paddingTop + height + paddingBottom + borderBottom, 146 element.id + " bounding rect height" 147 ); 148 isEqualAppunits( 149 boundingrect.right - boundingrect.left, 150 boundingrect.width, 151 element.id + " bounding rect right" 152 ); 153 isEqualAppunits( 154 boundingrect.bottom - boundingrect.top, 155 boundingrect.height, 156 element.id + " bounding rect bottom" 157 ); 158 159 var rects = element.getClientRects(); 160 if (element.id == "div-displaynone" || element.id == "nonappended") { 161 is(rects.length, 0, element.id + " getClientRects empty"); 162 } else { 163 is(rects[0].left, boundingrect.left, element.id + " getClientRects left"); 164 is(rects[0].top, boundingrect.top, element.id + " getClientRects top"); 165 is( 166 rects[0].right, 167 boundingrect.right, 168 element.id + " getClientRects right" 169 ); 170 is( 171 rects[0].bottom, 172 boundingrect.bottom, 173 element.id + " getClientRects bottom" 174 ); 175 } 176 } 177 178 function checkScrolledElement(element, child) { 179 var elemrect = element.getBoundingClientRect(); 180 var childrect = child.getBoundingClientRect(); 181 182 var topdiff = childrect.top - elemrect.top; 183 184 element.scrollTop = 20; 185 is(element.scrollLeft, 0, element.id + " scrollLeft after vertical scroll"); 186 is(element.scrollTop, 20, element.id + " scrollTop after vertical scroll"); 187 // If the viewport has been transformed, then we might have scrolled to a subpixel value 188 // that's slightly different from what we requested. After rounding, however, it should 189 // be the same. 190 is( 191 Math.round(childrect.top - child.getBoundingClientRect().top), 192 20, 193 "child position after vertical scroll" 194 ); 195 196 element.scrollTop = 0; 197 is( 198 element.scrollLeft, 199 0, 200 element.id + " scrollLeft after vertical scroll reset" 201 ); 202 is( 203 element.scrollTop, 204 0, 205 element.id + " scrollTop after vertical scroll reset" 206 ); 207 // Scrolling back to the top should work precisely. 208 is( 209 child.getBoundingClientRect().top, 210 childrect.top, 211 "child position after vertical scroll reset" 212 ); 213 214 element.scrollTop = 10; 215 element.scrollTop = -30; 216 is( 217 element.scrollLeft, 218 0, 219 element.id + " scrollLeft after vertical scroll negative" 220 ); 221 is( 222 element.scrollTop, 223 0, 224 element.id + " scrollTop after vertical scroll negative" 225 ); 226 is( 227 child.getBoundingClientRect().top, 228 childrect.top, 229 "child position after vertical scroll negative" 230 ); 231 232 element.scrollLeft = 18; 233 is( 234 element.scrollLeft, 235 18, 236 element.id + " scrollLeft after horizontal scroll" 237 ); 238 is(element.scrollTop, 0, element.id + " scrollTop after horizontal scroll"); 239 is( 240 Math.round(childrect.left - child.getBoundingClientRect().left), 241 18, 242 "child position after horizontal scroll" 243 ); 244 245 element.scrollLeft = -30; 246 is( 247 element.scrollLeft, 248 0, 249 element.id + " scrollLeft after horizontal scroll reset" 250 ); 251 is( 252 element.scrollTop, 253 0, 254 element.id + " scrollTop after horizontal scroll reset" 255 ); 256 is( 257 child.getBoundingClientRect().left, 258 childrect.left, 259 "child position after horizontal scroll reset" 260 ); 261 } 262 263 function checkOffsetState(element, left, top, width, height, parent, testname) { 264 checkCoords(element, "offset", left, top, width, height, testname); 265 is(element.offsetParent, parent, testname + " offsetParent"); 266 } 267 268 function checkScrollState(element, left, top, width, height, testname) { 269 checkCoords(element, "scroll", left, top, width, height, testname); 270 } 271 272 function checkClientState(element, left, top, width, height, testname) { 273 checkCoords(element, "client", left, top, width, height, testname); 274 } 275 276 function checkCoord(element, type, val, testname) { 277 if (val != -10000) { 278 is(element[type], Math.round(val), testname + " " + type); 279 } 280 } 281 282 function checkCoordFuzzy(element, type, val, fuzz, testname) { 283 if (val != -10000) { 284 let v = element[type]; 285 ok( 286 Math.abs(v - Math.round(val)) <= fuzz, 287 `${testname} ${type}: ${v} vs. ${val}` 288 ); 289 } 290 } 291 292 function checkCoords(element, type, left, top, width, height, testname) { 293 checkCoord(element, type + "Left", left, testname); 294 checkCoord(element, type + "Top", top, testname); 295 296 if (type == "scroll") { 297 // scrollWidth and scrollHeight can deviate by 1 pixel due to snapping. 298 checkCoordFuzzy(element, type + "Width", width, 1, testname); 299 checkCoordFuzzy(element, type + "Height", height, 1, testname); 300 } else { 301 checkCoord(element, type + "Width", width, testname); 302 checkCoord(element, type + "Height", height, testname); 303 } 304 305 if (usesSVGLayout(element)) { 306 return; 307 } 308 309 if (element.id == "outerpopup" && !element.parentNode.open) { 310 // closed popup 311 return; 312 } 313 314 if (element.id == "div-displaynone" || element.id == "nonappended") { 315 // hidden elements 316 ok( 317 element[type + "Width"] == 0 && element[type + "Height"] == 0, 318 element.id + " has zero " + type + " width and height" 319 ); 320 } 321 } 322 323 function gcs(element, prop) { 324 var propVal = 325 usesSVGLayout(element) && (prop == "width" || prop == "height") 326 ? element.getAttribute(prop) 327 : getComputedStyle(element, "")[prop]; 328 if (propVal == "auto" || element.id == "nonappended") { 329 return 0; 330 } 331 return parseFloat(propVal); 332 }