browser_rules_edit-size-property-dragging.js (13905B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Test that increasing / decreasing values in rule view by dragging with 7 // the mouse works correctly. 8 9 const TEST_URI = ` 10 <style> 11 #test { 12 padding-top: 10px; 13 margin-top: unset; 14 margin-bottom: 0px; 15 width: 0px; 16 border: 1px solid red; 17 line-height: 2; 18 border-width: var(--12px); 19 max-height: +10.2e3vmin; 20 min-height: 1% !important; 21 font-size: 10Q; 22 transform: rotate(45deg); 23 margin-left: 28.3em; 24 animation-delay: +15s; 25 margin-right: -2px; 26 padding-bottom: .9px; 27 rotate: 90deg; 28 } 29 </style> 30 <div id="test"></div> 31 `; 32 33 const DRAGGABLE_VALUE_CLASSNAME = "ruleview-propertyvalue-draggable"; 34 35 add_task(async function () { 36 await pushPref("devtools.inspector.draggable_properties", true); 37 38 await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); 39 40 const { inspector, view } = await openRuleView(); 41 await selectNode("#test", inspector); 42 43 testDraggingClassIsAddedWhenNeeded(view); 44 45 // Check that toggling the feature updates the UI immediately. 46 await pushPref("devtools.inspector.draggable_properties", false); 47 testDraggingClassIsRemovedAfterPrefChange(view); 48 49 await pushPref("devtools.inspector.draggable_properties", true); 50 testDraggingClassIsAddedWhenNeeded(view); 51 52 await testDraggingClassIsAddedOnValueUpdate(view); 53 await testPressingEscapeWhileDragging(view); 54 await testPressingEscapeWhileDraggingWithinDeadzone(view); 55 await testUpdateDisabledValue(view); 56 await testWidthIncrements(view); 57 // This needs to happen last as dragging angles are causing further trouble. 58 // This will be fixed as part of Bug 1885232. 59 await testIncrementAngleValue(view); 60 }); 61 62 const PROPERTIES = [ 63 { 64 name: "border", 65 value: "1px solid red", 66 shouldBeDraggable: false, 67 }, 68 { 69 name: "line-height", 70 value: "2", 71 shouldBeDraggable: false, 72 }, 73 { 74 name: "border-width", 75 value: "var(--12px)", 76 shouldBeDraggable: false, 77 }, 78 { 79 name: "transform", 80 value: "rotate(45deg)", 81 shouldBeDraggable: false, 82 }, 83 { 84 name: "max-height", 85 value: "+10.2e3vmin", 86 shouldBeDraggable: true, 87 }, 88 { 89 name: "min-height", 90 value: "1%", 91 shouldBeDraggable: true, 92 }, 93 { 94 name: "font-size", 95 value: "10Q", 96 shouldBeDraggable: true, 97 }, 98 { 99 name: "margin-left", 100 value: "28.3em", 101 shouldBeDraggable: true, 102 }, 103 { 104 name: "animation-delay", 105 value: "+15s", 106 shouldBeDraggable: true, 107 }, 108 { 109 name: "margin-right", 110 value: "-2px", 111 shouldBeDraggable: true, 112 }, 113 { 114 name: "padding-bottom", 115 value: ".9px", 116 shouldBeDraggable: true, 117 }, 118 { 119 name: "rotate", 120 value: "90deg", 121 shouldBeDraggable: true, 122 }, 123 ]; 124 125 function testDraggingClassIsAddedWhenNeeded(view) { 126 info("Testing class is added or not on different property values"); 127 runIsDraggableTest(view, PROPERTIES); 128 } 129 130 function testDraggingClassIsRemovedAfterPrefChange(view) { 131 info("Testing class is removed if the feature is disabled"); 132 runIsDraggableTest( 133 view, 134 // Create a temporary copy of the test PROPERTIES, where shouldBeDraggable is 135 // always false. 136 PROPERTIES.map(prop => 137 Object.assign({}, prop, { shouldBeDraggable: false }) 138 ) 139 ); 140 } 141 142 async function testIncrementAngleValue(view) { 143 info("Testing updating an angle value with the angle swatch span"); 144 const rotatePropEditor = getTextProperty(view, 1, { 145 rotate: "90deg", 146 }).editor; 147 await runIncrementTest(rotatePropEditor, view, [ 148 { 149 startValue: "90deg", 150 expectedEndValue: "100deg", 151 distance: 10, 152 description: "updating angle value", 153 }, 154 ]); 155 } 156 157 async function testPressingEscapeWhileDragging(view) { 158 info("Testing pressing escape while dragging with mouse"); 159 const marginPropEditor = getTextProperty(view, 1, { 160 "margin-bottom": "0px", 161 }).editor; 162 await runIncrementTest(marginPropEditor, view, [ 163 { 164 startValue: "0px", 165 expectedEndValue: "0px", 166 expectedEndValueBeforeEscape: "100px", 167 escape: true, 168 distance: 100, 169 description: "Pressing escape to check if value has been reset", 170 }, 171 ]); 172 } 173 174 async function testPressingEscapeWhileDraggingWithinDeadzone(view) { 175 info("Testing pressing escape while dragging with mouse within the deadzone"); 176 const marginPropEditor = getTextProperty(view, 1, { 177 "margin-bottom": "0px", 178 }).editor; 179 await runIncrementTest(marginPropEditor, view, [ 180 { 181 startValue: "0px", 182 expectedEndValue: "0px", 183 expectedEndValueBeforeEscape: "0px", 184 escape: true, 185 deadzoneIncluded: true, 186 distance: marginPropEditor._DRAGGING_DEADZONE_DISTANCE - 1, 187 description: "Pressing escape to check if value has been reset", 188 }, 189 ]); 190 } 191 192 async function testUpdateDisabledValue(view) { 193 info("Testing updating a disabled value by dragging mouse"); 194 195 const textProperty = getTextProperty(view, 1, { "padding-top": "10px" }); 196 const editor = textProperty.editor; 197 198 await togglePropStatus(view, textProperty); 199 ok(!editor.enable.checked, "Should be disabled"); 200 await runIncrementTest(editor, view, [ 201 { 202 startValue: "10px", 203 expectedEndValue: "110px", 204 distance: 100, 205 description: "Updating disabled value", 206 }, 207 ]); 208 ok(editor.enable.checked, "Should be enabled"); 209 } 210 211 async function testWidthIncrements(view) { 212 info("Testing dragging the mouse on the width property"); 213 214 const marginPropEditor = getTextProperty(view, 1, { width: "0px" }).editor; 215 await runIncrementTest(marginPropEditor, view, [ 216 { 217 startValue: "0px", 218 expectedEndValue: "20px", 219 distance: 20, 220 description: "Increasing value while dragging", 221 }, 222 { 223 startValue: "20px", 224 expectedEndValue: "0px", 225 distance: -20, 226 description: "Decreasing value while dragging", 227 }, 228 { 229 startValue: "0px", 230 expectedEndValue: "2px", 231 ...getSmallIncrementKey(), 232 distance: 20, 233 description: 234 "Increasing value with small increments by pressing ctrl or alt", 235 }, 236 { 237 startValue: "2px", 238 expectedEndValue: "202px", 239 shift: true, 240 distance: 20, 241 description: "Increasing value with large increments by pressing shift", 242 }, 243 { 244 startValue: "202px", 245 expectedEndValue: "402px", 246 distance: 200, 247 description: "Increasing value with long distance", 248 }, 249 { 250 startValue: "402px", 251 expectedEndValue: "402px", 252 distance: marginPropEditor._DRAGGING_DEADZONE_DISTANCE - 1, 253 description: "No change in the deadzone (positive value)", 254 deadzoneIncluded: true, 255 }, 256 { 257 startValue: "402px", 258 expectedEndValue: "402px", 259 distance: -1 * (marginPropEditor._DRAGGING_DEADZONE_DISTANCE - 1), 260 description: "No change in the deadzone (negative value)", 261 deadzoneIncluded: true, 262 }, 263 { 264 startValue: "402px", 265 expectedEndValue: "403px", 266 distance: marginPropEditor._DRAGGING_DEADZONE_DISTANCE + 1, 267 description: "Changed by 1 when leaving the deadzone (positive value)", 268 deadzoneIncluded: true, 269 }, 270 { 271 startValue: "403px", 272 expectedEndValue: "402px", 273 distance: -1 * (marginPropEditor._DRAGGING_DEADZONE_DISTANCE + 1), 274 description: "Changed by 1 when leaving the deadzone (negative value)", 275 deadzoneIncluded: true, 276 }, 277 ]); 278 } 279 280 async function testDraggingClassIsAddedOnValueUpdate(view) { 281 info("Testing dragging class is added when a supported unit is detected"); 282 283 const editor = getTextProperty(view, 1, { "margin-top": "unset" }).editor; 284 const valueSpan = editor.valueSpan; 285 ok( 286 !valueSpan.classList.contains(DRAGGABLE_VALUE_CLASSNAME), 287 "Should not be draggable" 288 ); 289 valueSpan.scrollIntoView(); 290 await setProperty(view, editor.prop, "23em"); 291 ok( 292 valueSpan.classList.contains(DRAGGABLE_VALUE_CLASSNAME), 293 "Should be draggable" 294 ); 295 } 296 297 /** 298 * Runs each test and check whether or not the property is draggable 299 * 300 * @param {CSSRuleView} view 301 * @param {{name: string, value: string, shouldBeDraggable: boolean}[]} tests 302 */ 303 function runIsDraggableTest(view, tests) { 304 for (const test of tests) { 305 const property = test; 306 info(`Testing ${property.name} with value ${property.value}`); 307 const editor = getTextProperty(view, 1, { 308 [property.name]: property.value, 309 }).editor; 310 const valueSpan = editor.valueSpan; 311 if (property.shouldBeDraggable) { 312 ok( 313 valueSpan.classList.contains(DRAGGABLE_VALUE_CLASSNAME), 314 "Should be draggable" 315 ); 316 } else { 317 ok( 318 !valueSpan.classList.contains(DRAGGABLE_VALUE_CLASSNAME), 319 "Should not be draggable" 320 ); 321 } 322 } 323 } 324 325 /** 326 * Runs each test in the tests array by synthesizing a mouse dragging 327 * 328 * @param {TextPropertyEditor} editor 329 * @param {CSSRuleView} view 330 * @param {Array} tests 331 */ 332 async function runIncrementTest(editor, view, tests) { 333 for (const test of tests) { 334 await testIncrement(editor, test, view); 335 } 336 view.debounce.flush(); 337 } 338 339 /** 340 * Runs an increment test 341 * 342 * 1. We initialize the TextProperty value with "startValue" 343 * 2. We synthesize a mouse dragging of "distance" length 344 * 3. We check the value of TextProperty is equal to "expectedEndValue" 345 * 346 * @param {TextPropertyEditor} editor 347 * @param {Array} options 348 * @param {string} options.startValue 349 * @param {string} options.expectedEndValue 350 * @param {boolean} options.shift Whether or not we press the shift key 351 * @param {number} options.distance Distance of the dragging 352 * @param {string} options.description 353 * @param {boolean} options.ctrl Small increment key 354 * @param {boolean} options.alt Small increment key for macosx 355 * @param {boolean} options.deadzoneIncluded True if the provided distance 356 * accounts for the deadzone. When false, the deadzone will automatically 357 * be added to the distance. 358 */ 359 async function testIncrement(editor, options) { 360 info("Running subtest: " + options.description); 361 362 editor.valueSpan.scrollIntoView(); 363 await setProperty(editor.ruleView, editor.prop, options.startValue); 364 365 is( 366 editor.prop.value, 367 options.startValue, 368 "Value initialized at " + options.startValue 369 ); 370 371 const onMouseUp = once(editor.valueSpan, "mouseup"); 372 373 await synthesizeMouseDragging(editor, options.distance, options); 374 375 // mouseup event not triggered when escape is pressed 376 if (!options.escape) { 377 info("Waiting mouseup"); 378 await onMouseUp; 379 info("Received mouseup"); 380 } 381 382 is( 383 editor.prop.value, 384 options.expectedEndValue, 385 "Value changed to " + editor.prop.value 386 ); 387 } 388 389 /** 390 * Synthesizes mouse dragging (mousedown + mousemove + mouseup) 391 * 392 * @param {TextPropertyEditor} editor 393 * @param {number} distance length of the horizontal dragging (negative if dragging left) 394 * @param {object} option 395 * @param {boolean} option.escape 396 * @param {boolean} option.alt 397 * @param {boolean} option.shift 398 * @param {boolean} option.ctrl 399 * @param {boolean} option.deadzoneIncluded 400 */ 401 async function synthesizeMouseDragging(editor, distance, options = {}) { 402 info(`Start to synthesize mouse dragging (from ${1} to ${1 + distance})`); 403 404 const styleWindow = editor.ruleView.styleWindow; 405 const elm = editor.valueSpan; 406 const startPosition = [1, 1]; 407 408 // Handle the pixel based deadzone. 409 const deadzone = editor._DRAGGING_DEADZONE_DISTANCE; 410 if (!options.deadzoneIncluded) { 411 // Most tests do not care about the deadzone and the provided distance does 412 // not account for the deadzone. Add it automatically. 413 distance = distance + Math.sign(distance) * deadzone; 414 } 415 const updateExpected = Math.abs(options.distance) > deadzone; 416 417 const endPosition = [startPosition[0] + distance, startPosition[1]]; 418 419 EventUtils.synthesizeMouse( 420 elm, 421 startPosition[0], 422 startPosition[1], 423 { type: "mousedown" }, 424 styleWindow 425 ); 426 427 // If the drag will not trigger any update, simply wait for 100ms. 428 // Otherwise, wait for the next property-updated-by-dragging event and the rule view change. 429 const updated = updateExpected 430 ? Promise.all([ 431 editor.ruleView.once("ruleview-changed"), 432 editor.ruleView.once("property-updated-by-dragging"), 433 ]) 434 : wait(100); 435 436 EventUtils.synthesizeMouse( 437 elm, 438 endPosition[0], 439 endPosition[1], 440 { 441 type: "mousemove", 442 shiftKey: !!options.shift, 443 ctrlKey: !!options.ctrl, 444 altKey: !!options.alt, 445 }, 446 styleWindow 447 ); 448 449 // We wait because the mousemove event listener is throttled to 30ms 450 // in the TextPropertyEditor class 451 info("waiting for event property-updated-by-dragging"); 452 await updated; 453 ok(true, "received event property-updated-by-dragging"); 454 455 if (options.escape) { 456 is( 457 editor.prop.value, 458 options.expectedEndValueBeforeEscape, 459 "testing value before pressing escape" 460 ); 461 const onRuleViewChanged = updateExpected 462 ? editor.ruleView.once("ruleview-changed") 463 : null; 464 EventUtils.synthesizeKey("VK_ESCAPE", {}, styleWindow); 465 await onRuleViewChanged; 466 } 467 468 EventUtils.synthesizeMouse( 469 elm, 470 endPosition[0], 471 endPosition[1], 472 { 473 type: "mouseup", 474 }, 475 styleWindow 476 ); 477 478 // If the drag did not trigger any update, mouseup might open an inline editor. 479 // Leave the editor. 480 const inplaceEditor = styleWindow.document.querySelector( 481 ".styleinspector-propertyeditor" 482 ); 483 if (inplaceEditor) { 484 const onBlur = once(inplaceEditor, "blur"); 485 EventUtils.synthesizeKey("VK_ESCAPE", {}, styleWindow); 486 await onBlur; 487 } 488 489 info("Finish to synthesize mouse dragging"); 490 } 491 492 function getSmallIncrementKey() { 493 if (AppConstants.platform === "macosx") { 494 return { alt: true }; 495 } 496 return { ctrl: true }; 497 }