anchor-invalid-fallback.html (8655B)
1 <!DOCTYPE html> 2 <title>CSS Anchor Position Test: invalid at computed-value time</title> 3 <link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#anchor-valid"> 4 <link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#anchor-size-valid"> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <style> 8 :root { 9 --top: top; 10 } 11 #cb { 12 position: relative; 13 width: 200px; 14 height: 200px; 15 border: 1px solid black; 16 } 17 18 #anchor { 19 anchor-name: --a; 20 position: absolute; 21 width: 50px; 22 height: 40px; 23 left: 75px; 24 top: 75px; 25 background: coral; 26 } 27 28 #main > div, #ref { 29 position: absolute; 30 background: seagreen; 31 } 32 33 #ref { 34 inset: unset; 35 width: unset; 36 height: unset; 37 min-width: unset; 38 min-height: unset; 39 max-width: unset; 40 max-height: unset; 41 } 42 43 </style> 44 <div id=cb> 45 <div id=anchor></div> 46 <div id=main></div> 47 <div id=ref>X</div> 48 </div> 49 <script> 50 51 // Append <div>X</div> to `container`, and remove it again once the test (`t`) 52 // is finished. 53 function createTarget(t, container) { 54 t.add_cleanup(() => { container.replaceChildren(); }); 55 let target = document.createElement('div'); 56 target.textContent = 'X'; 57 container.append(target); 58 return target; 59 } 60 61 // First, some sanity checks to verify that the anchor etc is set up correctly, 62 // and that anchor() queries can produce results if done correctly. 63 64 test((t) => { 65 let target = createTarget(t, main); 66 target.style = ` 67 position-anchor: --a; 68 left:anchor(right); 69 top:anchor(top); 70 width:anchor-size(width); 71 height:anchor-size(height); 72 `; 73 let cs = getComputedStyle(target); 74 assert_equals(cs.left, '125px'); 75 assert_equals(cs.top, '75px'); 76 assert_equals(cs.width, '50px'); 77 assert_equals(cs.height, '40px'); 78 }, 'Element can be anchor positioned'); 79 80 test((t) => { 81 let target = createTarget(t, main); 82 target.style = ` 83 /* No position-anchor here */ 84 left:anchor(right, 17px); 85 top:anchor(top, 18px); 86 width:anchor-size(width, 42px); 87 height:anchor-size(height, 43px); 88 `; 89 let cs = getComputedStyle(target); 90 assert_equals(cs.left, '17px'); 91 assert_equals(cs.top, '18px'); 92 assert_equals(cs.width, '42px'); 93 assert_equals(cs.height, '43px'); 94 }, 'Element can use <length> fallback if present'); 95 96 test((t) => { 97 let target = createTarget(t, main); 98 target.style = ` 99 /* No position-anchor here */ 100 left:anchor(right, 8.5%); 101 top:calc(8.5% + anchor(top, 1px)); 102 width:anchor-size(width, 21%); 103 height:calc(21% + anchor-size(height, 1px)); 104 `; 105 let cs = getComputedStyle(target); 106 assert_equals(cs.left, '17px'); 107 assert_equals(cs.top, '18px'); 108 assert_equals(cs.width, '42px'); 109 assert_equals(cs.height, '43px'); 110 }, 'Element can use <length> fallback with percentage'); 111 112 test((t) => { 113 let target = createTarget(t, main); 114 target.style = ` 115 /* No position-anchor here */ 116 max-width:anchor-size(width, 28%); 117 max-height:calc(28% + anchor-size(height, 1px)); 118 `; 119 let cs = getComputedStyle(target); 120 assert_equals(cs.maxWidth, '28%'); 121 assert_equals(cs.maxHeight, 'calc(28% + 1px)'); 122 }, 'Element can use <length> fallback for max size with percentage'); 123 124 test((t) => { 125 let target = createTarget(t, main); 126 target.style = ` 127 /* No position-anchor here */ 128 margin-left:anchor-size(width, 6%); 129 margin-top:calc(6% + anchor-size(height, 1px)); 130 `; 131 let cs = getComputedStyle(target); 132 assert_equals(cs.marginLeft, '12px'); 133 assert_equals(cs.marginTop, '13px'); 134 }, 'Element can use <length> fallback for margin with percentage'); 135 136 // Now test that any invalid anchor*() behaves as invalid at computed-value 137 // time if there's no fallback specified. 138 139 // Check that an anchored element with the specified style has the same 140 // computed insets and sizing as the reference element (#ref), i.e. all 141 // insets and sizing properties behave as 'unset'. 142 function test_ref(style, description) { 143 test((t) => { 144 let target = createTarget(t, main); 145 target.style = style; 146 let cs = getComputedStyle(target); 147 let ref_cs = getComputedStyle(ref); 148 assert_equals(cs.top, ref_cs.top, 'top'); 149 assert_equals(cs.left, ref_cs.left, 'left'); 150 assert_equals(cs.right, ref_cs.right, 'right'); 151 assert_equals(cs.bottom, ref_cs.bottom, 'bottom'); 152 assert_equals(cs.width, ref_cs.width, 'width'); 153 assert_equals(cs.height, ref_cs.height, 'height'); 154 assert_equals(cs.minWidth, ref_cs.minWidth, 'minWidth'); 155 assert_equals(cs.minHeight, ref_cs.minHeight, 'minHeight'); 156 assert_equals(cs.maxWidth, ref_cs.maxWidth, 'maxWidth'); 157 assert_equals(cs.maxHeight, ref_cs.maxHeight, 'maxHeight'); 158 }, `Invalid anchor function, ${description}`); 159 } 160 161 // No default anchor (position-anchor): 162 test_ref('left:anchor(left)', 'left'); 163 test_ref('right:anchor(right)', 'right'); 164 test_ref('bottom:anchor(bottom)', 'bottom'); 165 test_ref('top:anchor(top)', 'top'); 166 test_ref('width:anchor-size(width)', 'width'); 167 test_ref('height:anchor-size(height)', 'height'); 168 test_ref('min-width:anchor-size(width)', 'min-width'); 169 test_ref('min-height:anchor-size(height)', 'min-height'); 170 test_ref('max-width:anchor-size(width)', 'max-width'); 171 test_ref('max-height:anchor-size(height)', 'max-height'); 172 173 // Unknown anchor reference: 174 test_ref('left:anchor(--unknown left)', '--unknown left'); 175 test_ref('width:anchor-size(--unknown width)', '--unknown width'); 176 177 // Wrong axis; 178 test_ref('left:anchor(--a top)', 'cross-axis query (vertical)'); 179 test_ref('top:anchor(--a left)', ' cross-axis query (horizontal)'); 180 181 // Wrong query for the given property: 182 test_ref('width:anchor(--a left)', 'anchor() in sizing property'); 183 184 // Invalid anchor*() deeper within calc(): 185 test_ref('left:calc(anchor(left) + 10px)', 'nested left'); 186 test_ref('right:calc(anchor(right) + 10px)', 'nested right'); 187 test_ref('bottom:calc(anchor(bottom) + 10px)', 'nested bottom'); 188 test_ref('top:calc(anchor(top) + 10px)', 'nested top'); 189 test_ref('min-width:calc(anchor-size(width) + 10px)', 'nested min-width'); 190 test_ref('min-height:calc(anchor-size(height) + 10px)', 'nested min-height'); 191 test_ref('max-width:calc(anchor-size(width) + 10px)', 'nested max-width'); 192 test_ref('max-height:calc(anchor-size(height) + 10px)', 'nested max-height'); 193 194 // Invalid anchor*() within fallback: 195 test_ref('top:anchor(top, anchor(--unknown top))', 'invalid anchor() in fallback'); 196 test_ref('width:anchor-size(width, anchor-size(--unknown width))', 'invalid anchor-size() in fallback'); 197 198 // Non-calc() functions: 199 test_ref('top:min(10px, anchor(top))', 'min()'); 200 test_ref('top:max(10px, anchor(top))', 'max()'); 201 test_ref('top:abs(anchor(top) - 100px)', 'abs()'); 202 test_ref('top:calc(sign(anchor(top) - 100px) * 20px)', 'sign()'); 203 204 // var(): 205 test_ref('top:anchor(var(--top))', 'anchor(var())'); 206 test_ref('top:anchor(var(--unknown, top))', 'anchor(unknown var()) (fallback)'); 207 test_ref('top:anchor(var(--unknown))', 'anchor(unknown var()) (no fallback)'); 208 209 // Reverting to an invalid anchor(): 210 test((t) => { 211 let target = createTarget(t, main); 212 target.setAttribute('id', 'target'); 213 214 let css = document.createElement('style'); 215 css.textContent = ` 216 @layer base { 217 #target { 218 top: anchor(top); /* Invalid */ 219 color: green; 220 } 221 } 222 #target { 223 top: revert-layer; /* Reverts to 'base'. */ 224 } 225 `; 226 227 t.add_cleanup(() => { css.remove(); }) 228 cb.append(css); 229 230 let cs = getComputedStyle(target); 231 let ref_cs = getComputedStyle(ref); 232 // The color check verifies that the rule is applied at all. 233 assert_equals(cs.color, 'rgb(0, 128, 0)'); 234 assert_equals(cs.top, ref_cs.top); 235 }, 'Revert to invalid anchor()'); 236 237 // Using <try-tactic> to flip to an invalid anchor(): 238 test((t) => { 239 let target = createTarget(t, main); 240 target.setAttribute('id', 'target'); 241 242 let css = document.createElement('style'); 243 css.textContent = ` 244 @position-try --pt { 245 /* Undo force overflow, and also use this value to check that 246 the rule is applied at all. */ 247 left: 10px; 248 249 /* Invalid. Becomes bottom:anchor(bottom) (also invalid) 250 after flip-block. */ 251 top: anchor(top); 252 } 253 254 #target { 255 left: 9999px; /* Force overflow. */ 256 position-try-fallbacks: --pt flip-block; 257 } 258 `; 259 260 t.add_cleanup(() => { css.remove(); }) 261 cb.append(css); 262 263 let cs = getComputedStyle(target); 264 let ref_cs = getComputedStyle(ref); 265 assert_equals(cs.left, '10px', 'left'); 266 // 'right' is not important in this test. 267 268 assert_equals(cs.top, ref_cs.top, 'top'); 269 assert_equals(cs.bottom, ref_cs.bottom, 'bottom'); 270 }, 'Flip to invalid anchor()'); 271 272 </script>