1 /* See license.txt for terms of usage */ 2 3 define([ 4 "firebug/lib/object", 5 "firebug/firebug", 6 "firebug/lib/domplate", 7 "firebug/lib/locale", 8 "firebug/lib/events", 9 "firebug/lib/url", 10 "firebug/lib/css", 11 "firebug/lib/dom", 12 "firebug/lib/xml", 13 "firebug/lib/xpath", 14 "firebug/console/console", 15 "firebug/chrome/infotip", 16 ], 17 function(Obj, Firebug, Domplate, Locale, Events, Url, Css, Dom, Xml, Xpath) { 18 19 // ************************************************************************************************ 20 // Constants 21 22 var singleSpaceTag = Domplate.DIV({"class" : "a11y1emSize"}, "x"); 23 24 var KeyEvent = window.KeyEvent; 25 26 // ************************************************************************************************ 27 // Module Management 28 29 Firebug.A11yModel = Obj.extend(Firebug.Module, 30 { 31 dispatchName: "a11y", 32 33 initialize: function() 34 { 35 Firebug.Module.initialize.apply(this, arguments); 36 37 this.handleTabBarFocus = Obj.bind(this.handleTabBarFocus, this); 38 this.handleTabBarBlur = Obj.bind(this.handleTabBarBlur, this); 39 this.handlePanelBarKeyPress = Obj.bind(this.handlePanelBarKeyPress, this); 40 this.onNavigablePanelKeyPress = Obj.bind(this.onNavigablePanelKeyPress, this); 41 this.onConsoleMouseDown = Obj.bind(this.onConsoleMouseDown, this); 42 this.onLayoutKeyPress = Obj.bind(this.onLayoutKeyPress, this); 43 this.onCSSKeyPress = Obj.bind(this.onCSSKeyPress, this); 44 this.onCSSMouseDown = Obj.bind(this.onCSSMouseDown, this); 45 this.onHTMLKeyPress = Obj.bind(this.onHTMLKeyPress, this); 46 this.onHTMLFocus = Obj.bind(this.onHTMLFocus, this); 47 this.onHTMLBlur = Obj.bind(this.onHTMLBlur, this); 48 this.onPanelFocus = Obj.bind(this.onPanelFocus, this); 49 this.onLayoutFocus = Obj.bind(this.onLayoutFocus, this); 50 this.onLayoutBlur = Obj.bind(this.onLayoutBlur, this); 51 this.onScriptContextMenu = Obj.bind(this.onScriptContextMenu, this); 52 this.onCSSPanelContextMenu = Obj.bind(this.onCSSPanelContextMenu, this); 53 this.onScriptKeyPress = Obj.bind(this.onScriptKeyPress, this); 54 this.onScriptKeyUp = Obj.bind(this.onScriptKeyUp, this); 55 this.onScriptMouseUp = Obj.bind(this.onScriptMouseUp, this); 56 this.onNetMouseDown = Obj.bind(this.onNetMouseDown, this); 57 this.onNetFocus = Obj.bind(this.onNetFocus, this); 58 this.onNetBlur = Obj.bind(this.onNetBlur, this); 59 60 // Mark ourselves disabled, so we don't performDisable() if we are not enabled. 61 Firebug.chrome.window.a11yEnabled = false; 62 63 Firebug.connection.addListener(this); 64 Firebug.Console.addListener(this); 65 Firebug.DOMModule.addListener(this); 66 }, 67 68 shutdown: function() 69 { 70 Firebug.connection.removeListener(this); 71 Firebug.Console.removeListener(this); 72 Firebug.DOMModule.removeListener(this); 73 74 Firebug.Module.shutdown.apply(this, arguments); 75 }, 76 77 initializeUI: function() 78 { 79 //Initialize according to the current pref value. 80 this.updateOption("a11y.enable", this.isEnabled()); 81 }, 82 83 isEnabled: function() 84 { 85 return Firebug.Options.get("a11y.enable"); 86 }, 87 88 updateOption: function(name, value) 89 { 90 if (FBTrace.DBG_A11Y) 91 { 92 FBTrace.sysout("a11y.updateOption; " + name + ": " + value + 93 ", Current chrome: " + Firebug.chrome.getName() + 94 ", Original chrome: " + Firebug.originalChrome.getName()); 95 } 96 97 if (name == "a11y.enable") 98 { 99 // Update for current chrome 100 this.set(value, Firebug.chrome); 101 // If the current chrome is an external window, update also the original chrome. 102 if (Firebug.chrome != Firebug.originalChrome) 103 { 104 this.set(value, Firebug.originalChrome); 105 if (FBTrace.DBG_A11Y) 106 FBTrace.sysout("a11y.updateOption; (original chrome)"); 107 } 108 } 109 }, 110 111 set: function(enable, chrome) 112 { 113 if (chrome.window.a11yEnabled == enable) 114 return; 115 116 if (enable) 117 this.performEnable(chrome); 118 else 119 this.performDisable(chrome); 120 chrome.window.a11yEnabled = enable; 121 }, 122 123 performEnable: function(chrome) 124 { 125 var tmpElem; 126 //add class used by all a11y related css styles (e.g. :focus and -moz-user-focus styles) 127 Css.setClass(chrome.$("fbContentBox"), "useA11y"); 128 //Css.setClass(chrome.$("fbStatusBar"), "useA11y"); 129 tmpElem = chrome.$("fbStatusPrefix"); 130 if (tmpElem) tmpElem.setAttribute("value", Locale.$STR("a11y.labels.firebug status")); 131 132 //manage all key events in toolbox (including tablists) 133 tmpElem = chrome.$("fbContentBox"); 134 if (tmpElem) 135 Events.addEventListener(tmpElem, "keypress", this.handlePanelBarKeyPress, true); 136 137 //make focus stick to inspect button when clicked 138 tmpElem = chrome.$("fbInspectButton"); 139 if (tmpElem) 140 Events.addEventListener(tmpElem, "mousedown", this.focusTarget, true); 141 142 tmpElem = chrome.$("fbPanelBar1-panelTabs"); 143 if (tmpElem) 144 Events.addEventListener(tmpElem, "focus", this.handleTabBarFocus, true); 145 146 tmpElem = chrome.$("fbPanelBar1-panelTabs"); 147 if (tmpElem) 148 Events.addEventListener(tmpElem, "blur", this.handleTabBarBlur, true); 149 150 tmpElem = chrome.$("fbPanelBar2-panelTabs"); 151 if (tmpElem) 152 Events.addEventListener(tmpElem, "focus", this.handleTabBarFocus, true); 153 154 tmpElem = chrome.$("fbPanelBar2-panelTabs"); 155 if (tmpElem) 156 Events.addEventListener(tmpElem, "blur", this.handleTabBarBlur, true); 157 158 tmpElem = chrome.$("fbPanelBar1"); 159 if (tmpElem) 160 Css.setClass(tmpElem.browser.contentDocument.body, "useA11y"); 161 162 tmpElem = chrome.$("fbPanelBar2"); 163 if (tmpElem) 164 Css.setClass(tmpElem.browser.contentDocument.body, "useA11y"); 165 Firebug.Editor.addListener(this); 166 this.listeningToEditor = true; 167 }, 168 169 performDisable: function(chrome) 170 { 171 var tmpElem; 172 //undo everything we did in performEnable 173 Css.removeClass(chrome.$("fbContentBox"), "useA11y"); 174 Css.removeClass(chrome.$("fbStatusBar"), "useA11y"); 175 176 tmpElem = chrome.$("fbPanelBar1"); 177 if (tmpElem) 178 Events.removeEventListener(tmpElem, "keypress", this.handlePanelBarKeyPress, true); 179 180 tmpElem = chrome.$("fbInspectButton"); 181 if (tmpElem) 182 Events.removeEventListener(tmpElem, "mousedown", this.focusTarget, true); 183 184 tmpElem = chrome.$("fbPanelBar1-panelTabs"); 185 if (tmpElem) 186 Events.removeEventListener(tmpElem, "focus", this.handleTabBarFocus, true); 187 188 tmpElem = chrome.$("fbPanelBar1-panelTabs") 189 if (tmpElem) 190 Events.removeEventListener(tmpElem, "blur", this.handleTabBarBlur, true); 191 192 tmpElem = chrome.$("fbPanelBar2-panelTabs"); 193 if (tmpElem) 194 Events.removeEventListener(tmpElem, "focus", this.handleTabBarFocus, true); 195 196 tmpElem = chrome.$("fbPanelBar2-panelTabs"); 197 if (tmpElem) 198 Events.removeEventListener(tmpElem, "blur", this.handleTabBarBlur, true); 199 200 tmpElem = chrome.$("fbPanelBar1"); 201 if (tmpElem) 202 { 203 Css.removeClass(tmpElem.browser.contentDocument.body, "useA11y"); 204 tmpElem.browser.setAttribute("showcaret", false); 205 } 206 207 tmpElem = chrome.$("fbPanelBar2"); 208 if (tmpElem) 209 Css.removeClass(tmpElem.browser.contentDocument.body, "useA11y"); 210 211 if (this.listeningToEditor) 212 Firebug.Editor.removeListener(this); 213 }, 214 215 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 216 217 onCreatePanel: function(context, panel, panelType) 218 { 219 if (!panel.enableA11y) 220 return; 221 222 if (panel.addListener) 223 panel.addListener(this); 224 }, 225 226 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 227 // Context & Panel Management 228 229 onInitializeNode: function(panel) 230 { 231 var panelA11y = this.getPanelA11y(panel, true); 232 if (!panelA11y) 233 return; 234 235 panelA11y.tabStop = null; 236 panelA11y.manageFocus = false; 237 panelA11y.lastIsDefault = false; 238 panelA11y.type = panel.deriveA11yFrom ? panel.deriveA11yFrom : panel.name; 239 240 //panel.context.chrome.$("fbContentBox").addEventListener("focus", this.reportFocus, true); 241 this.makeFocusable(panel.panelNode, false); 242 243 switch (panelA11y.type) 244 { 245 case "console": 246 panelA11y.manageFocus = true; 247 switch (panel.name) 248 { 249 case "console": 250 panel.panelNode.setAttribute("aria-label", 251 Locale.$STR("a11y.labels.log rows")); 252 panelA11y.lastIsDefault = true; 253 panel.panelNode.setAttribute("role", "list"); 254 break; 255 256 case "callstack": 257 panel.panelNode.setAttribute("role", "list"); 258 panel.panelNode.setAttribute("aria-label", 259 Locale.$STR("a11y.labels.call stack")); 260 break; 261 262 default: 263 panel.panelNode.setAttribute("role", "presentation"); 264 } 265 panel.panelNode.setAttribute("aria-live", "polite"); 266 panel.panelNode.setAttribute("aria-relevant", "additions"); 267 Events.addEventListener(panel.panelNode, "keypress", this.onNavigablePanelKeyPress, false); 268 Events.addEventListener(panel.panelNode, "focus", this.onPanelFocus, true); 269 Events.addEventListener(panel.panelNode, "mousedown", this.onConsoleMouseDown, false); 270 if (panel.name == "breakpoints") 271 panel.panelNode.style.overflowX = "hidden"; 272 break; 273 274 case "html": 275 panel.panelNode.setAttribute("role", "tree"); 276 panel.panelNode.setAttribute("aria-label", 277 Locale.$STR("a11y.labels.document structure")); 278 Events.addEventListener(panel.panelNode, "keypress", this.onHTMLKeyPress, false); 279 Events.addEventListener(panel.panelNode, "focus", this.onHTMLFocus, true); 280 Events.addEventListener(panel.panelNode, "blur", this.onHTMLBlur, true); 281 break; 282 283 case "css": 284 panelA11y.manageFocus = true; 285 Events.addEventListener(panel.panelNode, "keypress", this.onCSSKeyPress, false); 286 Events.addEventListener(panel.panelNode, "mousedown", this.onCSSMouseDown, false); 287 Events.addEventListener(panel.panelNode, "focus", this.onPanelFocus, true); 288 Events.addEventListener(panel.panelNode, "contextmenu", this.onCSSPanelContextMenu, false) 289 this.insertHiddenText(panel, panel.panelNode, 290 Locale.$STR("a11y.labels.overridden"), false, "CSSOverriddenDescription"); 291 panel.panelNode.setAttribute("role", panel.name == "stylesheet" ? 292 "list" : "presentation"); 293 break; 294 295 case "layout": 296 panelA11y.manageFocus = true; 297 Events.addEventListener(panel.panelNode, "keypress", this.onLayoutKeyPress, false); 298 Events.addEventListener(panel.panelNode, "focus", this.onLayoutFocus, true); 299 Events.addEventListener(panel.panelNode, "blur", this.onLayoutBlur, true); 300 break; 301 302 case "script": 303 Events.addEventListener(panel.panelNode, "contextmenu", this.onScriptContextMenu, true); 304 Events.addEventListener(panel.panelNode, "keypress", this.onScriptKeyPress, true); 305 Events.addEventListener(panel.panelNode, "keyup", this.onScriptKeyUp, true); 306 Events.addEventListener(panel.panelNode, "mouseup", this.onScriptMouseUp, true); 307 panelA11y.oneEmElem = this.addSingleSpaceElem(panel.panelNode); 308 break; 309 310 case "net": 311 panelA11y.manageFocus = true; 312 Events.addEventListener(panel.panelNode, "keypress", this.onNavigablePanelKeyPress, false); 313 Events.addEventListener(panel.panelNode, "focus", this.onPanelFocus, true); 314 Events.addEventListener(panel.panelNode, "focus", this.onNetFocus, true); 315 Events.addEventListener(panel.panelNode, "blur", this.onNetBlur, true); 316 Events.addEventListener(panel.panelNode, "mousedown", this.onNetMouseDown, false); 317 break; 318 } 319 }, 320 321 onDestroyNode: function(panel) 322 { 323 var panelA11y = this.getPanelA11y(panel); 324 if (!panelA11y) 325 return; 326 327 panelA11y = null; 328 329 // Remove all event handlers we added in onInitializeNode. 330 var actAsPanel = panel.deriveA11yFrom ? panel.deriveA11yFrom : panel.name; 331 switch (actAsPanel) 332 { 333 case "console": 334 Events.removeEventListener(panel.panelNode, "keypress", this.onNavigablePanelKeyPress, 335 false); 336 Events.removeEventListener(panel.panelNode, "focus", this.onPanelFocus, true); 337 Events.removeEventListener(panel.panelNode, "mousedown", this.onConsoleMouseDown, false); 338 break; 339 340 case "html": 341 Events.removeEventListener(panel.panelNode, "keypress", this.onHTMLKeyPress, false); 342 Events.removeEventListener(panel.panelNode, "focus", this.onHTMLFocus, true); 343 Events.removeEventListener(panel.panelNode, "blur", this.onHTMLBlur, true); 344 break; 345 346 case "css": 347 Events.removeEventListener(panel.panelNode, "keypress", this.onCSSKeyPress, false); 348 Events.removeEventListener(panel.panelNode, "mousedown", this.onCSSMouseDown, false); 349 Events.removeEventListener(panel.panelNode, "focus", this.onPanelFocus, true); 350 Events.removeEventListener(panel.panelNode, "blur", this.onPanelBlur, true); 351 Events.removeEventListener(panel.panelNode, "contextmenu", this.onCSSPanelContextMenu, 352 false) 353 break; 354 355 case "layout": 356 Events.removeEventListener(panel.panelNode, "keypress", this.onLayoutKeyPress, false); 357 Events.removeEventListener(panel.panelNode, "focus", this.onLayoutFocus, true); 358 Events.removeEventListener(panel.panelNode, "blur", this.onLayoutBlur, true); 359 break; 360 361 case "script": 362 Events.removeEventListener(panel.panelNode, "contextmenu", this.onScriptContextMenu, true); 363 Events.removeEventListener(panel.panelNode, "keypress", this.onScriptKeyPress, true); 364 Events.removeEventListener(panel.panelNode, "keyup", this.onScriptKeyUp, true); 365 Events.removeEventListener(panel.panelNode, "mouseup", this.onScriptMouseUp, true) 366 break; 367 368 case "net": 369 Events.removeEventListener(panel.panelNode, "keypress", this.onNavigablePanelKeyPress, 370 false); 371 Events.removeEventListener(panel.panelNode, "focus", this.onPanelFocus, true); 372 Events.removeEventListener(panel.panelNode, "focus", this.onNetFocus, true); 373 Events.removeEventListener(panel.panelNode, "blur", this.onNetBlur, true); 374 Events.removeEventListener(panel.panelNode, "mousedown", this.onNetMouseDown, false); 375 break; 376 } 377 }, 378 379 showPanel: function(browser, panel) 380 { 381 var panelA11y = this.getPanelA11y(panel); 382 if (!panelA11y) 383 return; 384 var title = panel.name; 385 var panelType = Firebug.getPanelType(panel.name); 386 if (panelType) 387 title = Firebug.getPanelTitle(panelType); 388 Firebug.chrome.$("fbToolbar").setAttribute("aria-label", title + " " + 389 Locale.$STR("a11y.labels.panel tools")) 390 var panelBrowser = Firebug.chrome.getPanelBrowser(panel); 391 panelBrowser.setAttribute("showcaret", (panel.name == "script")); 392 panelBrowser.contentDocument.body.setAttribute("aria-label", 393 Locale.$STRF("a11y.labels.title panel", [title])); 394 }, 395 396 showSidePanel: function(browser, sidePanel) 397 { 398 var panelA11y = this.getPanelA11y(sidePanel); 399 if (!panelA11y) 400 return; 401 var panelBrowser = Firebug.chrome.getPanelBrowser(sidePanel); 402 var panelType = Firebug.getPanelType(sidePanel.name); 403 if (panelType) 404 title = Firebug.getPanelTitle(panelType); 405 panelBrowser.contentDocument.body.setAttribute("aria-label", 406 Locale.$STRF("a11y.labels.title side panel", [title])); 407 }, 408 409 addLiveElem: function(panel, role, politeness) 410 { 411 var panelA11y = this.getPanelA11y(panel); 412 if (!panelA11y) 413 return; 414 if (panelA11y.liveElem && Dom.isElement(panelA11y.liveElem)) 415 return; 416 417 var attrName = attrValue = ""; 418 if (role) 419 { 420 attrName = "role"; 421 attrValue = role; 422 } 423 else 424 { 425 attrName = "aria-live"; 426 attrValue = politeness ? politeness : "polite"; 427 } 428 var elem = panel.document.createElement("div"); 429 elem.setAttribute(attrName, attrValue); 430 elem.className = "offScreen"; 431 panel.document.body.appendChild(elem); 432 panelA11y.liveElem = elem; 433 return elem; 434 }, 435 436 updateLiveElem: function(panel, msg, useAlert) 437 { 438 var panelA11y = this.getPanelA11y(panel); 439 if (!panelA11y) 440 return; 441 var elem = panelA11y.liveElem; 442 if (!elem) 443 elem = this.addLiveElem(panel); 444 elem.textContent = msg; 445 if (useAlert) 446 elem.setAttribute("role", "alert"); 447 }, 448 449 addSingleSpaceElem: function(parent) 450 { 451 return singleSpaceTag.append({}, parent, this); 452 }, 453 454 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 455 // Toolbars & Tablists 456 457 focusTarget: function(event) 458 { 459 this.focus(event.target); 460 }, 461 462 handlePanelBarKeyPress: function (event) 463 { 464 var target = event.originalTarget; 465 var isTab = target.nodeName.toLowerCase() == "paneltab"; 466 var isButton = target.nodeName.search(/(xul:)?((toolbar)?button)|(checkbox)/) != -1; 467 var isDropDownMenu = isButton && (target.getAttribute("type") == "menu" || 468 target.id == "fbLocationList"); 469 var siblingTab, forward, toolbar, buttons; 470 var keyCode = event.keyCode || (event.type == "keypress" ? event.charCode : null); 471 if (keyCode == KeyEvent.DOM_VK_TAB) 472 this.ensurePanelTabStops(); //TODO: need a better solution to prevent loss of panel tabstop 473 if (isTab || isButton) 474 { 475 switch (keyCode) 476 { 477 case KeyEvent.DOM_VK_LEFT: 478 case KeyEvent.DOM_VK_RIGHT: 479 case KeyEvent.DOM_VK_UP: 480 case KeyEvent.DOM_VK_DOWN: 481 forward = (event.keyCode == KeyEvent.DOM_VK_RIGHT || 482 event.keyCode == KeyEvent.DOM_VK_DOWN); 483 if (isTab) 484 { 485 //will only work as long as long as siblings only consist of paneltab elements 486 siblingTab = target[forward ? "nextSibling" : "previousSibling"]; 487 if (!siblingTab) 488 siblingTab = target.parentNode[forward ? "firstChild" : "lastChild"]; 489 if (siblingTab) 490 { 491 var panelBar = Dom.getAncestorByClass(target, "panelBar"); 492 setTimeout(Obj.bindFixed(function() 493 { 494 panelBar.selectTab(siblingTab); 495 this.focus(siblingTab); 496 }, this)); 497 } 498 } 499 else if (isButton) 500 { 501 if (target.id == "fbFirebugMenu" && !forward) 502 { 503 Events.cancelEvent(event); 504 return; 505 } 506 toolbar = Dom.getAncestorByClass(target, "innerToolbar"); 507 if (toolbar) 508 { 509 var doc = target.ownerDocument; 510 //temporarily make all buttons in the toolbar part of the tab order, 511 //to allow smooth, native focus advancement 512 Css.setClass(toolbar, "hasTabOrder"); 513 setTimeout(Obj.bindFixed(function() // time out needed to fix this behavior in 3.6 514 { 515 doc.commandDispatcher[forward ? "advanceFocus" : "rewindFocus"](); 516 //remove the buttons from the tab order again, so that it will remain uncluttered 517 //Very ugly hack, but it works well. This prevents focus to 'spill out' of a 518 //toolbar when using the left and right arrow keys 519 if (!Dom.isAncestor(doc.commandDispatcher.focusedElement, toolbar)) 520 { 521 //we moved focus to somewhere out of the toolbar: not good. Move it back to where it was. 522 doc.commandDispatcher[!forward ? 523 "advanceFocus" : "rewindFocus"](); 524 } 525 Css.removeClass(toolbar, "hasTabOrder"); 526 }, this)); 527 } 528 Events.cancelEvent(event); 529 return; 530 } 531 break; 532 533 case KeyEvent.DOM_VK_RETURN: 534 case KeyEvent.DOM_VK_SPACE: 535 if (isTab && target.tabMenu) 536 { 537 target.tabMenu.popup.showPopup(target.tabMenu, -1, -1, "popup", 538 "bottomleft", "topleft"); 539 } 540 else if (isButton && isDropDownMenu) 541 { 542 if (target.id == "fbLocationList") 543 target.showPopup(); 544 else 545 target.open = true; 546 Events.cancelEvent(event); 547 return false; 548 } 549 break; 550 551 case KeyEvent.DOM_VK_F4: 552 if (isTab && target.tabMenu) 553 { 554 target.tabMenu.popup.showPopup(target.tabMenu, -1, -1, "popup", 555 "bottomleft", "topleft"); 556 } 557 break; 558 } 559 } 560 }, 561 562 handleTabBarFocus: function(event) 563 { 564 this.tabFocused = true; 565 }, 566 567 handleTabBarBlur: function(event) 568 { 569 this.tabFocused = false; 570 }, 571 572 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 573 // Panel Focus & Tab Order Management 574 575 getPanelTabStop: function(panel) 576 { 577 var panelA11y = this.getPanelA11y(panel); 578 if (panelA11y) 579 return panelA11y.tabStop; 580 if (FBTrace.DBG_ERRORS) 581 FBTrace.sysout("a11y.getPanelTabStop null panel.context"); 582 return null; 583 }, 584 585 ensurePanelTabStops: function() 586 { 587 // XXXjjb: seems like this should be !Firebug.chrome 588 if (!Firebug.currentContext || !Firebug.currentContext.chrome) 589 return; 590 var panel = Firebug.chrome.getSelectedPanel(); 591 var sidePanel = Firebug.chrome.getSelectedSidePanel(); 592 this.ensurePanelTabStop(panel); 593 if (sidePanel) 594 this.ensurePanelTabStop(sidePanel); 595 }, 596 597 ensurePanelTabStop: function(panel) 598 { 599 var panelA11y = this.getPanelA11y(panel); 600 if (!panelA11y) 601 return; 602 603 if (panelA11y.manageFocus) 604 { 605 var tabStop = this.getPanelTabStop(panel); 606 if (!tabStop || !this.isVisibleByStyle(tabStop) || !Xml.isVisible(tabStop)) 607 { 608 this.tabStop = null; 609 this.findPanelTabStop(panel, "focusRow", panelA11y.lastIsDefault); 610 } 611 else if (tabStop.getAttribute("tabindex") !== "0") 612 { 613 tabStop.setAttribute("tabindex", "0"); 614 } 615 616 if (tabStop) 617 this.checkModifiedState(panel, tabStop, true); 618 } 619 }, 620 621 checkModifiedState: function(panel, elem, makeTab) 622 { 623 var panelA11y = this.getPanelA11y(panel); 624 if (!panelA11y || !elem) 625 return; 626 627 if (panelA11y.type == "console" && Css.hasClass(elem, "focusRow")) 628 this.modifyPanelRow(panel, elem, makeTab); 629 }, 630 631 setPanelTabStop: function (panel, elem) 632 { 633 var panelA11y = this.getPanelA11y(panel); 634 if (!panelA11y) 635 return; 636 637 var tabStop = this.getPanelTabStop(panel) 638 if (tabStop) 639 { 640 this.makeFocusable(tabStop, false); 641 if (["treeitem", "listitem", "option"].indexOf(tabStop.getAttribute("role")) != -1) 642 tabStop.setAttribute("aria-selected", "false"); 643 } 644 panelA11y.tabStop = elem; 645 if (elem) 646 { 647 panelA11y.reFocusId = null; 648 this.makeFocusable(elem, true); 649 if (["treeitem", "listitem", "option"].indexOf(elem.getAttribute("role")) != -1) 650 elem.setAttribute("aria-selected", "true"); 651 } 652 }, 653 654 findPanelTabStop: function(panel, className, last) 655 { 656 var candidates = panel.panelNode.getElementsByClassName(className); 657 candidates= Array.filter(candidates, function(e, i, a){return this.isVisibleByStyle(e) 658 && Xml.isVisible(e);}, this); 659 if (candidates.length > 0) 660 { 661 var chosenRow = candidates[last ? candidates.length -1 : 0]; 662 this.modifyPanelRow(panel, chosenRow, true) 663 this.setPanelTabStop(panel, chosenRow); 664 } 665 else 666 { 667 this.setPanelTabStop(panel, null); 668 } 669 }, 670 671 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 672 // Console Panel 673 674 onLogRowCreated: function(panel, row) 675 { 676 var panelA11y = this.getPanelA11y(panel); 677 if (!panelA11y) 678 return; 679 680 if (Css.hasClass(row, "logRow-dir")) 681 { 682 row.setAttribute("role", "listitem"); 683 Css.setClass(row, "outerFocusRow"); 684 var memberRows = row.getElementsByClassName("memberRow"); 685 if (memberRows.length > 0) 686 this.onMemberRowsAdded(panel, memberRows); 687 } 688 else if (Css.hasClass(row, "logRow-group") || Css.hasClass(row, "logRow-profile")) 689 { 690 row.setAttribute("role", "presentation"); 691 var focusRow = row.getElementsByClassName("logGroupLabel").item(0); 692 if (focusRow) 693 { 694 this.setPanelTabStop(panel, focusRow); 695 focusRow.setAttribute("aria-expanded", Css.hasClass(row, "opened") + ""); 696 if (!Css.hasClass(row, "logRow-profile")) 697 this.insertHiddenText(panel, focusRow, "group label: "); 698 } 699 } 700 else if (Css.hasClass(row, "logRow-errorMessage") || Css.hasClass(row, 701 "logRow-warningMessage")) 702 { 703 Css.setClass(row, "outerFocusRow"); 704 row.setAttribute("role", "presentation"); 705 var focusRow = row.getElementsByClassName("errorTitle").item(0); 706 if (focusRow) 707 { 708 this.setPanelTabStop(panel, focusRow); 709 focusRow.setAttribute("aria-expanded", 710 Css.hasClass(focusRow.parentNode, "opened") + ""); 711 } 712 } 713 else if (Css.hasClass(row, "logRow-stackTrace")) 714 { 715 Css.setClass(row, "outerFocusRow"); 716 row.setAttribute("role", "listitem"); 717 var stackFrames = row.getElementsByClassName("focusRow"); 718 Array.forEach(stackFrames, function(e, i, a) 719 { 720 e.setAttribute("role", "listitem"); 721 if ((panelA11y.lastIsDefault && i === stackFrames.length - 1) || 722 (!panelA11y.lastIsDefault && i === 0)) 723 { 724 this.setPanelTabStop(panel, e); 725 } 726 else 727 { 728 this.makeFocusable(e, false); 729 } 730 }, this); 731 } 732 else if (Css.hasClass(row, "logRow-spy")) 733 { 734 var focusRow = Dom.getChildByClass(row, "spyHeadTable"); 735 if (focusRow) 736 this.makeFocusable(focusRow, true); 737 } 738 else 739 { 740 row.setAttribute("role", "listitem"); 741 Css.setClass(row, "focusRow"); 742 Css.setClass(row, "outerFocusRow"); 743 if (Xml.isVisible(row)) 744 this.setPanelTabStop(panel, row); 745 } 746 }, 747 748 modifyLogRow: function(panel, row, inTabOrder) 749 { 750 this.makeFocusable(row, inTabOrder); 751 var logRowType = this.getLogRowType(row); 752 if (logRowType) 753 this.insertHiddenText(panel, row, logRowType + ": "); 754 var arrayNode = Dom.getChildByClass(row, "objectBox-array"); 755 if (arrayNode) 756 { 757 arrayNode.setAttribute("role", "group"); 758 this.insertHiddenText(panel, row, "array" + ": "); 759 } 760 var focusObjects = this.getFocusObjects(row); 761 Array.forEach(focusObjects, function(e, i, a) 762 { 763 this.makeFocusable(e); 764 var prepend = ""; 765 var append = " (" + this.getObjectType(e) + ") "; 766 if (e.textContent != "") 767 e.setAttribute("aria-label", prepend + e.textContent + append); 768 if (arrayNode) 769 e.setAttribute("role", "listitem"); 770 }, this); 771 }, 772 773 onNavigablePanelKeyPress: function(event) 774 { 775 var target = event.target; 776 var keyCode = event.keyCode || (event.type == "keypress" ? event.charCode : null); 777 if (!this.isTabWorthy(target) && !this.isFocusNoTabObject(target)) 778 return; 779 else if (event.shiftKey || event.altKey) 780 return; 781 else if ([13, 32, 33, 34, 35, 36, 37, 38, 39, 40, 46].indexOf(keyCode) == -1) 782 return; //not interested in any other keys, than arrows, pg, home/end, del space & enter 783 var panel = Firebug.getElementPanel(target) 784 var panelA11y = this.getPanelA11y(panel); 785 if (!panelA11y) 786 return; 787 788 var newTarget = target 789 if (!this.isOuterFocusRow(target)) 790 { 791 if (Events.isControl(event)) 792 { 793 newTarget = this.getAncestorRow(target); 794 if (newTarget) 795 { 796 newTarget = [33, 38].indexOf(keyCode) == -1 ? 797 this.getLastFocusChild(newTarget) : this.getFirstFocusChild(newTarget); 798 } 799 } 800 else if (!this.isDirCell(target) || Css.hasClass(target, "netInfoTab") || 801 Css.hasClass(target, "netCol")) 802 { 803 newTarget = this.getAncestorRow(target, true); 804 } 805 806 if (!newTarget) 807 newTarget = target; 808 } 809 switch (keyCode) 810 { 811 case KeyEvent.DOM_VK_UP: 812 case KeyEvent.DOM_VK_DOWN: 813 if (!this.isFocusNoTabObject(target)) 814 { 815 this.focusSiblingRow(panel, newTarget, keyCode == 38); 816 Events.cancelEvent(event); 817 } 818 break; 819 820 case KeyEvent.DOM_VK_LEFT: 821 case KeyEvent.DOM_VK_RIGHT: 822 var goLeft = keyCode == KeyEvent.DOM_VK_LEFT; 823 if (this.isDirCell(target)) 824 { 825 var row = Dom.getAncestorByClass(target, "memberRow"); 826 var toggleElem = Dom.getChildByClass(row.cells[1], "memberLabel"); 827 if (!goLeft && Css.hasClass(row, "hasChildren")) 828 { 829 if (Css.hasClass(row, "opened")) 830 { 831 this.focusSiblingRow(panel, target, false); 832 } 833 else if (toggleElem) 834 { 835 if (Css.hasClass(row, "hasChildren")) 836 target.setAttribute("aria-expanded", "true"); 837 this.dispatchMouseEvent(toggleElem, "click"); 838 } 839 } 840 else if (goLeft) 841 { 842 var level = parseInt(row.getAttribute("level")); 843 if (Css.hasClass(row, "opened")) 844 { 845 if (Css.hasClass(row, "hasChildren")) 846 target.setAttribute("aria-expanded", "false"); 847 this.dispatchMouseEvent(toggleElem, "click"); 848 } 849 else if (level > 0) 850 { 851 var targetLevel = (level - 1) + ""; 852 var newRows = Array.filter(row.parentNode.rows, function(e, i, a) 853 { 854 return e.rowIndex < row.rowIndex && 855 e.getAttribute("level") == targetLevel; 856 }, this); 857 858 if (newRows.length) 859 this.focus(newRows[newRows.length -1].cells[2].firstChild); 860 } 861 } 862 Events.cancelEvent(event); 863 } 864 else if (this.isOuterFocusRow(target, true)) 865 { 866 if (target.hasAttribute("aria-expanded")) 867 { 868 if (target.getAttribute("role") == "row" || 869 Css.hasClass(target, "spyHeadTable")) 870 { 871 if (goLeft && target.getAttribute("aria-expanded") == "true") 872 { 873 var toggleElem = Css.hasClass(target, "spyHeadTable") ? 874 target.getElementsByClassName("spyTitleCol").item(0) : target; 875 if (toggleElem) 876 this.dispatchMouseEvent(toggleElem, "click"); 877 } 878 } 879 else if (target.getAttribute("aria-expanded") == (goLeft ? 880 "true" : "false")) 881 { 882 this.dispatchMouseEvent(target, Css.hasClass(target, "logGroupLabel") ? 883 "mousedown" : "click"); 884 } 885 } 886 if (goLeft) 887 { 888 //check if we"re in an expanded section 889 var inExpanded = false, groupClass, groupLabelClass, group, groupLabel; 890 if (Css.hasClass(target, "objectBox-stackFrame")) 891 { 892 inExpanded = true; 893 groupClass = "errorTrace"; 894 groupLabelClass = "errorTitle"; 895 } 896 else if (Dom.getAncestorByClass(target, "logGroupBody")) 897 { 898 inExpanded = true; 899 groupClass = "logGroupBody"; 900 groupLabelClass = "logGroupLabel"; 901 } 902 if (inExpanded) 903 { 904 group = Dom.getAncestorByClass(target, groupClass); 905 if (group) 906 { 907 groupLabel = this.getPreviousByClass(target, groupLabelClass, 908 false, panel.panelNode); 909 if (groupLabel) 910 { 911 this.modifyPanelRow(panel, groupLabel); 912 this.focus(groupLabel); 913 } 914 } 915 } 916 } 917 else if (!goLeft) 918 { 919 920 var focusItems = this.getFocusObjects(target); 921 if (focusItems.length > 0) 922 { 923 this.focus(Events.isControl(event) ? 924 focusItems[focusItems.length -1] : focusItems[0]); 925 } 926 } 927 } 928 else if (this.isFocusObject(target)) 929 { 930 var parentRow = this.getAncestorRow(target, true); 931 var focusObjects = this.getFocusObjects(parentRow); 932 if (!Events.isControl(event)) 933 { 934 var focusIndex = Array.indexOf(focusObjects, target); 935 var newIndex = goLeft ? --focusIndex : ++focusIndex; 936 this.focus(goLeft && newIndex < 0 ? parentRow : focusObjects[newIndex]); 937 } 938 else 939 { 940 this.focus(goLeft ? parentRow : focusObjects[focusObjects.length -1]); 941 } 942 Events.cancelEvent(event); 943 } 944 break; 945 946 case KeyEvent.DOM_VK_END: 947 case KeyEvent.DOM_VK_HOME: 948 this.focusEdgeRow(panel, newTarget, keyCode == KeyEvent.DOM_VK_HOME); 949 Events.cancelEvent(event); 950 break; 951 952 case KeyEvent.DOM_VK_PAGE_UP: 953 case KeyEvent.DOM_VK_PAGE_DOWN: 954 this.focusPageSiblingRow(panel, newTarget, keyCode == KeyEvent.DOM_VK_PAGE_UP); 955 Events.cancelEvent(event); 956 break; 957 958 case KeyEvent.DOM_VK_RETURN: 959 if (this.isFocusObject(target)) 960 { 961 this.dispatchMouseEvent(target, "click"); 962 } 963 else if (Css.hasClass(target, "watchEditBox")) 964 { 965 this.dispatchMouseEvent(target, "mousedown"); 966 Events.cancelEvent(event); 967 } 968 else if (Css.hasClass(target, "breakpointRow")) 969 { 970 var sourceLink = 971 target.getElementsByClassName("objectLink-sourceLink").item(0); 972 if (sourceLink) 973 this.dispatchMouseEvent(sourceLink, "click"); 974 } 975 else if (target.hasAttribute("aria-expanded") && 976 (target.getAttribute("role") == "row" || 977 target.getAttribute("role") == "listitem")) 978 { 979 var toggleElem = Css.hasClass(target, "spyHeadTable") ? 980 target.getElementsByClassName("spyTitleCol").item(0) : target; 981 if (toggleElem) 982 this.dispatchMouseEvent(toggleElem, "click"); 983 } 984 break; 985 986 case KeyEvent.DOM_VK_SPACE: 987 if (this.isFocusObject(target) && target.hasAttribute("role", "checkbox")) 988 { 989 this.dispatchMouseEvent(target, "click"); 990 var objectBox = Dom.getAncestorByClass(target, "hasBreakSwitch"); 991 if (objectBox) 992 { 993 target.setAttribute("aria-checked", 994 Css.hasClass(objectBox, "breakForError") + ""); 995 } 996 } 997 else if (Css.hasClass(target, "breakpointRow")) 998 { 999 var checkbox = target.getElementsByClassName("breakpointCheckbox").item(0); 1000 if (checkbox) 1001 { 1002 target.setAttribute("aria-checked", checkbox.checked ? "false" : "true"); 1003 this.dispatchMouseEvent(checkbox, "click"); 1004 } 1005 } 1006 break; 1007 1008 case KeyEvent.DOM_VK_DELETE: 1009 if (Css.hasClass(target, "breakpointRow")) 1010 { 1011 var closeBtn = target.getElementsByClassName("closeButton").item(0); 1012 if (closeBtn) 1013 { 1014 var prevBreakpoint = Dom.getPreviousByClass(target, "breakpointRow"); 1015 if (prevBreakpoint) 1016 this.makeFocusable(prevBreakpoint, true); 1017 Firebug.chrome.window.document.commandDispatcher.rewindFocus(); 1018 this.dispatchMouseEvent(closeBtn, "click"); 1019 } 1020 } 1021 break; 1022 } 1023 }, 1024 1025 focusPanelRow: function(panel, row) 1026 { 1027 var panelA11y = this.getPanelA11y(panel); 1028 if (!panelA11y || !row) 1029 return; 1030 this.modifyPanelRow(panel, row, false); 1031 1032 //allows up / down navigation in columns, if columns are used in this panel 1033 if (panelA11y.cellIndex !== undefined && row.cells && row.cells[panelA11y.cellIndex]) 1034 { 1035 var cell = row.cells[panelA11y.cellIndex]; 1036 if (!Css.hasClass(cell, "a11yFocus")) 1037 cell = Dom.getChildByClass(cell, "a11yFocus"); 1038 this.focus(cell); 1039 } 1040 // for Net Panel. Focus selected tab rather than the tab list 1041 else if (Css.hasClass(row, "netInfoTabs")) 1042 { 1043 var tabs = row.getElementsByClassName("netInfoTab"); 1044 tabs = Array.filter(tabs, function(e, i, a) 1045 { 1046 return e.hasAttribute("selected"); 1047 }); 1048 this.focus(tabs.length > 0 ? tabs[0] : row); 1049 } 1050 else 1051 { 1052 this.focus(row); 1053 } 1054 }, 1055 1056 getRowIndex: function(rows, target) 1057 { 1058 return Array.indexOf(rows, target); 1059 }, 1060 1061 getAncestorRow: function(elem, useSubRow) 1062 { 1063 return Dom.getAncestorByClass(elem, useSubRow ? "focusRow" : "outerFocusRow"); 1064 }, 1065 1066 onConsoleMouseDown: function(event) 1067 { 1068 var node = Dom.getAncestorByClass(event.target, "focusRow"); 1069 if (node) 1070 { 1071 this.modifyPanelRow(Firebug.getElementPanel(node), node, false); 1072 } 1073 else 1074 { 1075 node = Dom.getAncestorByClass(event.target, "memberRow"); 1076 if (!node) 1077 return; 1078 var focusRow = node.getElementsByClassName("focusRow").item(0); 1079 if (!focusRow) 1080 return; 1081 1082 this.focusPanelRow(Firebug.getElementPanel(focusRow), focusRow); 1083 node = Dom.getAncestorByClass(event.target, "memberLabel") 1084 if (!(node && Css.hasClass(node, "hasChildren"))) 1085 Events.cancelEvent(event); 1086 } 1087 }, 1088 1089 getValidRow: function(rows, index) 1090 { 1091 var min = 0; var max = rows.length -1; 1092 if (index < min || index > max) 1093 index = index < min ? 0 : max; 1094 return rows[index]; 1095 }, 1096 1097 getFocusObjects: function(container) 1098 { 1099 var nodes = container.getElementsByClassName("a11yFocus") 1100 return Array.filter(nodes, this.isVisibleByStyle, this); 1101 }, 1102 1103 modifyConsoleRow: function(panel, row, inTabOrder) 1104 { 1105 if (this.isDirCell(row)) 1106 { 1107 this.modifyMemberRow(panel, row, inTabOrder); 1108 } 1109 else if (this.isProfileRow(row)) 1110 { 1111 this.modifyProfileRow(panel, row, inTabOrder); 1112 } 1113 else if (this.isOuterFocusRow(row, true)) 1114 { 1115 if (Css.hasClass(row, "spyHeadTable") || Css.hasClass(row, "netInfoTabs")) 1116 this.modifyNetRow(panel, row, row.getAttribute("tabindex") === "0"); 1117 else 1118 this.modifyLogRow(panel, row, row.getAttribute("tabindex") === "0"); 1119 } 1120 else return; 1121 }, 1122 1123 modifyProfileRow: function(panel, row, inTabOrder) 1124 { 1125 var panelA11y = this.getPanelA11y(panel); 1126 if (!panelA11y || !row) 1127 return; 1128 1129 this.makeFocusable(row, inTabOrder); 1130 var focusObjects = this.getFocusObjects(row); 1131 Array.forEach(focusObjects, function(e, i, a) 1132 { 1133 this.makeFocusable(e); 1134 if (Css.hasClass(e.parentNode, "profileCell")) 1135 e.setAttribute("role", "gridcell"); 1136 }, this); 1137 }, 1138 1139 onConsoleSearchMatchFound: function(panel, text, matches) 1140 { 1141 var panelA11y = this.getPanelA11y(panel); 1142 if (!panelA11y) 1143 return; 1144 1145 var matchFeedback = ""; 1146 if (!matches || matches.length == 0) 1147 { 1148 matchFeedback = Locale.$STRF("a11y.updates.no matches found", [text]); 1149 } 1150 else 1151 { 1152 matchFeedback = Locale.$STRF("a11y.updates.match found in logrows", 1153 [text, matches.length]); 1154 } 1155 this.updateLiveElem(panel, matchFeedback, true); //should not use alert 1156 }, 1157 1158 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1159 // HTML Panel 1160 1161 onHTMLKeyPress: function(event) 1162 { 1163 var target = event.target; 1164 var keyCode = event.keyCode || (event.type == "keypress" ? event.charCode : null); 1165 if ([KeyEvent.DOM_VK_RETURN, KeyEvent.DOM_VK_SPACE, 1166 KeyEvent.DOM_VK_F2].indexOf(keyCode) == -1) 1167 { 1168 return; 1169 } 1170 if (!Css.hasClass(target, "nodeLabelBox")) 1171 return; 1172 1173 var panel = Firebug.getElementPanel(target); 1174 switch (keyCode) 1175 { 1176 case KeyEvent.DOM_VK_RETURN: 1177 case KeyEvent.DOM_VK_SPACE: 1178 var isEnter = (keyCode == KeyEvent.DOM_VK_RETURN); 1179 var nodeLabels = null; 1180 if (isEnter) 1181 { 1182 var nodeLabels = target.getElementsByClassName("nodeName"); 1183 if (nodeLabels.length > 0) 1184 { 1185 Firebug.Editor.startEditing(nodeLabels[0]); 1186 Events.cancelEvent(event); 1187 } 1188 } 1189 if (!isEnter || nodeLabels.length == 0) 1190 { 1191 var nodeBox = Dom.getAncestorByClass(target, "nodeBox"); 1192 if (nodeBox.repObject && panel.editNewAttribute) 1193 { 1194 panel.editNewAttribute(nodeBox.repObject) 1195 Events.cancelEvent(event); 1196 } 1197 } 1198 break; 1199 1200 case KeyEvent.DOM_VK_F2: 1201 if (Css.hasClass(target.parentNode.parentNode, "textNodeBox")) 1202 { 1203 var textNode = Dom.getChildByClass(target, "nodeText"); 1204 if (textNode) 1205 Firebug.Editor.startEditing(textNode); 1206 } 1207 break; 1208 } 1209 }, 1210 1211 onHTMLFocus: function(event) 1212 { 1213 if (Css.hasClass(event.target, "nodeLabelBox")) 1214 { 1215 this.dispatchMouseEvent(event.target, "mouseover"); 1216 var nodeLabel = Dom.getAncestorByClass(event.target, "nodeLabel"); 1217 if (nodeLabel) 1218 Css.setClass(nodeLabel, "focused"); 1219 event.target.setAttribute("aria-selected", "true"); 1220 Events.cancelEvent(event); 1221 } 1222 }, 1223 1224 onHTMLBlur: function(event) 1225 { 1226 if (Css.hasClass(event.target, "nodeLabelBox")) 1227 { 1228 this.dispatchMouseEvent(event.target, "mouseout"); 1229 var nodeLabel = Dom.getAncestorByClass(event.target, "nodeLabel"); 1230 if (nodeLabel) 1231 Css.removeClass(nodeLabel, "focused"); 1232 event.target.setAttribute("aria-selected", "false"); 1233 Events.cancelEvent(event); 1234 } 1235 }, 1236 1237 onObjectBoxSelected: function(objectBox, forceFocus) 1238 { 1239 var panel = Firebug.getElementPanel(objectBox); 1240 1241 // See issue 5934 1242 if (panel.editing) 1243 return; 1244 1245 var panelA11y = this.getPanelA11y(panel); 1246 if (!panelA11y) 1247 return; 1248 var label = objectBox.firstChild.getElementsByClassName("nodeLabelBox").item(0); 1249 if (label) 1250 { 1251 this.makeFocusable(label, true); 1252 if (this.panelHasFocus(panel) || forceFocus) 1253 this.focus(label); 1254 } 1255 }, 1256 1257 onObjectBoxUnselected: function(objectBox) 1258 { 1259 if (!this.isEnabled() || !objectBox) 1260 return; 1261 var label = objectBox.firstChild.getElementsByClassName("nodeLabelBox").item(0); 1262 if (label) 1263 this.makeUnfocusable(label, true); 1264 }, 1265 1266 onHTMLSearchMatchFound: function(panel, match) 1267 { 1268 var panelA11y = this.getPanelA11y(panel); 1269 if (!panelA11y) 1270 return; 1271 1272 var node = match.node; 1273 var elem; 1274 var matchFeedback = ""; 1275 switch (node.nodeType) 1276 { 1277 case Node.ELEMENT_NODE: 1278 elem = node; 1279 matchFeedback += Locale.$STRF("a11y.updates.match found in element", 1280 [match.match[0], elem.nodeName, Xpath.getElementTreeXPath(elem)]); 1281 break; 1282 1283 case Node.ATTRIBUTE_NODE: 1284 elem = node.ownerElement; 1285 matchFeedback += Locale.$STRF("a11y.updates.match found in attribute", 1286 [match.match[0], node.name, node.value, elem.nodeName, 1287 Xpath.getElementTreeXPath(elem)]); 1288 break; 1289 1290 case Node.TEXT_NODE: 1291 elem = node.parentNode; 1292 matchFeedback += Locale.$STRF("a11y.updates.match found in text content", 1293 [match.match[0], match.match.input]); 1294 break; 1295 } 1296 this.updateLiveElem(panel, matchFeedback, true); //should not use alert 1297 }, 1298 1299 onHTMLSearchNoMatchFound: function(panel, text) 1300 { 1301 this.updateLiveElem(panel, Locale.$STRF("a11y.updates.no matches found", [text]), true); 1302 }, 1303 1304 moveToSearchMatch: function() 1305 { 1306 if (!this.isEnabled()) 1307 return; 1308 var panel = Firebug.chrome.getSelectedPanel(); 1309 var panelA11y = this.getPanelA11y(panel); 1310 if (!panelA11y || !panel.searchable) 1311 return; 1312 1313 var popup = Firebug.chrome.$("fbSearchOptionsPopup"); 1314 if (popup) 1315 popup.hidePopup(); 1316 var type = panel.searchType ? panel.searchType : panelA11y.type; 1317 switch (type) 1318 { 1319 case "html": 1320 var match = panel.lastSearch.lastMatch; 1321 if (!match) 1322 return; 1323 var nodeBox = panel.lastSearch.openToNode(match.node, match.isValue); 1324 if (!nodeBox) 1325 return; 1326 1327 nodeBox = Dom.getAncestorByClass(nodeBox, "nodeBox"); 1328 //select call will not trigger focus because focus is outside the HTML panel (i.e. the search field), 1329 panel.select(nodeBox.repObject, true); 1330 // Manually force selected node to be focused 1331 this.onObjectBoxSelected(nodeBox, true); 1332 break; 1333 1334 case "css": 1335 if (panel.currentSearch && panel.currentSearch.currentNode) 1336 { 1337 var focusRow = Dom.getAncestorByClass(panel.currentSearch.currentNode, 1338 "focusRow"); 1339 if (focusRow) 1340 this.focusPanelRow(panel, focusRow); 1341 } 1342 break 1343 1344 case "script": 1345 if (panel.currentSearch && panel.selectedSourceBox) 1346 { 1347 var box = panel.selectedSourceBox; 1348 var lineNo = panel.currentSearch.mark; 1349 box.a11yCaretLine = lineNo + 1; 1350 box.a11yCaretOffset = 0; 1351 panel.scrollToLine(box.repObject.href, lineNo, 1352 panel.jumpHighlightFactory(lineNo+1, panel.context)); 1353 var viewport = box.getElementsByClassName("sourceViewport").item(0); 1354 if (viewport) 1355 { 1356 this.focus(viewport); 1357 this.insertCaretIntoLine(panel, box); 1358 } 1359 } 1360 break; 1361 1362 case "dom": 1363 if (panel.currentSearch && panel.currentSearch.currentNode) 1364 { 1365 var focusRow = 1366 panel.currentSearch.currentNode.getElementsByClassName("focusRow").item(0); 1367 if (focusRow) 1368 this.focusPanelRow(panel, focusRow); 1369 } 1370 break; 1371 1372 case "net": 1373 if (panel.currentSearch && panel.currentSearch.currentNode) 1374 { 1375 var focusRow = Dom.getAncestorByClass(panel.currentSearch.currentNode, 1376 "focusRow"); 1377 if (focusRow) 1378 this.focusPanelRow(panel, focusRow); 1379 } 1380 break; 1381 } 1382 }, 1383 1384 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1385 // CSS Panel 1386 1387 onCSSKeyPress: function(event) 1388 { 1389 var target = event.target; 1390 var keyCode = event.keyCode || (event.type == "keypress" ? event.charCode : null); 1391 if (!this.isFocusRow(target) || event.altKey) 1392 return; 1393 1394 if ([KeyEvent.DOM_VK_RETURN, KeyEvent.DOM_VK_SPACE, KeyEvent.DOM_VK_PAGE_UP, 1395 KeyEvent.DOM_VK_PAGE_DOWN, KeyEvent.DOM_VK_END, KeyEvent.DOM_VK_HOME, 1396 KeyEvent.DOM_VK_UP, KeyEvent.DOM_VK_DOWN].indexOf(keyCode) == -1) 1397 { 1398 return; //not interested in any other keys than arrows, pg, home/end, space & enter 1399 } 1400 var panel = Firebug.getElementPanel(target) 1401 var panelA11y = this.getPanelA11y(panel); 1402 if (!panelA11y) 1403 return; 1404 1405 switch (keyCode) 1406 { 1407 case KeyEvent.DOM_VK_UP: 1408 case KeyEvent.DOM_VK_DOWN: 1409 var goUp = keyCode == 38; 1410 if (Events.isControl(event)) 1411 { 1412 if (event.shiftKey) 1413 { 1414 var node = this[goUp ? "getPreviousByClass" : "getNextByClass"](target, 1415 "cssInheritHeader", panel.panelNode); 1416 if (node) 1417 this.focusPanelRow(panel, node); 1418 else if (goUp) 1419 this.focusEdgeCSSRow(panel, target, true); 1420 } 1421 else 1422 this.focusSiblingHeadRow(panel, target, goUp); 1423 } 1424 else 1425 this.focusSiblingCSSRow(panel, target, goUp); 1426 break; 1427 1428 case KeyEvent.DOM_VK_END: 1429 case KeyEvent.DOM_VK_HOME: 1430 if (Events.isControl(event)) 1431 this.focusEdgeHeadRow(panel, target, keyCode == 36); 1432 else 1433 this.focusEdgeCSSRow(panel, target, keyCode == 36); 1434 break; 1435 1436 case KeyEvent.DOM_VK_PAGE_UP: 1437 case KeyEvent.DOM_VK_PAGE_DOWN: 1438 if (Events.isControl(event)) 1439 this.focusPageSiblingHeadRow(panel, target, keyCode == 33); 1440 else 1441 this.focusPageSiblingCSSRow(panel, target, keyCode == 33); 1442 break; 1443 1444 case KeyEvent.DOM_VK_RETURN: 1445 if (Css.hasClass(target, "cssProp")) 1446 { 1447 var node = Dom.getChildByClass(target, "cssPropName"); 1448 if (node) 1449 Firebug.Editor.startEditing(node); 1450 Events.cancelEvent(event); 1451 } 1452 else if (Css.hasClass(target, "cssHead")) 1453 { 1454 var node = Dom.getChildByClass(target, "cssSelector"); 1455 if (node && Css.hasClass(node, "editable")) 1456 Firebug.Editor.startEditing(node); 1457 Events.cancelEvent(event); 1458 } 1459 else if (Css.hasClass(target, "importRule")) 1460 { 1461 var node = Dom.getChildByClass(target, "objectLink"); 1462 if (node) 1463 this.dispatchMouseEvent(node, "click"); 1464 } 1465 break; 1466 1467 case KeyEvent.DOM_VK_SPACE: 1468 if (Css.hasClass(target, "cssProp")) 1469 { 1470 //our focus is about to be wiped out, we'll try to get it back after 1471 panelA11y.reFocusId = Xpath.getElementXPath(target); 1472 panel.disablePropertyRow(target); 1473 if (panel.name == "stylesheet") 1474 { 1475 target.setAttribute("aria-checked", 1476 !Css.hasClass(target, "disabledStyle")); 1477 } 1478 Events.cancelEvent(event); 1479 } 1480 break; 1481 } 1482 if (!event.shiftKey) 1483 event.preventDefault(); 1484 }, 1485 1486 onCSSMouseDown: function(event) 1487 { 1488 var row = Dom.getAncestorByClass(event.target, "focusRow"); 1489 if (row) 1490 this.modifyPanelRow(Firebug.getElementPanel(row), row, false); 1491 }, 1492 1493 focusSiblingCSSRow: function(panel, target, goUp) 1494 { 1495 var newRow = this[goUp ? "getPreviousByClass" : "getNextByClass"](target, "focusRow", 1496 panel.panelNode); 1497 if (!newRow) 1498 return; 1499 this.focusPanelRow(panel, newRow, false); 1500 }, 1501 1502 focusPageSiblingCSSRow: function(panel, target, goUp) 1503 { 1504 var rows = this.getFocusRows(panel); 1505 var index = this.getRowIndex(rows, target); 1506 var newRow = this.getValidRow(rows, goUp ? index - 10 : index + 10); 1507 this.focusPanelRow(panel, newRow, false); 1508 }, 1509 1510 focusEdgeCSSRow: function(panel, target, goUp) 1511 { 1512 var rows = this.getFocusRows(panel); 1513 var newRow = this.getValidRow(rows, goUp ? 0 : rows.length -1); 1514 this.focusPanelRow(panel, newRow, false); 1515 }, 1516 1517 getHeadRowsAndIndex: function(panel, elem) 1518 { 1519 var rows = this.getFocusRows(panel); 1520 var headRow = Css.hasClass(elem, "cssHead") ? 1521 elem : Dom.getPreviousByClass(elem, "cssHead"); 1522 var headRows = Array.filter(rows, function(e, i, a) { 1523 return Css.hasClass(e, "cssHead"); 1524 }); 1525 var index = Array.indexOf(headRows, headRow); 1526 if (index == -1) 1527 index = 0; 1528 return [headRows, index]; 1529 }, 1530 1531 focusSiblingHeadRow: function(panel, elem, goUp) 1532 { 1533 var rowInfo = this.getHeadRowsAndIndex(panel, elem); 1534 var newRow = this.getValidRow(rowInfo[0], goUp ? rowInfo[1] - 1 : rowInfo[1] + 1); 1535 this.focusPanelRow(panel, newRow, false); 1536 }, 1537 1538 focusPageSiblingHeadRow: function(panel, elem, goUp) 1539 { 1540 var rowInfo = this.getHeadRowsAndIndex(panel, elem); 1541 var newRow = this.getValidRow(rowInfo[0], goUp ? rowInfo[1] - 10 : rowInfo[1] + 10); 1542 this.focusPanelRow(panel, newRow, false); 1543 }, 1544 1545 focusEdgeHeadRow: function(panel, elem, goUp) 1546 { 1547 var rowInfo = this.getHeadRowsAndIndex(panel, elem); 1548 var newRow = this.getValidRow(rowInfo[0], goUp ? 0 : rowInfo[0].length - 1); 1549 this.focusPanelRow(panel, newRow, false); 1550 }, 1551 1552 onBeforeCSSRulesAdded: function(panel) 1553 { 1554 // Panel content is about to be recreated, possibly wiping out focus. 1555 // Use the focused element's xpath to remember which rule had focus, 1556 // so that it can be refocused when the panel content is drawn again 1557 var panelA11y = this.getPanelA11y(panel); 1558 if (!panelA11y || !this.panelHasFocus(panel)) 1559 return; 1560 if (panelA11y.tabStop && Css.hasClass(panelA11y.tabStop, "focusRow")) 1561 panelA11y.reFocusId = Xpath.getElementXPath(panelA11y.tabStop); 1562 }, 1563 1564 onCSSRulesAdded: function(panel, rootNode) 1565 { 1566 var panelA11y = this.getPanelA11y(panel); 1567 if (!panelA11y) 1568 return; 1569 var row; 1570 if (panelA11y.reFocusId) 1571 { //we need to put focus back to where it was before it was wiped out 1572 var reFocusRows = Xpath.getElementsByXPath(rootNode.ownerDocument, 1573 panelA11y.reFocusId); 1574 panelA11y.reFocusId = null; 1575 if (reFocusRows.length > 0) 1576 { 1577 row = reFocusRows[0]; 1578 this.modifyPanelRow(panel, row, true); 1579 this.focus(row, true); 1580 this.setPanelTabStop(panel, row); 1581 return; 1582 } 1583 } 1584 //no refocus needed, just make first rule the panel's tab stop 1585 row = rootNode.getElementsByClassName("focusRow").item(0); 1586 this.modifyPanelRow(panel, row, true); 1587 return; 1588 }, 1589 //applies a11y changes (keyboard and screen reader related) to an individual row 1590 //To improve performance, this only happens when absolutely necessary, e.g. when the user navigates to the row in question 1591 1592 modifyCSSRow: function(panel, row, inTabOrder) 1593 { 1594 if (!panel || !row) 1595 return; 1596 var rule = Dom.getAncestorByClass(row, "cssRule"); 1597 if (inTabOrder) 1598 this.setPanelTabStop(panel, row); 1599 else 1600 this.makeFocusable(row); 1601 if (rule && !Css.hasClass(rule, "a11yModified")) 1602 { 1603 var listBox = rule.getElementsByClassName("cssPropertyListBox").item(0); 1604 var selector = rule.getElementsByClassName("cssSelector").item(0); 1605 if (listBox && selector) 1606 { 1607 listBox.setAttribute("aria-label", 1608 Locale.$STRF("a11y.labels.declarations for selector", [selector.textContent])); 1609 } 1610 Css.setClass(rule, "a11yModified") 1611 } 1612 if (Css.hasClass(row, "cssHead")) 1613 { 1614 if (panel.name == "css") 1615 { 1616 var sourceLink = rule.parentNode.lastChild; 1617 if (sourceLink && Css.hasClass(sourceLink, "objectLink")) 1618 { 1619 row.setAttribute("aria-label", row.textContent + " " + 1620 Locale.$STRF("a11y.labels.defined in file", [sourceLink.textContent])); 1621 } 1622 } 1623 } 1624 else if (Css.hasClass(row, "cssProp")) 1625 { 1626 row.setAttribute("aria-checked", !Css.hasClass(row, "disabledStyle")); 1627 if (Css.hasClass(row, "cssOverridden")) 1628 { 1629 row.setAttribute("aria-label", Locale.$STR("aria.labels.overridden") + " " + 1630 row.textContent); 1631 } 1632 } 1633 return; 1634 }, 1635 1636 onCSSPanelContextMenu: function(event) 1637 { 1638 var panelA11y = this.getPanelA11y(panel); 1639 if (!panelA11y) 1640 return; 1641 1642 if (event.button == 0) //the event was created by keyboard, not right mouse click 1643 { 1644 var panel = Firebug.getElementPanel(event.target); 1645 if (panel && Css.hasClass(event.target, "focusRow")) 1646 { 1647 var node = event.target; 1648 if (panel.name == "css") 1649 { 1650 if (Css.hasClass(event.target, "cssHead")) 1651 { 1652 node = event.target.parentNode.getElementsByClassName("objectLink") 1653 .item(0); 1654 } 1655 else if (Css.hasClass(event.target, "cssInheritHeader")) 1656 { 1657 node = event.target.getElementsByClassName("objectLink").item(0); 1658 } 1659 1660 if (!node || Css.hasClass(node, "collapsed")) 1661 { 1662 node = event.target; 1663 } 1664 } 1665 //these context menu options are likely to destroy current focus 1666 panelA11y.reFocusId = Xpath.getElementXPath(event.target); 1667 document.popupNode = node; 1668 Firebug.chrome.$("fbContextMenu").openPopup(node, "overlap", 0,0,true); 1669 Events.cancelEvent(event); //no need for default handlers anymore 1670 } 1671 } 1672 }, 1673 1674 onCSSSearchMatchFound: function(panel, text, matchRow) 1675 { 1676 var panelA11y = this.getPanelA11y(panel); 1677 if (!panelA11y || !text) 1678 return; 1679 1680 if (!matchRow) 1681 { 1682 this.updateLiveElem(panel, 1683 Locale.$STRF("a11y.updates.no matches found", [text]), true); //should not use alert 1684 return; 1685 } 1686 var matchFeedback = ""; 1687 var matchType = ""; 1688 var selector; 1689 if (Css.hasClass(matchRow, "cssSelector")) 1690 { 1691 matchFeedback = " " + Locale.$STRF("a11y.updates.match found in selector", 1692 [text, matchRow.textContent]); 1693 } 1694 else 1695 { 1696 selector = Dom.getPreviousByClass(matchRow, "cssSelector"); 1697 selector = selector ? selector.textContent : ""; 1698 if (Css.hasClass(matchRow, "cssPropName") || Css.hasClass(matchRow, "cssPropValue")) 1699 { 1700 var propRow = Dom.getAncestorByClass(matchRow, "cssProp"); 1701 if (propRow) 1702 { 1703 matchFeedback = Locale.$STRF("a11y.updates.match found in style declaration", 1704 [text, propRow.textContent, selector]); 1705 } 1706 } 1707 } 1708 this.updateLiveElem(panel, matchFeedback, true); // should not use alert 1709 }, 1710 1711 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1712 // Layout Panel 1713 1714 onLayoutBoxCreated: function(panel, node, detailsObj) 1715 { 1716 var panelA11y = this.getPanelA11y(panel); 1717 if (!panelA11y) 1718 return; 1719 1720 var focusGroups = node.getElementsByClassName("focusGroup"); 1721 Array.forEach(focusGroups, function(e, i, a) { 1722 this.makeFocusable(e, Css.hasClass(e, "positionLayoutBox")); 1723 e.setAttribute("role", "group"); 1724 e.setAttribute("aria-label", this.getLayoutBoxLabel(e, detailsObj)); 1725 e.setAttribute("aria-setsize", a.length); 1726 e.setAttribute("aria-posinset", i + 1); 1727 }, this); 1728 }, 1729 1730 getLayoutBoxLabel: function(elem, detailsObj) 1731 { 1732 var className = elem.className.match(/\b(\w+)LayoutBox\b/); 1733 if (!className) 1734 return ""; 1735 1736 var styleName = className[1]; 1737 var output = ""; 1738 switch (styleName) 1739 { 1740 case "position": 1741 output += Css.hasClass(elem, "blankEdge") ? 1742 "" : Locale.$STR("a11y.layout.position"); 1743 styleName = "outer"; 1744 break; 1745 1746 case "margin": 1747 output += Locale.$STR("a11y.layout.margin"); 1748 break; 1749 1750 case "border": 1751 output += Locale.$STR("a11y.layout.border"); 1752 break; 1753 1754 case "padding": 1755 output += Locale.$STR("a11y.layout.padding"); 1756 break; 1757 1758 case "content": 1759 output += Locale.$STR("a11y.layout.size"); 1760 break; 1761 } 1762 output += ": "; 1763 var valNames = []; 1764 var vals = {}; 1765 switch (styleName) 1766 { 1767 case "outer": 1768 valNames = ["top", "left", "position", "z-index"]; 1769 vals.top = detailsObj[styleName + "Top"]; 1770 vals.left = detailsObj[styleName + "Left"]; 1771 vals.position = detailsObj.position; 1772 vals["z-index"] = detailsObj.zIndex; 1773 break; 1774 1775 case "content": 1776 valNames = ["width", "height"] 1777 vals.width = detailsObj["width"]; 1778 vals.height = detailsObj["height"]; 1779 break; 1780 1781 default: 1782 valNames = ["top", "right", "bottom", "left"]; 1783 vals.top = detailsObj[styleName + "Top"]; 1784 vals.right = detailsObj[styleName + "Right"]; 1785 vals.bottom = detailsObj[styleName + "Bottom"]; 1786 vals.left = detailsObj[styleName + "Left"]; 1787 break; 1788 } 1789 1790 for (var i = 0; i < valNames.length; i++) 1791 { 1792 output += Locale.$STR("a11y.layout." + valNames[i]) + " = " + vals[valNames[i]]; 1793 output += i == valNames.length -1 ? "" : ", "; 1794 } 1795 return output; 1796 }, 1797 1798 onLayoutKeyPress: function(event) 1799 { 1800 var target = event.target; 1801 var keyCode = event.keyCode || (event.type == "keypress" ? event.charCode : null); 1802 if ([KeyEvent.DOM_VK_RETURN, KeyEvent.DOM_VK_LEFT, KeyEvent.DOM_VK_UP, 1803 KeyEvent.DOM_VK_RIGHT, KeyEvent.DOM_VK_DOWN].indexOf(keyCode) == -1) 1804 { 1805 return; 1806 } 1807 if (!Css.hasClass(target, "focusGroup")) 1808 return; 1809 1810 var panel = Firebug.getElementPanel(target); 1811 switch (keyCode) 1812 { 1813 case KeyEvent.DOM_VK_LEFT: 1814 case KeyEvent.DOM_VK_UP: 1815 case KeyEvent.DOM_VK_RIGHT: 1816 case KeyEvent.DOM_VK_DOWN: 1817 var node, goLeft = (keyCode == KeyEvent.DOM_VK_LEFT || 1818 keyCode == KeyEvent.DOM_VK_UP); 1819 if (goLeft) 1820 node = Dom.getAncestorByClass(target.parentNode, "focusGroup"); 1821 else 1822 node = Dom.getChildByClass(target, "focusGroup"); 1823 1824 if (node) 1825 this.focus(node); 1826 break; 1827 1828 case KeyEvent.DOM_VK_RETURN: 1829 var editable = target.getElementsByClassName("editable").item(0); 1830 if (editable) 1831 Firebug.Editor.startEditing(editable); 1832 Events.cancelEvent(event); 1833 break; 1834 } 1835 }, 1836 1837 onLayoutFocus: function(event) 1838 { 1839 if (Css.hasClass(event.target, "focusGroup")) 1840 { 1841 this.dispatchMouseEvent(event.target, "mouseover"); 1842 this.setPanelTabStop(Firebug.getElementPanel(event.target), event.target); 1843 } 1844 }, 1845 1846 onLayoutBlur: function(event) 1847 { 1848 if (Css.hasClass(event.target, "focusGroup")) 1849 this.dispatchMouseEvent(event.target, "mouseout"); 1850 }, 1851 1852 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1853 // Inline Editing 1854 onInlineEditorShow: function(panel, editor) 1855 { 1856 var panelA11y = this.getPanelA11y(panel); 1857 if (!panelA11y) 1858 return; 1859 1860 //recreate the input element rather than reusing the old one, otherwise AT won't pick it up 1861 editor.input.onkeypress = editor.input.oninput = editor.input.onoverflow = null; 1862 editor.inputTag.replace({}, editor.box.childNodes[1].firstChild, editor); 1863 editor.input = editor.box.childNodes[1].firstChild.firstChild; 1864 }, 1865 1866 onBeginEditing: function(panel, editor, target, value) 1867 { 1868 var panelA11y = this.getPanelA11y(panel); 1869 if (!panelA11y) 1870 return; 1871 1872 switch (panelA11y.type) 1873 { 1874 case "html": 1875 var tagName= nodeName = null; 1876 var setSize = posInSet = 0; var setElems; 1877 var label = Locale.$STR("a11y.labels.inline editor") + ": "; 1878 if (Css.hasClass(target, "nodeName") || Css.hasClass(target, "nodeValue")) 1879 { 1880 var isName = Css.hasClass(target, "nodeName"); 1881 setElems = target.parentNode.parentNode 1882 .getElementsByClassName(isName ? "nodeName" : "nodeValue"); 1883 setSize = (setElems.length * 2); 1884 posInSet = ((Array.indexOf(setElems, target) + 1) * 2) - (isName ? 1 : 0); 1885 editor.input.setAttribute("role", "listitem"); 1886 editor.input.setAttribute("aria-setsize", setSize); 1887 editor.input.setAttribute("aria-posinset", posInSet); 1888 nodeTag = Dom.getPreviousByClass(target, "nodeTag"); 1889 if (!isName) 1890 { 1891 nodeName = Dom.getPreviousByClass(target, "nodeName"); 1892 label += Locale.$STRF("a11y.labels.value for attribute in element", 1893 [nodeName.textContent, nodeTag.textContent]); 1894 } 1895 else 1896 { 1897 label += Locale.$STRF("a11y.label.attribute for element", 1898 [nodeTag.textContent]); 1899 } 1900 } 1901 else if (Css.hasClass(target, "nodeText")) 1902 { 1903 nodeTag = Dom.getPreviousByClass(target, "nodeTag"); 1904 label += Locale.$STRF("a11y.labels.text contents for element", 1905 [nodeTag.textContent]); 1906 } 1907 editor.input.setAttribute("aria-label", label); 1908 break; 1909 1910 case "css": 1911 case "stylesheet": 1912 var selector = Dom.getPreviousByClass(target, "cssSelector"); 1913 selector = selector ? selector.textContent : ""; 1914 var label = Locale.$STR("a11y.labels.inline editor") + ": "; 1915 if (Css.hasClass(target, "cssPropName")) 1916 { 1917 label += Locale.$STRF("a11y.labels.property for selector", [selector]); 1918 } 1919 else if (Css.hasClass(target, "cssPropValue")) 1920 { 1921 var propName = Dom.getPreviousByClass(target, "cssPropName"); 1922 propName = propName ? propName.textContent : ""; 1923 label += Locale.$STRF("a11y.labels.value property in selector", 1924 [propName, selector]); 1925 } 1926 else if (Css.hasClass(target, "cssSelector")) 1927 { 1928 label += Locale.$STR("a11y.labels.css selector"); 1929 } 1930 1931 editor.input.setAttribute("aria-label", label); 1932 editor.setAttribute("aria-autocomplete", "inline"); 1933 break; 1934 1935 case "layout": 1936 editor.input.setAttribute("aria-label", target.getAttribute("aria-label")); 1937 break; 1938 1939 case "dom": 1940 case "domSide": 1941 if (target.cells && target.cells[1]) 1942 editor.input.setAttribute("aria-label", target.cells[1].textContent); 1943 break; 1944 } 1945 }, 1946 1947 onInlineEditorClose: function(panel, target, removeGroup) 1948 { 1949 var panelA11y = this.getPanelA11y(panel); 1950 if (!panelA11y) 1951 return; 1952 1953 switch (panelA11y.type) 1954 { 1955 case "layout": 1956 var box = Dom.getAncestorByClass(target, "focusGroup") 1957 if (box) 1958 this.focus(box, true); 1959 break; 1960 1961 case "css": 1962 case "stylesheet": 1963 var node = target.parentNode; 1964 if (removeGroup) 1965 node = this.getPreviousByClass(node, "focusRow", panel.panelNode); 1966 if (node) 1967 this.focusPanelRow(panel, node, true); 1968 break; 1969 1970 case "html": 1971 var box = Dom.getAncestorByClass(target, "nodeBox") 1972 if (box) 1973 panel.select(box.repObject, true); 1974 break; 1975 1976 case "watches": 1977 var node = target.getElementsByClassName("watchEditBox").item(0); 1978 if (node) 1979 this.focus(node, true); 1980 break; 1981 1982 case "script": 1983 panel.selectedSourceBox.focus(); 1984 break; 1985 } 1986 }, 1987 1988 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1989 // Script Panel 1990 1991 onStartDebugging: function(context) 1992 { 1993 if (!context) 1994 return; 1995 1996 var panel = context.getPanel("script"); 1997 var panelA11y = this.getPanelA11y(panel); 1998 if (!panelA11y) 1999 return; 2000 2001 var frame = context.stoppedFrame; 2002 var fileName = frame.script.fileName.split("/"); // XXXjjb I think this should be contxt.executingSourceFile.href 2003 fileName = fileName.pop(); 2004 // XXXjjb the frame.functionName is often anonymous, since the compiler is lame. 2005 var alertString = Locale.$STRF("a11y.updates.script_suspended_on_line_in_file", 2006 [frame.line, frame.functionName, fileName]); 2007 this.updateLiveElem(panel, alertString, true); 2008 this.onShowSourceLink(panel, frame.line); 2009 }, 2010 2011 onShowSourceLink: function (panel, lineNo) 2012 { 2013 if (!this.isEnabled()) 2014 return; 2015 2016 var box = panel.selectedSourceBox; 2017 var viewport = box.getElementsByClassName("sourceViewport").item(0); 2018 box.a11yCaretLine = lineNo; 2019 if (viewport && this.panelHasFocus(panel)) 2020 { 2021 this.focus(viewport); 2022 this.insertCaretIntoLine(panel, box, lineNo); 2023 } 2024 }, 2025 2026 onScriptKeyPress: function(event) 2027 { 2028 var target = event.target; 2029 var keyCode = event.keyCode || (event.type == "keypress" ? event.charCode : null); 2030 if (!Css.hasClass(target, "sourceViewport")) 2031 return; 2032 2033 if ([KeyEvent.DOM_VK_RETURN, KeyEvent.DOM_VK_PAGE_UP, KeyEvent.DOM_VK_PAGE_DOWN, 2034 KeyEvent.DOM_VK_END, KeyEvent.DOM_VK_HOME, KeyEvent.DOM_VK_LEFT, KeyEvent.DOM_VK_UP, 2035 KeyEvent.DOM_VK_RIGHT, KeyEvent.DOM_VK_DOWN].indexOf(keyCode) == -1) 2036 { 2037 return; 2038 } 2039 2040 var panel = Firebug.getElementPanel(target); 2041 var panelA11y = this.getPanelA11y(panel); 2042 if (!panelA11y) 2043 return; 2044 2045 var box = panel.selectedSourceBox 2046 var lastLineNo = box.lastViewableLine; 2047 var firstLineNo = box.firstViewableLine; 2048 var caretDetails = this.getCaretDetails(event.target.ownerDocument); 2049 if (!caretDetails || caretDetails.length != 2) 2050 return; 2051 2052 var lineNode = Dom.getAncestorByClass(caretDetails[0].parentNode, "sourceRow"); 2053 if (!lineNode) 2054 return; 2055 2056 var lineNo = parseInt(lineNode.getElementsByClassName("sourceLine").item(0).textContent); 2057 box.a11yCaretLine = lineNo; 2058 box.a11yCaretOffset = caretDetails[1]; 2059 var newLineNo = 1; 2060 var linesToScroll = 0; 2061 var goUp; 2062 switch (keyCode) 2063 { 2064 case KeyEvent.DOM_VK_UP: 2065 case KeyEvent.DOM_VK_DOWN: 2066 goUp = (keyCode == KeyEvent.DOM_VK_UP); 2067 linesToScroll = goUp ? -1 : 1; 2068 if (!Events.isControl(event)) 2069 { 2070 if ((goUp && lineNo > firstLineNo + 1) || 2071 (!goUp && lineNo < lastLineNo - 1)) 2072 { 2073 return; 2074 } 2075 2076 box.a11yCaretLine = goUp ? lineNo - 1 : lineNo +1; 2077 } 2078 box.scrollTop = box.scrollTop + (linesToScroll * box.lineHeight); 2079 break; 2080 2081 case KeyEvent.DOM_VK_PAGE_UP: 2082 case KeyEvent.DOM_VK_PAGE_DOWN: 2083 goUp = (keyCode == KeyEvent.DOM_VK_PAGE_UP); 2084 if ((goUp && box.scrollTop == 0) || 2085 (!goUp && box.scrollTop == box.scrollHeight - box.clientHeight)) 2086 { 2087 box.a11yCaretLine = goUp ? 0 : box.totalMax; 2088 box.a11yCaretOffset = 0; 2089 this.insertCaretIntoLine(panel, box); 2090 Events.cancelEvent(event); 2091 return; 2092 } 2093 box.a11yCaretLine = goUp ? lineNo - box.viewableLines : lineNo + box.viewableLines; 2094 linesToScroll = goUp ? -box.viewableLines : box.viewableLines; 2095 box.scrollTop = box.scrollTop + (linesToScroll * box.lineHeight); 2096 Events.cancelEvent(event); 2097 break; 2098 2099 case KeyEvent.DOM_VK_HOME: 2100 case KeyEvent.DOM_VK_END: 2101 goUp = (keyCode == KeyEvent.DOM_VK_HOME); 2102 if (Events.isControl(event)) 2103 { 2104 box.a11yCaretLine = goUp ? 0 : box.totalMax; 2105 box.a11yCaretOffset = 0; 2106 if ((goUp && box.scrollTop == 0) || 2107 (!goUp && box.scrollTop == box.scrollHeight - box.clientHeight)) 2108 { 2109 this.insertCaretIntoLine(panel, box); 2110 } 2111 else 2112 { 2113 box.scrollTop = goUp ? 0 : box.scrollHeight - box.clientHeight;; 2114 } 2115 Events.cancelEvent(event); 2116 return; 2117 } 2118 2119 if (goUp) 2120 { 2121 //move caret to beginning of line. Override default behavior, as that would take the caret into the line number 2122 this.insertCaretIntoLine(panel, box, lineNo, 0); 2123 box.scrollLeft = 0; //in case beginning of line is scrolled out of view 2124 Events.cancelEvent(event); 2125 } 2126 break; 2127 2128 case KeyEvent.DOM_VK_RETURN: 2129 var liveString = ""; 2130 var caretDetails = this.getCaretDetails(event.target.ownerDocument); 2131 var lineNode = Dom.getAncestorByClass(caretDetails[0].parentNode, "sourceRow"); 2132 var lineNo = parseInt(lineNode.getElementsByClassName("sourceLine").item(0) 2133 .textContent); 2134 liveString += "Line " + lineNo; 2135 if (lineNode.getAttribute("breakpoint") == "true") 2136 { 2137 var breakpointStr; 2138 if (lineNode.getAttribute("disabledbreakpoint") == "true") 2139 breakpointStr = "a11y.updates.has disabled breakpoint"; 2140 if (lineNode.getAttribute("condition") == "true") 2141 breakpointStr = "a11y.updates.has conditional breakpoint"; 2142 liveString += ", " + Locale.$STR(breakpointStr); 2143 } 2144 if (lineNode.getAttribute("executable") == "true") 2145 liveString += ", executable"; 2146 if (lineNode.getAttribute("exe_line") == "true") 2147 liveString += ", currently stopped"; 2148 var sourceText = lineNode.getElementsByClassName("sourceRowText").item(0); 2149 if (sourceText) 2150 liveString += ": " + sourceText.textContent; 2151 this.updateLiveElem(panel, liveString, true); //should not use alert 2152 break; 2153 } 2154 }, 2155 2156 onScriptKeyUp: function(event) 2157 { 2158 var target = event.target; 2159 var keyCode = event.keyCode || (event.type == "keypress" ? event.charCode : null); 2160 if (!Css.hasClass(target, "sourceViewport")) 2161 return; 2162 2163 if ([KeyEvent.DOM_VK_RETURN, KeyEvent.DOM_VK_PAGE_UP, KeyEvent.DOM_VK_PAGE_DOWN, 2164 KeyEvent.DOM_VK_END, KeyEvent.DOM_VK_HOME, KeyEvent.DOM_VK_LEFT, KeyEvent.DOM_VK_UP, 2165 KeyEvent.DOM_VK_RIGHT, KeyEvent.DOM_VK_DOWN].indexOf(keyCode) == -1) 2166 { 2167 return; 2168 } 2169 2170 var panel = Firebug.getElementPanel(target); 2171 var panelA11y = this.getPanelA11y(panel); 2172 if (!panelA11y) 2173 return; 2174 2175 var box = panel.selectedSourceBox 2176 var caretDetails = this.getCaretDetails(target.ownerDocument); 2177 var lineNode = Dom.getAncestorByClass(caretDetails[0].parentNode, "sourceRow"); 2178 if (!lineNode) 2179 return; 2180 2181 var lineNo = parseInt(lineNode.getElementsByClassName("sourceLine").item(0).textContent); 2182 box.a11yCaretLine = lineNo; 2183 box.a11yCaretOffset = caretDetails[1]; 2184 }, 2185 2186 onScriptMouseUp: function(event) 2187 { 2188 var target = event.target; 2189 if (event.button !== 0) 2190 return; 2191 2192 var panel = Firebug.getElementPanel(target); 2193 var panelA11y = this.getPanelA11y(panel); 2194 if (!panelA11y) 2195 return; 2196 2197 var box = panel.selectedSourceBox 2198 var caretDetails = this.getCaretDetails(target.ownerDocument); 2199 var lineNode = null; 2200 if (caretDetails[0] && caretDetails[0].parentNode) 2201 lineNode = Dom.getAncestorByClass(caretDetails[0].parentNode, "sourceRow"); 2202 if (!lineNode) 2203 return; 2204 2205 var lineNo = parseInt(lineNode.getElementsByClassName("sourceLine").item(0).textContent); 2206 box.a11yCaretLine = lineNo; 2207 box.a11yCaretOffset = caretDetails[1]; 2208 }, 2209 2210 onBeforeViewportChange: function(panel) 2211 { 2212 var panelA11y = this.getPanelA11y(panel); 2213 if (!panelA11y) 2214 return; 2215 2216 var box = panel.selectedSourceBox; 2217 if (!box) 2218 return; 2219 2220 this.insertCaretIntoLine(panel, box); 2221 }, 2222 2223 insertCaretIntoLine: function(panel, box, lineNo, offset) 2224 { 2225 var panelA11y = this.getPanelA11y(panel); 2226 if (!panelA11y || !box) 2227 return; 2228 2229 if (typeof lineNo == "undefined") 2230 lineNo = box.a11yCaretLine ? box.a11yCaretLine : 0; 2231 //to prevent the caret from (partially) being placed just out of sight, 2232 //adjust the viewable line boundaries by 1 (unless the current line is the first or last line) 2233 var lineAdjust = lineNo == 0 || lineNo == box.totalMax ? 0 : 1; 2234 var firstLine = box.firstViewableLine + lineAdjust; 2235 var lastLine = box.lastViewableLine - lineAdjust; 2236 if (lineNo < (firstLine) || lineNo > lastLine) 2237 box.a11yCaretLine = lineNo = lineNo < firstLine ? firstLine : lastLine; 2238 var node = box.getLineNode(lineNo); 2239 if (!node) 2240 return; 2241 2242 if (typeof offset == "undefined") 2243 { 2244 if (box.a11yCaretOffset) 2245 offset = box.a11yCaretOffset; 2246 else 2247 box.a11yCaretOffset = offset = 0; 2248 } 2249 var startNode = node.getElementsByClassName("sourceRowText").item(0) 2250 if (startNode && startNode.firstChild && startNode.firstChild.nodeType == Node.TEXT_NODE) 2251 { 2252 startNode = startNode.firstChild; 2253 if (offset >= startNode.length) 2254 box.a11yCaretOffset = offset = startNode.length - 1; 2255 } 2256 else 2257 { 2258 startNode = node; //offset is now the number of nodes, not characters within a text node 2259 offset = 1; 2260 } 2261 this.insertCaretToNode(panel, startNode, offset); 2262 }, 2263 2264 getCaretDetails: function(doc) 2265 { 2266 var sel = doc.defaultView.getSelection(); 2267 return [sel.focusNode, sel.focusOffset]; 2268 }, 2269 2270 onUpdateScriptLocation: function(panel, file) 2271 { 2272 var panelA11y = this.getPanelA11y(panel); 2273 if (!panelA11y) 2274 return; 2275 2276 var box = panel.selectedSourceBox 2277 var viewport = panel.selectedSourceBox.getElementsByClassName("sourceViewport").item(0); 2278 box.tabIndex = -1; 2279 viewport.tabIndex = 0; 2280 viewport.setAttribute("role", "textbox"); 2281 viewport.setAttribute("aria-multiline", "true"); 2282 viewport.setAttribute("aria-readonly", "true"); 2283 fileName = Url.getFileName(file.href); 2284 viewport.setAttribute("aria-label", Locale.$STRF("a11y.labels.source code for file", 2285 [fileName])); 2286 //bit ugly, but not sure how else I can get the caret into the sourcebox without a mouse 2287 var focusElem = Firebug.chrome.window.document.commandDispatcher.focusedElement; 2288 var line = box.getLineNode(box.firstViewableLine); 2289 if (!line) 2290 return; 2291 2292 var node = line.getElementsByClassName("sourceRowText").item(0); 2293 this.insertCaretToNode(panel, node); 2294 // move focus back to where it was 2295 this.focus(focusElem); 2296 }, 2297 2298 insertCaretToNode: function(panel, node, startOffset) 2299 { 2300 if (!startOffset) 2301 startOffset = 0; 2302 var sel = panel.document.defaultView.getSelection(); 2303 sel.removeAllRanges(); 2304 var range = panel.document.createRange(); 2305 range.setStart(node, startOffset); 2306 range.setEnd(node, startOffset); 2307 sel.addRange(range); 2308 }, 2309 2310 onScriptContextMenu: function(event) 2311 { 2312 if (event.button == 0) //i.e. keyboard, not right mouse click 2313 { 2314 //Try to find the line node based on the caret and manually trigger the context menu 2315 var panel = Firebug.getElementPanel(event.target); 2316 var panelA11y = this.getPanelA11y(panel); 2317 if (!panelA11y) 2318 return; 2319 2320 var sel = event.target.ownerDocument.defaultView.getSelection(); 2321 var node = sel.focusNode.parentNode; 2322 var x = event.pageX 2323 if (x == 0) 2324 { 2325 //TODO: This is ugly and way too inaccurate, how to get xy coordinates from selection object? 2326 var charWidth = panelA11y.oneEmElem ? panelA11y.oneEmElem.clientWidth * 0.65 : 7.5; 2327 x = node.offsetLeft + sel.focusOffset * charWidth; 2328 } 2329 var y = event.pageY; 2330 if (y >= event.target.clientHeight) 2331 y = node.offsetTop; 2332 Firebug.chrome.window.document.popupNode = node; 2333 Firebug.chrome.$("fbContextMenu").openPopup(node.ownerDocument.body, "overlap", x, y, 2334 true); 2335 Events.cancelEvent(event); 2336 } 2337 }, 2338 2339 onWatchPanelRefreshed: function(panel) 2340 { 2341 var panelA11y = this.getPanelA11y(panel); 2342 if (!panelA11y) 2343 return; 2344 2345 var watchEditTrigger = panel.panelNode.getElementsByClassName("watchEditCell").item(0); 2346 if (watchEditTrigger) 2347 this.makeFocusable(watchEditTrigger, true); 2348 }, 2349 2350 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2351 // Call Stack Panel 2352 2353 onStackCreated: function(panel) 2354 { 2355 var panelA11y = this.getPanelA11y(panel); 2356 if (!panelA11y) 2357 return; 2358 var rows = panel.panelNode.getElementsByClassName("focusRow"); 2359 Array.forEach(rows, function(e, i, a) { 2360 if ((panelA11y.lastIsDefault && i === rows.length - 1) || 2361 (!panelA11y.lastIsDefault && i === 0)) 2362 { 2363 this.setPanelTabStop(panel, e); 2364 } 2365 else 2366 { 2367 this.makeFocusable(e, false); 2368 } 2369 }, this); 2370 }, 2371 2372 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2373 // Breakpoints Panel 2374 2375 onBreakRowsRefreshed: function(panel, rootNode) 2376 { 2377 var rows = rootNode.getElementsByClassName("focusRow"); 2378 for (var i = 0; i < rows.length; i++) 2379 { 2380 this.makeFocusable(rows[i], i == 0); 2381 if (i == 0) 2382 this.setPanelTabStop(panel, rows[i]); 2383 } 2384 var groupHeaders = rootNode.getElementsByClassName("breakpointHeader"); 2385 for (var i = 0; i < groupHeaders.length; i++) 2386 { 2387 var listBox = Dom.getNextByClass(groupHeaders[i], "breakpointsGroupListBox"); 2388 if (listBox) 2389 listBox.setAttribute("aria-label", groupHeaders[i].textContent); 2390 } 2391 }, 2392 2393 onScriptSearchMatchFound: function(panel, text, sourceBox, lineNo) 2394 { 2395 var panelA11y = this.getPanelA11y(panel); 2396 if (!panelA11y || !text) 2397 return; 2398 2399 var matchFeedback = ""; 2400 if (!sourceBox || lineNo === null) 2401 { 2402 matchFeedback = Locale.$STRF("a11y.updates.no matches found", [text]); 2403 } 2404 else 2405 { 2406 var line = sourceBox.getLine(panel.context, lineNo + 1); 2407 if (!line) 2408 line = ""; 2409 matchFeedback = Locale.$STRF("a11y.updates.match found for on line", 2410 [text, lineNo + 1, Url.getFileName(sourceBox.href)]); 2411 } 2412 this.updateLiveElem(panel, matchFeedback, true); 2413 }, 2414 2415 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2416 // DOM Panel 2417 2418 onMemberRowsAdded: function(panel, rows) 2419 { 2420 if (!panel) 2421 panel = Firebug.getElementPanel(rows[0]); 2422 var panelA11y = this.getPanelA11y(panel); 2423 if (!panelA11y || !rows) 2424 return; 2425 2426 var setSize; 2427 var posInset; 2428 var setSize = rows.length; 2429 var posInset = 0; 2430 for (var i = 0; i < rows.length; i++) 2431 { 2432 var makeTab = (panelA11y.lastIsDefault && i === rows.length - 1) || 2433 (!panelA11y.lastIsDefault && i === 0) 2434 this.prepareMemberRow(panel, rows[i], makeTab, ++posInset, setSize); 2435 } 2436 }, 2437 2438 onMemberRowSliceAdded: function(panel, borderRows, posInSet, setSize) 2439 { 2440 if (!borderRows) 2441 return; 2442 var startRow = borderRows[0]; 2443 var endRow = borderRows[1]; 2444 if (!panel) 2445 panel = Firebug.getElementPanel(startRow); 2446 var panelA11y = this.getPanelA11y(panel); 2447 if (!panelA11y) 2448 return; 2449 2450 var reFocusId = panelA11y.reFocusId; 2451 var row = startRow; 2452 do 2453 { 2454 this.prepareMemberRow(panel, row, false, posInSet++, setSize, reFocusId); 2455 if (row === endRow) 2456 break; 2457 } 2458 while (row = row.nextSibling); 2459 }, 2460 2461 prepareMemberRow: function(panel, row, makeTab, posInSet, setSize, reFocusId) 2462 { 2463 var panelA11y = this.getPanelA11y(panel); 2464 if (!panelA11y|| !row) 2465 return; 2466 2467 if (!row.cells[2]) 2468 return; 2469 2470 var cellChild = row.cells[2].firstChild; 2471 if (cellChild) 2472 { 2473 if (Css.hasClass(row, "hasChildren")) 2474 cellChild.setAttribute("aria-expanded", Css.hasClass(row, "opened")); 2475 if (makeTab) 2476 this.modifyPanelRow(panel, cellChild, true); 2477 cellChild.setAttribute("role", "treeitem"); 2478 cellChild.setAttribute("aria-level", parseInt(row.getAttribute("level")) + 1); 2479 if (posInSet && setSize) 2480 { 2481 cellChild.setAttribute("aria-setsize", setSize); 2482 cellChild.setAttribute("aria-posinset", posInSet); 2483 } 2484 Css.setClass(cellChild, "focusRow"); 2485 if (typeof reFocusId == "number" && row.rowIndex == reFocusId) 2486 { 2487 this.modifyMemberRow(panel, cellChild, true); 2488 this.focus(cellChild, true, true); 2489 panelA11y.reFocusId = null; 2490 } 2491 } 2492 }, 2493 2494 modifyMemberRow: function(panel, row, inTabOrder) 2495 { 2496 var type = this.getObjectType(row) 2497 var labelCell = row.parentNode.previousSibling; 2498 row.setAttribute("aria-label", labelCell.textContent + 2499 ": " + " " + row.textContent + (type ? " (" + type + ")" : "")); 2500 if (inTabOrder) 2501 this.setPanelTabStop(panel, row); 2502 else 2503 this.makeFocusable(row, false); 2504 }, 2505 2506 onBeforeDomUpdateSelection: function (panel) 2507 { 2508 var panelA11y = this.getPanelA11y(panel); 2509 if (!panelA11y) 2510 return; 2511 2512 var focusNode = panel.document.activeElement; 2513 if (this.isDirCell(focusNode)) 2514 panelA11y.reFocusId = focusNode.parentNode.parentNode.rowIndex; 2515 }, 2516 2517 onWatchEndEditing: function(panel, row) 2518 { 2519 var panelA11y = this.getPanelA11y(panel); 2520 if (!panelA11y) 2521 return; 2522 2523 panelA11y.reFocusId = 2; 2524 }, 2525 2526 onDomSearchMatchFound: function (panel, text, matchRow) 2527 { 2528 var panelA11y = this.getPanelA11y(panel); 2529 if (!panelA11y || !text) 2530 return; 2531 2532 var matchFeedback = ""; 2533 if (matchRow && matchRow.cells) 2534 { 2535 var dirCell = matchRow.getElementsByClassName("focusRow").item(0); 2536 if (dirCell) 2537 { 2538 this.modifyPanelRow(panel, dirCell); 2539 var rowLabel = dirCell.getAttribute("aria-label"); 2540 matchFeedback = Locale.$STRF("a11y.updates.match found in dom property", 2541 [text, rowLabel]); 2542 } 2543 } 2544 else 2545 { 2546 matchFeedback = Locale.$STRF("a11y.updates.no matches found", [text]); 2547 } 2548 this.updateLiveElem(panel, matchFeedback, true); 2549 }, 2550 2551 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2552 // Net Panel 2553 2554 isSubFocusRow: function(elem) 2555 { 2556 return Css.hasClass(elem, "focusRow") || Css.hasClass(elem, "wrappedText"); 2557 }, 2558 2559 modifyNetRow: function(panel, row, inTabOrder) 2560 { 2561 var panelA11y = this.getPanelA11y(panel); 2562 if (!panelA11y || !row) 2563 return; 2564 2565 if (this.isOuterFocusRow(row, true)) 2566 { 2567 if (!Css.hasClass(row, "netInfoTabs")) 2568 this.makeFocusable(row, inTabOrder); 2569 if ((Css.hasClass(row, "netRow") || 2570 Css.hasClass(row, "spyHeadTable")) && !row.hasAttribute("aria-expanded")) 2571 { 2572 row.setAttribute("aria-expanded", Css.hasClass(row, "opened") + ""); 2573 } 2574 var focusObjects = this.getFocusObjects(row); 2575 Array.forEach(focusObjects, function(e, i, a) { 2576 this.makeFocusable(e); 2577 if (Css.hasClass(e, "netTimeCol") && Dom.getAncestorByClass(e, "fromCache")) 2578 { 2579 e.setAttribute("aria-label", e.textContent + " (" + 2580 Locale.$STR("a11y.labels.cached") +")"); 2581 } 2582 }, this); 2583 } 2584 else return; 2585 }, 2586 2587 getNetAncestorRow: function(elem, useSubRow) 2588 { 2589 return useSubRow ? Dom.getAncestorByClass(elem, "subFocusRow") || 2590 Dom.getAncestorByClass(elem, "netRow") : Dom.getAncestorByClass(elem, "netRow"); 2591 }, 2592 2593 onNetMouseDown: function(event) 2594 { 2595 var node = Dom.getAncestorByClass(event.target, "focusRow"); 2596 if (node) 2597 { 2598 this.modifyPanelRow(Firebug.getElementPanel(node), node, false); 2599 } 2600 else 2601 { 2602 node = Dom.getAncestorByClass(event.target, "subFocusRow"); 2603 if (!node) 2604 return; 2605 2606 var focusRow = node.getElementsByClassName("focusRow").item(0); 2607 if (!focusRow) 2608 return; 2609 2610 this.modifyPanelRow(Firebug.getElementPanel(focusRow), focusRow, false); 2611 this.focus(focusRow); 2612 } 2613 }, 2614 2615 onNetFocus: function(e) { 2616 var target = e.target; 2617 var panel = Firebug.getElementPanel(target); 2618 var panelA11y = this.getPanelA11y(panel); 2619 if (!panelA11y) 2620 return; 2621 2622 if (!Css.hasClass(target, "netCol") && !Css.hasClass(target, "netHeaderCell")) 2623 return; 2624 2625 if (Css.hasClass(target, "netHrefCol")) 2626 { 2627 var hrefLabel = target.getElementsByClassName("netHrefLabel").item(0); 2628 var fullHrefLabel = target.getElementsByClassName("netFullHrefLabel").item(0); 2629 if (hrefLabel && fullHrefLabel) 2630 { 2631 Css.setClass(fullHrefLabel, "a11yShowFullLabel"); 2632 fullHrefLabel.style.marginTop = (hrefLabel.offsetHeight + 4) + "px"; 2633 return; 2634 } 2635 } 2636 var rangeParent = Dom.getAncestorByClass(target, "netRow"); 2637 var browser = Firebug.chrome.getPanelBrowser(panel); 2638 // these two lines are necessary, because otherwise the info tip will not have the correct 2639 // dimensions when it's positioned, and the contentscould be placed outside of Firebug's 2640 // viewport (making it impossible to read for keyboard users) 2641 // This will be called again in showInfoTip 2642 panel.showInfoTip(browser.infoTip, target, target.offsetLeft, target.offsetTop, 2643 rangeParent, 0); 2644 browser.infoTip.setAttribute("active", "true"); 2645 var left = Css.hasClass(target, "netTimeCol") ? 2646 target.offsetLeft - browser.infoTip.offsetWidth - 12 : 2647 target.offsetLeft + target.offsetWidth - 4; 2648 Firebug.InfoTip.showInfoTip(browser.infoTip, panel, target, left, 2649 target.offsetTop - panel.panelNode.scrollTop - 12, rangeParent, 0); 2650 }, 2651 2652 onNetBlur: function(e) { 2653 var target = e.target; 2654 var panel = Firebug.getElementPanel(target); 2655 var panelA11y = this.getPanelA11y(panel); 2656 if (!panelA11y) 2657 return; 2658 2659 if (Css.hasClass(target, "netHrefCol")) 2660 { 2661 var hrefLabel = target.getElementsByClassName("netHrefLabel").item(0); 2662 var fullHrefLabel = target.getElementsByClassName("netFullHrefLabel").item(0); 2663 if (hrefLabel && fullHrefLabel) 2664 { 2665 Css.removeClass(fullHrefLabel, "a11yShowFullLabel"); 2666 fullHrefLabel.style.marginTop = "0px"; 2667 } 2668 } 2669 var browser = Firebug.chrome.getPanelBrowser(panel); 2670 Firebug.InfoTip.hideInfoTip(browser.infoTip); 2671 }, 2672 2673 onNetMatchFound: function(panel, text, row) 2674 { 2675 //TODO localize for 1.5 2676 var panelA11y = this.getPanelA11y(panel); 2677 if (!panelA11y) 2678 return; 2679 2680 var matchFeedback = ""; 2681 if (!row) 2682 { 2683 matchFeedback = Locale.$STRF("a11y.updates.no matches found", [text]); 2684 } 2685 else 2686 { 2687 var foundWhere = ""; 2688 var parentRow = Dom.getAncestorByClass(row, "netRow"); 2689 if (!parentRow) 2690 { 2691 parentRow = Dom.getAncestorByClass(row, "netInfoRow"); 2692 if (parentRow) 2693 parentRow = parentRow.previousSibling; 2694 } 2695 if (Css.hasClass(row, "netHrefLabel")) 2696 foundWhere = Locale.$STR("net.header.URL"); 2697 else if (Css.hasClass(row, "netStatusLabel")) 2698 foundWhere = Locale.$STR("net.header.Status"); 2699 else if (Css.hasClass(row, "netDomainLabel")) 2700 foundWhere = Locale.$STR("net.header.Domain"); 2701 else if (Css.hasClass(row, "netSizeLabel")) 2702 foundWhere = Locale.$STR("net.header.Size"); 2703 else if (Css.hasClass(row, "netTimeLabel")) 2704 foundWhere = Locale.$STR("net.header.Timeline"); 2705 else 2706 foundWhere = "request details"; 2707 if (parentRow && parentRow.repObject) 2708 { 2709 var file = parentRow.repObject; 2710 var href = (file.method ? file.method.toUpperCase() : "?") + " " + 2711 Url.getFileName(file.href); 2712 matchFeedback = Locale.$STRF("a11y.updates.match found in net row", 2713 [text, href, foundWhere, row.textContent]); 2714 } 2715 else if (Dom.getAncestorByClass(row, "netSummaryRow")) 2716 { 2717 matchFeedback = Locale.$STRF("a11y.updates.match found in net summary row", 2718 [text, row.textContent]); 2719 } 2720 } 2721 this.updateLiveElem(panel, matchFeedback, true); //should not use alert 2722 }, 2723 2724 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2725 // Panel Navigation 2726 2727 insertHiddenText: function(panel, elem, text, asLastNode, id) 2728 { 2729 var span = panel.document.createElement("span"); 2730 span.className ="offScreen"; 2731 span.textContent = text; 2732 2733 if (id) 2734 span.id = id; 2735 2736 if (asLastNode) 2737 elem.appendChild(span); 2738 else 2739 elem.insertBefore(span, elem.firstChild); 2740 }, 2741 2742 getLogRowType: function(elem) 2743 { 2744 var type = ""; 2745 if (!elem) 2746 return type; 2747 2748 var className = elem.className.match(/\logRow-(\w+)\b/); 2749 if (className) 2750 type = className[1]; 2751 2752 if (!type) 2753 { 2754 if (Css.hasClass(elem, "errorTitle")) 2755 type = "detailed error"; 2756 else if (Css.hasClass(elem, "errorSourceBox")) 2757 type = "error source line"; 2758 else 2759 type = this.getObjectType(elem); 2760 } 2761 2762 if (type == "stackFrame") 2763 type=""; 2764 2765 return type; 2766 }, 2767 2768 getObjectType: function(elem) 2769 { 2770 var type = ""; 2771 if (elem.nodeName == "img") 2772 return type; 2773 2774 var className = elem.className.match(/\bobject(Box|Link)-(\w+)/); 2775 if (className) 2776 type = className[2]; 2777 2778 switch (type) 2779 { 2780 case "null": 2781 case "undefined": 2782 type = ""; 2783 break; 2784 2785 case "number": 2786 if (elem.textContent == "true" || elem.textContent == "false") 2787 type = "boolean"; 2788 2789 case "": 2790 case "object": 2791 if (elem.repObject) 2792 { 2793 try 2794 { 2795 var obj = elem.repObject; 2796 if (!obj) 2797 return type; 2798 2799 type = typeof obj; 2800 if (obj instanceof Array) 2801 type = "array"; 2802 2803 if (typeof obj.lineNo != "undefined") 2804 type = "function call"; 2805 } 2806 catch(e) {} 2807 } 2808 } 2809 2810 return type; 2811 }, 2812 2813 modifyPanelRow: function (panel, row, inTabOrder) 2814 { 2815 if (Css.hasClass(row, "a11yModified")) 2816 return; 2817 2818 var panelA11y = this.getPanelA11y(panel); 2819 if (!panelA11y || !row) 2820 return; 2821 2822 switch (panelA11y.type) 2823 { 2824 case "console": 2825 this.modifyConsoleRow(panel,row, inTabOrder); 2826 break; 2827 2828 case "css": 2829 this.modifyCSSRow(panel, row, inTabOrder); 2830 break; 2831 2832 case "net": 2833 this.modifyNetRow(panel, row, inTabOrder); 2834 break; 2835 } 2836 Css.setClass(row, "a11yModified"); 2837 }, 2838 2839 focusSiblingRow: function(panel, target, goUp) 2840 { 2841 var newRow = this[goUp ? "getPreviousByClass" : "getNextByClass"](target, "focusRow", 2842 true, panel.panelNode) 2843 if (!newRow) 2844 return; 2845 2846 this.focusPanelRow(panel, newRow) 2847 }, 2848 2849 focusPageSiblingRow: function(panel, target, goUp) 2850 { 2851 var rows = this.getFocusRows(panel); 2852 var index = this.getRowIndex(rows, target); 2853 var newRow = this.getValidRow(rows, goUp ? index - 10 : index + 10); 2854 this.focusPanelRow(panel, newRow); 2855 }, 2856 2857 focusEdgeRow: function(panel, target, goUp) 2858 { 2859 var rows = this.getFocusRows(panel); 2860 var newRow = this.getValidRow(rows, goUp ? 0 : rows.length -1); 2861 this.focusPanelRow(panel, newRow); 2862 }, 2863 2864 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2865 // Utils 2866 2867 onPanelFocus: function(event) 2868 { 2869 var panel = Firebug.getElementPanel(event.target); 2870 var panelA11y = this.getPanelA11y(panel); 2871 if (!panelA11y) 2872 return; 2873 2874 var target = event.target; 2875 if (this.isTabWorthy(target) && target !== this.getPanelTabStop(panel)) 2876 this.setPanelTabStop(panel, target); 2877 if (target.getAttribute("role").match(/gridcell|rowheader|columnheader/)) 2878 { 2879 var cell = (target.nodeName.toLowerCase() == "td" || 2880 (target.nodeName.toLowerCase() == "th" ? target : target.parentNode)); 2881 panelA11y.cellIndex = (cell.cellIndex !== undefined ? cell.cellIndex : undefined); 2882 } 2883 else 2884 { 2885 if (Css.hasClass(target, "netInfoTab")) 2886 this.dispatchMouseEvent(target, "click"); 2887 panelA11y.cellIndex = undefined; //reset if no longer in grid 2888 } 2889 }, 2890 2891 getFocusRows: function(panel) 2892 { 2893 var nodes = panel.panelNode.getElementsByClassName("focusRow"); 2894 return Array.filter(nodes, function(e, i, a) 2895 { 2896 return this.isVisibleByStyle(e) && Xml.isVisible(e); 2897 }, this); 2898 }, 2899 2900 getLastFocusChild: function(target) 2901 { 2902 var focusChildren = target.getElementsByClassName("focusRow"); 2903 return focusChildren.length > 0 ? focusChildren[focusChildren.length -1] : null; 2904 }, 2905 2906 getFirstFocusChild: function(target) 2907 { 2908 var focusChildren = target.getElementsByClassName("focusRow"); 2909 return focusChildren.length > 0 ? focusChildren[0] : null; 2910 }, 2911 2912 focus: function(elem, noVisiCheck, needsMoreTime) 2913 { 2914 if (Dom.isElement(elem) && (noVisiCheck || this.isVisibleByStyle(elem))) 2915 { 2916 Firebug.currentContext.setTimeout(function() { 2917 elem.focus() 2918 }, needsMoreTime ? 500 : 10 2919 ); 2920 } 2921 }, 2922 2923 makeFocusable: function(elem, inTabOrder) 2924 { 2925 if (elem) 2926 elem.setAttribute("tabindex", inTabOrder ? "0" : "-1"); 2927 }, 2928 2929 makeUnfocusable: function(elem) 2930 { 2931 if (elem) 2932 elem.removeAttribute("tabindex"); 2933 }, 2934 2935 reportFocus: function(event) 2936 { 2937 FBTrace.sysout("focus: " + event.target.nodeName + "#" + event.target.id + "." + 2938 event.target.className, event.target); 2939 }, 2940 2941 dispatchMouseEvent: function (node, eventType, clientX, clientY, button) 2942 { 2943 if (!clientX) 2944 clientX = 0; 2945 if (!clientY) 2946 clientY = 0; 2947 if (!button) 2948 button = 0; 2949 if (typeof node == "string") 2950 throw new Error("a11y.dispatchMouseEvent obsolete API"); 2951 var doc = node.ownerDocument; 2952 var event = doc.createEvent("MouseEvents"); 2953 event.initMouseEvent(eventType, true, true, doc.defaultView, 2954 0, 0, 0, clientX, clientY, false, false, false, false, button, null); 2955 node.dispatchEvent(event); 2956 }, 2957 2958 isVisibleByStyle: function (elem) 2959 { 2960 if (!elem || elem.nodeType != Node.ELEMENT_NODE) 2961 return false; 2962 var style = elem.ownerDocument.defaultView.getComputedStyle(elem, null); 2963 return style.visibility !== "hidden" && style.display !== "none"; 2964 }, 2965 2966 isTabWorthy: function (elem) 2967 { 2968 return this.isFocusRow(elem) || this.isFocusObject(elem); 2969 }, 2970 2971 isOuterFocusRow: function(elem, includeSubRow) 2972 { 2973 return includeSubRow ? this.isSubFocusRow(elem) : Css.hasClass(elem, "outerFocusRow"); 2974 }, 2975 2976 isProfileRow: function(elem) 2977 { 2978 return Css.hasClass(elem, "profileRow"); 2979 }, 2980 2981 isFocusRow: function(elem) 2982 { 2983 return Css.hasClass(elem, "focusRow"); 2984 }, 2985 2986 isFocusObject: function(elem) 2987 { 2988 return Css.hasClass(elem, "a11yFocus"); 2989 }, 2990 2991 isFocusNoTabObject: function(elem) 2992 { 2993 return Css.hasClass(elem, "a11yFocusNoTab"); 2994 }, 2995 2996 isDirCell: function(elem) 2997 { 2998 return Css.hasClass(elem.parentNode, "memberValueCell"); 2999 }, 3000 3001 panelHasFocus: function(panel) 3002 { 3003 if (!panel || !panel.context) 3004 return false; 3005 var focusedElement = Firebug.chrome.window.document.commandDispatcher.focusedElement; 3006 var focusedPanel = Firebug.getElementPanel(focusedElement) 3007 return focusedPanel && (focusedPanel.name == panel.name); 3008 }, 3009 3010 getPanelA11y: function(panel, create) 3011 { 3012 var a11yPanels, panelA11y; 3013 if (!this.isEnabled() || !panel || !panel.name || !panel.context) 3014 return false; 3015 3016 a11yPanels = panel.context.a11yPanels; 3017 if (!a11yPanels) 3018 a11yPanels = panel.context.a11yPanels = {}; 3019 panelA11y = a11yPanels[panel.name]; 3020 if (!panelA11y) 3021 { 3022 if (create) 3023 panelA11y = a11yPanels[panel.name] = {}; 3024 else 3025 return false; 3026 } 3027 return panelA11y 3028 }, 3029 3030 // These utils are almost the same as their DOM namesakes, 3031 // except that the routine skips invisible containers 3032 // (rather than wasting time on their child nodes) 3033 getPreviousByClass: function (node, className, downOnly, maxRoot) 3034 { 3035 if (!node) 3036 return null; 3037 3038 function criteria(node) { 3039 return node.nodeType == Node.ELEMENT_NODE && Css.hasClass(node, className); 3040 } 3041 3042 for (var sib = node.previousSibling; sib; sib = sib.previousSibling) 3043 { 3044 if (!this.isVisibleByStyle(sib) || !Xml.isVisible(sib)) 3045 continue; 3046 3047 var prev = this.findPreviousUp(sib, criteria); 3048 if (prev) 3049 return prev; 3050 3051 if (criteria(sib)) 3052 return sib; 3053 } 3054 if (!downOnly) 3055 { 3056 var next = this.findPreviousUp(node, criteria); 3057 if (next) 3058 return next; 3059 } 3060 if (node.parentNode && node.parentNode != maxRoot) 3061 { 3062 if (criteria(node.parentNode)) 3063 return node.parentNode; 3064 return this.getPreviousByClass(node.parentNode, className, true); 3065 } 3066 }, 3067 3068 getNextByClass: function (node, className, upOnly, maxRoot) 3069 { 3070 if (!node) 3071 return null; 3072 3073 function criteria(node) { 3074 return node.nodeType == Node.ELEMENT_NODE && Css.hasClass(node, className); 3075 } 3076 3077 if (!upOnly) 3078 { 3079 var next = this.findNextDown(node, criteria); 3080 if (next) 3081 return next; 3082 } 3083 3084 for (var sib = node.nextSibling; sib; sib = sib.nextSibling) 3085 { 3086 if (!this.isVisibleByStyle(sib) || !Xml.isVisible(sib)) 3087 continue; 3088 if (criteria(sib)) 3089 return sib; 3090 3091 var next = this.findNextDown(sib, criteria); 3092 if (next) 3093 return next; 3094 } 3095 3096 if (node.parentNode && node.parentNode != maxRoot) 3097 return this.getNextByClass(node.parentNode, className, true); 3098 }, 3099 3100 findNextDown: function(node, criteria) 3101 { 3102 if (!node) 3103 return null; 3104 3105 for (var child = node.firstChild; child; child = child.nextSibling) 3106 { 3107 if (!this.isVisibleByStyle(child) || !Xml.isVisible(child)) 3108 continue; 3109 3110 if (criteria(child)) 3111 return child; 3112 3113 var next = this.findNextDown(child, criteria); 3114 if (next) 3115 return next; 3116 } 3117 }, 3118 3119 findPreviousUp: function(node, criteria) 3120 { 3121 if (!node) 3122 return null; 3123 3124 for (var child = node.lastChild; child; child = child.previousSibling) 3125 { 3126 if (!this.isVisibleByStyle(child) || !Xml.isVisible(child)) 3127 continue; 3128 3129 var next = this.findPreviousUp(child, criteria); 3130 if (next) 3131 return next; 3132 3133 if (criteria(child)) 3134 return child; 3135 } 3136 } 3137 }); 3138 3139 // ************************************************************************************************ 3140 // Registration 3141 3142 Firebug.registerModule(Firebug.A11yModel); 3143 3144 return Firebug.A11yModel; 3145 3146 // ************************************************************************************************ 3147 }); 3148