browser_rules_registered-custom-properties.js (21267B)
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 registed custom properties (@property/Css.registerProperty) are displayed 7 // in a dedicated section and that they are properly reflected in the `var()` popup. 8 9 const CSS_NO_INHERIT_INITIAL_VALUE = "tomato"; 10 const CSS_INHERIT_INITIAL_VALUE = "gold"; 11 const CSS_NOT_DEFINED_INITIAL_VALUE = "purple"; 12 const JS_NO_INHERIT_INITIAL_VALUE = "42px"; 13 14 const CSS_NO_INHERIT_MAIN_VALUE = "#0000FF"; 15 const CSS_INHERIT_MAIN_VALUE = "#FF0000"; 16 const JS_NO_INHERIT_MAIN_VALUE = "100%"; 17 const JS_INHERIT_MAIN_VALUE = "50vw"; 18 19 const TEST_URI = `https://example.org/document-builder.sjs?html=${encodeURIComponent(` 20 <script> 21 CSS.registerProperty({ 22 name: "--js-no-inherit", 23 syntax: "<length>", 24 inherits: false, 25 initialValue: "${JS_NO_INHERIT_INITIAL_VALUE}", 26 }); 27 CSS.registerProperty({ 28 name: "--js-inherit", 29 syntax: "*", 30 inherits: true, 31 }); 32 </script> 33 <style> 34 @property --css-no-inherit { 35 syntax: "<color>"; 36 inherits: false; 37 initial-value: ${CSS_NO_INHERIT_INITIAL_VALUE}; 38 } 39 40 @property --css-inherit { 41 syntax: "<color>"; 42 inherits: true; 43 initial-value: ${CSS_INHERIT_INITIAL_VALUE}; 44 } 45 46 @property --css-not-defined { 47 syntax: "<color>"; 48 inherits: true; 49 initial-value: ${CSS_NOT_DEFINED_INITIAL_VALUE}; 50 } 51 52 @property --empty { 53 syntax: "*"; 54 inherits: true; 55 initial-value: ; 56 } 57 58 main { 59 --js-no-inherit: ${JS_NO_INHERIT_MAIN_VALUE}; 60 --js-inherit: ${JS_INHERIT_MAIN_VALUE}; 61 --css-no-inherit: ${CSS_NO_INHERIT_MAIN_VALUE}; 62 --css-inherit: ${CSS_INHERIT_MAIN_VALUE}; 63 } 64 65 h1 { 66 background-color: var(--css-no-inherit); 67 color: var(--css-inherit); 68 border-color: var(--css-not-defined); 69 height: var(--js-no-inherit); 70 width: var(--js-inherit); 71 outline: 10px solid var(--constructed, green); 72 text-decoration-color: var(--js-not-defined, blue); 73 caret-color: var(--css-dynamic-registered, turquoise); 74 --test-empty: var(--empty); 75 } 76 77 aside { 78 /* registered property has <color> syntax, this declaration is invalid at computed-value time */ 79 --css-inherit: dashed; 80 /* valid, complex value */ 81 --js-no-inherit: calc(100px * cos(45deg)); 82 /* based on another property */ 83 --css-dynamic-registered: var(--css-no-inherit); 84 } 85 </style> 86 <main> 87 <h1>Hello world</h1> 88 <aside>fries</aside> 89 <iframe src="https://example.com/document-builder.sjs?html=iframe"></iframe> 90 </main> 91 `)}`; 92 93 add_task(async function () { 94 await pushPref("layout.css.properties-and-values.enabled", true); 95 const tab = await addTab(TEST_URI); 96 const { inspector, view } = await openRuleView(); 97 const doc = view.styleDocument; 98 await selectNode("h1", inspector); 99 100 info("Check the content of the @property section"); 101 is( 102 doc.querySelector(".ruleview-expandable-header").textContent, 103 "@property", 104 "The @property section header is displayed" 105 ); 106 const registeredPropertiesContainer = doc.getElementById( 107 "registered-properties-container" 108 ); 109 ok(!!registeredPropertiesContainer, "The @property container is displayed"); 110 111 const expectedProperties = [ 112 { 113 header: `--css-inherit {`, 114 propertyDefinition: [ 115 ` syntax: "<color>";`, 116 ` inherits: true;`, 117 ` initial-value: ${CSS_INHERIT_INITIAL_VALUE};`, 118 ], 119 }, 120 { 121 header: `--css-no-inherit {`, 122 propertyDefinition: [ 123 ` syntax: "<color>";`, 124 ` inherits: false;`, 125 ` initial-value: ${CSS_NO_INHERIT_INITIAL_VALUE};`, 126 ], 127 }, 128 { 129 header: `--css-not-defined {`, 130 propertyDefinition: [ 131 ` syntax: "<color>";`, 132 ` inherits: true;`, 133 ` initial-value: ${CSS_NOT_DEFINED_INITIAL_VALUE};`, 134 ], 135 }, 136 { 137 header: `--empty {`, 138 propertyDefinition: [ 139 ` syntax: "*";`, 140 ` inherits: true;`, 141 ` initial-value: ;`, 142 ], 143 }, 144 { 145 header: `--js-inherit {`, 146 propertyDefinition: [ 147 ` name: "--js-inherit",`, 148 ` syntax: "*",`, 149 ` inherits: true,`, 150 ], 151 }, 152 { 153 header: `--js-no-inherit {`, 154 propertyDefinition: [ 155 ` name: "--js-no-inherit",`, 156 ` syntax: "<length>",`, 157 ` inherits: false,`, 158 ` initialValue: "${JS_NO_INHERIT_INITIAL_VALUE}",`, 159 ], 160 }, 161 ]; 162 163 checkRegisteredProperties(view, expectedProperties); 164 165 info("Check that var() tooltips handle registered properties"); 166 await assertVariableTooltipForProperty(view, "h1", "background-color", { 167 // The variable value is the initial value since the variable does not inherit 168 header: 169 // prettier-ignore 170 '<span xmlns="http://www.w3.org/1999/xhtml" data-color="tomato" class="color-swatch-container">' + 171 '<span ' + 172 'class="inspector-swatch inspector-colorswatch" ' + 173 'style="background-color:tomato">' + 174 '</span>' + 175 `<span class="ruleview-color">${CSS_NO_INHERIT_INITIAL_VALUE}</span>` + 176 '</span>', 177 registeredProperty: { 178 syntax: `"<color>"`, 179 inherits: "false", 180 "initial-value": 181 // prettier-ignore 182 '<span xmlns="http://www.w3.org/1999/xhtml" data-color="tomato" class="color-swatch-container">' + 183 '<span ' + 184 'class="inspector-swatch inspector-colorswatch" ' + 185 'style="background-color:tomato">' + 186 '</span>' + 187 `<span class="ruleview-color">${CSS_NO_INHERIT_INITIAL_VALUE}</span>` + 188 '</span>', 189 }, 190 }); 191 await assertVariableTooltipForProperty(view, "h1", "color", { 192 // The variable value is the value set in the main selector, since the variable does inherit 193 header: 194 // prettier-ignore 195 '<span xmlns="http://www.w3.org/1999/xhtml" data-color="#FF0000" class="color-swatch-container">' + 196 '<span ' + 197 'class="inspector-swatch inspector-colorswatch" ' + 198 'style="background-color:#FF0000">' + 199 '</span>' + 200 `<span class="ruleview-color">${CSS_INHERIT_MAIN_VALUE}</span>` + 201 '</span>', 202 computed: 203 // prettier-ignore 204 '<span xmlns="http://www.w3.org/1999/xhtml" data-color="rgb(255, 0, 0)" class="color-swatch-container">' + 205 '<span ' + 206 'class="inspector-swatch inspector-colorswatch" ' + 207 'style="background-color:rgb(255, 0, 0)">' + 208 '</span>' + 209 `<span class="ruleview-color">rgb(255, 0, 0)</span>` + 210 '</span>', 211 registeredProperty: { 212 syntax: `"<color>"`, 213 inherits: "true", 214 "initial-value": 215 // prettier-ignore 216 '<span xmlns="http://www.w3.org/1999/xhtml" data-color="gold" class="color-swatch-container">' + 217 '<span ' + 218 'class="inspector-swatch inspector-colorswatch" ' + 219 'style="background-color:gold">' + 220 '</span>' + 221 `<span class="ruleview-color">${CSS_INHERIT_INITIAL_VALUE}</span>` + 222 '</span>', 223 }, 224 }); 225 await assertVariableTooltipForProperty( 226 view, 227 "h1", 228 "border-color", 229 // The variable value is the initial value since the variable is not set 230 { 231 header: 232 // prettier-ignore 233 `<span xmlns="http://www.w3.org/1999/xhtml" data-color="purple" class="color-swatch-container">` + 234 `<span ` + 235 `class="inspector-swatch inspector-colorswatch" ` + 236 `style="background-color:purple">` + 237 `</span>` + 238 `<span class="ruleview-color">${CSS_NOT_DEFINED_INITIAL_VALUE}</span>` + 239 `</span>`, 240 registeredProperty: { 241 syntax: `"<color>"`, 242 inherits: "true", 243 "initial-value": 244 // prettier-ignore 245 `<span xmlns="http://www.w3.org/1999/xhtml" data-color="purple" class="color-swatch-container">` + 246 `<span ` + 247 `class="inspector-swatch inspector-colorswatch" ` + 248 `style="background-color:purple">` + 249 `</span>` + 250 `<span class="ruleview-color">${CSS_NOT_DEFINED_INITIAL_VALUE}</span>` + 251 `</span>`, 252 }, 253 } 254 ); 255 await assertVariableTooltipForProperty( 256 view, 257 "h1", 258 "height", 259 // The variable value is the initial value since the variable does not inherit 260 { 261 header: JS_NO_INHERIT_INITIAL_VALUE, 262 registeredProperty: { 263 syntax: `"<length>"`, 264 inherits: "false", 265 "initial-value": JS_NO_INHERIT_INITIAL_VALUE, 266 }, 267 } 268 ); 269 await assertVariableTooltipForProperty( 270 view, 271 "h1", 272 "width", 273 // The variable value is the value set in the main selector, since the variable does inherit 274 { 275 header: JS_INHERIT_MAIN_VALUE, 276 // Computed value isn't displayed when it's the same as we put in the header 277 computed: null, 278 registeredProperty: { syntax: `"*"`, inherits: "true" }, 279 } 280 ); 281 await assertVariableTooltipForProperty(view, "h1", "--test-empty", { 282 header: "<empty>", 283 headerClasses: ["empty-css-variable"], 284 registeredProperty: { 285 syntax: `"*"`, 286 inherits: "true", 287 "initial-value": "<empty>", 288 }, 289 }); 290 291 info( 292 "Check that registered properties from new regular stylesheets are displayed" 293 ); 294 let onRuleViewRefreshed = view.once("ruleview-refreshed"); 295 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 296 const s = content.wrappedJSObject.document.createElement("style"); 297 s.id = "added"; 298 s.textContent = ` 299 @property --css-dynamic-registered { 300 syntax: "<color>"; 301 inherits: false; 302 initial-value: orchid; 303 } 304 `; 305 306 content.wrappedJSObject.document.head.append(s); 307 }); 308 info("Wait for the new registered property to be displayed"); 309 await onRuleViewRefreshed; 310 311 checkRegisteredProperties( 312 view, 313 [ 314 ...expectedProperties, 315 { 316 header: `--css-dynamic-registered {`, 317 propertyDefinition: [ 318 ` syntax: "<color>";`, 319 ` inherits: false;`, 320 ` initial-value: orchid;`, 321 ], 322 }, 323 ].sort((a, b) => (a.header < b.header ? -1 : 1)) 324 ); 325 326 // The var() tooltip should show the initial value of the new property 327 await assertVariableTooltipForProperty(view, "h1", "caret-color", { 328 header: 329 // prettier-ignore 330 `<span xmlns="http://www.w3.org/1999/xhtml" data-color="orchid" class="color-swatch-container">` + 331 `<span ` + 332 `class="inspector-swatch inspector-colorswatch" ` + 333 `style="background-color:orchid">` + 334 `</span>` + 335 `<span class="ruleview-color">orchid</span>` + 336 `</span>`, 337 registeredProperty: { 338 syntax: `"<color>"`, 339 inherits: "false", 340 "initial-value": 341 // prettier-ignore 342 `<span xmlns="http://www.w3.org/1999/xhtml" data-color="orchid" class="color-swatch-container">` + 343 `<span ` + 344 `class="inspector-swatch inspector-colorswatch" ` + 345 `style="background-color:orchid">` + 346 `</span>` + 347 `<span class="ruleview-color">orchid</span>` + 348 `</span>`, 349 }, 350 }); 351 352 info("Check that updating property does update rules view"); 353 onRuleViewRefreshed = view.once("ruleview-refreshed"); 354 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 355 content.wrappedJSObject.document.querySelector("style#added").textContent = 356 ` 357 @property --css-dynamic-registered { 358 syntax: "<color>"; 359 inherits: true; 360 initial-value: purple; 361 } 362 `; 363 }); 364 info("Wait for the rules view to be updated"); 365 await onRuleViewRefreshed; 366 367 checkRegisteredProperties( 368 view, 369 [ 370 ...expectedProperties, 371 { 372 header: `--css-dynamic-registered {`, 373 propertyDefinition: [ 374 ` syntax: "<color>";`, 375 ` inherits: true;`, 376 ` initial-value: purple;`, 377 ], 378 }, 379 ].sort((a, b) => (a.header < b.header ? -1 : 1)) 380 ); 381 382 // The var() tooltip should show the new initial value of the updated property 383 await assertVariableTooltipForProperty(view, "h1", "caret-color", { 384 header: 385 // prettier-ignore 386 `<span xmlns="http://www.w3.org/1999/xhtml" data-color="purple" class="color-swatch-container">` + 387 `<span ` + 388 `class="inspector-swatch inspector-colorswatch" ` + 389 `style="background-color:purple">` + 390 `</span>` + 391 `<span class="ruleview-color">purple</span>` + 392 `</span>`, 393 registeredProperty: { 394 syntax: `"<color>"`, 395 inherits: "true", 396 "initial-value": 397 // prettier-ignore 398 `<span xmlns="http://www.w3.org/1999/xhtml" data-color="purple" class="color-swatch-container">` + 399 `<span ` + 400 `class="inspector-swatch inspector-colorswatch" ` + 401 `style="background-color:purple">` + 402 `</span>` + 403 `<span class="ruleview-color">purple</span>` + 404 `</span>`, 405 }, 406 }); 407 408 info("Check that removing property does update rules view"); 409 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 410 content.wrappedJSObject.document.querySelector("style#added").remove(); 411 }); 412 info("Wait for registered property to be removed"); 413 await waitFor( 414 () => 415 view.styleDocument.querySelector( 416 `[data-name="--css-dynamic-registered"]` 417 ) == null 418 ); 419 ok(true, `--css-dynamic-registered was removed`); 420 checkRegisteredProperties(view, expectedProperties); 421 422 // The var() tooltip should indicate that the property isn't set anymore 423 await assertVariableTooltipForProperty(view, "h1", "caret-color", { 424 header: `--css-dynamic-registered is not set`, 425 headerClasses: [], 426 isMatched: false, 427 }); 428 429 info( 430 "Check that registered properties from new constructed stylesheets are displayed" 431 ); 432 is( 433 getRuleViewProperty(view, "h1", "outline").valueSpan.querySelector( 434 ".inspector-unmatched" 435 ).textContent, 436 "--constructed", 437 "The --constructed variable is set as unmatched since it's not defined nor registered" 438 ); 439 440 onRuleViewRefreshed = view.once("ruleview-refreshed"); 441 await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { 442 const s = new content.wrappedJSObject.CSSStyleSheet(); 443 s.replaceSync(` 444 @property --constructed { 445 syntax: "<color>"; 446 inherits: true; 447 initial-value: aqua; 448 } 449 `); 450 content.wrappedJSObject.document.adoptedStyleSheets.push(s); 451 }); 452 await onRuleViewRefreshed; 453 454 info("Wait for the new registered property to be displayed"); 455 checkRegisteredProperties( 456 view, 457 [ 458 ...expectedProperties, 459 { 460 header: `--constructed {`, 461 propertyDefinition: [ 462 ` syntax: "<color>";`, 463 ` inherits: true;`, 464 ` initial-value: aqua;`, 465 ], 466 }, 467 ].sort((a, b) => (a.header < b.header ? -1 : 1)) 468 ); 469 470 // The `var()` tooltip should show the initial-value of the new property 471 await assertVariableTooltipForProperty(view, "h1", "outline", { 472 header: 473 // prettier-ignore 474 `<span xmlns="http://www.w3.org/1999/xhtml" data-color="aqua" class="color-swatch-container">` + 475 `<span ` + 476 `class="inspector-swatch inspector-colorswatch" ` + 477 `style="background-color:aqua">` + 478 `</span>` + 479 `<span class="ruleview-color">aqua</span>` + 480 `</span>`, 481 registeredProperty: { 482 syntax: `"<color>"`, 483 inherits: "true", 484 "initial-value": 485 // prettier-ignore 486 `<span xmlns="http://www.w3.org/1999/xhtml" data-color="aqua" class="color-swatch-container">` + 487 `<span ` + 488 `class="inspector-swatch inspector-colorswatch" ` + 489 `style="background-color:aqua">` + 490 `</span>` + 491 `<span class="ruleview-color">aqua</span>` + 492 `</span>`, 493 }, 494 }); 495 496 info( 497 "Check that selecting a node in another document with no registered property hides the container" 498 ); 499 await selectNodeInFrames(["iframe", "body"], inspector); 500 is( 501 getRegisteredPropertiesContainer(view), 502 null, 503 "registered properties container isn't displayed" 504 ); 505 506 info( 507 "Check that registering a property will cause the @property container to be displayed" 508 ); 509 const iframeBrowsingContext = await SpecialPowers.spawn( 510 tab.linkedBrowser, 511 [], 512 () => content.document.querySelector("iframe").browsingContext 513 ); 514 515 await SpecialPowers.spawn(iframeBrowsingContext, [], () => { 516 content.CSS.registerProperty({ 517 name: "--js-iframe", 518 syntax: "<color>", 519 inherits: true, 520 initialValue: "turquoise", 521 }); 522 content.CSS.registerProperty({ 523 name: "--js-inherit", 524 syntax: "*", 525 inherits: true, 526 }); 527 }); 528 529 await waitFor(() => getRegisteredPropertiesContainer(view)); 530 ok(true, "@property container is diplayed when registering a property"); 531 532 // Wait for the 2 properties to be added. 533 await waitFor(() => getRegisteredPropertiesElements(view).length == 2); 534 checkRegisteredProperties(view, [ 535 { 536 header: `--js-iframe {`, 537 propertyDefinition: [ 538 ` name: "--js-iframe",`, 539 ` syntax: "<color>",`, 540 ` inherits: true,`, 541 ` initialValue: turquoise,`, 542 ], 543 }, 544 { 545 header: `--js-inherit {`, 546 propertyDefinition: [ 547 ` name: "--js-inherit",`, 548 ` syntax: "*",`, 549 ` inherits: true,`, 550 ], 551 }, 552 ]); 553 554 info("Select a node from the top-level document"); 555 await selectNode("main", inspector); 556 557 checkRegisteredProperties( 558 view, 559 [ 560 ...expectedProperties, 561 { 562 header: `--constructed {`, 563 propertyDefinition: [ 564 ` syntax: "<color>";`, 565 ` inherits: true;`, 566 ` initial-value: aqua;`, 567 ], 568 }, 569 ].sort((a, b) => (a.header < b.header ? -1 : 1)) 570 ); 571 572 await selectNode("aside", inspector); 573 574 info( 575 "Check that the invalid at computed-value time icon is displayed when needed" 576 ); 577 checkInvalidAtComputedValueTime(view, { 578 ruleIndex: 1, 579 declaration: { "--css-inherit": "dashed" }, 580 invalid: true, 581 syntax: `<color>`, 582 }); 583 584 info( 585 "Check that the invalid at computed-value time icon is not displayed for valid properties" 586 ); 587 checkInvalidAtComputedValueTime(view, { 588 ruleIndex: 1, 589 declaration: { "--js-no-inherit": "calc(100px * cos(45deg))" }, 590 invalid: false, 591 }); 592 593 info( 594 "Declaration of variable based on other variable are not marked as invalid" 595 ); 596 checkInvalidAtComputedValueTime(view, { 597 ruleIndex: 1, 598 declaration: { "--css-dynamic-registered": "var(--css-no-inherit)" }, 599 invalid: false, 600 }); 601 }); 602 603 function checkInvalidAtComputedValueTime( 604 view, 605 { ruleIndex, declaration, invalid, syntax } 606 ) { 607 const prop = getTextProperty(view, ruleIndex, declaration); 608 const warningIcon = prop.editor.element.querySelector( 609 ".ruleview-invalid-at-computed-value-time-warning:not([hidden])" 610 ); 611 if (invalid) { 612 ok( 613 !!warningIcon, 614 `invalid at computed-value time icon is displayed for ${JSON.stringify( 615 declaration 616 )}` 617 ); 618 is( 619 warningIcon?.title, 620 `Property value does not match expected "${syntax}" syntax`, 621 `invalid at computed-value time icon has expected title for ${JSON.stringify( 622 declaration 623 )}` 624 ); 625 } else { 626 ok( 627 !warningIcon, 628 `invalid at computed-value time icon is not displayed for ${JSON.stringify( 629 declaration 630 )}` 631 ); 632 } 633 } 634 635 function getRegisteredPropertiesContainer(view) { 636 return view.styleDocument.querySelector("#registered-properties-container"); 637 } 638 639 function getRegisteredPropertiesElements(view) { 640 const container = getRegisteredPropertiesContainer(view); 641 if (!container) { 642 return []; 643 } 644 645 return Array.from( 646 container.querySelectorAll( 647 "#registered-properties-container .ruleview-rule" 648 ) 649 ); 650 } 651 652 function checkRegisteredProperties(view, expectedProperties) { 653 const registeredPropertiesEl = getRegisteredPropertiesElements(view); 654 655 is( 656 registeredPropertiesEl.length, 657 expectedProperties.length, 658 "There are the expected number of registered properties" 659 ); 660 for (let i = 0; i < expectedProperties.length; i++) { 661 info(`Checking registered property #${i}`); 662 const { header, propertyDefinition } = expectedProperties[i]; 663 const registeredPropertyEl = registeredPropertiesEl[i]; 664 665 is( 666 registeredPropertyEl.querySelector("header").textContent, 667 header, 668 `Registered property #${i} has the expected header text` 669 ); 670 const propertyDefinitionEl = Array.from( 671 registeredPropertyEl.querySelectorAll("div[role=listitem]") 672 ); 673 is( 674 propertyDefinitionEl.length, 675 propertyDefinition.length, 676 `Registered property #${i} have the expected number of items in its definition` 677 ); 678 for (let j = 0; j < expectedProperties.length; j++) { 679 is( 680 propertyDefinitionEl[j]?.textContent, 681 propertyDefinition[j], 682 `Registered property #${i} have the expected definition at index #${j}` 683 ); 684 } 685 } 686 }