| <!DOCTYPE html> |
| <title>CSSOM View - getBoxQuads() options</title> |
| <link rel="help" href="https://drafts.csswg.org/cssom-view/#the-geometryutils-interface"> |
| <link rel="help" href="https://hacks.mozilla.org/2014/03/introducing-the-getboxquads-api/"> |
| <script src="/resources/testharness.js"></script> |
| <script src="/resources/testharnessreport.js"></script> |
| <script src="resources/geometry-utils.js"></script> |
| <style> |
| html, body { |
| margin: 0; |
| } |
| |
| #target { |
| position: absolute; |
| left: 40px; |
| top: 30px; |
| width: 200px; |
| height: 160px; |
| margin: 11px 13px 17px 19px; |
| border: solid; |
| border-width: 2px 4px 6px 8px; |
| padding: 3px 5px 7px 9px; |
| box-sizing: content-box; |
| background: lightblue; |
| } |
| |
| #relative { |
| position: absolute; |
| left: 20px; |
| top: 15px; |
| width: 300px; |
| height: 220px; |
| border: 10px solid gray; |
| padding: 12px; |
| } |
| |
| #child { |
| position: absolute; |
| left: 50px; |
| top: 45px; |
| width: 80px; |
| height: 40px; |
| margin: 10px 14px 18px 22px; |
| border: solid; |
| border-width: 1px 3px 5px 7px; |
| padding: 2px 4px 6px 8px; |
| box-sizing: content-box; |
| background: gold; |
| } |
| |
| #rtl { |
| position: absolute; |
| left: 320px; |
| top: 30px; |
| width: 60px; |
| height: 25px; |
| direction: rtl; |
| writing-mode: vertical-rl; |
| border: 4px solid black; |
| padding: 6px; |
| } |
| |
| #inline-container { |
| position: absolute; |
| left: 420px; |
| top: 30px; |
| width: 115px; |
| font: 18px/2 sans-serif; |
| } |
| |
| #inline { |
| margin: 7px 11px; |
| border: solid; |
| border-width: 3px 5px; |
| padding: 2px 4px; |
| } |
| |
| #scroll-spacer { |
| width: calc(100vw + 200px); |
| height: calc(100vh + 200px); |
| } |
| </style> |
| <div id="target"></div> |
| <div id="relative"> |
| <div id="child"></div> |
| </div> |
| <div id="rtl"></div> |
| <div id="inline-container"><span id="inline">inline content wraps here</span></div> |
| <div id="scroll-spacer"></div> |
| <script> |
| const boxes = ["margin", "border", "padding", "content"]; |
| const target = document.getElementById("target"); |
| const relative = document.getElementById("relative"); |
| const child = document.getElementById("child"); |
| const rtl = document.getElementById("rtl"); |
| const inline = document.getElementById("inline"); |
| |
| test(() => { |
| assert_geometry_utils_exist(); |
| }, "GeometryUtils APIs exist on Element"); |
| |
| for (const box of boxes) { |
| test(() => { |
| const quad = target.getBoxQuads({box})[0]; |
| const expected = local_box_rect(target, box); |
| const borderRect = target.getBoundingClientRect(); |
| assert_point_approx_equals(quad.p1, |
| new DOMPoint(borderRect.left + expected.x, |
| borderRect.top + expected.y), |
| `${box} p1`); |
| assert_point_approx_equals(quad.p3, |
| new DOMPoint(borderRect.left + expected.x + |
| expected.width, |
| borderRect.top + expected.y + |
| expected.height), |
| `${box} p3`); |
| assert_approx_equals(quad.getBounds().width, expected.width, |
| GEOMETRY_UTILS_EPSILON, `${box} width`); |
| assert_approx_equals(quad.getBounds().height, expected.height, |
| GEOMETRY_UTILS_EPSILON, `${box} height`); |
| }, `getBoxQuads({box: "${box}"}) returns the requested CSS box`); |
| } |
| |
| test(() => { |
| const defaultQuad = target.getBoxQuads()[0]; |
| const borderQuad = target.getBoxQuads({box: "border"})[0]; |
| assert_quad_approx_equals(defaultQuad, borderQuad, |
| "default getBoxQuads() border box"); |
| }, "getBoxQuads() defaults to the border box"); |
| |
| test(() => { |
| const quad = child.getBoxQuads({relativeTo: relative})[0]; |
| const childRect = child.getBoundingClientRect(); |
| const relativeRect = relative.getBoundingClientRect(); |
| assert_point_approx_equals(quad.p1, |
| new DOMPoint(childRect.left - relativeRect.left, |
| childRect.top - relativeRect.top), |
| "child border p1 relative to relative border box"); |
| assert_point_approx_equals(quad.p3, |
| new DOMPoint(childRect.right - relativeRect.left, |
| childRect.bottom - relativeRect.top), |
| "child border p3 relative to relative border box"); |
| }, "getBoxQuads({relativeTo}) uses the relative element border-box origin"); |
| |
| test(() => { |
| const quad = child.getBoxQuads({box: "content", relativeTo: relative})[0]; |
| const childRect = child.getBoundingClientRect(); |
| const relativeRect = relative.getBoundingClientRect(); |
| const contentRect = local_box_rect(child, "content"); |
| assert_point_approx_equals(quad.p1, |
| new DOMPoint(childRect.left - relativeRect.left + |
| contentRect.x, |
| childRect.top - relativeRect.top + |
| contentRect.y), |
| "child content p1 relative to relative"); |
| assert_point_approx_equals(quad.p3, |
| new DOMPoint(childRect.left - relativeRect.left + |
| contentRect.x + contentRect.width, |
| childRect.top - relativeRect.top + |
| contentRect.y + contentRect.height), |
| "child content p3 relative to relative"); |
| }, "getBoxQuads() combines box and relativeTo options"); |
| |
| test(() => { |
| const quad = rtl.getBoxQuads()[0]; |
| assert_true(quad.p1.x < quad.p2.x, "p1 remains the physical left corner"); |
| assert_true(quad.p1.y < quad.p4.y, "p1 remains the physical top corner"); |
| }, "getBoxQuads() point order is physical for bidi and writing-mode"); |
| |
| for (const box of boxes) { |
| test(() => { |
| const style = getComputedStyle(inline); |
| const borderRects = inline.getClientRects(); |
| const quads = inline.getBoxQuads({box}); |
| assert_greater_than(quads.length, 1, "inline produces fragments"); |
| assert_equals(quads.length, borderRects.length, "fragment count"); |
| |
| let left = 0; |
| let top = 0; |
| let right = 0; |
| let bottom = 0; |
| if (box === "margin") { |
| left = i => i === 0 ? -parseFloat(style.marginLeft) : 0; |
| top = -parseFloat(style.marginTop); |
| right = i => i === quads.length - 1 ? parseFloat(style.marginRight) : 0; |
| bottom = parseFloat(style.marginBottom); |
| } else if (box === "padding" || box === "content") { |
| left = i => i === 0 ? parseFloat(style.borderLeftWidth) : 0; |
| top = parseFloat(style.borderTopWidth); |
| right = i => i === quads.length - 1 |
| ? -parseFloat(style.borderRightWidth) : 0; |
| bottom = -parseFloat(style.borderBottomWidth); |
| if (box === "content") { |
| const borderLeft = left; |
| const borderRight = right; |
| left = i => borderLeft(i) + |
| (i === 0 ? parseFloat(style.paddingLeft) : 0); |
| top += parseFloat(style.paddingTop); |
| right = i => borderRight(i) - |
| (i === quads.length - 1 ? parseFloat(style.paddingRight) : 0); |
| bottom -= parseFloat(style.paddingBottom); |
| } |
| } |
| |
| for (let i = 0; i < quads.length; ++i) { |
| const rect = borderRects[i]; |
| const fragmentLeft = typeof left === "function" ? left(i) : left; |
| const fragmentRight = typeof right === "function" ? right(i) : right; |
| assert_rect_approx_equals( |
| quads[i].getBounds(), |
| new DOMRect(rect.left + fragmentLeft, rect.top + top, |
| rect.width + fragmentRight - fragmentLeft, |
| rect.height + bottom - top), |
| `${box} fragment ${i}` |
| ); |
| } |
| }, `getBoxQuads({box: "${box}"}) applies box edges to inline fragments`); |
| } |
| |
| for (const box of boxes) { |
| test(() => { |
| assert_greater_than(document.scrollingElement.scrollWidth, |
| document.documentElement.clientWidth, |
| "the document has horizontal overflow"); |
| assert_greater_than(document.scrollingElement.scrollHeight, |
| document.documentElement.clientHeight, |
| "the document has vertical overflow"); |
| |
| const quad = document.getBoxQuads({box})[0]; |
| assert_point_approx_equals(quad.p1, new DOMPoint(0, 0), |
| "document quad origin"); |
| assert_approx_equals(quad.getBounds().width, |
| document.documentElement.clientWidth, |
| GEOMETRY_UTILS_EPSILON, |
| "document quad excludes the vertical scrollbar"); |
| assert_approx_equals(quad.getBounds().height, |
| document.documentElement.clientHeight, |
| GEOMETRY_UTILS_EPSILON, |
| "document quad excludes the horizontal scrollbar"); |
| }, `Document.getBoxQuads({box: "${box}"}) returns the layout viewport`); |
| } |
| </script> |