head.js (7785B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /* exported testCachedRelation, testRelated */ 8 9 // Load the shared-head file first. 10 Services.scriptloader.loadSubScript( 11 "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js", 12 this 13 ); 14 15 // Loading and common.js from accessible/tests/mochitest/ for all tests, as 16 // well as promisified-events.js and relations.js. 17 /* import-globals-from ../../mochitest/relations.js */ 18 loadScripts( 19 { name: "common.js", dir: MOCHITESTS_DIR }, 20 { name: "promisified-events.js", dir: MOCHITESTS_DIR }, 21 { name: "relations.js", dir: MOCHITESTS_DIR } 22 ); 23 24 /** 25 * Test the accessible relation. 26 * 27 * @param identifier [in] identifier to get an accessible, may be ID 28 * attribute or DOM element or accessible object 29 * @param relType [in] relation type (see constants above) 30 * @param relatedIdentifiers [in] identifier or array of identifiers of 31 * expected related accessibles 32 */ 33 async function testCachedRelation( 34 identifier, 35 relType, 36 relatedIdentifiers = [] 37 ) { 38 const relDescr = getRelationErrorMsg(identifier, relType); 39 40 const relatedIds = 41 relatedIdentifiers instanceof Array 42 ? relatedIdentifiers 43 : [relatedIdentifiers]; 44 45 await untilCacheIs( 46 () => { 47 let r = getRelationByType(identifier, relType); 48 return r ? r.targetsCount : -1; 49 }, 50 relatedIds.length, 51 "Found correct number of expected relations" 52 ); 53 54 let targetSet = new Set(relatedIds.map(id => getAccessible(id))); 55 56 await untilCacheOk(function () { 57 const relation = getRelationByType(identifier, relType); 58 const actualTargets = relation ? relation.getTargets() : null; 59 if (!actualTargets) { 60 info("Could not fetch relations"); 61 return false; 62 } 63 64 const actualTargetsSet = new Set( 65 Array.from({ length: actualTargets.length }, (_, idx) => 66 actualTargets.queryElementAt(idx, Ci.nsIAccessible) 67 ) 68 ); 69 70 const unexpectedTargets = actualTargetsSet.difference(targetSet); 71 for (let extraAcc of unexpectedTargets) { 72 info( 73 prettyName(extraAcc) + 74 " was found, but shouldn't be in relation: " + 75 relDescr 76 ); 77 } 78 79 const missingTargets = targetSet.difference(actualTargetsSet); 80 for (let missingAcc of missingTargets) { 81 info( 82 prettyName(missingAcc) + " could not be found in relation: " + relDescr 83 ); 84 } 85 86 return unexpectedTargets.size == 0 && missingTargets.size == 0; 87 }, "Expected targets match"); 88 } 89 90 /** 91 * Asynchronously set or remove content element's reflected elements attribute 92 * (in content process if e10s is enabled). 93 * 94 * @param {object} browser current "tabbrowser" element 95 * @param {string} id content element id 96 * @param {string} attr attribute name 97 * @param {string?} value optional attribute value, if not present, remove 98 * attribute 99 * @return {Promise} promise indicating that attribute is set/removed 100 */ 101 function invokeSetReflectedElementsAttribute(browser, id, attr, targetIds) { 102 if (targetIds) { 103 Logger.log( 104 `Setting reflected ${attr} attribute to ${targetIds} for node with id: ${id}` 105 ); 106 } else { 107 Logger.log(`Removing reflected ${attr} attribute from node with id: ${id}`); 108 } 109 110 return invokeContentTask( 111 browser, 112 [id, attr, targetIds], 113 (contentId, contentAttr, contentTargetIds) => { 114 let elm = content.document.getElementById(contentId); 115 if (contentTargetIds) { 116 elm[contentAttr] = contentTargetIds.map(targetId => 117 content.document.getElementById(targetId) 118 ); 119 } else { 120 elm[contentAttr] = null; 121 } 122 } 123 ); 124 } 125 126 const REFLECTEDATTR_NAME_MAP = { 127 "aria-controls": "ariaControlsElements", 128 "aria-describedby": "ariaDescribedByElements", 129 "aria-details": "ariaDetailsElements", 130 "aria-errormessage": "ariaErrorMessageElements", 131 "aria-flowto": "ariaFlowToElements", 132 "aria-labelledby": "ariaLabelledByElements", 133 }; 134 135 async function testRelated( 136 browser, 137 accDoc, 138 attr, 139 hostRelation, 140 dependantRelation 141 ) { 142 let host = findAccessibleChildByID(accDoc, "host"); 143 let dependant1 = findAccessibleChildByID(accDoc, "dependant1"); 144 let dependant2 = findAccessibleChildByID(accDoc, "dependant2"); 145 146 /** 147 * Test data has the format of: 148 * { 149 * desc {String} description for better logging 150 * attrs {?Array} an optional list of attributes to update 151 * reflectedattr {?Array} an optional list of reflected attributes to update 152 * expected {Array} expected relation values for dependant1, dependant2 153 * and host respectively. 154 * } 155 */ 156 let tests = [ 157 { 158 desc: "No attribute", 159 expected: [null, null, null], 160 }, 161 { 162 desc: "Set attribute", 163 attrs: [{ id: "host", key: attr, value: "dependant1" }], 164 expected: [host, null, dependant1], 165 }, 166 { 167 desc: "Change attribute", 168 attrs: [{ id: "host", key: attr, value: "dependant2" }], 169 expected: [null, host, dependant2], 170 }, 171 { 172 desc: "Change attribute to multiple targets", 173 attrs: [{ id: "host", key: attr, value: "dependant1 dependant2" }], 174 expected: [host, host, [dependant1, dependant2]], 175 }, 176 { 177 desc: "Change 'dependent2' id to 'invalid'", 178 attrs: [{ id: "dependant2", key: "id", value: "invalid" }], 179 expected: [host, null, dependant1], 180 }, 181 { 182 desc: "Change 'invalid' id back to 'dependent2'", 183 attrs: [{ id: "invalid", key: "id", value: "dependant2" }], 184 expected: [host, host, [dependant1, dependant2]], 185 }, 186 { 187 desc: "Remove attribute", 188 attrs: [{ id: "host", key: attr }], 189 expected: [null, null, null], 190 }, 191 ]; 192 193 let reflectedAttrName = REFLECTEDATTR_NAME_MAP[attr]; 194 if (reflectedAttrName) { 195 tests = tests.concat([ 196 { 197 desc: "Set reflected attribute", 198 reflectedattr: [ 199 { id: "host", key: reflectedAttrName, value: ["dependant1"] }, 200 ], 201 expected: [host, null, dependant1], 202 }, 203 { 204 desc: "Change reflected attribute", 205 reflectedattr: [ 206 { id: "host", key: reflectedAttrName, value: ["dependant2"] }, 207 ], 208 expected: [null, host, dependant2], 209 }, 210 { 211 desc: "Change reflected attribute to multiple targets", 212 reflectedattr: [ 213 { 214 id: "host", 215 key: reflectedAttrName, 216 value: ["dependant2", "dependant1"], 217 }, 218 ], 219 expected: [host, host, [dependant1, dependant2]], 220 }, 221 { 222 desc: "Remove reflected attribute", 223 reflectedattr: [{ id: "host", key: reflectedAttrName, value: null }], 224 expected: [null, null, null], 225 }, 226 ]); 227 } 228 229 for (let { desc, attrs, reflectedattr, expected } of tests) { 230 info(desc); 231 232 if (attrs) { 233 for (let { id, key, value } of attrs) { 234 await invokeSetAttribute(browser, id, key, value); 235 } 236 } else if (reflectedattr) { 237 for (let { id, key, value } of reflectedattr) { 238 await invokeSetReflectedElementsAttribute(browser, id, key, value); 239 } 240 } 241 242 await testCachedRelation( 243 dependant1, 244 dependantRelation, 245 expected[0] ? expected[0] : [] 246 ); 247 await testCachedRelation( 248 dependant2, 249 dependantRelation, 250 expected[1] ? expected[1] : [] 251 ); 252 await testCachedRelation( 253 host, 254 hostRelation, 255 expected[2] ? expected[2] : [] 256 ); 257 } 258 }