Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
michael@0 | 1 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 2 | // Interfaces |
michael@0 | 3 | |
michael@0 | 4 | const nsIAccessibleRetrieval = Components.interfaces.nsIAccessibleRetrieval; |
michael@0 | 5 | |
michael@0 | 6 | const nsIAccessibleEvent = Components.interfaces.nsIAccessibleEvent; |
michael@0 | 7 | const nsIAccessibleStateChangeEvent = |
michael@0 | 8 | Components.interfaces.nsIAccessibleStateChangeEvent; |
michael@0 | 9 | const nsIAccessibleCaretMoveEvent = |
michael@0 | 10 | Components.interfaces.nsIAccessibleCaretMoveEvent; |
michael@0 | 11 | const nsIAccessibleTextChangeEvent = |
michael@0 | 12 | Components.interfaces.nsIAccessibleTextChangeEvent; |
michael@0 | 13 | const nsIAccessibleVirtualCursorChangeEvent = |
michael@0 | 14 | Components.interfaces.nsIAccessibleVirtualCursorChangeEvent; |
michael@0 | 15 | |
michael@0 | 16 | const nsIAccessibleStates = Components.interfaces.nsIAccessibleStates; |
michael@0 | 17 | const nsIAccessibleRole = Components.interfaces.nsIAccessibleRole; |
michael@0 | 18 | const nsIAccessibleScrollType = Components.interfaces.nsIAccessibleScrollType; |
michael@0 | 19 | const nsIAccessibleCoordinateType = Components.interfaces.nsIAccessibleCoordinateType; |
michael@0 | 20 | |
michael@0 | 21 | const nsIAccessibleRelation = Components.interfaces.nsIAccessibleRelation; |
michael@0 | 22 | |
michael@0 | 23 | const nsIAccessible = Components.interfaces.nsIAccessible; |
michael@0 | 24 | |
michael@0 | 25 | const nsIAccessibleDocument = Components.interfaces.nsIAccessibleDocument; |
michael@0 | 26 | const nsIAccessibleApplication = Components.interfaces.nsIAccessibleApplication; |
michael@0 | 27 | |
michael@0 | 28 | const nsIAccessibleText = Components.interfaces.nsIAccessibleText; |
michael@0 | 29 | const nsIAccessibleEditableText = Components.interfaces.nsIAccessibleEditableText; |
michael@0 | 30 | |
michael@0 | 31 | const nsIAccessibleHyperLink = Components.interfaces.nsIAccessibleHyperLink; |
michael@0 | 32 | const nsIAccessibleHyperText = Components.interfaces.nsIAccessibleHyperText; |
michael@0 | 33 | |
michael@0 | 34 | const nsIAccessibleCursorable = Components.interfaces.nsIAccessibleCursorable; |
michael@0 | 35 | const nsIAccessibleImage = Components.interfaces.nsIAccessibleImage; |
michael@0 | 36 | const nsIAccessiblePivot = Components.interfaces.nsIAccessiblePivot; |
michael@0 | 37 | const nsIAccessibleSelectable = Components.interfaces.nsIAccessibleSelectable; |
michael@0 | 38 | const nsIAccessibleTable = Components.interfaces.nsIAccessibleTable; |
michael@0 | 39 | const nsIAccessibleTableCell = Components.interfaces.nsIAccessibleTableCell; |
michael@0 | 40 | const nsIAccessibleTraversalRule = Components.interfaces.nsIAccessibleTraversalRule; |
michael@0 | 41 | const nsIAccessibleValue = Components.interfaces.nsIAccessibleValue; |
michael@0 | 42 | |
michael@0 | 43 | const nsIObserverService = Components.interfaces.nsIObserverService; |
michael@0 | 44 | |
michael@0 | 45 | const nsIDOMDocument = Components.interfaces.nsIDOMDocument; |
michael@0 | 46 | const nsIDOMEvent = Components.interfaces.nsIDOMEvent; |
michael@0 | 47 | const nsIDOMHTMLDocument = Components.interfaces.nsIDOMHTMLDocument; |
michael@0 | 48 | const nsIDOMNode = Components.interfaces.nsIDOMNode; |
michael@0 | 49 | const nsIDOMHTMLElement = Components.interfaces.nsIDOMHTMLElement; |
michael@0 | 50 | const nsIDOMWindow = Components.interfaces.nsIDOMWindow; |
michael@0 | 51 | const nsIDOMXULElement = Components.interfaces.nsIDOMXULElement; |
michael@0 | 52 | |
michael@0 | 53 | const nsIPropertyElement = Components.interfaces.nsIPropertyElement; |
michael@0 | 54 | |
michael@0 | 55 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 56 | // OS detect |
michael@0 | 57 | |
michael@0 | 58 | const MAC = (navigator.platform.indexOf("Mac") != -1); |
michael@0 | 59 | const LINUX = (navigator.platform.indexOf("Linux") != -1); |
michael@0 | 60 | const SOLARIS = (navigator.platform.indexOf("SunOS") != -1); |
michael@0 | 61 | const WIN = (navigator.platform.indexOf("Win") != -1); |
michael@0 | 62 | |
michael@0 | 63 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 64 | // Application detect |
michael@0 | 65 | // Firefox is assumed by default. |
michael@0 | 66 | |
michael@0 | 67 | const SEAMONKEY = navigator.userAgent.match(/ SeaMonkey\//); |
michael@0 | 68 | |
michael@0 | 69 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 70 | // Accessible general |
michael@0 | 71 | |
michael@0 | 72 | const STATE_BUSY = nsIAccessibleStates.STATE_BUSY; |
michael@0 | 73 | |
michael@0 | 74 | const SCROLL_TYPE_ANYWHERE = nsIAccessibleScrollType.SCROLL_TYPE_ANYWHERE; |
michael@0 | 75 | |
michael@0 | 76 | const COORDTYPE_SCREEN_RELATIVE = nsIAccessibleCoordinateType.COORDTYPE_SCREEN_RELATIVE; |
michael@0 | 77 | const COORDTYPE_WINDOW_RELATIVE = nsIAccessibleCoordinateType.COORDTYPE_WINDOW_RELATIVE; |
michael@0 | 78 | const COORDTYPE_PARENT_RELATIVE = nsIAccessibleCoordinateType.COORDTYPE_PARENT_RELATIVE; |
michael@0 | 79 | |
michael@0 | 80 | const kEmbedChar = String.fromCharCode(0xfffc); |
michael@0 | 81 | |
michael@0 | 82 | const kDiscBulletChar = String.fromCharCode(0x2022); |
michael@0 | 83 | const kDiscBulletText = kDiscBulletChar + " "; |
michael@0 | 84 | const kCircleBulletText = String.fromCharCode(0x25e6) + " "; |
michael@0 | 85 | const kSquareBulletText = String.fromCharCode(0x25aa) + " "; |
michael@0 | 86 | |
michael@0 | 87 | const MAX_TRIM_LENGTH = 100; |
michael@0 | 88 | |
michael@0 | 89 | /** |
michael@0 | 90 | * nsIAccessibleRetrieval service. |
michael@0 | 91 | */ |
michael@0 | 92 | var gAccRetrieval = Components.classes["@mozilla.org/accessibleRetrieval;1"]. |
michael@0 | 93 | getService(nsIAccessibleRetrieval); |
michael@0 | 94 | |
michael@0 | 95 | /** |
michael@0 | 96 | * Enable/disable logging. |
michael@0 | 97 | */ |
michael@0 | 98 | function enableLogging(aModules) |
michael@0 | 99 | { |
michael@0 | 100 | gAccRetrieval.setLogging(aModules); |
michael@0 | 101 | } |
michael@0 | 102 | function disableLogging() |
michael@0 | 103 | { |
michael@0 | 104 | gAccRetrieval.setLogging(""); |
michael@0 | 105 | } |
michael@0 | 106 | function isLogged(aModule) |
michael@0 | 107 | { |
michael@0 | 108 | return gAccRetrieval.isLogged(aModule); |
michael@0 | 109 | } |
michael@0 | 110 | |
michael@0 | 111 | /** |
michael@0 | 112 | * Invokes the given function when document is loaded and focused. Preferable |
michael@0 | 113 | * to mochitests 'addLoadEvent' function -- additionally ensures state of the |
michael@0 | 114 | * document accessible is not busy. |
michael@0 | 115 | * |
michael@0 | 116 | * @param aFunc the function to invoke |
michael@0 | 117 | */ |
michael@0 | 118 | function addA11yLoadEvent(aFunc, aWindow) |
michael@0 | 119 | { |
michael@0 | 120 | function waitForDocLoad() |
michael@0 | 121 | { |
michael@0 | 122 | window.setTimeout( |
michael@0 | 123 | function() |
michael@0 | 124 | { |
michael@0 | 125 | var targetDocument = aWindow ? aWindow.document : document; |
michael@0 | 126 | var accDoc = getAccessible(targetDocument); |
michael@0 | 127 | var state = {}; |
michael@0 | 128 | accDoc.getState(state, {}); |
michael@0 | 129 | if (state.value & STATE_BUSY) |
michael@0 | 130 | return waitForDocLoad(); |
michael@0 | 131 | |
michael@0 | 132 | window.setTimeout(aFunc, 0); |
michael@0 | 133 | }, |
michael@0 | 134 | 0 |
michael@0 | 135 | ); |
michael@0 | 136 | } |
michael@0 | 137 | |
michael@0 | 138 | SimpleTest.waitForFocus(waitForDocLoad, aWindow); |
michael@0 | 139 | } |
michael@0 | 140 | |
michael@0 | 141 | /** |
michael@0 | 142 | * Analogy of SimpleTest.is function used to compare objects. |
michael@0 | 143 | */ |
michael@0 | 144 | function isObject(aObj, aExpectedObj, aMsg) |
michael@0 | 145 | { |
michael@0 | 146 | if (aObj == aExpectedObj) { |
michael@0 | 147 | ok(true, aMsg); |
michael@0 | 148 | return; |
michael@0 | 149 | } |
michael@0 | 150 | |
michael@0 | 151 | ok(false, |
michael@0 | 152 | aMsg + " - got '" + prettyName(aObj) + |
michael@0 | 153 | "', expected '" + prettyName(aExpectedObj) + "'"); |
michael@0 | 154 | } |
michael@0 | 155 | |
michael@0 | 156 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 157 | // Helpers for getting DOM node/accessible |
michael@0 | 158 | |
michael@0 | 159 | /** |
michael@0 | 160 | * Return the DOM node by identifier (may be accessible, DOM node or ID). |
michael@0 | 161 | */ |
michael@0 | 162 | function getNode(aAccOrNodeOrID, aDocument) |
michael@0 | 163 | { |
michael@0 | 164 | if (!aAccOrNodeOrID) |
michael@0 | 165 | return null; |
michael@0 | 166 | |
michael@0 | 167 | if (aAccOrNodeOrID instanceof nsIDOMNode) |
michael@0 | 168 | return aAccOrNodeOrID; |
michael@0 | 169 | |
michael@0 | 170 | if (aAccOrNodeOrID instanceof nsIAccessible) |
michael@0 | 171 | return aAccOrNodeOrID.DOMNode; |
michael@0 | 172 | |
michael@0 | 173 | node = (aDocument || document).getElementById(aAccOrNodeOrID); |
michael@0 | 174 | if (!node) { |
michael@0 | 175 | ok(false, "Can't get DOM element for " + aAccOrNodeOrID); |
michael@0 | 176 | return null; |
michael@0 | 177 | } |
michael@0 | 178 | |
michael@0 | 179 | return node; |
michael@0 | 180 | } |
michael@0 | 181 | |
michael@0 | 182 | /** |
michael@0 | 183 | * Constants indicates getAccessible doesn't fail if there is no accessible. |
michael@0 | 184 | */ |
michael@0 | 185 | const DONOTFAIL_IF_NO_ACC = 1; |
michael@0 | 186 | |
michael@0 | 187 | /** |
michael@0 | 188 | * Constants indicates getAccessible won't fail if accessible doesn't implement |
michael@0 | 189 | * the requested interfaces. |
michael@0 | 190 | */ |
michael@0 | 191 | const DONOTFAIL_IF_NO_INTERFACE = 2; |
michael@0 | 192 | |
michael@0 | 193 | /** |
michael@0 | 194 | * Return accessible for the given identifier (may be ID attribute or DOM |
michael@0 | 195 | * element or accessible object) or null. |
michael@0 | 196 | * |
michael@0 | 197 | * @param aAccOrElmOrID [in] identifier to get an accessible implementing |
michael@0 | 198 | * the given interfaces |
michael@0 | 199 | * @param aInterfaces [in, optional] the interface or an array interfaces |
michael@0 | 200 | * to query it/them from obtained accessible |
michael@0 | 201 | * @param aElmObj [out, optional] object to store DOM element which |
michael@0 | 202 | * accessible is obtained for |
michael@0 | 203 | * @param aDoNotFailIf [in, optional] no error for special cases (see |
michael@0 | 204 | * constants above) |
michael@0 | 205 | */ |
michael@0 | 206 | function getAccessible(aAccOrElmOrID, aInterfaces, aElmObj, aDoNotFailIf) |
michael@0 | 207 | { |
michael@0 | 208 | if (!aAccOrElmOrID) |
michael@0 | 209 | return null; |
michael@0 | 210 | |
michael@0 | 211 | var elm = null; |
michael@0 | 212 | |
michael@0 | 213 | if (aAccOrElmOrID instanceof nsIAccessible) { |
michael@0 | 214 | elm = aAccOrElmOrID.DOMNode; |
michael@0 | 215 | |
michael@0 | 216 | } else if (aAccOrElmOrID instanceof nsIDOMNode) { |
michael@0 | 217 | elm = aAccOrElmOrID; |
michael@0 | 218 | |
michael@0 | 219 | } else { |
michael@0 | 220 | elm = document.getElementById(aAccOrElmOrID); |
michael@0 | 221 | if (!elm) { |
michael@0 | 222 | ok(false, "Can't get DOM element for " + aAccOrElmOrID); |
michael@0 | 223 | return null; |
michael@0 | 224 | } |
michael@0 | 225 | } |
michael@0 | 226 | |
michael@0 | 227 | if (aElmObj && (typeof aElmObj == "object")) |
michael@0 | 228 | aElmObj.value = elm; |
michael@0 | 229 | |
michael@0 | 230 | var acc = (aAccOrElmOrID instanceof nsIAccessible) ? aAccOrElmOrID : null; |
michael@0 | 231 | if (!acc) { |
michael@0 | 232 | try { |
michael@0 | 233 | acc = gAccRetrieval.getAccessibleFor(elm); |
michael@0 | 234 | } catch (e) { |
michael@0 | 235 | } |
michael@0 | 236 | |
michael@0 | 237 | if (!acc) { |
michael@0 | 238 | if (!(aDoNotFailIf & DONOTFAIL_IF_NO_ACC)) |
michael@0 | 239 | ok(false, "Can't get accessible for " + aAccOrElmOrID); |
michael@0 | 240 | |
michael@0 | 241 | return null; |
michael@0 | 242 | } |
michael@0 | 243 | } |
michael@0 | 244 | |
michael@0 | 245 | if (!aInterfaces) |
michael@0 | 246 | return acc; |
michael@0 | 247 | |
michael@0 | 248 | if (!(aInterfaces instanceof Array)) |
michael@0 | 249 | aInterfaces = [ aInterfaces ]; |
michael@0 | 250 | |
michael@0 | 251 | for (var index = 0; index < aInterfaces.length; index++) { |
michael@0 | 252 | try { |
michael@0 | 253 | acc.QueryInterface(aInterfaces[index]); |
michael@0 | 254 | } catch (e) { |
michael@0 | 255 | if (!(aDoNotFailIf & DONOTFAIL_IF_NO_INTERFACE)) |
michael@0 | 256 | ok(false, "Can't query " + aInterfaces[index] + " for " + aAccOrElmOrID); |
michael@0 | 257 | |
michael@0 | 258 | return null; |
michael@0 | 259 | } |
michael@0 | 260 | } |
michael@0 | 261 | |
michael@0 | 262 | return acc; |
michael@0 | 263 | } |
michael@0 | 264 | |
michael@0 | 265 | /** |
michael@0 | 266 | * Return true if the given identifier has an accessible, or exposes the wanted |
michael@0 | 267 | * interfaces. |
michael@0 | 268 | */ |
michael@0 | 269 | function isAccessible(aAccOrElmOrID, aInterfaces) |
michael@0 | 270 | { |
michael@0 | 271 | return getAccessible(aAccOrElmOrID, aInterfaces, null, |
michael@0 | 272 | DONOTFAIL_IF_NO_ACC | DONOTFAIL_IF_NO_INTERFACE) ? |
michael@0 | 273 | true : false; |
michael@0 | 274 | } |
michael@0 | 275 | |
michael@0 | 276 | /** |
michael@0 | 277 | * Return an accessible that contains the DOM node for the given identifier. |
michael@0 | 278 | */ |
michael@0 | 279 | function getContainerAccessible(aAccOrElmOrID) |
michael@0 | 280 | { |
michael@0 | 281 | var node = getNode(aAccOrElmOrID); |
michael@0 | 282 | if (!node) |
michael@0 | 283 | return null; |
michael@0 | 284 | |
michael@0 | 285 | while ((node = node.parentNode) && !isAccessible(node)); |
michael@0 | 286 | return node ? getAccessible(node) : null; |
michael@0 | 287 | } |
michael@0 | 288 | |
michael@0 | 289 | /** |
michael@0 | 290 | * Return root accessible for the given identifier. |
michael@0 | 291 | */ |
michael@0 | 292 | function getRootAccessible(aAccOrElmOrID) |
michael@0 | 293 | { |
michael@0 | 294 | var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document); |
michael@0 | 295 | return acc ? acc.rootDocument.QueryInterface(nsIAccessible) : null; |
michael@0 | 296 | } |
michael@0 | 297 | |
michael@0 | 298 | /** |
michael@0 | 299 | * Return tab document accessible the given accessible is contained by. |
michael@0 | 300 | */ |
michael@0 | 301 | function getTabDocAccessible(aAccOrElmOrID) |
michael@0 | 302 | { |
michael@0 | 303 | var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document); |
michael@0 | 304 | |
michael@0 | 305 | var docAcc = acc.document.QueryInterface(nsIAccessible); |
michael@0 | 306 | var containerDocAcc = docAcc.parent.document; |
michael@0 | 307 | |
michael@0 | 308 | // Test is running is stand-alone mode. |
michael@0 | 309 | if (acc.rootDocument == containerDocAcc) |
michael@0 | 310 | return docAcc; |
michael@0 | 311 | |
michael@0 | 312 | // In the case of running all tests together. |
michael@0 | 313 | return containerDocAcc.QueryInterface(nsIAccessible); |
michael@0 | 314 | } |
michael@0 | 315 | |
michael@0 | 316 | /** |
michael@0 | 317 | * Return application accessible. |
michael@0 | 318 | */ |
michael@0 | 319 | function getApplicationAccessible() |
michael@0 | 320 | { |
michael@0 | 321 | return gAccRetrieval.getApplicationAccessible(). |
michael@0 | 322 | QueryInterface(nsIAccessibleApplication); |
michael@0 | 323 | } |
michael@0 | 324 | |
michael@0 | 325 | /** |
michael@0 | 326 | * A version of accessible tree testing, doesn't fail if tree is not complete. |
michael@0 | 327 | */ |
michael@0 | 328 | function testElm(aID, aTreeObj) |
michael@0 | 329 | { |
michael@0 | 330 | testAccessibleTree(aID, aTreeObj, kSkipTreeFullCheck); |
michael@0 | 331 | } |
michael@0 | 332 | |
michael@0 | 333 | /** |
michael@0 | 334 | * Flags used for testAccessibleTree |
michael@0 | 335 | */ |
michael@0 | 336 | const kSkipTreeFullCheck = 1; |
michael@0 | 337 | |
michael@0 | 338 | /** |
michael@0 | 339 | * Compare expected and actual accessibles trees. |
michael@0 | 340 | * |
michael@0 | 341 | * @param aAccOrElmOrID [in] accessible identifier |
michael@0 | 342 | * @param aAccTree [in] JS object, each field corresponds to property of |
michael@0 | 343 | * accessible object. Additionally special properties |
michael@0 | 344 | * are presented: |
michael@0 | 345 | * children - an array of JS objects representing |
michael@0 | 346 | * children of accessible |
michael@0 | 347 | * states - an object having states and extraStates |
michael@0 | 348 | * fields |
michael@0 | 349 | * @param aFlags [in, optional] flags, see constants above |
michael@0 | 350 | */ |
michael@0 | 351 | function testAccessibleTree(aAccOrElmOrID, aAccTree, aFlags) |
michael@0 | 352 | { |
michael@0 | 353 | var acc = getAccessible(aAccOrElmOrID); |
michael@0 | 354 | if (!acc) |
michael@0 | 355 | return; |
michael@0 | 356 | |
michael@0 | 357 | var accTree = aAccTree; |
michael@0 | 358 | |
michael@0 | 359 | // Support of simplified accessible tree object. |
michael@0 | 360 | var key = Object.keys(accTree)[0]; |
michael@0 | 361 | var roleName = "ROLE_" + key; |
michael@0 | 362 | if (roleName in nsIAccessibleRole) { |
michael@0 | 363 | accTree = { |
michael@0 | 364 | role: nsIAccessibleRole[roleName], |
michael@0 | 365 | children: accTree[key] |
michael@0 | 366 | }; |
michael@0 | 367 | } |
michael@0 | 368 | |
michael@0 | 369 | // Test accessible properties. |
michael@0 | 370 | for (var prop in accTree) { |
michael@0 | 371 | var msg = "Wrong value of property '" + prop + "' for " + prettyName(acc) + "."; |
michael@0 | 372 | |
michael@0 | 373 | switch (prop) { |
michael@0 | 374 | case "actions": { |
michael@0 | 375 | testActionNames(acc, accTree.actions); |
michael@0 | 376 | break; |
michael@0 | 377 | } |
michael@0 | 378 | |
michael@0 | 379 | case "attributes": |
michael@0 | 380 | testAttrs(acc, accTree[prop], true); |
michael@0 | 381 | break; |
michael@0 | 382 | |
michael@0 | 383 | case "absentAttributes": |
michael@0 | 384 | testAbsentAttrs(acc, accTree[prop]); |
michael@0 | 385 | break; |
michael@0 | 386 | |
michael@0 | 387 | case "interfaces": { |
michael@0 | 388 | var ifaces = (accTree[prop] instanceof Array) ? |
michael@0 | 389 | accTree[prop] : [ accTree[prop] ]; |
michael@0 | 390 | for (var i = 0; i < ifaces.length; i++) { |
michael@0 | 391 | ok((acc instanceof ifaces[i]), |
michael@0 | 392 | "No " + ifaces[i] + " interface on " + prettyName(acc)); |
michael@0 | 393 | } |
michael@0 | 394 | break; |
michael@0 | 395 | } |
michael@0 | 396 | |
michael@0 | 397 | case "relations": { |
michael@0 | 398 | for (var rel in accTree[prop]) |
michael@0 | 399 | testRelation(acc, window[rel], accTree[prop][rel]); |
michael@0 | 400 | break; |
michael@0 | 401 | } |
michael@0 | 402 | |
michael@0 | 403 | case "role": |
michael@0 | 404 | isRole(acc, accTree[prop], msg); |
michael@0 | 405 | break; |
michael@0 | 406 | |
michael@0 | 407 | case "states": |
michael@0 | 408 | case "extraStates": |
michael@0 | 409 | case "absentStates": |
michael@0 | 410 | case "absentExtraStates": { |
michael@0 | 411 | testStates(acc, accTree["states"], accTree["extraStates"], |
michael@0 | 412 | accTree["absentStates"], accTree["absentExtraStates"]); |
michael@0 | 413 | break; |
michael@0 | 414 | } |
michael@0 | 415 | |
michael@0 | 416 | case "tagName": |
michael@0 | 417 | is(accTree[prop], acc.DOMNode.tagName, msg); |
michael@0 | 418 | break; |
michael@0 | 419 | |
michael@0 | 420 | case "textAttrs": { |
michael@0 | 421 | var prevOffset = -1; |
michael@0 | 422 | for (var offset in accTree[prop]) { |
michael@0 | 423 | if (prevOffset !=- 1) { |
michael@0 | 424 | var attrs = accTree[prop][prevOffset]; |
michael@0 | 425 | testTextAttrs(acc, prevOffset, attrs, { }, prevOffset, offset, true); |
michael@0 | 426 | } |
michael@0 | 427 | prevOffset = offset; |
michael@0 | 428 | } |
michael@0 | 429 | |
michael@0 | 430 | if (prevOffset != -1) { |
michael@0 | 431 | var charCount = getAccessible(acc, [nsIAccessibleText]).characterCount; |
michael@0 | 432 | var attrs = accTree[prop][prevOffset]; |
michael@0 | 433 | testTextAttrs(acc, prevOffset, attrs, { }, prevOffset, charCount, true); |
michael@0 | 434 | } |
michael@0 | 435 | |
michael@0 | 436 | break; |
michael@0 | 437 | } |
michael@0 | 438 | |
michael@0 | 439 | default: |
michael@0 | 440 | if (prop.indexOf("todo_") == 0) |
michael@0 | 441 | todo(false, msg); |
michael@0 | 442 | else if (prop != "children") |
michael@0 | 443 | is(acc[prop], accTree[prop], msg); |
michael@0 | 444 | } |
michael@0 | 445 | } |
michael@0 | 446 | |
michael@0 | 447 | // Test children. |
michael@0 | 448 | if ("children" in accTree && accTree["children"] instanceof Array) { |
michael@0 | 449 | var children = acc.children; |
michael@0 | 450 | var childCount = children.length; |
michael@0 | 451 | |
michael@0 | 452 | is(childCount, accTree.children.length, |
michael@0 | 453 | "Different amount of expected children of " + prettyName(acc) + "."); |
michael@0 | 454 | |
michael@0 | 455 | if (accTree.children.length == childCount) { |
michael@0 | 456 | if (aFlags & kSkipTreeFullCheck) { |
michael@0 | 457 | for (var i = 0; i < childCount; i++) { |
michael@0 | 458 | var child = children.queryElementAt(i, nsIAccessible); |
michael@0 | 459 | testAccessibleTree(child, accTree.children[i], aFlags); |
michael@0 | 460 | } |
michael@0 | 461 | return; |
michael@0 | 462 | } |
michael@0 | 463 | |
michael@0 | 464 | // nsIAccessible::firstChild |
michael@0 | 465 | var expectedFirstChild = childCount > 0 ? |
michael@0 | 466 | children.queryElementAt(0, nsIAccessible) : null; |
michael@0 | 467 | var firstChild = null; |
michael@0 | 468 | try { firstChild = acc.firstChild; } catch (e) {} |
michael@0 | 469 | is(firstChild, expectedFirstChild, |
michael@0 | 470 | "Wrong first child of " + prettyName(acc)); |
michael@0 | 471 | |
michael@0 | 472 | // nsIAccessible::lastChild |
michael@0 | 473 | var expectedLastChild = childCount > 0 ? |
michael@0 | 474 | children.queryElementAt(childCount - 1, nsIAccessible) : null; |
michael@0 | 475 | var lastChild = null; |
michael@0 | 476 | try { lastChild = acc.lastChild; } catch (e) {} |
michael@0 | 477 | is(lastChild, expectedLastChild, |
michael@0 | 478 | "Wrong last child of " + prettyName(acc)); |
michael@0 | 479 | |
michael@0 | 480 | for (var i = 0; i < childCount; i++) { |
michael@0 | 481 | var child = children.queryElementAt(i, nsIAccessible); |
michael@0 | 482 | |
michael@0 | 483 | // nsIAccessible::parent |
michael@0 | 484 | var parent = null; |
michael@0 | 485 | try { parent = child.parent; } catch (e) {} |
michael@0 | 486 | is(parent, acc, "Wrong parent of " + prettyName(child)); |
michael@0 | 487 | |
michael@0 | 488 | // nsIAccessible::indexInParent |
michael@0 | 489 | var indexInParent = -1; |
michael@0 | 490 | try { indexInParent = child.indexInParent; } catch(e) {} |
michael@0 | 491 | is(indexInParent, i, |
michael@0 | 492 | "Wrong index in parent of " + prettyName(child)); |
michael@0 | 493 | |
michael@0 | 494 | // nsIAccessible::nextSibling |
michael@0 | 495 | var expectedNextSibling = (i < childCount - 1) ? |
michael@0 | 496 | children.queryElementAt(i + 1, nsIAccessible) : null; |
michael@0 | 497 | var nextSibling = null; |
michael@0 | 498 | try { nextSibling = child.nextSibling; } catch (e) {} |
michael@0 | 499 | is(nextSibling, expectedNextSibling, |
michael@0 | 500 | "Wrong next sibling of " + prettyName(child)); |
michael@0 | 501 | |
michael@0 | 502 | // nsIAccessible::previousSibling |
michael@0 | 503 | var expectedPrevSibling = (i > 0) ? |
michael@0 | 504 | children.queryElementAt(i - 1, nsIAccessible) : null; |
michael@0 | 505 | var prevSibling = null; |
michael@0 | 506 | try { prevSibling = child.previousSibling; } catch (e) {} |
michael@0 | 507 | is(prevSibling, expectedPrevSibling, |
michael@0 | 508 | "Wrong previous sibling of " + prettyName(child)); |
michael@0 | 509 | |
michael@0 | 510 | // Go down through subtree |
michael@0 | 511 | testAccessibleTree(child, accTree.children[i], aFlags); |
michael@0 | 512 | } |
michael@0 | 513 | } |
michael@0 | 514 | } |
michael@0 | 515 | } |
michael@0 | 516 | |
michael@0 | 517 | /** |
michael@0 | 518 | * Return true if accessible for the given node is in cache. |
michael@0 | 519 | */ |
michael@0 | 520 | function isAccessibleInCache(aNodeOrId) |
michael@0 | 521 | { |
michael@0 | 522 | var node = getNode(aNodeOrId); |
michael@0 | 523 | return gAccRetrieval.getAccessibleFromCache(node) ? true : false; |
michael@0 | 524 | } |
michael@0 | 525 | |
michael@0 | 526 | /** |
michael@0 | 527 | * Test accessible tree for defunct accessible. |
michael@0 | 528 | * |
michael@0 | 529 | * @param aAcc [in] the defunct accessible |
michael@0 | 530 | * @param aNodeOrId [in] the DOM node identifier for the defunct accessible |
michael@0 | 531 | */ |
michael@0 | 532 | function testDefunctAccessible(aAcc, aNodeOrId) |
michael@0 | 533 | { |
michael@0 | 534 | if (aNodeOrId) |
michael@0 | 535 | ok(!isAccessible(aNodeOrId), |
michael@0 | 536 | "Accessible for " + aNodeOrId + " wasn't properly shut down!"); |
michael@0 | 537 | |
michael@0 | 538 | var msg = " doesn't fail for shut down accessible " + prettyName(aNodeOrId) + "!"; |
michael@0 | 539 | |
michael@0 | 540 | // firstChild |
michael@0 | 541 | var success = false; |
michael@0 | 542 | try { |
michael@0 | 543 | aAcc.firstChild; |
michael@0 | 544 | } catch (e) { |
michael@0 | 545 | success = (e.result == Components.results.NS_ERROR_FAILURE) |
michael@0 | 546 | } |
michael@0 | 547 | ok(success, "firstChild" + msg); |
michael@0 | 548 | |
michael@0 | 549 | // lastChild |
michael@0 | 550 | success = false; |
michael@0 | 551 | try { |
michael@0 | 552 | aAcc.lastChild; |
michael@0 | 553 | } catch (e) { |
michael@0 | 554 | success = (e.result == Components.results.NS_ERROR_FAILURE) |
michael@0 | 555 | } |
michael@0 | 556 | ok(success, "lastChild" + msg); |
michael@0 | 557 | |
michael@0 | 558 | // childCount |
michael@0 | 559 | success = false; |
michael@0 | 560 | try { |
michael@0 | 561 | aAcc.childCount; |
michael@0 | 562 | } catch (e) { |
michael@0 | 563 | success = (e.result == Components.results.NS_ERROR_FAILURE) |
michael@0 | 564 | } |
michael@0 | 565 | ok(success, "childCount" + msg); |
michael@0 | 566 | |
michael@0 | 567 | // children |
michael@0 | 568 | success = false; |
michael@0 | 569 | try { |
michael@0 | 570 | aAcc.children; |
michael@0 | 571 | } catch (e) { |
michael@0 | 572 | success = (e.result == Components.results.NS_ERROR_FAILURE) |
michael@0 | 573 | } |
michael@0 | 574 | ok(success, "children" + msg); |
michael@0 | 575 | |
michael@0 | 576 | // nextSibling |
michael@0 | 577 | success = false; |
michael@0 | 578 | try { |
michael@0 | 579 | aAcc.nextSibling; |
michael@0 | 580 | } catch (e) { |
michael@0 | 581 | success = (e.result == Components.results.NS_ERROR_FAILURE); |
michael@0 | 582 | } |
michael@0 | 583 | ok(success, "nextSibling" + msg); |
michael@0 | 584 | |
michael@0 | 585 | // previousSibling |
michael@0 | 586 | success = false; |
michael@0 | 587 | try { |
michael@0 | 588 | aAcc.previousSibling; |
michael@0 | 589 | } catch (e) { |
michael@0 | 590 | success = (e.result == Components.results.NS_ERROR_FAILURE); |
michael@0 | 591 | } |
michael@0 | 592 | ok(success, "previousSibling" + msg); |
michael@0 | 593 | |
michael@0 | 594 | // parent |
michael@0 | 595 | success = false; |
michael@0 | 596 | try { |
michael@0 | 597 | aAcc.parent; |
michael@0 | 598 | } catch (e) { |
michael@0 | 599 | success = (e.result == Components.results.NS_ERROR_FAILURE); |
michael@0 | 600 | } |
michael@0 | 601 | ok(success, "parent" + msg); |
michael@0 | 602 | } |
michael@0 | 603 | |
michael@0 | 604 | /** |
michael@0 | 605 | * Convert role to human readable string. |
michael@0 | 606 | */ |
michael@0 | 607 | function roleToString(aRole) |
michael@0 | 608 | { |
michael@0 | 609 | return gAccRetrieval.getStringRole(aRole); |
michael@0 | 610 | } |
michael@0 | 611 | |
michael@0 | 612 | /** |
michael@0 | 613 | * Convert states to human readable string. |
michael@0 | 614 | */ |
michael@0 | 615 | function statesToString(aStates, aExtraStates) |
michael@0 | 616 | { |
michael@0 | 617 | var list = gAccRetrieval.getStringStates(aStates, aExtraStates); |
michael@0 | 618 | |
michael@0 | 619 | var str = ""; |
michael@0 | 620 | for (var index = 0; index < list.length - 1; index++) |
michael@0 | 621 | str += list.item(index) + ", "; |
michael@0 | 622 | |
michael@0 | 623 | if (list.length != 0) |
michael@0 | 624 | str += list.item(index) |
michael@0 | 625 | |
michael@0 | 626 | return str; |
michael@0 | 627 | } |
michael@0 | 628 | |
michael@0 | 629 | /** |
michael@0 | 630 | * Convert event type to human readable string. |
michael@0 | 631 | */ |
michael@0 | 632 | function eventTypeToString(aEventType) |
michael@0 | 633 | { |
michael@0 | 634 | return gAccRetrieval.getStringEventType(aEventType); |
michael@0 | 635 | } |
michael@0 | 636 | |
michael@0 | 637 | /** |
michael@0 | 638 | * Convert relation type to human readable string. |
michael@0 | 639 | */ |
michael@0 | 640 | function relationTypeToString(aRelationType) |
michael@0 | 641 | { |
michael@0 | 642 | return gAccRetrieval.getStringRelationType(aRelationType); |
michael@0 | 643 | } |
michael@0 | 644 | |
michael@0 | 645 | function getLoadContext() { |
michael@0 | 646 | const Ci = Components.interfaces; |
michael@0 | 647 | return window.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 648 | .getInterface(Ci.nsIWebNavigation) |
michael@0 | 649 | .QueryInterface(Ci.nsILoadContext); |
michael@0 | 650 | } |
michael@0 | 651 | |
michael@0 | 652 | /** |
michael@0 | 653 | * Return text from clipboard. |
michael@0 | 654 | */ |
michael@0 | 655 | function getTextFromClipboard() |
michael@0 | 656 | { |
michael@0 | 657 | var clip = Components.classes["@mozilla.org/widget/clipboard;1"]. |
michael@0 | 658 | getService(Components.interfaces.nsIClipboard); |
michael@0 | 659 | if (!clip) |
michael@0 | 660 | return ""; |
michael@0 | 661 | |
michael@0 | 662 | var trans = Components.classes["@mozilla.org/widget/transferable;1"]. |
michael@0 | 663 | createInstance(Components.interfaces.nsITransferable); |
michael@0 | 664 | trans.init(getLoadContext()); |
michael@0 | 665 | if (!trans) |
michael@0 | 666 | return ""; |
michael@0 | 667 | |
michael@0 | 668 | trans.addDataFlavor("text/unicode"); |
michael@0 | 669 | clip.getData(trans, clip.kGlobalClipboard); |
michael@0 | 670 | |
michael@0 | 671 | var str = new Object(); |
michael@0 | 672 | var strLength = new Object(); |
michael@0 | 673 | trans.getTransferData("text/unicode", str, strLength); |
michael@0 | 674 | |
michael@0 | 675 | if (str) |
michael@0 | 676 | str = str.value.QueryInterface(Components.interfaces.nsISupportsString); |
michael@0 | 677 | if (str) |
michael@0 | 678 | return str.data.substring(0, strLength.value / 2); |
michael@0 | 679 | |
michael@0 | 680 | return ""; |
michael@0 | 681 | } |
michael@0 | 682 | |
michael@0 | 683 | /** |
michael@0 | 684 | * Return pretty name for identifier, it may be ID, DOM node or accessible. |
michael@0 | 685 | */ |
michael@0 | 686 | function prettyName(aIdentifier) |
michael@0 | 687 | { |
michael@0 | 688 | if (aIdentifier instanceof Array) { |
michael@0 | 689 | var msg = ""; |
michael@0 | 690 | for (var idx = 0; idx < aIdentifier.length; idx++) { |
michael@0 | 691 | if (msg != "") |
michael@0 | 692 | msg += ", "; |
michael@0 | 693 | |
michael@0 | 694 | msg += prettyName(aIdentifier[idx]); |
michael@0 | 695 | } |
michael@0 | 696 | return msg; |
michael@0 | 697 | } |
michael@0 | 698 | |
michael@0 | 699 | if (aIdentifier instanceof nsIAccessible) { |
michael@0 | 700 | var acc = getAccessible(aIdentifier); |
michael@0 | 701 | var msg = "[" + getNodePrettyName(acc.DOMNode); |
michael@0 | 702 | try { |
michael@0 | 703 | msg += ", role: " + roleToString(acc.role); |
michael@0 | 704 | if (acc.name) |
michael@0 | 705 | msg += ", name: '" + shortenString(acc.name) + "'"; |
michael@0 | 706 | } catch (e) { |
michael@0 | 707 | msg += "defunct"; |
michael@0 | 708 | } |
michael@0 | 709 | |
michael@0 | 710 | if (acc) |
michael@0 | 711 | msg += ", address: " + getObjAddress(acc); |
michael@0 | 712 | msg += "]"; |
michael@0 | 713 | |
michael@0 | 714 | return msg; |
michael@0 | 715 | } |
michael@0 | 716 | |
michael@0 | 717 | if (aIdentifier instanceof nsIDOMNode) |
michael@0 | 718 | return "[ " + getNodePrettyName(aIdentifier) + " ]"; |
michael@0 | 719 | |
michael@0 | 720 | return " '" + aIdentifier + "' "; |
michael@0 | 721 | } |
michael@0 | 722 | |
michael@0 | 723 | /** |
michael@0 | 724 | * Shorten a long string if it exceeds MAX_TRIM_LENGTH. |
michael@0 | 725 | * @param aString the string to shorten. |
michael@0 | 726 | * @returns the shortened string. |
michael@0 | 727 | */ |
michael@0 | 728 | function shortenString(aString, aMaxLength) |
michael@0 | 729 | { |
michael@0 | 730 | if (aString.length <= MAX_TRIM_LENGTH) |
michael@0 | 731 | return aString; |
michael@0 | 732 | |
michael@0 | 733 | // Trim the string if its length is > MAX_TRIM_LENGTH characters. |
michael@0 | 734 | var trimOffset = MAX_TRIM_LENGTH / 2; |
michael@0 | 735 | return aString.substring(0, trimOffset - 1) + "..." + |
michael@0 | 736 | aString.substring(aString.length - trimOffset, aString.length); |
michael@0 | 737 | } |
michael@0 | 738 | |
michael@0 | 739 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 740 | // General Utils |
michael@0 | 741 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 742 | /** |
michael@0 | 743 | * Return main chrome window (crosses chrome boundary) |
michael@0 | 744 | */ |
michael@0 | 745 | function getMainChromeWindow(aWindow) |
michael@0 | 746 | { |
michael@0 | 747 | return aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor) |
michael@0 | 748 | .getInterface(Components.interfaces.nsIWebNavigation) |
michael@0 | 749 | .QueryInterface(Components.interfaces.nsIDocShellTreeItem) |
michael@0 | 750 | .rootTreeItem |
michael@0 | 751 | .QueryInterface(Components.interfaces.nsIInterfaceRequestor) |
michael@0 | 752 | .getInterface(Components.interfaces.nsIDOMWindow); |
michael@0 | 753 | } |
michael@0 | 754 | |
michael@0 | 755 | /** Sets the test plugin(s) initially expected enabled state. |
michael@0 | 756 | * It will automatically be reset to it's previous value after the test |
michael@0 | 757 | * ends. |
michael@0 | 758 | * @param aNewEnabledState [in] the enabled state, e.g. SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED |
michael@0 | 759 | * @param aPluginName [in, optional] The name of the plugin, defaults to "Test Plug-in" |
michael@0 | 760 | */ |
michael@0 | 761 | function setTestPluginEnabledState(aNewEnabledState, aPluginName) |
michael@0 | 762 | { |
michael@0 | 763 | var plugin = getTestPluginTag(aPluginName); |
michael@0 | 764 | var oldEnabledState = plugin.enabledState; |
michael@0 | 765 | plugin.enabledState = aNewEnabledState; |
michael@0 | 766 | SimpleTest.registerCleanupFunction(function() { |
michael@0 | 767 | getTestPluginTag(aPluginName).enabledState = oldEnabledState; |
michael@0 | 768 | }); |
michael@0 | 769 | } |
michael@0 | 770 | |
michael@0 | 771 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 772 | // Private |
michael@0 | 773 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 774 | |
michael@0 | 775 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 776 | // Accessible general |
michael@0 | 777 | |
michael@0 | 778 | function getNodePrettyName(aNode) |
michael@0 | 779 | { |
michael@0 | 780 | try { |
michael@0 | 781 | var tag = ""; |
michael@0 | 782 | if (aNode.nodeType == nsIDOMNode.DOCUMENT_NODE) { |
michael@0 | 783 | tag = "document"; |
michael@0 | 784 | } else { |
michael@0 | 785 | tag = aNode.localName; |
michael@0 | 786 | if (aNode.nodeType == nsIDOMNode.ELEMENT_NODE && aNode.hasAttribute("id")) |
michael@0 | 787 | tag += "@id=\"" + aNode.getAttribute("id") + "\""; |
michael@0 | 788 | } |
michael@0 | 789 | |
michael@0 | 790 | return "'" + tag + " node', address: " + getObjAddress(aNode); |
michael@0 | 791 | } catch (e) { |
michael@0 | 792 | return "' no node info '"; |
michael@0 | 793 | } |
michael@0 | 794 | } |
michael@0 | 795 | |
michael@0 | 796 | function getObjAddress(aObj) |
michael@0 | 797 | { |
michael@0 | 798 | var exp = /native\s*@\s*(0x[a-f0-9]+)/g; |
michael@0 | 799 | var match = exp.exec(aObj.toString()); |
michael@0 | 800 | if (match) |
michael@0 | 801 | return match[1]; |
michael@0 | 802 | |
michael@0 | 803 | return aObj.toString(); |
michael@0 | 804 | } |
michael@0 | 805 | |
michael@0 | 806 | function getTestPluginTag(aPluginName) |
michael@0 | 807 | { |
michael@0 | 808 | var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"] |
michael@0 | 809 | .getService(SpecialPowers.Ci.nsIPluginHost); |
michael@0 | 810 | var tags = ph.getPluginTags(); |
michael@0 | 811 | var name = aPluginName || "Test Plug-in"; |
michael@0 | 812 | for (var tag of tags) { |
michael@0 | 813 | if (tag.name == name) { |
michael@0 | 814 | return tag; |
michael@0 | 815 | } |
michael@0 | 816 | } |
michael@0 | 817 | |
michael@0 | 818 | ok(false, "Could not find plugin tag with plugin name '" + name + "'"); |
michael@0 | 819 | return null; |
michael@0 | 820 | } |