AccessibilityNodeView.js 15 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. export class AXNodeSubPane extends Accessibility.AccessibilitySubPane{constructor(){super(ls`Computed Properties`);this.contentElement.classList.add('ax-subpane');this._noNodeInfo=this.createInfo(ls`No accessibility node`);this._ignoredInfo=this.createInfo(ls`Accessibility node not exposed`,'ax-ignored-info hidden');this._treeOutline=this.createTreeOutline();this._ignoredReasonsTree=this.createTreeOutline();this.element.classList.add('accessibility-computed');this.registerRequiredCSS('accessibility/accessibilityNode.css');this._treeOutline.setFocusable(true);}
  2. setAXNode(axNode){if(this._axNode===axNode){return;}
  3. this._axNode=axNode;const treeOutline=this._treeOutline;treeOutline.removeChildren();const ignoredReasons=this._ignoredReasonsTree;ignoredReasons.removeChildren();if(!axNode){treeOutline.element.classList.add('hidden');this._ignoredInfo.classList.add('hidden');ignoredReasons.element.classList.add('hidden');this._noNodeInfo.classList.remove('hidden');this.element.classList.add('ax-ignored-node-pane');return;}
  4. if(axNode.ignored()){this._noNodeInfo.classList.add('hidden');treeOutline.element.classList.add('hidden');this.element.classList.add('ax-ignored-node-pane');this._ignoredInfo.classList.remove('hidden');ignoredReasons.element.classList.remove('hidden');function addIgnoredReason(property){ignoredReasons.appendChild(new Accessibility.AXNodeIgnoredReasonTreeElement(property,(axNode)));}
  5. const ignoredReasonsArray=(axNode.ignoredReasons());for(const reason of ignoredReasonsArray){addIgnoredReason(reason);}
  6. if(!ignoredReasons.firstChild()){ignoredReasons.element.classList.add('hidden');}
  7. return;}
  8. this.element.classList.remove('ax-ignored-node-pane');this._ignoredInfo.classList.add('hidden');ignoredReasons.element.classList.add('hidden');this._noNodeInfo.classList.add('hidden');treeOutline.element.classList.remove('hidden');function addProperty(property){treeOutline.appendChild(new Accessibility.AXNodePropertyTreePropertyElement(property,(axNode)));}
  9. for(const property of axNode.coreProperties()){addProperty(property);}
  10. const roleProperty=({name:'role',value:axNode.role()});addProperty(roleProperty);for(const property of(axNode.properties())){addProperty(property);}
  11. const firstNode=treeOutline.firstChild();if(firstNode){firstNode.select(true,false);}}
  12. setNode(node){super.setNode(node);this._axNode=null;}}
  13. export class AXNodePropertyTreeElement extends UI.TreeElement{constructor(axNode){super('');this._axNode=axNode;}
  14. static createSimpleValueElement(type,value){let valueElement;const AXValueType=Protocol.Accessibility.AXValueType;if(!type||type===AXValueType.ValueUndefined||type===AXValueType.ComputedString){valueElement=createElement('span');}else{valueElement=createElementWithClass('span','monospace');}
  15. let valueText;const isStringProperty=type&&Accessibility.AXNodePropertyTreeElement.StringProperties.has(type);if(isStringProperty){valueText='"'+value.replace(/\n/g,'\u21B5')+'"';valueElement._originalTextContent=value;}else{valueText=String(value);}
  16. if(type&&type in Accessibility.AXNodePropertyTreeElement.TypeStyles){valueElement.classList.add(Accessibility.AXNodePropertyTreeElement.TypeStyles[type]);}
  17. valueElement.setTextContentTruncatedIfNeeded(valueText||'');valueElement.title=String(value)||'';return valueElement;}
  18. static createExclamationMark(tooltip){const exclamationElement=createElement('span','dt-icon-label');exclamationElement.type='smallicon-warning';exclamationElement.title=tooltip;return exclamationElement;}
  19. appendNameElement(name){const nameElement=createElement('span');const AXAttributes=Accessibility.AccessibilityStrings.AXAttributes;if(name in AXAttributes){nameElement.textContent=AXAttributes[name].name;nameElement.title=AXAttributes[name].description;nameElement.classList.add('ax-readable-name');}else{nameElement.textContent=name;nameElement.classList.add('ax-name');nameElement.classList.add('monospace');}
  20. this.listItemElement.appendChild(nameElement);}
  21. appendValueElement(value){const AXValueType=Protocol.Accessibility.AXValueType;if(value.type===AXValueType.Idref||value.type===AXValueType.Node||value.type===AXValueType.IdrefList||value.type===AXValueType.NodeList){this.appendRelatedNodeListValueElement(value);return;}else if(value.sources){const sources=value.sources;for(let i=0;i<sources.length;i++){const source=sources[i];const child=new Accessibility.AXValueSourceTreeElement(source,this._axNode);this.appendChild(child);}
  22. this.expand();}
  23. const element=Accessibility.AXNodePropertyTreeElement.createSimpleValueElement(value.type,String(value.value));this.listItemElement.appendChild(element);}
  24. appendRelatedNode(relatedNode,index){const deferredNode=new SDK.DeferredDOMNode(this._axNode.accessibilityModel().target(),relatedNode.backendDOMNodeId);const nodeTreeElement=new Accessibility.AXRelatedNodeSourceTreeElement({deferredNode:deferredNode},relatedNode);this.appendChild(nodeTreeElement);}
  25. appendRelatedNodeInline(relatedNode){const deferredNode=new SDK.DeferredDOMNode(this._axNode.accessibilityModel().target(),relatedNode.backendDOMNodeId);const linkedNode=new Accessibility.AXRelatedNodeElement({deferredNode:deferredNode},relatedNode);this.listItemElement.appendChild(linkedNode.render());}
  26. appendRelatedNodeListValueElement(value){if(value.relatedNodes.length===1&&!value.value){this.appendRelatedNodeInline(value.relatedNodes[0]);return;}
  27. value.relatedNodes.forEach(this.appendRelatedNode,this);if(value.relatedNodes.length<=3){this.expand();}else{this.collapse();}}}
  28. export const TypeStyles={attribute:'ax-value-string',boolean:'object-value-boolean',booleanOrUndefined:'object-value-boolean',computedString:'ax-readable-string',idref:'ax-value-string',idrefList:'ax-value-string',integer:'object-value-number',internalRole:'ax-internal-role',number:'ax-value-number',role:'ax-role',string:'ax-value-string',tristate:'object-value-boolean',valueUndefined:'ax-value-undefined'};export const StringProperties=new Set([Protocol.Accessibility.AXValueType.String,Protocol.Accessibility.AXValueType.ComputedString,Protocol.Accessibility.AXValueType.IdrefList,Protocol.Accessibility.AXValueType.Idref]);export class AXNodePropertyTreePropertyElement extends AXNodePropertyTreeElement{constructor(property,axNode){super(axNode);this._property=property;this.toggleOnClick=true;this.listItemElement.classList.add('property');}
  29. onattach(){this._update();}
  30. _update(){this.listItemElement.removeChildren();this.appendNameElement(this._property.name);this.listItemElement.createChild('span','separator').textContent=':\xA0';this.appendValueElement(this._property.value);}}
  31. export class AXValueSourceTreeElement extends AXNodePropertyTreeElement{constructor(source,axNode){super(axNode);this._source=source;}
  32. onattach(){this._update();}
  33. appendRelatedNodeWithIdref(relatedNode,index,idref){const deferredNode=new SDK.DeferredDOMNode(this._axNode.accessibilityModel().target(),relatedNode.backendDOMNodeId);const nodeTreeElement=new Accessibility.AXRelatedNodeSourceTreeElement({deferredNode:deferredNode,idref:idref},relatedNode);this.appendChild(nodeTreeElement);}
  34. appendIDRefValueElement(value){const relatedNodes=value.relatedNodes;const idrefs=value.value.trim().split(/\s+/);if(idrefs.length===1){const idref=idrefs[0];const matchingNode=relatedNodes.find(node=>node.idref===idref);if(matchingNode){this.appendRelatedNodeWithIdref(matchingNode,0,idref);}else{this.listItemElement.appendChild(new Accessibility.AXRelatedNodeElement({idref:idref}).render());}}else{for(let i=0;i<idrefs.length;++i){const idref=idrefs[i];const matchingNode=relatedNodes.find(node=>node.idref===idref);if(matchingNode){this.appendRelatedNodeWithIdref(matchingNode,i,idref);}else{this.appendChild(new Accessibility.AXRelatedNodeSourceTreeElement({idref:idref}));}}}}
  35. appendRelatedNodeListValueElement(value){const relatedNodes=value.relatedNodes;const numNodes=relatedNodes.length;if(value.type===Protocol.Accessibility.AXValueType.IdrefList||value.type===Protocol.Accessibility.AXValueType.Idref){this.appendIDRefValueElement(value);}else{super.appendRelatedNodeListValueElement(value);}
  36. if(numNodes<=3){this.expand();}else{this.collapse();}}
  37. appendSourceNameElement(source){const nameElement=createElement('span');const AXValueSourceType=Protocol.Accessibility.AXValueSourceType;const type=source.type;switch(type){case AXValueSourceType.Attribute:case AXValueSourceType.Placeholder:case AXValueSourceType.RelatedElement:if(source.nativeSource){const AXNativeSourceTypes=Accessibility.AccessibilityStrings.AXNativeSourceTypes;const nativeSource=source.nativeSource;nameElement.textContent=AXNativeSourceTypes[nativeSource].name;nameElement.title=AXNativeSourceTypes[nativeSource].description;nameElement.classList.add('ax-readable-name');break;}
  38. nameElement.textContent=source.attribute;nameElement.classList.add('ax-name');nameElement.classList.add('monospace');break;default:const AXSourceTypes=Accessibility.AccessibilityStrings.AXSourceTypes;if(type in AXSourceTypes){nameElement.textContent=AXSourceTypes[type].name;nameElement.title=AXSourceTypes[type].description;nameElement.classList.add('ax-readable-name');}else{console.warn(type,'not in AXSourceTypes');nameElement.textContent=type;}}
  39. this.listItemElement.appendChild(nameElement);}
  40. _update(){this.listItemElement.removeChildren();if(this._source.invalid){const exclamationMark=Accessibility.AXNodePropertyTreeElement.createExclamationMark(ls`Invalid source.`);this.listItemElement.appendChild(exclamationMark);this.listItemElement.classList.add('ax-value-source-invalid');}else if(this._source.superseded){this.listItemElement.classList.add('ax-value-source-unused');}
  41. this.appendSourceNameElement(this._source);this.listItemElement.createChild('span','separator').textContent=':\xA0';if(this._source.attributeValue){this.appendValueElement(this._source.attributeValue);this.listItemElement.createTextChild('\xA0');}else if(this._source.nativeSourceValue){this.appendValueElement(this._source.nativeSourceValue);this.listItemElement.createTextChild('\xA0');if(this._source.value){this.appendValueElement(this._source.value);}}else if(this._source.value){this.appendValueElement(this._source.value);}else{const valueElement=Accessibility.AXNodePropertyTreeElement.createSimpleValueElement(Protocol.Accessibility.AXValueType.ValueUndefined,ls`Not specified`);this.listItemElement.appendChild(valueElement);this.listItemElement.classList.add('ax-value-source-unused');}
  42. if(this._source.value&&this._source.superseded){this.listItemElement.classList.add('ax-value-source-superseded');}}}
  43. export class AXRelatedNodeSourceTreeElement extends UI.TreeElement{constructor(node,value){super('');this._value=value;this._axRelatedNodeElement=new Accessibility.AXRelatedNodeElement(node,value);this.selectable=true;}
  44. onattach(){this.listItemElement.appendChild(this._axRelatedNodeElement.render());if(!this._value){return;}
  45. if(this._value.text){this.listItemElement.appendChild(Accessibility.AXNodePropertyTreeElement.createSimpleValueElement(Protocol.Accessibility.AXValueType.ComputedString,this._value.text));}}
  46. onenter(){this._axRelatedNodeElement.revealNode();return true;}}
  47. export class AXRelatedNodeElement{constructor(node,value){this._deferredNode=node.deferredNode;this._idref=node.idref;this._value=value;}
  48. render(){const element=createElement('span');let valueElement;if(this._deferredNode){valueElement=createElement('span');element.appendChild(valueElement);this._deferredNode.resolvePromise().then(node=>{Common.Linkifier.linkify(node,{preventKeyboardFocus:true}).then(linkfied=>valueElement.appendChild(linkfied));});}else if(this._idref){element.classList.add('invalid');valueElement=Accessibility.AXNodePropertyTreeElement.createExclamationMark(ls`No node with this ID.`);valueElement.createTextChild(this._idref);element.appendChild(valueElement);}
  49. return element;}
  50. revealNode(){this._deferredNode.resolvePromise().then(node=>Common.Revealer.reveal(node));}}
  51. export class AXNodeIgnoredReasonTreeElement extends AXNodePropertyTreeElement{constructor(property,axNode){super(axNode);this._property=property;this._axNode=axNode;this.toggleOnClick=true;this.selectable=false;}
  52. static createReasonElement(reason,axNode){let reasonElement=null;switch(reason){case'activeModalDialog':reasonElement=UI.formatLocalized('Element is hidden by active modal dialog:\xA0',[]);break;case'ancestorIsLeafNode':reasonElement=UI.formatLocalized('Ancestor\'s children are all presentational:\xA0',[]);break;case'ariaHiddenElement':{const ariaHiddenSpan=createElement('span','source-code').textContent='aria-hidden';reasonElement=UI.formatLocalized('Element is %s.',[ariaHiddenSpan]);break;}
  53. case'ariaHiddenSubtree':{const ariaHiddenSpan=createElement('span','source-code').textContent='aria-hidden';const trueSpan=createElement('span','source-code').textContent='true';reasonElement=UI.formatLocalized('%s is %s on ancestor:\xA0',[ariaHiddenSpan,trueSpan]);break;}
  54. case'emptyAlt':reasonElement=UI.formatLocalized('Element has empty alt text.',[]);break;case'emptyText':reasonElement=UI.formatLocalized('No text content.',[]);break;case'inertElement':reasonElement=UI.formatLocalized('Element is inert.',[]);break;case'inertSubtree':reasonElement=UI.formatLocalized('Element is in an inert subtree from\xA0',[]);break;case'inheritsPresentation':reasonElement=UI.formatLocalized('Element inherits presentational role from\xA0',[]);break;case'labelContainer':reasonElement=UI.formatLocalized('Part of label element:\xA0',[]);break;case'labelFor':reasonElement=UI.formatLocalized('Label for\xA0',[]);break;case'notRendered':reasonElement=UI.formatLocalized('Element is not rendered.',[]);break;case'notVisible':reasonElement=UI.formatLocalized('Element is not visible.',[]);break;case'presentationalRole':{const rolePresentationSpan=createElement('span','source-code').textContent='role='+axNode.role().value;reasonElement=UI.formatLocalized('Element has %s.',[rolePresentationSpan]);break;}
  55. case'probablyPresentational':reasonElement=UI.formatLocalized('Element is presentational.',[]);break;case'staticTextUsedAsNameFor':reasonElement=UI.formatLocalized('Static text node is used as name for\xA0',[]);break;case'uninteresting':reasonElement=UI.formatLocalized('Element not interesting for accessibility.',[]);break;}
  56. if(reasonElement){reasonElement.classList.add('ax-reason');}
  57. return reasonElement;}
  58. onattach(){this.listItemElement.removeChildren();this._reasonElement=Accessibility.AXNodeIgnoredReasonTreeElement.createReasonElement(this._property.name,this._axNode);this.listItemElement.appendChild(this._reasonElement);const value=this._property.value;if(value.type===Protocol.Accessibility.AXValueType.Idref){this.appendRelatedNodeListValueElement(value);}}}
  59. self.Accessibility=self.Accessibility||{};Accessibility=Accessibility||{};Accessibility.AXNodeSubPane=AXNodeSubPane;Accessibility.AXNodePropertyTreeElement=AXNodePropertyTreeElement;Accessibility.AXNodePropertyTreeElement.TypeStyles=TypeStyles;Accessibility.AXNodePropertyTreeElement.StringProperties=StringProperties;Accessibility.AXNodePropertyTreePropertyElement=AXNodePropertyTreePropertyElement;Accessibility.AXValueSourceTreeElement=AXValueSourceTreeElement;Accessibility.AXRelatedNodeSourceTreeElement=AXRelatedNodeSourceTreeElement;Accessibility.AXRelatedNodeElement=AXRelatedNodeElement;Accessibility.AXNodeIgnoredReasonTreeElement=AXNodeIgnoredReasonTreeElement;