+2014-03-28 James Craig <jcraig@apple.com>
+
+ Web Inspector: AXI: support for live regions
+ https://bugs.webkit.org/show_bug.cgi?id=130725
+
+ Reviewed by Timothy Hatcher.
+
+ Initial support for @aria-live, @aria-atomic, and @aria-busy.
+
+ * inspector-protocol/dom/getAccessibilityPropertiesForNode-expected.txt: Updated.
+ * inspector-protocol/dom/getAccessibilityPropertiesForNode.html: Updated.
+ * inspector-protocol/dom/getAccessibilityPropertiesForNode_liveRegion-expected.txt: Added.
+ * inspector-protocol/dom/getAccessibilityPropertiesForNode_liveRegion.html: Added.
+
2014-03-28 Joseph Pecoraro <pecoraro@apple.com>
Web Inspector: console.warn is showing as error instead of warning
exists: true
label:
role:
- childNodeIds.length: 21
+ childNodeIds.length: 25
-Total elements to be tested: 59.
+Total elements to be tested: 63.
<div onclick="void(0);">click</div>
exists: true
focused: false
parentNodeId: exists
+<div role="group" aria-live="assertive" aria-atomic="true">assertive (and atomic)</div>
+ exists: true
+ label:
+ role: group
+ childNodeIds.length: 1
+ liveRegionAtomic: true
+ liveRegionStatus: assertive
+ parentNodeId: exists
+
+<div role="group" aria-live="polite">polite</div>
+ exists: true
+ label:
+ role: group
+ childNodeIds.length: 1
+ liveRegionAtomic: false
+ liveRegionStatus: polite
+ parentNodeId: exists
+
+<div role="group" aria-live="off">off</div>
+ exists: true
+ label:
+ role: group
+ childNodeIds.length: 1
+ parentNodeId: exists
+
+<div role="listbox" aria-busy="true">
+ <!-- Despite having no required option children, this is valid because it is marked as busy. -->
+ <!-- For example, waiting for a script to load its contents. -->
+</div>
+ exists: true
+ label:
+ role: listbox
+ busy: true
+ parentNodeId: exists
+ required: false
+
<span aria-hidden="true"></span>
exists: true
label:
<span class="ex"></span>
<span class="ex" aria-hidden="true"></span>
+<div role="listbox" class="ex" aria-busy="true">
+ <!-- Despite having no required option children, this is valid because it is marked as busy. -->
+ <!-- For example, waiting for a script to load its contents. -->
+</div>
+
+<!-- Full coverage of live regions in getAccessibilityPropertiesForNode_liveRegion.html. -->
+<div class="ex" role="group" aria-live="off">off</div>
+<div class="ex" role="group" aria-live="polite">polite</div>
+<div class="ex" role="group" aria-live="assertive" aria-atomic="true">assertive (and atomic)</div>
+
<div class="ex" role="button" tabindex="0"></div>
<div class="ex" role="button" tabindex="0" aria-disabled="true">disabled</div>
<div class="ex" role="button" tabindex="0" aria-pressed="true">FIXME: Pressed is false. Expected true. http://webkit.org/b/129830</div>
--- /dev/null
+Checking Web Inspector protocol (specifically live region properties) for the Accessibility Node Inspector.
+
+Total elements to be tested: 17.
+
+<div role="timer">off (default)</div>
+ exists: true
+
+<div role="status" aria-live="off">off</div>
+ exists: true
+
+<div role="status" aria-live="assertive">assertive <!-- FIXME: atomic should be true http://webkit.org/b/130907 --></div>
+ exists: true
+ liveRegionAtomic: false
+ liveRegionStatus: assertive
+
+<div role="status">polite (default) <!-- FIXME: atomic should be true http://webkit.org/b/130907 --></div>
+ exists: true
+ liveRegionAtomic: false
+ liveRegionStatus: polite
+
+<div role="marquee">off (default)</div>
+ exists: true
+
+<div role="log" aria-live="off">off</div>
+ exists: true
+
+<div role="log" aria-live="assertive">assertive</div>
+ exists: true
+ liveRegionAtomic: false
+ liveRegionStatus: assertive
+
+<div role="log">polite (default)</div>
+ exists: true
+ liveRegionAtomic: false
+ liveRegionStatus: polite
+
+<div role="alert" aria-live="polite">polite <!-- FIXME: atomic should be true http://webkit.org/b/130907 --></div>
+ exists: true
+ liveRegionAtomic: false
+ liveRegionStatus: polite
+
+<div role="alert" aria-live="off">off</div>
+ exists: true
+
+<div role="alert">assertive (default) <!-- FIXME: atomic should be true http://webkit.org/b/130907 --></div>
+ exists: true
+ liveRegionAtomic: false
+ liveRegionStatus: assertive
+
+<div role="group" aria-live="assertive" aria-busy="true" aria-atomic="false">assertive</div>
+ exists: true
+ busy: true
+ liveRegionAtomic: false
+ liveRegionStatus: assertive
+
+<div role="group" aria-live="polite" aria-busy="true" aria-atomic="false">polite</div>
+ exists: true
+ busy: true
+ liveRegionAtomic: false
+ liveRegionStatus: polite
+
+<div role="group" aria-live="off" aria-busy="true" aria-atomic="false">off</div>
+ exists: true
+ busy: true
+
+<div role="group" aria-live="assertive" aria-busy="true" aria-atomic="true">assertive</div>
+ exists: true
+ busy: true
+ liveRegionAtomic: true
+ liveRegionStatus: assertive
+
+<div role="group" aria-live="polite" aria-busy="true" aria-atomic="true">polite</div>
+ exists: true
+ busy: true
+ liveRegionAtomic: true
+ liveRegionStatus: polite
+
+<div role="group" aria-live="off" aria-busy="true" aria-atomic="true">off</div>
+ exists: true
+ busy: true
+
--- /dev/null
+<html>
+<head>
+<script type="text/javascript" src="../../http/tests/inspector-protocol/resources/protocol-test.js"></script>
+</head>
+<body onLoad="runTest()">
+
+<p>Checking Web Inspector protocol (specifically live region properties) for the Accessibility Node Inspector.</p>
+
+<div id="examples">
+
+ <div class="ex" role="group" aria-live="off" aria-busy="true" aria-atomic="true">off</div>
+ <div class="ex" role="group" aria-live="polite" aria-busy="true" aria-atomic="true">polite</div>
+ <div class="ex" role="group" aria-live="assertive" aria-busy="true" aria-atomic="true">assertive</div>
+ <div class="ex" role="group" aria-live="off" aria-busy="true" aria-atomic="false">off</div>
+ <div class="ex" role="group" aria-live="polite" aria-busy="true" aria-atomic="false">polite</div>
+ <div class="ex" role="group" aria-live="assertive" aria-busy="true" aria-atomic="false">assertive</div>
+
+ <div class="ex" role="alert">assertive (default) <!-- FIXME: atomic should be true http://webkit.org/b/130907 --></div>
+ <div class="ex" role="alert" aria-live="off">off</div>
+ <div class="ex" role="alert" aria-live="polite">polite <!-- FIXME: atomic should be true http://webkit.org/b/130907 --></div>
+ <div class="ex" role="log">polite (default)</div>
+ <div class="ex" role="log" aria-live="assertive">assertive</div>
+ <div class="ex" role="log" aria-live="off">off</div>
+ <div class="ex" role="marquee">off (default)</div>
+ <div class="ex" role="status">polite (default) <!-- FIXME: atomic should be true http://webkit.org/b/130907 --></div>
+ <div class="ex" role="status" aria-live="assertive">assertive <!-- FIXME: atomic should be true http://webkit.org/b/130907 --></div>
+ <div class="ex" role="status" aria-live="off">off</div>
+ <div class="ex" role="timer">off (default)</div>
+
+</div>
+
+<script type="text/javascript">
+
+function $(id) {
+ return document.getElementById(id);
+}
+
+function cleanup() {
+ // Hide the test element container to avoid irrelevant output diffs on subsequent updates.
+ $("examples").style.display = "none";
+}
+
+function test() {
+
+ var examples = [];
+ var documentNodeId = null;
+ var bodyNodeId = null;
+
+ function onGotDocument(response) {
+ InspectorTest.checkForError(response);
+ documentNodeId = response.result.root.nodeId;
+ InspectorTest.sendCommand("DOM.querySelectorAll", {"nodeId": documentNodeId, "selector": ".ex"}, onGotQuerySelectorAll);
+ }
+
+ function onGotQuerySelectorAll(response) {
+ InspectorTest.checkForError(response);
+ examples = response.result.nodeIds;
+ InspectorTest.log("Total elements to be tested: " + examples.length + ".");
+ loop();
+ }
+
+ function loop() {
+ if (examples.length) {
+ InspectorTest.sendCommand("DOM.getOuterHTML", {"nodeId": examples[examples.length-1]}, onGotOuterHTML);
+ } else {
+ finishTest();
+ }
+ }
+
+ function onGotOuterHTML(response) {
+ InspectorTest.checkForError(response);
+ var outerHTML = response.result.outerHTML;
+ outerHTML = outerHTML.replace(/ class="ex"/g, ""); // remove any duplicated, unnecessary class attributes
+ InspectorTest.log("\n" + outerHTML);
+ InspectorTest.sendCommand("DOM.getAccessibilityPropertiesForNode", {"nodeId": examples[examples.length-1]}, onGotAccessibilityProperties);
+ }
+
+ function onGotAccessibilityProperties(response) {
+ InspectorTest.checkForError(response);
+ logAccessibilityProperties(response.result.properties);
+ examples.pop();
+ loop();
+ }
+
+ function logAccessibilityProperties(properties) {
+ for (var key in properties) {
+ var value = properties[key];
+ switch (key){
+ case "busy":
+ case "exists":
+ case "liveRegionAtomic":
+ case "liveRegionStatus":
+ InspectorTest.log(" " + key + ": " + value);
+ break;
+ default:
+ continue;
+ }
+ }
+ }
+
+ function finishTest() {
+ InspectorTest.sendCommand("Runtime.evaluate", {"expression": "cleanup()"}, function(){
+ InspectorTest.completeTest();
+ });
+ }
+
+ InspectorTest.sendCommand("DOM.getDocument", {}, onGotDocument);
+
+}
+</script>
+</body>
+</html>
\ No newline at end of file
+2014-03-28 James Craig <jcraig@apple.com>
+
+ Web Inspector: AXI: support for live regions
+ https://bugs.webkit.org/show_bug.cgi?id=130725
+
+ Reviewed by Timothy Hatcher.
+
+ Tests: inspector-protocol/dom/getAccessibilityPropertiesForNode.html
+ inspector-protocol/dom/getAccessibilityPropertiesForNode_liveRegion.html
+
+ Initial support for @aria-live, @aria-atomic, and @aria-busy.
+
+ * inspector/InspectorDOMAgent.cpp:
+ (WebCore::InspectorDOMAgent::buildObjectForAccessibilityProperties):
+ * inspector/protocol/DOM.json:
+
2014-03-28 Darin Adler <darin@apple.com>
Fix recently-introduced off-by-one error in centerTruncateToBuffer
WebCore::AXObjectCache::enableAccessibility();
Node* activeDescendantNode = nullptr;
+ bool busy = false;
TypeBuilder::DOM::AccessibilityProperties::Checked::Enum checked = TypeBuilder::DOM::AccessibilityProperties::Checked::False;
RefPtr<Inspector::TypeBuilder::Array<int>> childNodeIds;
RefPtr<Inspector::TypeBuilder::Array<int>> controlledNodeIds;
TypeBuilder::DOM::AccessibilityProperties::Invalid::Enum invalid = TypeBuilder::DOM::AccessibilityProperties::Invalid::False;
bool hidden = false;
String label; // FIXME: Waiting on http://webkit.org/b/121134
+ bool liveRegionAtomic = false;
+ TypeBuilder::DOM::AccessibilityProperties::LiveRegionStatus::Enum liveRegionStatus = TypeBuilder::DOM::AccessibilityProperties::LiveRegionStatus::Off;
Node* mouseEventNode = nullptr;
RefPtr<Inspector::TypeBuilder::Array<int>> ownedNodeIds;
Node* parentNode = nullptr;
RefPtr<Inspector::TypeBuilder::Array<int>> selectedChildNodeIds;
bool supportsChecked = false;
bool supportsExpanded = false;
+ bool supportsLiveRegion = false;
bool supportsPressed = false;
bool supportsRequired = false;
bool supportsFocused = false;
if (AccessibilityObject* activeDescendant = axObject->activeDescendant())
activeDescendantNode = activeDescendant->node();
+ busy = axObject->ariaLiveRegionBusy();
+
supportsChecked = axObject->supportsChecked();
if (supportsChecked) {
int checkValue = axObject->checkboxOrRadioValue(); // Element using aria-checked.
if (axObject->isARIAHidden() || axObject->isDOMHidden())
hidden = true;
+ if (axObject->supportsARIALiveRegion()) {
+ supportsLiveRegion = true;
+ liveRegionAtomic = axObject->ariaLiveRegionAtomic();
+ String ariaLive = axObject->ariaLiveRegionStatus();
+ if (ariaLive == "assertive")
+ liveRegionStatus = TypeBuilder::DOM::AccessibilityProperties::LiveRegionStatus::Assertive;
+ else if (ariaLive == "polite")
+ liveRegionStatus = TypeBuilder::DOM::AccessibilityProperties::LiveRegionStatus::Polite;
+ }
+
if (axObject->isAccessibilityNodeObject())
mouseEventNode = toAccessibilityNodeObject(axObject)->mouseButtonListener(MouseButtonListenerResultFilter::IncludeBodyElement);
if (exists) {
if (activeDescendantNode)
value->setActiveDescendantNodeId(pushNodePathToFrontend(activeDescendantNode));
+ if (busy)
+ value->setBusy(busy);
if (supportsChecked)
value->setChecked(checked);
if (childNodeIds)
value->setInvalid(invalid);
if (hidden)
value->setHidden(hidden);
+ if (supportsLiveRegion) {
+ value->setLiveRegionAtomic(liveRegionAtomic);
+ value->setLiveRegionStatus(liveRegionStatus);
+ }
if (mouseEventNode)
value->setMouseEventNodeId(pushNodePathToFrontend(mouseEventNode));
if (ownedNodeIds)
"type": "object",
"properties": [
{ "name": "activeDescendantNodeId", "$ref": "NodeId", "optional": true, "description": "<code>DOMNode</code> id of the accessibility object referenced by aria-activedescendant." },
+ { "name": "busy", "type": "boolean", "optional": true, "description": "Value of @aria-busy on current or ancestor node." },
{ "name": "checked", "type": "string", "optional": true, "enum": ["true", "false", "mixed"], "description": "Checked state of certain form controls." },
{ "name": "childNodeIds", "type": "array", "items": { "$ref": "NodeId" }, "optional": true, "description": "Array of <code>DOMNode</code> ids of the accessibility tree children if available." },
{ "name": "controlledNodeIds", "type": "array", "items": { "$ref": "NodeId" }, "optional": true, "description": "Array of <code>DOMNode</code> ids of any nodes referenced via @aria-controls." },
{ "name": "invalid", "type": "string", "optional": true, "enum": ["true", "false", "grammar", "spelling"], "description": "Invalid status of form controls." },
{ "name": "hidden", "type": "boolean", "optional": true, "description": "Hidden state. True if node or an ancestor is hidden via CSS or explicit @aria-hidden, to clarify why the element is ignored." },
{ "name": "label", "type": "string", "description": "Computed label value for the node, sometimes calculated by referencing other nodes." },
+ { "name": "liveRegionAtomic", "type": "boolean", "optional": true, "description": "Value of @aria-atomic." },
+ { "name": "liveRegionStatus", "type": "string", "optional": true, "enum": ["assertive", "polite", "off"], "description": "Value of element's @aria-live attribute." },
{ "name": "mouseEventNodeId", "$ref": "NodeId", "optional": true, "description": "<code>DOMNode</code> id of node or closest ancestor node that has a mousedown, mouseup, or click event handler." },
{ "name": "nodeId", "$ref": "NodeId", "description": "Target <code>DOMNode</code> id." },
{ "name": "ownedNodeIds", "type": "array", "items": { "$ref": "NodeId" }, "optional": true, "description": "Array of <code>DOMNode</code> ids of any nodes referenced via @aria-owns." },
+2014-03-28 James Craig <jcraig@apple.com>
+
+ Web Inspector: AXI: support for live regions
+ https://bugs.webkit.org/show_bug.cgi?id=130725
+
+ Reviewed by Timothy Hatcher.
+
+ Initial support for @aria-live, @aria-atomic, and @aria-busy.
+
+ * Localizations/en.lproj/localizedStrings.js:
+ * UserInterface/Models/DOMNode.js:
+ * UserInterface/Views/DOMNodeDetailsSidebarPanel.js:
+ * UserInterface/Views/Main.css:
+
2014-03-28 Joseph Pecoraro <pecoraro@apple.com>
Web Inspector: console.warn is showing as error instead of warning
if (!error && callback && accessibilityProperties) {
callback({
activeDescendantNodeId: accessibilityProperties.activeDescendantNodeId,
+ busy: accessibilityProperties.busy,
checked: accessibilityProperties.checked,
childNodeIds: accessibilityProperties.childNodeIds,
controlledNodeIds: accessibilityProperties.controlledNodeIds,
invalid: accessibilityProperties.invalid,
hidden: accessibilityProperties.hidden,
label: accessibilityProperties.label,
+ liveRegionAtomic: accessibilityProperties.liveRegionAtomic,
+ liveRegionStatus: accessibilityProperties.liveRegionStatus,
mouseEventNodeId: accessibilityProperties.mouseEventNodeId,
nodeId: accessibilityProperties.nodeId,
ownedNodeIds: accessibilityProperties.ownedNodeIds,
if (this._accessibilitySupported()) {
this._accessibilityEmptyRow = new WebInspector.DetailsSectionRow(WebInspector.UIString("No Accessibility Information"));
this._accessibilityNodeActiveDescendantRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Shared Focus"));
+ this._accessibilityNodeBusyRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Busy"));
this._accessibilityNodeCheckedRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Checked"));
this._accessibilityNodeChildrenRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Children"));
this._accessibilityNodeControlsRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Controls"));
this._accessibilityNodeFocusedRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Focused"));
this._accessibilityNodeIgnoredRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Ignored"));
this._accessibilityNodeInvalidRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Invalid"));
+ this._accessibilityNodeLiveRegionStatusRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Live"));
this._accessibilityNodeMouseEventRow = new WebInspector.DetailsSectionSimpleRow("");
this._accessibilityNodeLabelRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Label"));
this._accessibilityNodeOwnsRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Owns"));
if (accessibilityProperties && accessibilityProperties.exists) {
var activeDescendantLink = linkForNodeId(accessibilityProperties.activeDescendantNodeId);
+ var busy = booleanValueToLocalizedStringIfPropertyDefined("busy");
var checked = "";
if (accessibilityProperties.checked !== undefined) {
else if (accessibilityProperties.invalid === DOMAgent.AccessibilityPropertiesInvalid.Spelling)
invalid = WebInspector.UIString("Spelling");
+ var liveRegionStatus = "";
+ var liveRegionStatusNode = null;
+ var liveRegionStatusToken = accessibilityProperties.liveRegionStatus;
+ switch(liveRegionStatusToken) {
+ case DOMAgent.AccessibilityPropertiesLiveRegionStatus.Assertive:
+ liveRegionStatus = WebInspector.UIString("Assertive");
+ break;
+ case DOMAgent.AccessibilityPropertiesLiveRegionStatus.Polite:
+ liveRegionStatus = WebInspector.UIString("Polite");
+ break;
+ default:
+ liveRegionStatus = "";
+ }
+ if (liveRegionStatus && accessibilityProperties.liveRegionAtomic === true) {
+ liveRegionStatusNode = document.createElement("div");
+ liveRegionStatusNode.className = "value-with-clarification";
+ liveRegionStatusNode.setAttribute("role", "text");
+ liveRegionStatusNode.appendChild(document.createTextNode(liveRegionStatus));
+ var clarificationNode = document.createElement("div");
+ clarificationNode.className = "clarification";
+ clarificationNode.appendChild(document.createTextNode(WebInspector.UIString("Region announced in its entirety.")));
+ liveRegionStatusNode.appendChild(clarificationNode);
+ }
+
var mouseEventNodeId = accessibilityProperties.mouseEventNodeId;
var mouseEventTextValue = "";
var mouseEventNodeLink = null;
// Assign all the properties to their respective views.
this._accessibilityNodeActiveDescendantRow.value = activeDescendantLink || "";
+ this._accessibilityNodeBusyRow.value = busy;
this._accessibilityNodeCheckedRow.value = checked;
this._accessibilityNodeChildrenRow.value = childNodeLinkList || "";
this._accessibilityNodeControlsRow.value = controlledNodeLinkList || "";
this._accessibilityNodeFocusedRow.value = focused;
this._accessibilityNodeIgnoredRow.value = ignored;
this._accessibilityNodeInvalidRow.value = invalid;
+ this._accessibilityNodeLabelRow.value = label;
+ this._accessibilityNodeLiveRegionStatusRow.value = liveRegionStatusNode || liveRegionStatus;
// Row label changes based on whether the value is a delegate node link.
this._accessibilityNodeMouseEventRow.label = mouseEventNodeLink ? WebInspector.UIString("Click Listener") : WebInspector.UIString("Clickable");
this._accessibilityNodeMouseEventRow.value = mouseEventNodeLink || mouseEventTextValue;
- this._accessibilityNodeLabelRow.value = label;
this._accessibilityNodeOwnsRow.value = ownedNodeLinkList || "";
this._accessibilityNodeParentRow.value = parentNodeLink || "";
this._accessibilityNodePressedRow.value = pressed;
this._accessibilityNodeFlowsRow,
this._accessibilityNodeMouseEventRow,
this._accessibilityNodeFocusedRow,
+ this._accessibilityNodeBusyRow,
+ this._accessibilityNodeLiveRegionStatusRow,
// Properties exposed for all input-type elements.
this._accessibilityNodeDisabledRow,
.node-link-list li:last-child {
margin: 0;
}
+
+.value-with-clarification .clarification {
+ color: #666;
+}
+2014-03-28 James Craig <jcraig@apple.com>
+
+ Web Inspector: AXI: support for live regions
+ https://bugs.webkit.org/show_bug.cgi?id=130725
+
+ Reviewed by Timothy Hatcher.
+
+ Demo update to show off the new Inspector support for live regions.
+
+ * blog-files/aria1.0/combobox_with_live_region_status.html:
+
2014-03-27 James Craig <jcraig@apple.com>
Web Inspector: AXI: expose selectedChildNodeIds of list boxes, tree controls, etc., and reconcile UI with childNodeIds
<p>Some <a href="#">focusable content</a> before the combobox.</p>
<form class="pc">
<!-- Note that this field is labelled by itself, which in this case, is an explicit pointer to use the placeholder attribute value. -->
- <input type="text" tabindex="0" id="state" aria-labelledby="state" role="combobox" aria-autocomplete="list" aria-owns="statelist" placeholder="US State or Territory" autocomplete="off" autocorrect="off" autocapitalize="off">
- <div role="status" aria-live="polite" aria-expanded="false">
+ <input type="text" tabindex="0" id="state" aria-labelledby="state" role="combobox" aria-autocomplete="list" aria-owns="statelist" aria-expanded="false" placeholder="US State or Territory" autocomplete="off" autocorrect="off" autocapitalize="off">
+ <div role="status">
<!-- This is the list status live region: e.g. "4 items." -->
- <!-- The attribute value, aria-live="polite" is the default for role="status". -->
- <!-- It's just included here for demo clarity. -->
</div>
<ul role="listbox" id="statelist" hidden>
<li id="AL" role="option">Alabama</li>