browser_rules_linear-easing-swatch.js (13185B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Tests that linear easing widget pickers appear when clicking on linear 7 // swatches. 8 9 const TEST_URI = ` 10 <style type="text/css"> 11 div { 12 animation: move 3s linear(0, 0.2, 1); 13 transition: top 4s linear(0 10%, 0.5 20% 80%, 0 90%); 14 } 15 .test { 16 animation-timing-function: linear(0, 1 50% 100%); 17 transition-timing-function: linear(1 -10%, 0, -1 110%); 18 } 19 </style> 20 <div class="test">Testing the linear easing tooltip!</div> 21 `; 22 23 add_task(async function testSwatches() { 24 await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); 25 const { inspector, view } = await openRuleView(); 26 await selectNode("div", inspector); 27 28 const tooltip = view.tooltips.getTooltip("linearEaseFunction"); 29 ok(tooltip, "The rule-view has the expected linear tooltip"); 30 const panel = tooltip.tooltip.panel; 31 ok(panel, "The XUL panel for the linear tooltip exists"); 32 33 const expectedData = [ 34 { 35 selector: "div", 36 property: "animation", 37 expectedPoints: [ 38 [0, 0], 39 [0.5, 0.2], 40 [1, 1], 41 ], 42 }, 43 { 44 selector: "div", 45 property: "transition", 46 expectedPoints: [ 47 [0.1, 0], 48 [0.2, 0.5], 49 [0.8, 0.5], 50 [0.9, 0], 51 ], 52 }, 53 { 54 selector: ".test", 55 property: "animation-timing-function", 56 expectedPoints: [ 57 [0, 0], 58 [0.5, 1], 59 [1, 1], 60 ], 61 }, 62 { 63 selector: ".test", 64 property: "transition-timing-function", 65 expectedPoints: [ 66 [-0.1, 1], 67 [0.5, 0], 68 [1.1, -1], 69 ], 70 }, 71 ]; 72 73 for (const { selector, property, expectedPoints } of expectedData) { 74 const messagePrefix = `[${selector}|${property}]`; 75 const swatch = getRuleViewLinearEasingSwatch(view, selector, property); 76 ok(swatch, `${messagePrefix} the swatch exists`); 77 78 const onWidgetReady = tooltip.once("ready"); 79 swatch.click(); 80 await onWidgetReady; 81 ok(true, `${messagePrefix} clicking the swatch displayed the tooltip`); 82 83 ok( 84 !inplaceEditor(swatch.parentNode), 85 `${messagePrefix} inplace editor wasn't shown` 86 ); 87 88 checkChartState(panel, expectedPoints); 89 90 await hideTooltipAndWaitForRuleViewChanged(tooltip, view); 91 } 92 }); 93 94 add_task(async function testChart() { 95 await pushPref("ui.prefersReducedMotion", 0); 96 97 await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); 98 const { inspector, view } = await openRuleView(); 99 await selectNode("div", inspector); 100 101 const tooltip = view.tooltips.getTooltip("linearEaseFunction"); 102 ok(tooltip, "The rule-view has the expected linear tooltip"); 103 const panel = tooltip.tooltip.panel; 104 ok(panel, "The XUL panel for the linear tooltip exists"); 105 106 const selector = ".test"; 107 const property = "animation-timing-function"; 108 109 const swatch = getRuleViewLinearEasingSwatch(view, selector, property); 110 const onWidgetReady = tooltip.once("ready"); 111 swatch.click(); 112 await onWidgetReady; 113 const widget = await tooltip.widget; 114 115 const svgEl = panel.querySelector(`svg.chart`); 116 const svgRect = svgEl.getBoundingClientRect(); 117 118 checkChartState( 119 panel, 120 [ 121 [0, 0], 122 [0.5, 1], 123 [1, 1], 124 ], 125 "testChart - initial state:" 126 ); 127 128 info("Check that double clicking a point removes it"); 129 const middlePoint = panel.querySelector( 130 `svg.chart .control-points-group .control-point:nth-of-type(2)` 131 ); 132 let onWidgetUpdated = widget.once("updated"); 133 let onRuleViewChanged = view.once("ruleview-changed"); 134 EventUtils.sendMouseEvent( 135 { type: "dblclick" }, 136 middlePoint, 137 widget.parent.ownerGlobal 138 ); 139 140 let newValue = await onWidgetUpdated; 141 is(newValue, `linear(0 0%, 1 100%)`); 142 checkChartState( 143 panel, 144 [ 145 [0, 0], 146 [1, 1], 147 ], 148 "testChart - after point removed:" 149 ); 150 await onRuleViewChanged; 151 await checkRuleView(view, selector, property, newValue); 152 153 info( 154 "Check that double clicking a point when there are only 2 points on the line does not remove it" 155 ); 156 const timeoutRes = Symbol(); 157 let onTimeout = wait(1000).then(() => timeoutRes); 158 onWidgetUpdated = widget.once("updated"); 159 EventUtils.sendMouseEvent( 160 { type: "dblclick" }, 161 panel.querySelector(`svg.chart .control-points-group .control-point`), 162 widget.parent.ownerGlobal 163 ); 164 let raceWinner = await Promise.race([onWidgetUpdated, onTimeout]); 165 is( 166 raceWinner, 167 timeoutRes, 168 "The widget wasn't updated after double clicking one of the 2 last points" 169 ); 170 checkChartState( 171 panel, 172 [ 173 [0, 0], 174 [1, 1], 175 ], 176 "testChart - no point removed:" 177 ); 178 179 info("Check that double clicking on the svg does add a point"); 180 onWidgetUpdated = widget.once("updated"); 181 onRuleViewChanged = view.once("ruleview-changed"); 182 // Clicking on svg center with shiftKey so it snaps to the grid 183 EventUtils.synthesizeMouseAtCenter( 184 panel.querySelector(`svg.chart`), 185 { clickCount: 2, shiftKey: true }, 186 widget.parent.ownerGlobal 187 ); 188 189 newValue = await onWidgetUpdated; 190 is( 191 newValue, 192 `linear(0 0%, 0.5 50%, 1 100%)`, 193 "Widget was updated with expected value" 194 ); 195 checkChartState( 196 panel, 197 [ 198 [0, 0], 199 [0.5, 0.5], 200 [1, 1], 201 ], 202 "testChart - new point added" 203 ); 204 await onRuleViewChanged; 205 await checkRuleView(view, selector, property, newValue); 206 207 info("Check that points can be moved"); 208 onWidgetUpdated = widget.once("updated"); 209 onRuleViewChanged = view.once("ruleview-changed"); 210 EventUtils.synthesizeMouseAtCenter( 211 panel.querySelector(`svg.chart .control-points-group .control-point`), 212 { type: "mousedown" }, 213 widget.parent.ownerGlobal 214 ); 215 216 EventUtils.synthesizeMouse( 217 svgEl, 218 svgRect.width / 3, 219 svgRect.height / 3, 220 { type: "mousemove", shiftKey: true }, 221 widget.parent.ownerGlobal 222 ); 223 newValue = await onWidgetUpdated; 224 is( 225 newValue, 226 `linear(0.7 30%, 0.5 50%, 1 100%)`, 227 "Widget was updated with expected value" 228 ); 229 checkChartState( 230 panel, 231 [ 232 [0.3, 0.7], 233 [0.5, 0.5], 234 [1, 1], 235 ], 236 "testChart - point moved" 237 ); 238 await onRuleViewChanged; 239 await checkRuleView(view, selector, property, newValue); 240 241 info("Check that the points can be moved past the next point"); 242 onWidgetUpdated = widget.once("updated"); 243 onRuleViewChanged = view.once("ruleview-changed"); 244 245 // the second point is at 50%, so simulate a mousemove all the way to the right (which 246 // should be ~100%) 247 EventUtils.synthesizeMouse( 248 svgEl, 249 svgRect.width, 250 svgRect.height / 3, 251 { type: "mousemove", shiftKey: true }, 252 widget.parent.ownerGlobal 253 ); 254 newValue = await onWidgetUpdated; 255 is( 256 newValue, 257 `linear(0.7 50%, 0.5 50%, 1 100%)`, 258 "point wasn't moved past the next point (50%)" 259 ); 260 checkChartState( 261 panel, 262 [ 263 [0.5, 0.7], 264 [0.5, 0.5], 265 [1, 1], 266 ], 267 "testChart - point moved constrained by next point" 268 ); 269 await onRuleViewChanged; 270 await checkRuleView(view, selector, property, newValue); 271 272 info("Stop dragging"); 273 EventUtils.synthesizeMouseAtCenter( 274 svgEl, 275 { type: "mouseup" }, 276 widget.parent.ownerGlobal 277 ); 278 279 onTimeout = wait(1000).then(() => timeoutRes); 280 onWidgetUpdated = widget.once("updated"); 281 EventUtils.synthesizeMouseAtCenter( 282 svgEl, 283 { type: "mousemove" }, 284 widget.parent.ownerGlobal 285 ); 286 raceWinner = await Promise.race([onWidgetUpdated, onTimeout]); 287 is(raceWinner, timeoutRes, "Dragging is disabled after mouseup"); 288 289 info("Add another point, which should be the first one for the line"); 290 onWidgetUpdated = widget.once("updated"); 291 onRuleViewChanged = view.once("ruleview-changed"); 292 293 EventUtils.synthesizeMouse( 294 svgEl, 295 svgRect.width / 3, 296 svgRect.height - 1, 297 { clickCount: 2, shiftKey: true }, 298 widget.parent.ownerGlobal 299 ); 300 301 newValue = await onWidgetUpdated; 302 is( 303 newValue, 304 `linear(0 30%, 0.7 50%, 0.5 50%, 1 100%)`, 305 "Widget was updated with expected value" 306 ); 307 checkChartState( 308 panel, 309 [ 310 [0.3, 0], 311 [0.5, 0.7], 312 [0.5, 0.5], 313 [1, 1], 314 ], 315 "testChart - point added at beginning" 316 ); 317 await onRuleViewChanged; 318 await checkRuleView(view, selector, property, newValue); 319 320 info("Check that the points can't be moved past previous point"); 321 onWidgetUpdated = widget.once("updated"); 322 onRuleViewChanged = view.once("ruleview-changed"); 323 EventUtils.synthesizeMouseAtCenter( 324 panel.querySelector( 325 `svg.chart .control-points-group .control-point:nth-of-type(2)` 326 ), 327 { type: "mousedown" }, 328 widget.parent.ownerGlobal 329 ); 330 331 EventUtils.synthesizeMouse( 332 svgEl, 333 0, 334 svgRect.height / 3, 335 { type: "mousemove", shiftKey: true }, 336 widget.parent.ownerGlobal 337 ); 338 newValue = await onWidgetUpdated; 339 is( 340 newValue, 341 `linear(0 30%, 0.7 30%, 0.5 50%, 1 100%)`, 342 "point wasn't moved past previous point (30%)" 343 ); 344 checkChartState( 345 panel, 346 [ 347 [0.3, 0], 348 [0.3, 0.7], 349 [0.5, 0.5], 350 [1, 1], 351 ], 352 "testChart - point moved constrained by previous point" 353 ); 354 await onRuleViewChanged; 355 await checkRuleView(view, selector, property, newValue); 356 357 info("Stop dragging"); 358 EventUtils.synthesizeMouseAtCenter( 359 svgEl, 360 { type: "mouseup" }, 361 widget.parent.ownerGlobal 362 ); 363 364 info( 365 "Check that timing preview is destroyed if prefers-reduced-motion gets enabled" 366 ); 367 const getTimingFunctionPreview = () => 368 panel.querySelector(".timing-function-preview"); 369 ok(getTimingFunctionPreview(), "By default, timing preview is visible"); 370 info("Enable prefersReducedMotion"); 371 await pushPref("ui.prefersReducedMotion", 1); 372 await waitFor(() => !getTimingFunctionPreview()); 373 ok(true, "timing preview was removed after enabling prefersReducedMotion"); 374 375 info("Disable prefersReducedMotion"); 376 await pushPref("ui.prefersReducedMotion", 0); 377 await waitFor(() => getTimingFunctionPreview()); 378 ok( 379 true, 380 "timing preview was added back after disabling prefersReducedMotion" 381 ); 382 383 info("Hide tooltip with escape to cancel modification"); 384 const onHidden = tooltip.tooltip.once("hidden"); 385 const onModifications = view.once("ruleview-changed"); 386 focusAndSendKey(widget.parent.ownerDocument.defaultView, "ESCAPE"); 387 await onHidden; 388 await onModifications; 389 390 await checkRuleView( 391 view, 392 selector, 393 property, 394 "linear(0, 1 50% 100%)", 395 "linear(0 0%, 1 50%, 1 100%)" 396 ); 397 }); 398 399 /** 400 * Check that the svg chart line and control points are placed where we expect them. 401 * 402 * @param {ToolipPanel} panel 403 * @param {Array<Array<number>>} expectedPoints: Array of coordinated 404 * @param {string} messagePrefix 405 */ 406 function checkChartState(panel, expectedPoints, messagePrefix = "") { 407 const svgLine = panel.querySelector("svg.chart .chart-linear"); 408 is( 409 svgLine.getAttribute("points"), 410 expectedPoints.map(([x, y]) => `${x},${1 - y}`).join(" "), 411 `${messagePrefix} line has the expected points` 412 ); 413 414 const controlPoints = panel.querySelectorAll( 415 `svg.chart .control-points-group .control-point` 416 ); 417 418 is( 419 controlPoints.length, 420 expectedPoints.length, 421 `${messagePrefix} the expected number of control points were created` 422 ); 423 controlPoints.forEach((controlPoint, i) => { 424 is( 425 parseFloat(controlPoint.getAttribute("cx")), 426 expectedPoints[i][0], 427 `${messagePrefix} Control point ${i} has correct cx` 428 ); 429 is( 430 parseFloat(controlPoint.getAttribute("cy")), 431 // XXX work around floating point issues 432 Math.round((1 - expectedPoints[i][1]) * 10) / 10, 433 `${messagePrefix} Control point ${i} has correct cy` 434 ); 435 }); 436 } 437 438 /** 439 * Checks if the property in the rule view has the expected state 440 * 441 * @param {RuleView} view 442 * @param {string} selector 443 * @param {string} property 444 * @param {string} expectedLinearValue: Expected value in the rule view 445 * @param {string} expectedComputedLinearValue: Expected computed value. Defaults to expectedLinearValue. 446 * @returns {Element|null} 447 */ 448 async function checkRuleView( 449 view, 450 selector, 451 property, 452 expectedLinearValue, 453 expectedComputedLinearValue = expectedLinearValue 454 ) { 455 await waitForComputedStyleProperty( 456 selector, 457 null, 458 property, 459 expectedComputedLinearValue 460 ); 461 462 is( 463 getRuleViewProperty(view, selector, property).valueSpan.textContent, 464 expectedLinearValue, 465 `${selector} ${property} has expected value` 466 ); 467 const swatch = getRuleViewLinearEasingSwatch(view, selector, property); 468 is( 469 swatch.getAttribute("data-linear"), 470 expectedLinearValue, 471 `${selector} ${property} swatch has expected "data-linear" attribute` 472 ); 473 } 474 475 /** 476 * Returns the linear easing swatch for a rule (defined by its selector), and a property. 477 * 478 * @param {RuleView} view 479 * @param {string} selector 480 * @param {string} property 481 * @returns {Element|null} 482 */ 483 function getRuleViewLinearEasingSwatch(view, selector, property) { 484 return getRuleViewProperty(view, selector, property).valueSpan.querySelector( 485 ".inspector-lineareasingswatch" 486 ); 487 }