common.js (26824B)
1 // This file has circular dependencies that may require other files. Rather 2 // than use import-globals-from, we list the globals individually here to save 3 // confusing ESLint. 4 // actions.js 5 /* globals testActionNames */ 6 // attributes.js 7 /* globals testAttrs, testAbsentAttrs, testTextAttrs */ 8 // relations.js 9 /* globals testRelation */ 10 // role.js 11 /* globals isRole */ 12 // state.js 13 /* globals testStates */ 14 15 // ////////////////////////////////////////////////////////////////////////////// 16 // Interfaces 17 18 const nsIAccessibilityService = Ci.nsIAccessibilityService; 19 20 const nsIAccessibleEvent = Ci.nsIAccessibleEvent; 21 const nsIAccessibleStateChangeEvent = Ci.nsIAccessibleStateChangeEvent; 22 const nsIAccessibleCaretMoveEvent = Ci.nsIAccessibleCaretMoveEvent; 23 const nsIAccessibleScrollingEvent = Ci.nsIAccessibleScrollingEvent; 24 const nsIAccessibleTextChangeEvent = Ci.nsIAccessibleTextChangeEvent; 25 const nsIAccessibleTextSelectionChangeEvent = 26 Ci.nsIAccessibleTextSelectionChangeEvent; 27 const nsIAccessibleObjectAttributeChangedEvent = 28 Ci.nsIAccessibleObjectAttributeChangedEvent; 29 const nsIAccessibleAnnouncementEvent = Ci.nsIAccessibleAnnouncementEvent; 30 31 const nsIAccessibleStates = Ci.nsIAccessibleStates; 32 const nsIAccessibleRole = Ci.nsIAccessibleRole; 33 const nsIAccessibleScrollType = Ci.nsIAccessibleScrollType; 34 const nsIAccessibleCoordinateType = Ci.nsIAccessibleCoordinateType; 35 36 const nsIAccessibleRelation = Ci.nsIAccessibleRelation; 37 const nsIAccessibleTextRange = Ci.nsIAccessibleTextRange; 38 39 const nsIAccessible = Ci.nsIAccessible; 40 41 const nsIAccessibleDocument = Ci.nsIAccessibleDocument; 42 const nsIAccessibleApplication = Ci.nsIAccessibleApplication; 43 44 const nsIAccessibleText = Ci.nsIAccessibleText; 45 const nsIAccessibleEditableText = Ci.nsIAccessibleEditableText; 46 47 const nsIAccessibleHyperLink = Ci.nsIAccessibleHyperLink; 48 const nsIAccessibleHyperText = Ci.nsIAccessibleHyperText; 49 50 const nsIAccessibleImage = Ci.nsIAccessibleImage; 51 const nsIAccessiblePivot = Ci.nsIAccessiblePivot; 52 const nsIAccessibleSelectable = Ci.nsIAccessibleSelectable; 53 const nsIAccessibleTable = Ci.nsIAccessibleTable; 54 const nsIAccessibleTableCell = Ci.nsIAccessibleTableCell; 55 const nsIAccessibleTraversalRule = Ci.nsIAccessibleTraversalRule; 56 const nsIAccessibleValue = Ci.nsIAccessibleValue; 57 58 const nsIObserverService = Ci.nsIObserverService; 59 60 const nsIDOMWindow = Ci.nsIDOMWindow; 61 62 const nsIPropertyElement = Ci.nsIPropertyElement; 63 64 // ////////////////////////////////////////////////////////////////////////////// 65 // OS detect 66 67 const MAC = navigator.platform.includes("Mac"); 68 const LINUX = navigator.platform.includes("Linux"); 69 const SOLARIS = navigator.platform.includes("SunOS"); 70 const WIN = navigator.platform.includes("Win"); 71 72 // ////////////////////////////////////////////////////////////////////////////// 73 // Application detect 74 // Firefox is assumed by default. 75 76 const SEAMONKEY = navigator.userAgent.match(/ SeaMonkey\//); 77 78 // ////////////////////////////////////////////////////////////////////////////// 79 // Accessible general 80 81 const STATE_BUSY = nsIAccessibleStates.STATE_BUSY; 82 83 const SCROLL_TYPE_TOP_EDGE = nsIAccessibleScrollType.SCROLL_TYPE_TOP_EDGE; 84 const SCROLL_TYPE_ANYWHERE = nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE; 85 86 const COORDTYPE_SCREEN_RELATIVE = 87 nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE; 88 const COORDTYPE_WINDOW_RELATIVE = 89 nsIAccessibleCoordinateType.COORDTYPE_WINDOW_RELATIVE; 90 const COORDTYPE_PARENT_RELATIVE = 91 nsIAccessibleCoordinateType.COORDTYPE_PARENT_RELATIVE; 92 93 const kEmbedChar = String.fromCharCode(0xfffc); 94 95 const kDiscBulletChar = String.fromCharCode(0x2022); 96 const kDiscBulletText = kDiscBulletChar + " "; 97 const kCircleBulletText = String.fromCharCode(0x25e6) + " "; 98 const kSquareBulletText = String.fromCharCode(0x25aa) + " "; 99 100 const MAX_TRIM_LENGTH = 100; 101 102 /** 103 * Services to determine if e10s is enabled. 104 */ 105 106 /** 107 * nsIAccessibilityService service. 108 */ 109 var gAccService = Cc["@mozilla.org/accessibilityService;1"].getService( 110 nsIAccessibilityService 111 ); 112 113 /** 114 * Enable/disable logging. 115 */ 116 function enableLogging(aModules) { 117 gAccService.setLogging(aModules); 118 } 119 function disableLogging() { 120 gAccService.setLogging(""); 121 } 122 function isLogged(aModule) { 123 return gAccService.isLogged(aModule); 124 } 125 126 /** 127 * Dumps the accessible tree into console. 128 */ 129 function dumpTree(aId, aMsg) { 130 function dumpTreeIntl(acc, indent) { 131 dump(indent + prettyName(acc) + "\n"); 132 133 var children = acc.children; 134 for (var i = 0; i < children.length; i++) { 135 var child = children.queryElementAt(i, nsIAccessible); 136 dumpTreeIntl(child, indent + " "); 137 } 138 } 139 140 function dumpDOMTreeIntl(node, indent) { 141 dump(indent + prettyName(node) + "\n"); 142 143 var children = node.childNodes; 144 for (var i = 0; i < children.length; i++) { 145 var child = children.item(i); 146 dumpDOMTreeIntl(child, indent + " "); 147 } 148 } 149 150 dump(aMsg + "\n"); 151 var root = getAccessible(aId); 152 dumpTreeIntl(root, " "); 153 154 dump("DOM tree:\n"); 155 dumpDOMTreeIntl(getNode(aId), " "); 156 } 157 158 /** 159 * Invokes the given function when document is loaded and focused. Preferable 160 * to mochitests 'addLoadEvent' function -- additionally ensures state of the 161 * document accessible is not busy. 162 * 163 * @param aFunc the function to invoke 164 */ 165 function addA11yLoadEvent(aFunc, aWindow) { 166 function waitForDocLoad() { 167 window.setTimeout(function () { 168 var targetDocument = aWindow ? aWindow.document : document; 169 var accDoc = getAccessible(targetDocument); 170 var state = {}; 171 accDoc.getState(state, {}); 172 if (state.value & STATE_BUSY) { 173 waitForDocLoad(); 174 return; 175 } 176 177 window.setTimeout(aFunc, 0); 178 }, 0); 179 } 180 181 if ( 182 aWindow && 183 aWindow.document.activeElement && 184 aWindow.document.activeElement.localName == "browser" 185 ) { 186 waitForDocLoad(); 187 } else { 188 SimpleTest.waitForFocus(waitForDocLoad, aWindow); 189 } 190 } 191 192 /** 193 * Analogy of SimpleTest.is function used to compare objects. 194 */ 195 function isObject(aObj, aExpectedObj, aMsg) { 196 if (aObj == aExpectedObj) { 197 ok(true, aMsg); 198 return; 199 } 200 201 ok( 202 false, 203 aMsg + 204 " - got '" + 205 prettyName(aObj) + 206 "', expected '" + 207 prettyName(aExpectedObj) + 208 "'" 209 ); 210 } 211 212 /** 213 * is() function checking the expected value is within the range. 214 */ 215 function isWithin(aExpected, aGot, aWithin, aMsg) { 216 if (Math.abs(aGot - aExpected) <= aWithin) { 217 ok(true, `${aMsg} - Got ${aGot}`); 218 } else { 219 ok( 220 false, 221 `${aMsg} - Got ${aGot}, expected ${aExpected} with error of ${aWithin}` 222 ); 223 } 224 } 225 226 // ////////////////////////////////////////////////////////////////////////////// 227 // Helpers for getting DOM node/accessible 228 229 /** 230 * Return the DOM node by identifier (may be accessible, DOM node or ID). 231 */ 232 function getNode(aAccOrNodeOrID, aDocument) { 233 if (!aAccOrNodeOrID) { 234 return null; 235 } 236 237 if (Node.isInstance(aAccOrNodeOrID)) { 238 return aAccOrNodeOrID; 239 } 240 241 if (aAccOrNodeOrID instanceof nsIAccessible) { 242 return aAccOrNodeOrID.DOMNode; 243 } 244 245 var node = (aDocument || document).getElementById(aAccOrNodeOrID); 246 if (!node) { 247 ok(false, "Can't get DOM element for " + aAccOrNodeOrID); 248 return null; 249 } 250 251 return node; 252 } 253 254 /** 255 * Constants indicates getAccessible doesn't fail if there is no accessible. 256 */ 257 const DONOTFAIL_IF_NO_ACC = 1; 258 259 /** 260 * Constants indicates getAccessible won't fail if accessible doesn't implement 261 * the requested interfaces. 262 */ 263 const DONOTFAIL_IF_NO_INTERFACE = 2; 264 265 /** 266 * Return accessible for the given identifier (may be ID attribute or DOM 267 * element or accessible object) or null. 268 * 269 * @param aAccOrElmOrID [in] identifier to get an accessible implementing 270 * the given interfaces 271 * @param aInterfaces [in, optional] the interface or an array interfaces 272 * to query it/them from obtained accessible 273 * @param aElmObj [out, optional] object to store DOM element which 274 * accessible is obtained for 275 * @param aDoNotFailIf [in, optional] no error for special cases (see 276 * constants above) 277 */ 278 function getAccessible(aAccOrElmOrID, aInterfaces, aElmObj, aDoNotFailIf) { 279 if (!aAccOrElmOrID) { 280 return null; 281 } 282 283 var elm = null; 284 285 if (aAccOrElmOrID instanceof nsIAccessible) { 286 try { 287 elm = aAccOrElmOrID.DOMNode; 288 } catch (e) {} 289 } else if (Node.isInstance(aAccOrElmOrID)) { 290 elm = aAccOrElmOrID; 291 } else { 292 elm = document.getElementById(aAccOrElmOrID); 293 if (!elm) { 294 ok(false, "Can't get DOM element for " + aAccOrElmOrID); 295 return null; 296 } 297 } 298 299 if (aElmObj && typeof aElmObj == "object") { 300 aElmObj.value = elm; 301 } 302 303 var acc = aAccOrElmOrID instanceof nsIAccessible ? aAccOrElmOrID : null; 304 if (!acc) { 305 try { 306 acc = gAccService.getAccessibleFor(elm); 307 } catch (e) {} 308 309 if (!acc) { 310 if (!(aDoNotFailIf & DONOTFAIL_IF_NO_ACC)) { 311 ok(false, "Can't get accessible for " + prettyName(aAccOrElmOrID)); 312 } 313 314 return null; 315 } 316 } 317 318 if (!aInterfaces) { 319 return acc; 320 } 321 322 if (!(aInterfaces instanceof Array)) { 323 aInterfaces = [aInterfaces]; 324 } 325 326 for (var index = 0; index < aInterfaces.length; index++) { 327 if (acc instanceof aInterfaces[index]) { 328 continue; 329 } 330 try { 331 acc.QueryInterface(aInterfaces[index]); 332 } catch (e) { 333 if (!(aDoNotFailIf & DONOTFAIL_IF_NO_INTERFACE)) { 334 ok( 335 false, 336 "Can't query " + aInterfaces[index] + " for " + aAccOrElmOrID 337 ); 338 } 339 340 return null; 341 } 342 } 343 344 return acc; 345 } 346 347 /** 348 * Return true if the given identifier has an accessible, or exposes the wanted 349 * interfaces. 350 */ 351 function isAccessible(aAccOrElmOrID, aInterfaces) { 352 return !!getAccessible( 353 aAccOrElmOrID, 354 aInterfaces, 355 null, 356 DONOTFAIL_IF_NO_ACC | DONOTFAIL_IF_NO_INTERFACE 357 ); 358 } 359 360 /** 361 * Return an accessible that contains the DOM node for the given identifier. 362 */ 363 function getContainerAccessible(aAccOrElmOrID) { 364 var node = getNode(aAccOrElmOrID); 365 if (!node) { 366 return null; 367 } 368 369 // eslint-disable-next-line no-empty 370 while ((node = node.parentNode) && !isAccessible(node)) {} 371 return node ? getAccessible(node) : null; 372 } 373 374 /** 375 * Return root accessible for the given identifier. 376 */ 377 function getRootAccessible(aAccOrElmOrID) { 378 var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document); 379 return acc ? acc.rootDocument.QueryInterface(nsIAccessible) : null; 380 } 381 382 /** 383 * Return tab document accessible the given accessible is contained by. 384 */ 385 function getTabDocAccessible(aAccOrElmOrID) { 386 var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document); 387 388 var docAcc = acc.document.QueryInterface(nsIAccessible); 389 var containerDocAcc = docAcc.parent.document; 390 391 // Test is running is stand-alone mode. 392 if (acc.rootDocument == containerDocAcc) { 393 return docAcc; 394 } 395 396 // In the case of running all tests together. 397 return containerDocAcc.QueryInterface(nsIAccessible); 398 } 399 400 /** 401 * Return application accessible. 402 */ 403 function getApplicationAccessible() { 404 return gAccService 405 .getApplicationAccessible() 406 .QueryInterface(nsIAccessibleApplication); 407 } 408 409 /** 410 * A version of accessible tree testing, doesn't fail if tree is not complete. 411 */ 412 function testElm(aID, aTreeObj) { 413 testAccessibleTree(aID, aTreeObj, kSkipTreeFullCheck); 414 } 415 416 /** 417 * Flags used for testAccessibleTree 418 */ 419 const kSkipTreeFullCheck = 1; 420 421 /** 422 * Compare expected and actual accessibles trees. 423 * 424 * @param aAccOrElmOrID [in] accessible identifier 425 * @param aAccTree [in] JS object, each field corresponds to property of 426 * accessible object. Additionally special properties 427 * are presented: 428 * children - an array of JS objects representing 429 * children of accessible 430 * states - an object having states and extraStates 431 * fields 432 * @param aFlags [in, optional] flags, see constants above 433 */ 434 // eslint-disable-next-line complexity 435 function testAccessibleTree(aAccOrElmOrID, aAccTree, aFlags) { 436 var acc = getAccessible(aAccOrElmOrID); 437 if (!acc) { 438 return; 439 } 440 441 var accTree = aAccTree; 442 443 // Support of simplified accessible tree object. 444 accTree = normalizeAccTreeObj(accTree); 445 446 // Test accessible properties. 447 for (var prop in accTree) { 448 var msg = 449 "Wrong value of property '" + prop + "' for " + prettyName(acc) + "."; 450 451 switch (prop) { 452 case "actions": { 453 testActionNames(acc, accTree.actions); 454 break; 455 } 456 457 case "attributes": 458 testAttrs(acc, accTree[prop], true); 459 break; 460 461 case "absentAttributes": 462 testAbsentAttrs(acc, accTree[prop]); 463 break; 464 465 case "interfaces": { 466 var ifaces = 467 accTree[prop] instanceof Array ? accTree[prop] : [accTree[prop]]; 468 for (let i = 0; i < ifaces.length; i++) { 469 ok( 470 acc instanceof ifaces[i], 471 "No " + ifaces[i] + " interface on " + prettyName(acc) 472 ); 473 } 474 break; 475 } 476 477 case "relations": { 478 for (var rel in accTree[prop]) { 479 testRelation(acc, window[rel], accTree[prop][rel]); 480 } 481 break; 482 } 483 484 case "role": 485 isRole(acc, accTree[prop], msg); 486 break; 487 488 case "states": 489 case "extraStates": 490 case "absentStates": 491 case "absentExtraStates": { 492 testStates( 493 acc, 494 accTree.states, 495 accTree.extraStates, 496 accTree.absentStates, 497 accTree.absentExtraStates 498 ); 499 break; 500 } 501 502 case "tagName": 503 is(accTree[prop], acc.DOMNode.tagName, msg); 504 break; 505 506 case "textAttrs": { 507 var prevOffset = -1; 508 for (var offset in accTree[prop]) { 509 if (prevOffset != -1) { 510 let attrs = accTree[prop][prevOffset]; 511 testTextAttrs( 512 acc, 513 prevOffset, 514 attrs, 515 {}, 516 prevOffset, 517 +offset, 518 true 519 ); 520 } 521 prevOffset = +offset; 522 } 523 524 if (prevOffset != -1) { 525 var charCount = getAccessible(acc, [ 526 nsIAccessibleText, 527 ]).characterCount; 528 let attrs = accTree[prop][prevOffset]; 529 testTextAttrs( 530 acc, 531 prevOffset, 532 attrs, 533 {}, 534 prevOffset, 535 charCount, 536 true 537 ); 538 } 539 540 break; 541 } 542 543 default: 544 if (prop.indexOf("todo_") == 0) { 545 todo(false, msg); 546 } else if (prop != "children") { 547 is(acc[prop], accTree[prop], msg); 548 } 549 } 550 } 551 552 // Test children. 553 if ("children" in accTree && accTree.children instanceof Array) { 554 var children = acc.children; 555 var childCount = children.length; 556 557 if (accTree.children.length != childCount) { 558 for (let i = 0; i < Math.max(accTree.children.length, childCount); i++) { 559 var accChild = null, 560 testChild = null; 561 try { 562 testChild = accTree.children[i]; 563 accChild = children.queryElementAt(i, nsIAccessible); 564 565 if (!testChild) { 566 ok( 567 false, 568 prettyName(acc) + 569 " has an extra child at index " + 570 i + 571 " : " + 572 prettyName(accChild) 573 ); 574 continue; 575 } 576 577 testChild = normalizeAccTreeObj(testChild); 578 if (accChild.role !== testChild.role) { 579 ok( 580 false, 581 prettyName(accTree) + 582 " and " + 583 prettyName(acc) + 584 " have different children at index " + 585 i + 586 " : " + 587 prettyName(testChild) + 588 ", " + 589 prettyName(accChild) 590 ); 591 } 592 info( 593 "Matching " + 594 prettyName(accTree) + 595 " and " + 596 prettyName(acc) + 597 " child at index " + 598 i + 599 " : " + 600 prettyName(accChild) 601 ); 602 } catch (e) { 603 ok( 604 false, 605 prettyName(accTree) + 606 " is expected to have a child at index " + 607 i + 608 " : " + 609 prettyName(testChild) + 610 ", original tested: " + 611 prettyName(aAccOrElmOrID) + 612 ", " + 613 e 614 ); 615 } 616 } 617 } else { 618 if (aFlags & kSkipTreeFullCheck) { 619 for (let i = 0; i < childCount; i++) { 620 let child = children.queryElementAt(i, nsIAccessible); 621 testAccessibleTree(child, accTree.children[i], aFlags); 622 } 623 return; 624 } 625 626 // nsIAccessible::firstChild 627 var expectedFirstChild = 628 childCount > 0 ? children.queryElementAt(0, nsIAccessible) : null; 629 var firstChild = null; 630 try { 631 firstChild = acc.firstChild; 632 } catch (e) {} 633 is( 634 firstChild, 635 expectedFirstChild, 636 "Wrong first child of " + prettyName(acc) 637 ); 638 639 // nsIAccessible::lastChild 640 var expectedLastChild = 641 childCount > 0 642 ? children.queryElementAt(childCount - 1, nsIAccessible) 643 : null; 644 var lastChild = null; 645 try { 646 lastChild = acc.lastChild; 647 } catch (e) {} 648 is( 649 lastChild, 650 expectedLastChild, 651 "Wrong last child of " + prettyName(acc) 652 ); 653 654 for (var i = 0; i < childCount; i++) { 655 let child = children.queryElementAt(i, nsIAccessible); 656 657 // nsIAccessible::parent 658 var parent = null; 659 try { 660 parent = child.parent; 661 } catch (e) {} 662 is(parent, acc, "Wrong parent of " + prettyName(child)); 663 664 // nsIAccessible::indexInParent 665 var indexInParent = -1; 666 try { 667 indexInParent = child.indexInParent; 668 } catch (e) {} 669 is(indexInParent, i, "Wrong index in parent of " + prettyName(child)); 670 671 // nsIAccessible::nextSibling 672 var expectedNextSibling = 673 i < childCount - 1 674 ? children.queryElementAt(i + 1, nsIAccessible) 675 : null; 676 var nextSibling = null; 677 try { 678 nextSibling = child.nextSibling; 679 } catch (e) {} 680 is( 681 nextSibling, 682 expectedNextSibling, 683 "Wrong next sibling of " + prettyName(child) 684 ); 685 686 // nsIAccessible::previousSibling 687 var expectedPrevSibling = 688 i > 0 ? children.queryElementAt(i - 1, nsIAccessible) : null; 689 var prevSibling = null; 690 try { 691 prevSibling = child.previousSibling; 692 } catch (e) {} 693 is( 694 prevSibling, 695 expectedPrevSibling, 696 "Wrong previous sibling of " + prettyName(child) 697 ); 698 699 // Go down through subtree 700 testAccessibleTree(child, accTree.children[i], aFlags); 701 } 702 } 703 } 704 } 705 706 /** 707 * Return true if accessible for the given node is in cache. 708 */ 709 function isAccessibleInCache(aNodeOrId) { 710 var node = getNode(aNodeOrId); 711 return !!gAccService.getAccessibleFromCache(node); 712 } 713 714 /** 715 * Test accessible tree for defunct accessible. 716 * 717 * @param aAcc [in] the defunct accessible 718 * @param aNodeOrId [in] the DOM node identifier for the defunct accessible 719 */ 720 function testDefunctAccessible(aAcc, aNodeOrId) { 721 if (aNodeOrId) { 722 ok( 723 !isAccessible(aNodeOrId), 724 "Accessible for " + aNodeOrId + " wasn't properly shut down!" 725 ); 726 } 727 728 var msg = 729 " doesn't fail for shut down accessible " + prettyName(aNodeOrId) + "!"; 730 731 // firstChild 732 var success = false; 733 try { 734 aAcc.firstChild; 735 } catch (e) { 736 success = e.result == Cr.NS_ERROR_FAILURE; 737 } 738 ok(success, "firstChild" + msg); 739 740 // lastChild 741 success = false; 742 try { 743 aAcc.lastChild; 744 } catch (e) { 745 success = e.result == Cr.NS_ERROR_FAILURE; 746 } 747 ok(success, "lastChild" + msg); 748 749 // childCount 750 success = false; 751 try { 752 aAcc.childCount; 753 } catch (e) { 754 success = e.result == Cr.NS_ERROR_FAILURE; 755 } 756 ok(success, "childCount" + msg); 757 758 // children 759 success = false; 760 try { 761 aAcc.children; 762 } catch (e) { 763 success = e.result == Cr.NS_ERROR_FAILURE; 764 } 765 ok(success, "children" + msg); 766 767 // nextSibling 768 success = false; 769 try { 770 aAcc.nextSibling; 771 } catch (e) { 772 success = e.result == Cr.NS_ERROR_FAILURE; 773 } 774 ok(success, "nextSibling" + msg); 775 776 // previousSibling 777 success = false; 778 try { 779 aAcc.previousSibling; 780 } catch (e) { 781 success = e.result == Cr.NS_ERROR_FAILURE; 782 } 783 ok(success, "previousSibling" + msg); 784 785 // parent 786 success = false; 787 try { 788 aAcc.parent; 789 } catch (e) { 790 success = e.result == Cr.NS_ERROR_FAILURE; 791 } 792 ok(success, "parent" + msg); 793 } 794 795 /** 796 * Convert role to human readable string. 797 */ 798 function roleToString(aRole) { 799 return gAccService.getStringRole(aRole); 800 } 801 802 /** 803 * Convert states to human readable string. 804 */ 805 function statesToString(aStates, aExtraStates) { 806 var list = gAccService.getStringStates(aStates, aExtraStates); 807 808 var str = ""; 809 for (var index = 0; index < list.length - 1; index++) { 810 str += list.item(index) + ", "; 811 } 812 813 if (list.length) { 814 str += list.item(index); 815 } 816 817 return str; 818 } 819 820 /** 821 * Convert event type to human readable string. 822 */ 823 function eventTypeToString(aEventType) { 824 return gAccService.getStringEventType(aEventType); 825 } 826 827 /** 828 * Convert relation type to human readable string. 829 */ 830 function relationTypeToString(aRelationType) { 831 return gAccService.getStringRelationType(aRelationType); 832 } 833 834 function getLoadContext() { 835 return window.docShell.QueryInterface(Ci.nsILoadContext); 836 } 837 838 /** 839 * Return text from clipboard. 840 */ 841 function getTextFromClipboard() { 842 var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance( 843 Ci.nsITransferable 844 ); 845 trans.init(getLoadContext()); 846 if (!trans) { 847 return ""; 848 } 849 850 trans.addDataFlavor("text/plain"); 851 Services.clipboard.getData( 852 trans, 853 Services.clipboard.kGlobalClipboard, 854 SpecialPowers.wrap(window).browsingContext.currentWindowContext 855 ); 856 857 var str = {}; 858 trans.getTransferData("text/plain", str); 859 860 if (str) { 861 str = str.value.QueryInterface(Ci.nsISupportsString); 862 } 863 if (str) { 864 return str.data; 865 } 866 867 return ""; 868 } 869 870 /** 871 * Obtain DOMNode id from an accessible. This simply queries the .id property 872 * on the accessible, but it catches exceptions which might occur if the 873 * accessible has died. 874 * 875 * @param {nsIAccessible} accessible accessible 876 * @return {string?} DOMNode id if available 877 */ 878 function getAccessibleDOMNodeID(accessible) { 879 try { 880 return accessible.id; 881 } catch (e) { 882 // This will fail if the accessible has died. 883 } 884 return null; 885 } 886 887 /** 888 * Return pretty name for identifier, it may be ID, DOM node or accessible. 889 */ 890 function prettyName(aIdentifier) { 891 if (aIdentifier instanceof Array) { 892 let msg = ""; 893 for (var idx = 0; idx < aIdentifier.length; idx++) { 894 if (msg != "") { 895 msg += ", "; 896 } 897 898 msg += prettyName(aIdentifier[idx]); 899 } 900 return msg; 901 } 902 903 if (aIdentifier instanceof nsIAccessible) { 904 var acc = getAccessible(aIdentifier); 905 var domID = getAccessibleDOMNodeID(acc); 906 let msg = "["; 907 try { 908 if (Services.appinfo.browserTabsRemoteAutostart) { 909 if (domID) { 910 msg += `DOM node id: ${domID}, `; 911 } 912 } else { 913 msg += `${getNodePrettyName(acc.DOMNode)}, `; 914 } 915 msg += "role: " + roleToString(acc.role); 916 if (acc.name) { 917 msg += ", name: '" + shortenString(acc.name) + "'"; 918 } 919 } catch (e) { 920 msg += "defunct"; 921 } 922 923 if (acc) { 924 msg += ", address: " + getObjAddress(acc); 925 } 926 msg += "]"; 927 928 return msg; 929 } 930 931 if (Node.isInstance(aIdentifier)) { 932 return "[ " + getNodePrettyName(aIdentifier) + " ]"; 933 } 934 935 if (aIdentifier && typeof aIdentifier === "object") { 936 var treeObj = normalizeAccTreeObj(aIdentifier); 937 if ("role" in treeObj) { 938 function stringifyTree(aObj) { 939 var text = roleToString(aObj.role) + ": [ "; 940 if ("children" in aObj) { 941 for (var i = 0; i < aObj.children.length; i++) { 942 var c = normalizeAccTreeObj(aObj.children[i]); 943 text += stringifyTree(c); 944 if (i < aObj.children.length - 1) { 945 text += ", "; 946 } 947 } 948 } 949 return text + "] "; 950 } 951 return `{ ${stringifyTree(treeObj)} }`; 952 } 953 return JSON.stringify(aIdentifier); 954 } 955 956 return " '" + aIdentifier + "' "; 957 } 958 959 /** 960 * Shorten a long string if it exceeds MAX_TRIM_LENGTH. 961 * 962 * @param aString the string to shorten. 963 * @returns the shortened string. 964 */ 965 function shortenString(aString) { 966 if (aString.length <= MAX_TRIM_LENGTH) { 967 return aString; 968 } 969 970 // Trim the string if its length is > MAX_TRIM_LENGTH characters. 971 var trimOffset = MAX_TRIM_LENGTH / 2; 972 return ( 973 aString.substring(0, trimOffset - 1) + 974 "..." + 975 aString.substring(aString.length - trimOffset, aString.length) 976 ); 977 } 978 979 // ////////////////////////////////////////////////////////////////////////////// 980 // General Utils 981 // ////////////////////////////////////////////////////////////////////////////// 982 /** 983 * Return main chrome window (crosses chrome boundary) 984 */ 985 function getMainChromeWindow(aWindow) { 986 return aWindow.browsingContext.topChromeWindow; 987 } 988 989 // ////////////////////////////////////////////////////////////////////////////// 990 // Private 991 // ////////////////////////////////////////////////////////////////////////////// 992 993 // ////////////////////////////////////////////////////////////////////////////// 994 // Accessible general 995 996 function getNodePrettyName(aNode) { 997 try { 998 var tag = ""; 999 if (aNode.nodeType == Node.DOCUMENT_NODE) { 1000 tag = "document"; 1001 } else { 1002 tag = aNode.localName; 1003 if (aNode.nodeType == Node.ELEMENT_NODE && aNode.hasAttribute("id")) { 1004 tag += '@id="' + aNode.getAttribute("id") + '"'; 1005 } 1006 } 1007 1008 return "'" + tag + " node', address: " + getObjAddress(aNode); 1009 } catch (e) { 1010 return "' no node info '"; 1011 } 1012 } 1013 1014 function getObjAddress(aObj) { 1015 var exp = /native\s*@\s*(0x[a-f0-9]+)/g; 1016 var match = exp.exec(aObj.toString()); 1017 if (match) { 1018 return match[1]; 1019 } 1020 1021 return aObj.toString(); 1022 } 1023 1024 function normalizeAccTreeObj(aObj) { 1025 var key = Object.keys(aObj)[0]; 1026 var roleName = "ROLE_" + key; 1027 if (roleName in nsIAccessibleRole) { 1028 return { 1029 role: nsIAccessibleRole[roleName], 1030 children: aObj[key], 1031 }; 1032 } 1033 return aObj; 1034 }