attributes.js (13344B)
1 /* import-globals-from common.js */ 2 3 // ////////////////////////////////////////////////////////////////////////////// 4 // Object attributes. 5 6 /** 7 * Test object attributes. 8 * 9 * @param aAccOrElmOrID [in] the accessible identifier 10 * @param aAttrs [in] the map of expected object attributes 11 * (name/value pairs) 12 * @param aSkipUnexpectedAttrs [in] points this function doesn't fail if 13 * unexpected attribute is encountered 14 * @param aTodo [in] true if this is a 'todo' 15 */ 16 function testAttrs(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs, aTodo) { 17 testAttrsInternal(aAccOrElmOrID, aAttrs, aSkipUnexpectedAttrs, null, aTodo); 18 } 19 20 /** 21 * Test object attributes that must not be present. 22 * 23 * @param aAccOrElmOrID [in] the accessible identifier 24 * @param aAbsentAttrs [in] map of attributes that should not be 25 * present (name/value pairs) 26 * @param aTodo [in] true if this is a 'todo' 27 */ 28 function testAbsentAttrs(aAccOrElmOrID, aAbsentAttrs, aTodo) { 29 testAttrsInternal(aAccOrElmOrID, {}, true, aAbsentAttrs, aTodo); 30 } 31 32 /** 33 * Test object attributes that aren't right, but should be (todo) 34 * 35 * @param aAccOrElmOrID [in] the accessible identifier 36 * @param aKey [in] attribute name 37 * @param aExpectedValue [in] expected attribute value 38 */ 39 function todoAttr(aAccOrElmOrID, aKey, aExpectedValue) { 40 testAttrs( 41 aAccOrElmOrID, 42 Object.fromEntries([[aKey, aExpectedValue]]), 43 true, 44 true 45 ); 46 } 47 48 /** 49 * Test CSS based object attributes. 50 */ 51 function testCSSAttrs(aID) { 52 var node = document.getElementById(aID); 53 var computedStyle = document.defaultView.getComputedStyle(node); 54 55 var attrs = { 56 display: computedStyle.display, 57 "text-align": computedStyle.textAlign, 58 "text-indent": computedStyle.textIndent, 59 "margin-left": computedStyle.marginLeft, 60 "margin-right": computedStyle.marginRight, 61 "margin-top": computedStyle.marginTop, 62 "margin-bottom": computedStyle.marginBottom, 63 }; 64 testAttrs(aID, attrs, true); 65 } 66 67 /** 68 * Test the accessible that it doesn't have CSS-based object attributes. 69 */ 70 function testAbsentCSSAttrs(aID) { 71 var attrs = { 72 display: "", 73 "text-align": "", 74 "text-indent": "", 75 "margin-left": "", 76 "margin-right": "", 77 "margin-top": "", 78 "margin-bottom": "", 79 }; 80 testAbsentAttrs(aID, attrs); 81 } 82 83 /** 84 * Test group object attributes (posinset, setsize and level) and 85 * nsIAccessible::groupPosition() method. 86 * 87 * @param aAccOrElmOrID [in] the ID, DOM node or accessible 88 * @param aPosInSet [in] the value of 'posinset' attribute 89 * @param aSetSize [in] the value of 'setsize' attribute 90 * @param aLevel [in, optional] the value of 'level' attribute 91 */ 92 function testGroupAttrs(aAccOrElmOrID, aPosInSet, aSetSize, aLevel, aTodo) { 93 var acc = getAccessible(aAccOrElmOrID); 94 var levelObj = {}, 95 posInSetObj = {}, 96 setSizeObj = {}; 97 acc.groupPosition(levelObj, setSizeObj, posInSetObj); 98 99 let groupPos = {}, 100 expectedGroupPos = {}; 101 102 if (aPosInSet && aSetSize) { 103 groupPos.setsize = String(setSizeObj.value); 104 groupPos.posinset = String(posInSetObj.value); 105 106 expectedGroupPos.setsize = String(aSetSize); 107 expectedGroupPos.posinset = String(aPosInSet); 108 } 109 110 if (aLevel) { 111 groupPos.level = String(levelObj.value); 112 113 expectedGroupPos.level = String(aLevel); 114 } 115 116 compareSimpleObjects( 117 groupPos, 118 expectedGroupPos, 119 false, 120 "wrong groupPos", 121 aTodo 122 ); 123 124 testAttrs(aAccOrElmOrID, expectedGroupPos, true, aTodo); 125 } 126 127 function testGroupParentAttrs( 128 aAccOrElmOrID, 129 aChildItemCount, 130 aIsHierarchical, 131 aTodo 132 ) { 133 testAttrs( 134 aAccOrElmOrID, 135 { "child-item-count": String(aChildItemCount) }, 136 true, 137 aTodo 138 ); 139 140 if (aIsHierarchical) { 141 testAttrs(aAccOrElmOrID, { tree: "true" }, true, aTodo); 142 } else { 143 testAbsentAttrs(aAccOrElmOrID, { tree: "true" }); 144 } 145 } 146 147 // ////////////////////////////////////////////////////////////////////////////// 148 // Text attributes. 149 150 /** 151 * Test text attributes. 152 * 153 * @param aID [in] the ID of DOM element having text 154 * accessible 155 * @param aOffset [in] the offset inside text accessible to fetch 156 * text attributes 157 * @param aAttrs [in] the map of expected text attributes 158 * (name/value pairs) exposed at the offset 159 * @param aDefAttrs [in] the map of expected text attributes 160 * (name/value pairs) exposed on hyper text 161 * accessible 162 * @param aStartOffset [in] expected start offset where text attributes 163 * are applied 164 * @param aEndOffset [in] expected end offset where text attribute 165 * are applied 166 * @param aSkipUnexpectedAttrs [in] points the function doesn't fail if 167 * unexpected attribute is encountered 168 */ 169 function testTextAttrs( 170 aID, 171 aOffset, 172 aAttrs, 173 aDefAttrs, 174 aStartOffset, 175 aEndOffset, 176 aSkipUnexpectedAttrs 177 ) { 178 var accessible = getAccessible(aID, [nsIAccessibleText]); 179 if (!accessible) { 180 return; 181 } 182 183 var startOffset = { value: -1 }; 184 var endOffset = { value: -1 }; 185 186 // do not include attributes exposed on hyper text accessible 187 var attrs = getTextAttributes( 188 aID, 189 accessible, 190 false, 191 aOffset, 192 startOffset, 193 endOffset 194 ); 195 196 if (!attrs) { 197 return; 198 } 199 200 var errorMsg = " for " + aID + " at offset " + aOffset; 201 202 is(startOffset.value, aStartOffset, "Wrong start offset" + errorMsg); 203 is(endOffset.value, aEndOffset, "Wrong end offset" + errorMsg); 204 205 compareAttrs(errorMsg, attrs, aAttrs, aSkipUnexpectedAttrs); 206 207 // include attributes exposed on hyper text accessible 208 var expectedAttrs = {}; 209 for (let name in aAttrs) { 210 expectedAttrs[name] = aAttrs[name]; 211 } 212 213 for (let name in aDefAttrs) { 214 if (!(name in expectedAttrs)) { 215 expectedAttrs[name] = aDefAttrs[name]; 216 } 217 } 218 219 attrs = getTextAttributes( 220 aID, 221 accessible, 222 true, 223 aOffset, 224 startOffset, 225 endOffset 226 ); 227 228 if (!attrs) { 229 return; 230 } 231 232 compareAttrs(errorMsg, attrs, expectedAttrs, aSkipUnexpectedAttrs); 233 } 234 235 /** 236 * Test default text attributes. 237 * 238 * @param aID [in] the ID of DOM element having text 239 * accessible 240 * @param aDefAttrs [in] the map of expected text attributes 241 * (name/value pairs) 242 * @param aSkipUnexpectedAttrs [in] points the function doesn't fail if 243 * unexpected attribute is encountered 244 */ 245 function testDefaultTextAttrs(aID, aDefAttrs, aSkipUnexpectedAttrs) { 246 var accessible = getAccessible(aID, [nsIAccessibleText]); 247 if (!accessible) { 248 return; 249 } 250 251 var defAttrs = null; 252 try { 253 defAttrs = accessible.defaultTextAttributes; 254 } catch (e) {} 255 256 if (!defAttrs) { 257 ok(false, "Can't get default text attributes for " + aID); 258 return; 259 } 260 261 var errorMsg = ". Getting default text attributes for " + aID; 262 compareAttrs(errorMsg, defAttrs, aDefAttrs, aSkipUnexpectedAttrs); 263 } 264 265 /** 266 * Test text attributes for wrong offset. 267 */ 268 function testTextAttrsWrongOffset(aID, aOffset) { 269 var res = false; 270 try { 271 var s = {}, 272 e = {}; 273 // Bug 1602031 274 // eslint-disable-next-line no-undef 275 var acc = getAccessible(ID, [nsIAccessibleText]); 276 acc.getTextAttributes(false, 157, s, e); 277 } catch (ex) { 278 res = true; 279 } 280 281 ok( 282 res, 283 "text attributes are calculated successfully at wrong offset " + 284 aOffset + 285 " for " + 286 prettyName(aID) 287 ); 288 } 289 290 const kNormalFontWeight = function equalsToNormal(aWeight) { 291 return aWeight <= 400; 292 }; 293 294 const kBoldFontWeight = function equalsToBold(aWeight) { 295 return aWeight > 400; 296 }; 297 298 // The pt font size of the input element can vary by Linux distro. 299 const kInputFontSize = 300 WIN || MAC 301 ? "10pt" 302 : function () { 303 return true; 304 }; 305 306 const kAbsentFontFamily = function (aFontFamily) { 307 return aFontFamily != "sans-serif"; 308 }; 309 const kInputFontFamily = function (aFontFamily) { 310 return aFontFamily != "sans-serif"; 311 }; 312 313 const kMonospaceFontFamily = function (aFontFamily) { 314 return aFontFamily != "monospace"; 315 }; 316 const kSansSerifFontFamily = function (aFontFamily) { 317 return aFontFamily != "sans-serif"; 318 }; 319 const kSerifFontFamily = function (aFontFamily) { 320 return aFontFamily != "serif"; 321 }; 322 const kMathFontFamily = function (aFontFamily) { 323 return aFontFamily != "math"; 324 }; 325 326 const kCursiveFontFamily = LINUX ? "DejaVu Serif" : "Comic Sans MS"; 327 328 /** 329 * Return used font from the given computed style. 330 */ 331 function fontFamily(aComputedStyle) { 332 var name = aComputedStyle.fontFamily; 333 switch (name) { 334 case "monospace": 335 return kMonospaceFontFamily; 336 case "sans-serif": 337 return kSansSerifFontFamily; 338 case "serif": 339 return kSerifFontFamily; 340 case "math": 341 return kMathFontFamily; 342 default: 343 return name; 344 } 345 } 346 347 /** 348 * Returns a computed system color for this document. 349 */ 350 function getSystemColor(aColor) { 351 let { r, g, b, a } = InspectorUtils.colorToRGBA(aColor); 352 return a == 1 ? `rgb(${r}, ${g}, ${b})` : `rgba(${r}, ${g}, ${b}, ${a})`; 353 } 354 355 /** 356 * Build an object of default text attributes expected for the given accessible. 357 * 358 * @param aID [in] identifier of accessible 359 * @param aFontSize [in] font size 360 * @param aFontWeight [in, optional] kBoldFontWeight or kNormalFontWeight, 361 * default value is kNormalFontWeight 362 */ 363 function buildDefaultTextAttrs(aID, aFontSize, aFontWeight, aFontFamily) { 364 var elm = getNode(aID); 365 var computedStyle = document.defaultView.getComputedStyle(elm); 366 var bgColor = 367 computedStyle.backgroundColor == "rgba(0, 0, 0, 0)" 368 ? getSystemColor("Canvas") 369 : computedStyle.backgroundColor; 370 371 var defAttrs = { 372 "font-style": computedStyle.fontStyle, 373 "font-size": aFontSize, 374 "background-color": bgColor, 375 "font-weight": aFontWeight ? aFontWeight : kNormalFontWeight, 376 color: computedStyle.color, 377 "font-family": aFontFamily ? aFontFamily : fontFamily(computedStyle), 378 "text-position": computedStyle.verticalAlign, 379 }; 380 381 return defAttrs; 382 } 383 384 // ////////////////////////////////////////////////////////////////////////////// 385 // Private. 386 387 function getTextAttributes( 388 aID, 389 aAccessible, 390 aIncludeDefAttrs, 391 aOffset, 392 aStartOffset, 393 aEndOffset 394 ) { 395 // This function expects the passed in accessible to already be queried for 396 // nsIAccessibleText. 397 var attrs = null; 398 try { 399 attrs = aAccessible.getTextAttributes( 400 aIncludeDefAttrs, 401 aOffset, 402 aStartOffset, 403 aEndOffset 404 ); 405 } catch (e) {} 406 407 if (attrs) { 408 return attrs; 409 } 410 411 ok(false, "Can't get text attributes for " + aID); 412 return null; 413 } 414 415 function testAttrsInternal( 416 aAccOrElmOrID, 417 aAttrs, 418 aSkipUnexpectedAttrs, 419 aAbsentAttrs, 420 aTodo 421 ) { 422 var accessible = getAccessible(aAccOrElmOrID); 423 if (!accessible) { 424 return; 425 } 426 427 var attrs = null; 428 try { 429 attrs = accessible.attributes; 430 } catch (e) {} 431 432 if (!attrs) { 433 ok(false, "Can't get object attributes for " + prettyName(aAccOrElmOrID)); 434 return; 435 } 436 437 var errorMsg = " for " + prettyName(aAccOrElmOrID); 438 compareAttrs( 439 errorMsg, 440 attrs, 441 aAttrs, 442 aSkipUnexpectedAttrs, 443 aAbsentAttrs, 444 aTodo 445 ); 446 } 447 448 function compareAttrs( 449 aErrorMsg, 450 aAttrs, 451 aExpectedAttrs, 452 aSkipUnexpectedAttrs, 453 aAbsentAttrs, 454 aTodo 455 ) { 456 // Check if all obtained attributes are expected and have expected value. 457 let attrObject = {}; 458 for (let prop of aAttrs.enumerate()) { 459 attrObject[prop.key] = prop.value; 460 } 461 462 // Create expected attributes set by using the return values from 463 // embedded functions to determine the entry's value. 464 let expectedObj = Object.fromEntries( 465 Object.entries(aExpectedAttrs).map(([k, v]) => { 466 if (v instanceof Function) { 467 // If value is a function that returns true given the received 468 // attribute value, assign the attribute value to the entry. 469 // If it is false, stringify the function for good error reporting. 470 let value = v(attrObject[k]) ? attrObject[k] : v.toString(); 471 return [k, value]; 472 } 473 474 return [k, v]; 475 }) 476 ); 477 478 compareSimpleObjects( 479 attrObject, 480 expectedObj, 481 aSkipUnexpectedAttrs, 482 aErrorMsg, 483 aTodo 484 ); 485 486 // Check if all unexpected attributes are absent. 487 if (aAbsentAttrs) { 488 let presentAttrs = Object.keys(attrObject).filter( 489 k => aAbsentAttrs[k] !== undefined 490 ); 491 if (presentAttrs.length) { 492 (aTodo ? todo : ok)( 493 false, 494 `There were unexpected attributes: ${presentAttrs}` 495 ); 496 } 497 } 498 } 499 500 function compareSimpleObjects( 501 aObj, 502 aExpectedObj, 503 aSkipUnexpectedAttrs, 504 aMessage, 505 aTodo 506 ) { 507 let keys = aSkipUnexpectedAttrs 508 ? Object.keys(aExpectedObj).sort() 509 : Object.keys(aObj).sort(); 510 let o1 = JSON.stringify(aObj, keys); 511 let o2 = JSON.stringify(aExpectedObj, keys); 512 513 if (aTodo) { 514 todo_is(o1, o2, `${aMessage} - Got ${o1}, expected ${o2}`); 515 } else { 516 is(o1, o2, aMessage); 517 } 518 }