The Great Blink mv for source files, part 2.
Move and rename files.
NOAUTOREVERT=true
NOPRESUBMIT=true
NOTREECHECKS=true
Bug: 768828
[email protected]
NOTRY=true
Change-Id: I66d3b155808bc5bdbf237b80208e1e552bcf7f28
Reviewed-on: https://chromium-review.googlesource.com/1001153
Reviewed-by: Blink Reformat <[email protected]>
Commit-Queue: Blink Reformat <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#549061}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 0aee4434a4dba42a42abaea9bfbc0cd196a63bc1
diff --git a/front_end/accessibility/ARIAAttributesView.js b/front_end/accessibility/ARIAAttributesView.js
new file mode 100644
index 0000000..434cefe
--- /dev/null
+++ b/front_end/accessibility/ARIAAttributesView.js
@@ -0,0 +1,256 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+/**
+ * @unrestricted
+ */
+Accessibility.ARIAAttributesPane = class extends Accessibility.AccessibilitySubPane {
+ constructor() {
+ super(ls`ARIA Attributes`);
+
+ this._noPropertiesInfo = this.createInfo(ls`No ARIA attributes`);
+ this._treeOutline = this.createTreeOutline();
+ }
+
+ /**
+ * @override
+ * @param {?SDK.DOMNode} node
+ */
+ setNode(node) {
+ super.setNode(node);
+ this._treeOutline.removeChildren();
+ if (!this.node())
+ return;
+ const target = this.node().domModel().target();
+ const attributes = node.attributes();
+ for (let i = 0; i < attributes.length; ++i) {
+ const attribute = attributes[i];
+ if (Accessibility.ARIAAttributesPane._attributes.indexOf(attribute.name) < 0)
+ continue;
+ this._treeOutline.appendChild(new Accessibility.ARIAAttributesTreeElement(this, attribute, target));
+ }
+
+ const foundAttributes = (this._treeOutline.rootElement().childCount() !== 0);
+ this._noPropertiesInfo.classList.toggle('hidden', foundAttributes);
+ this._treeOutline.element.classList.toggle('hidden', !foundAttributes);
+ }
+};
+
+/**
+ * @unrestricted
+ */
+Accessibility.ARIAAttributesTreeElement = class extends UI.TreeElement {
+ /**
+ * @param {!Accessibility.ARIAAttributesPane} parentPane
+ * @param {!SDK.DOMNode.Attribute} attribute
+ * @param {!SDK.Target} target
+ */
+ constructor(parentPane, attribute, target) {
+ super('');
+
+ this._parentPane = parentPane;
+ this._attribute = attribute;
+
+ this.selectable = false;
+ }
+
+ /**
+ * @param {string} value
+ * @return {!Element}
+ */
+ static createARIAValueElement(value) {
+ const valueElement = createElementWithClass('span', 'monospace');
+ // TODO(aboxhall): quotation marks?
+ valueElement.setTextContentTruncatedIfNeeded(value || '');
+ return valueElement;
+ }
+
+ /**
+ * @override
+ */
+ onattach() {
+ this._populateListItem();
+ this.listItemElement.addEventListener('click', this._mouseClick.bind(this));
+ }
+
+ _populateListItem() {
+ this.listItemElement.removeChildren();
+ this.appendNameElement(this._attribute.name);
+ this.listItemElement.createChild('span', 'separator').textContent = ':\u00A0';
+ this.appendAttributeValueElement(this._attribute.value);
+ }
+
+ /**
+ * @param {string} name
+ */
+ appendNameElement(name) {
+ this._nameElement = createElement('span');
+ this._nameElement.textContent = name;
+ this._nameElement.classList.add('ax-name');
+ this._nameElement.classList.add('monospace');
+ this.listItemElement.appendChild(this._nameElement);
+ }
+
+ /**
+ * @param {string} value
+ */
+ appendAttributeValueElement(value) {
+ this._valueElement = Accessibility.ARIAAttributesTreeElement.createARIAValueElement(value);
+ this.listItemElement.appendChild(this._valueElement);
+ }
+
+ /**
+ * @param {!Event} event
+ */
+ _mouseClick(event) {
+ if (event.target === this.listItemElement)
+ return;
+
+ event.consume(true);
+
+ this._startEditing();
+ }
+
+ _startEditing() {
+ const valueElement = this._valueElement;
+
+ if (UI.isBeingEdited(valueElement))
+ return;
+
+ const previousContent = valueElement.textContent;
+
+ /**
+ * @param {string} previousContent
+ * @param {!Event} event
+ * @this {Accessibility.ARIAAttributesTreeElement}
+ */
+ function blurListener(previousContent, event) {
+ const text = event.target.textContent;
+ this._editingCommitted(text, previousContent);
+ }
+
+ this._prompt = new Accessibility.ARIAAttributesPane.ARIAAttributePrompt(
+ Accessibility.ariaMetadata().valuesForProperty(this._nameElement.textContent), this);
+ this._prompt.setAutocompletionTimeout(0);
+ const proxyElement = this._prompt.attachAndStartEditing(valueElement, blurListener.bind(this, previousContent));
+
+ proxyElement.addEventListener('keydown', this._editingValueKeyDown.bind(this, previousContent), false);
+
+ valueElement.getComponentSelection().selectAllChildren(valueElement);
+ }
+
+ _removePrompt() {
+ if (!this._prompt)
+ return;
+ this._prompt.detach();
+ delete this._prompt;
+ }
+
+ /**
+ * @param {string} userInput
+ * @param {string} previousContent
+ */
+ _editingCommitted(userInput, previousContent) {
+ this._removePrompt();
+
+ // Make the changes to the attribute
+ if (userInput !== previousContent)
+ this._parentPane.node().setAttributeValue(this._attribute.name, userInput);
+ }
+
+ _editingCancelled() {
+ this._removePrompt();
+ this._populateListItem();
+ }
+
+ /**
+ * @param {string} previousContent
+ * @param {!Event} event
+ */
+ _editingValueKeyDown(previousContent, event) {
+ if (event.handled)
+ return;
+
+ if (isEnterKey(event)) {
+ this._editingCommitted(event.target.textContent, previousContent);
+ event.consume();
+ return;
+ }
+
+ if (event.keyCode === UI.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === 'U+001B') {
+ this._editingCancelled();
+ event.consume();
+ return;
+ }
+ }
+};
+
+
+/**
+ * @unrestricted
+ */
+Accessibility.ARIAAttributesPane.ARIAAttributePrompt = class extends UI.TextPrompt {
+ /**
+ * @param {!Array<string>} ariaCompletions
+ * @param {!Accessibility.ARIAAttributesTreeElement} treeElement
+ */
+ constructor(ariaCompletions, treeElement) {
+ super();
+ this.initialize(this._buildPropertyCompletions.bind(this));
+
+ this._ariaCompletions = ariaCompletions;
+ this._treeElement = treeElement;
+ }
+
+ /**
+ * @param {string} expression
+ * @param {string} prefix
+ * @param {boolean=} force
+ * @return {!Promise<!UI.SuggestBox.Suggestions>}
+ */
+ _buildPropertyCompletions(expression, prefix, force) {
+ prefix = prefix.toLowerCase();
+ if (!prefix && !force && (this._isEditingName || expression))
+ return Promise.resolve([]);
+ return Promise.resolve(this._ariaCompletions.filter(value => value.startsWith(prefix)).map(c => ({text: c})));
+ }
+};
+
+Accessibility.ARIAAttributesPane._attributes = [
+ 'role',
+ 'aria-busy',
+ 'aria-checked',
+ 'aria-disabled',
+ 'aria-expanded',
+ 'aria-grabbed',
+ 'aria-hidden',
+ 'aria-invalid',
+ 'aria-pressed',
+ 'aria-selected',
+ 'aria-activedescendant',
+ 'aria-atomic',
+ 'aria-autocomplete',
+ 'aria-controls',
+ 'aria-describedby',
+ 'aria-dropeffect',
+ 'aria-flowto',
+ 'aria-haspopup',
+ 'aria-label',
+ 'aria-labelledby',
+ 'aria-level',
+ 'aria-live',
+ 'aria-multiline',
+ 'aria-multiselectable',
+ 'aria-orientation',
+ 'aria-owns',
+ 'aria-posinset',
+ 'aria-readonly',
+ 'aria-relevant',
+ 'aria-required',
+ 'aria-setsize',
+ 'aria-sort',
+ 'aria-valuemax',
+ 'aria-valuemin',
+ 'aria-valuenow',
+ 'aria-valuetext',
+];
diff --git a/front_end/accessibility/ARIAConfig.js b/front_end/accessibility/ARIAConfig.js
new file mode 100644
index 0000000..909b013
--- /dev/null
+++ b/front_end/accessibility/ARIAConfig.js
@@ -0,0 +1,440 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+Accessibility.ARIAMetadata._config = {
+ 'attributes': {
+ 'aria-activedescendant': {'type': 'IDREF'},
+ 'aria-atomic': {'default': 'false', 'type': 'boolean'},
+ 'aria-autocomplete': {'default': 'none', 'enum': ['inline', 'list', 'both', 'none'], 'type': 'token'},
+ 'aria-busy': {'default': 'false', 'type': 'boolean'},
+ 'aria-checked': {'default': 'undefined', 'enum': ['true', 'false', 'mixed', 'undefined'], 'type': 'token'},
+ 'aria-colcount': {'type': 'integer'},
+ 'aria-colindex': {'type': 'integer'},
+ 'aria-colspan': {'type': 'integer'},
+ 'aria-controls': {'type': 'IDREF_list'},
+ 'aria-current':
+ {'default': 'false', 'enum': ['page', 'step', 'location', 'date', 'time', 'true', 'false'], 'type': 'token'},
+ 'aria-describedby': {'type': 'IDREF_list'},
+ 'aria-details': {'type': 'IDREF'},
+ 'aria-disabled': {'default': 'false', 'type': 'boolean'},
+ 'aria-dropeffect':
+ {'default': 'none', 'enum': ['copy', 'move', 'link', 'execute', 'popup', 'none'], 'type': 'token_list'},
+ 'aria-errormessage': {'type': 'IDREF'},
+ 'aria-expanded': {'default': 'undefined', 'enum': ['true', 'false', 'undefined'], 'type': 'token'},
+ 'aria-flowto': {'type': 'IDREF_list'},
+ 'aria-grabbed': {'default': 'undefined', 'enum': ['true', 'false', 'undefined'], 'type': 'token'},
+ 'aria-haspopup':
+ {'default': 'false', 'enum': ['false', 'true', 'menu', 'listbox', 'tree', 'grid', 'dialog'], 'type': 'token'},
+ 'aria-hidden': {'default': 'undefined', 'enum': ['true', 'false', 'undefined'], 'type': 'token'},
+ 'aria-invalid': {'default': 'false', 'enum': ['grammar', 'false', 'spelling', 'true'], 'type': 'token'},
+ 'aria-keyshortcuts': {'type': 'string'},
+ 'aria-label': {'type': 'string'},
+ 'aria-labelledby': {'type': 'IDREF_list'},
+ 'aria-level': {'type': 'integer'},
+ 'aria-live': {'default': 'off', 'enum': ['off', 'polite', 'assertive'], 'type': 'token'},
+ 'aria-modal': {'default': 'false', 'type': 'boolean'},
+ 'aria-multiline': {'default': 'false', 'type': 'boolean'},
+ 'aria-multiselectable': {'default': 'false', 'type': 'boolean'},
+ 'aria-orientation': {'default': 'undefined', 'enum': ['horizontal', 'undefined', 'vertical'], 'type': 'token'},
+ 'aria-owns': {'type': 'IDREF_list'},
+ 'aria-placeholder': {'type': 'string'},
+ 'aria-posinset': {'type': 'integer'},
+ 'aria-pressed': {'default': 'undefined', 'enum': ['true', 'false', 'mixed', 'undefined'], 'type': 'token'},
+ 'aria-readonly': {'default': 'false', 'type': 'boolean'},
+ 'aria-relevant':
+ {'default': 'additions text', 'enum': ['additions', 'removals', 'text', 'all'], 'type': 'token_list'},
+ 'aria-required': {'default': 'false', 'type': 'boolean'},
+ 'aria-roledescription': {'type': 'string'},
+ 'aria-rowcount': {'type': 'integer'},
+ 'aria-rowindex': {'type': 'integer'},
+ 'aria-rowspan': {'type': 'integer'},
+ 'aria-selected': {'default': 'undefined', 'enum': ['true', 'false', 'undefined'], 'type': 'token'},
+ 'aria-setsize': {'type': 'integer'},
+ 'aria-sort': {'default': 'none', 'enum': ['ascending', 'descending', 'none', 'other'], 'type': 'token'},
+ 'aria-valuemax': {'type': 'decimal'},
+ 'aria-valuemin': {'type': 'decimal'},
+ 'aria-valuenow': {'type': 'decimal'},
+ 'aria-valuetext': {'type': 'string'},
+ 'tabindex': {'type': 'integer'}
+ },
+ 'roles': {
+ 'alert': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['section'],
+ 'implicitValues': {'aria-live': 'assertive', 'aria-atomic': 'true'}
+ },
+ 'alertdialog': {'nameFrom': ['author'], 'superclasses': ['alert', 'dialog'], 'nameRequired': true},
+ 'application': {'nameFrom': ['author'], 'superclasses': ['structure'], 'nameRequired': true},
+ 'article': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['document'],
+ 'supportedAttributes': ['aria-posinset', 'aria-setsize']
+ },
+ 'banner': {'nameFrom': ['author'], 'superclasses': ['landmark']},
+ 'button': {
+ 'nameFrom': ['contents', 'author'],
+ 'superclasses': ['command'],
+ 'supportedAttributes': ['aria-expanded', 'aria-pressed'],
+ 'nameRequired': true,
+ 'childrenPresentational': true
+ },
+ 'cell': {
+ 'namefrom': ['contents', 'author'],
+ 'scope': 'row',
+ 'superclasses': ['section'],
+ 'supportedAttributes': ['aria-colindex', 'aria-colspan', 'aria-rowindex', 'aria-rowspan']
+ },
+ 'checkbox': {
+ 'nameFrom': ['contents', 'author'],
+ 'requiredAttributes': ['aria-checked'],
+ 'superclasses': ['input'],
+ 'supportedAttributes': ['aria-readonly'],
+ 'nameRequired': true,
+ 'implicitValues': {'aria-checked': false}
+ },
+ 'columnheader': {
+ 'nameFrom': ['contents', 'author'],
+ 'scope': ['row'],
+ 'superclasses': ['gridcell', 'sectionhead', 'widget'],
+ 'supportedAttributes': ['aria-sort'],
+ 'nameRequired': true
+ },
+ 'combobox': {
+ // TODO(aboxhall): Follow up with Nektarios and Aaron regarding role on textbox
+ 'mustContain': ['textbox'],
+ 'nameFrom': ['author'],
+ 'requiredAttributes': ['aria-controls', 'aria-expanded'],
+ 'superclasses': ['select'],
+ 'supportedAttributes': ['aria-autocomplete', 'aria-readonly', 'aria-required'],
+ 'nameRequired': true,
+ 'implicitValues': {'aria-expanded': 'false', 'aria-haspopup': 'listbox'}
+ },
+ 'command': {'abstract': true, 'nameFrom': ['author'], 'superclasses': ['widget']},
+ 'complementary': {'nameFrom': ['author'], 'superclasses': ['landmark']},
+ 'composite': {
+ 'abstract': true,
+ 'nameFrom': ['author'],
+ 'superclasses': ['widget'],
+ 'supportedAttributes': ['aria-activedescendant'],
+ },
+ 'contentinfo': {'nameFrom': ['author'], 'superclasses': ['landmark']},
+ 'definition': {'nameFrom': ['author'], 'superclasses': ['section']},
+ 'dialog': {'nameFrom': ['author'], 'superclasses': ['window'], 'nameRequired': true},
+ 'directory': {'nameFrom': ['author'], 'superclasses': ['list']},
+ 'document': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['structure'],
+ 'supportedAttributes': ['aria-expanded'],
+ 'nameRequired': false
+ },
+ 'feed': {'nameFrom': ['author'], 'superclasses': ['list'], 'mustContain': ['article'], 'nameRequired': false},
+ 'figure': {'namefrom': ['author'], 'superclasses': ['section'], 'nameRequired': false},
+ 'form': {'nameFrom': ['author'], 'superclasses': ['landmark']},
+ 'grid': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['composite', 'table'],
+ // TODO(aboxhall): Figure out how to express "rowgroup --> row" here.
+ 'mustContain': ['row'],
+ 'supportedAttributes': ['aria-level', 'aria-multiselectable', 'aria-readonly'],
+ 'nameRequired': true
+ },
+ 'gridcell': {
+ 'nameFrom': ['contents', 'author'],
+ 'scope': ['row'],
+ 'superclasses': ['cell', 'widget'],
+ 'supportedAttributes': ['aria-readonly', 'aria-required', 'aria-selected'],
+ 'nameRequired': true
+ },
+ 'group': {'nameFrom': ['author'], 'superclasses': ['section'], 'supportedAttributes': ['aria-activedescendant']},
+ 'heading': {
+ 'namefrom': ['contents', 'author'],
+ 'superclasses': ['sectionhead'],
+ 'supportedAttributes': ['aria-level'],
+ 'nameRequired': true,
+ 'implicitValues': {'aria-level': '2'}
+ },
+ 'img': {'nameFrom': ['author'], 'superclasses': ['section'], 'nameRequired': true, 'childrenPresentational': true},
+ 'input': {'abstract': true, 'nameFrom': ['author'], 'superclasses': ['widget']},
+ 'landmark': {'abstract': true, 'nameFrom': ['author'], 'superclasses': ['section'], 'nameRequired': false},
+ 'link': {
+ 'nameFrom': ['contents', 'author'],
+ 'superclasses': ['command'],
+ 'supportedAttributes': ['aria-expanded'],
+ 'nameRequired': true
+ },
+ 'list': {
+ // TODO(aboxhall): Figure out how to express "group --> listitem"
+ 'mustContain': ['listitem'],
+ 'nameFrom': ['author'],
+ 'superclasses': ['section'],
+ 'implicitValues': {'aria-orientation': 'vertical'}
+ },
+ 'listbox': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['select'],
+ 'mustContain': ['option'],
+ 'supportedAttributes': ['aria-multiselectable', 'aria-readonly', 'aria-required'],
+ 'nameRequired': true,
+ 'implicitValues': {'aria-orientation': 'vertical'},
+ },
+ 'listitem': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['section'],
+ 'scope': ['group', 'list'],
+ 'supportedAttributes': ['aria-level', 'aria-posinset', 'aria-setsize']
+ },
+ 'log': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['section'],
+ 'nameRequired': true,
+ 'implicitValues': {'aria-live': 'polite'}
+ },
+ 'main': {'nameFrom': ['author'], 'superclasses': ['landmark']},
+ 'marquee': {'nameFrom': ['author'], 'superclasses': ['section'], 'nameRequired': true},
+ 'math': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['section'],
+ 'nameRequired': true,
+ // TODO(aboxhall/aleventhal): this is what the spec says, but seems wrong.
+ childrenPresentational: true
+ },
+ 'menu': {
+ 'mustContain': ['group', 'menuitemradio', 'menuitem', 'menuitemcheckbox', 'menuitemradio'],
+ 'nameFrom': ['author'],
+ 'superclasses': ['select'],
+ 'implicitValues': {'aria-orientation': 'vertical'}
+ },
+ 'menubar': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['menu'],
+ // TODO(aboxhall): figure out how to express "group --> {menuitem, menuitemradio, menuitemcheckbox}"
+ 'mustContain': ['menuitem', 'menuitemradio', 'menuitemcheckbox'],
+ 'implicitValues': {'aria-orientation': 'horizontal'}
+ },
+ 'menuitem': {
+ 'nameFrom': ['contents', 'author'],
+ 'scope': ['group', 'menu', 'menubar'],
+ 'superclasses': ['command'],
+ 'nameRequired': true
+ },
+ 'menuitemcheckbox': {
+ 'nameFrom': ['contents', 'author'],
+ 'scope': ['menu', 'menubar'],
+ 'superclasses': ['checkbox', 'menuitem'],
+ 'nameRequired': true,
+ 'childrenPresentational': true,
+ 'implicitValues': {'aria-checked': false}
+ },
+ 'menuitemradio': {
+ 'nameFrom': ['contents', 'author'],
+ 'scope': ['menu', 'menubar', 'group'],
+ 'superclasses': ['menuitemcheckbox', 'radio'],
+ 'nameRequired': true,
+ 'childrenPresentational': true,
+ 'implicitValues': {'aria-checked': false}
+ },
+ 'navigation': {'nameFrom': ['author'], 'superclasses': ['landmark']},
+ 'none': {'superclasses': ['structure']},
+ 'note': {'nameFrom': ['author'], 'superclasses': ['section']},
+ 'option': {
+ 'nameFrom': ['contents', 'author'],
+ 'scope': ['listbox'],
+ 'superclasses': ['input'],
+ 'requiredAttributes': ['aria-selected'],
+ 'supportedAttributes': ['aria-checked', 'aria-posinset', 'aria-setsize'],
+ 'nameRequired': true,
+ 'childrenPresentational': true,
+ 'implicitValues': {'aria-selected': 'false'}
+ },
+ 'presentation': {'superclasses': ['structure']},
+ 'progressbar':
+ {'nameFrom': ['author'], 'superclasses': ['range'], 'nameRequired': true, 'childrenPresentational': true},
+ 'radio': {
+ 'nameFrom': ['contents', 'author'],
+ 'superclasses': ['input'],
+ 'requiredAttributes': ['aria-checked'],
+ 'supportedAttributes': ['aria-posinset', 'aria-setsize'],
+ 'nameRequired': true,
+ 'childrenPresentational': true,
+ 'implicitValues': {'aria-checked': 'false'}
+ },
+ 'radiogroup': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['select'],
+ 'mustContain': ['radio'],
+ 'supportedAttributes': ['aria-readonly', 'aria-required'],
+ 'nameRequired': true
+ },
+ 'range': {
+ 'abstract': true,
+ 'nameFrom': ['author'],
+ 'superclasses': ['widget'],
+ 'supportedAttributes': ['aria-valuemax', 'aria-valuemin', 'aria-valuenow', 'aria-valuetext']
+ },
+ 'region': {'nameFrom': ['author'], 'superclasses': ['landmark'], 'nameRequired': true},
+ 'roletype': {
+ 'abstract': true,
+ 'supportedAttributes': [
+ 'aria-atomic', 'aria-busy', 'aria-controls', 'aria-current', 'aria-describedby', 'aria-details',
+ 'aria-disabled', 'aria-dropeffect', 'aria-errormessage', 'aria-flowto', 'aria-grabbed', 'aria-haspopup',
+ 'aria-hidden', 'aria-invalid', 'aria-keyshortcuts', 'aria-label', 'aria-labelledby', 'aria-live',
+ 'aria-owns', 'aria-relevant', 'aria-roledescription'
+ ]
+ },
+ 'row': {
+ 'nameFrom': ['contents', 'author'],
+ 'superclasses': ['group', 'widget'],
+ 'mustContain': ['cell', 'columnheader', 'gridcell', 'rowheader'],
+ 'scope': ['grid', 'rowgroup', 'table', 'treegrid'],
+ // TODO(aboxhall/aleventhal): This is not in the spec yet, but
+ // setsize and posinset are included here for treegrid
+ // purposes. Issue already filed on spec. Remove this comment
+ // when spec updated.
+ 'supportedAttributes':
+ ['aria-colindex', 'aria-level', 'aria-rowindex', 'aria-selected', 'aria-setsize', 'aria-posinset']
+ },
+ 'rowgroup': {
+ 'nameFrom': ['contents', 'author'],
+ 'superclasses': ['structure'],
+ 'mustContain': ['row'],
+ 'scope': ['grid', 'table', 'treegrid'],
+ },
+ 'rowheader': {
+ 'nameFrom': ['contents', 'author'],
+ 'scope': ['row'],
+ 'superclasses': ['cell', 'gridcell', 'sectionhead'],
+ 'supportedAttributes': ['aria-sort'],
+ 'nameRequired': true
+ },
+ 'scrollbar': {
+ 'nameFrom': ['author'],
+ 'requiredAttributes': ['aria-controls', 'aria-orientation', 'aria-valuemax', 'aria-valuemin', 'aria-valuenow'],
+ 'superclasses': ['range'],
+ 'nameRequired': false,
+ 'childrenPresentational': true,
+ 'implicitValues': {'aria-orientation': 'vertical', 'aria-valuemin': '0', 'aria-valuemax': '100'}
+ },
+ 'search': {'nameFrom': ['author'], 'superclasses': ['landmark']},
+ 'searchbox': {'nameFrom': ['author'], 'superclasses': ['textbox'], 'nameRequired': true},
+ 'section': {'abstract': true, 'superclasses': ['structure'], 'supportedAttributes': ['aria-expanded']},
+ 'sectionhead': {
+ 'abstract': true,
+ 'nameFrom': ['contents', 'author'],
+ 'superclasses': ['structure'],
+ 'supportedAttributes': ['aria-expanded']
+ },
+ 'select': {'abstract': true, 'nameFrom': ['author'], 'superclasses': ['composite', 'group']},
+ 'separator': {
+ 'nameFrom': ['author'],
+ // TODO(aboxhall): superclass depends on focusability, but
+ // doesn't affect required/supported attributes
+ 'superclasses': ['structure'],
+ // TODO(aboxhall): required attributes depend on focusability
+ 'supportedAttributes': ['aria-orientation', 'aria-valuemin', 'aria-valuemax', 'aria-valuenow', 'aria-valuetext']
+ },
+ 'slider': {
+ 'nameFrom': ['author'],
+ 'requiredAttributes': ['aria-valuemax', 'aria-valuemin', 'aria-valuenow'],
+ 'superclasses': ['input', 'range'],
+ 'supportedAttributes': ['aria-orientation'],
+ 'nameRequired': true,
+ 'childrenPresentational': true,
+ // TODO(aboxhall): aria-valuenow default is halfway between
+ // aria-valuemin and aria-valuemax
+ 'implicitValues': {'aria-orientation': 'horizontal', 'aria-valuemin': '0', 'aria-valuemax': '100'}
+ },
+ 'spinbutton': {
+ 'nameFrom': ['author'],
+ 'requiredAttributes': ['aria-valuemax', 'aria-valuemin', 'aria-valuenow'],
+ 'superclasses': ['composite', 'input', 'range'],
+ 'supportedAttributes': ['aria-required', 'aria-readonly'],
+ 'nameRequired': true,
+ 'implicitValues': {'aria-valuenow': '0'}
+ },
+ 'status': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['section'],
+ 'implicitValues': {'aria-live': 'polite', 'aria-atomic': 'true'}
+ },
+ 'structure': {'abstract': true, 'superclasses': ['roletype']},
+ 'switch': {
+ 'nameFrom': ['contents', 'author'],
+ 'superclasses': ['checkbox'],
+ 'requiredAttributes': ['aria-checked'],
+ 'nameRequired': true,
+ 'childrenPresentational': true,
+ 'implicitValues': {'aria-checked': 'false'}
+ },
+ 'tab': {
+ 'nameFrom': ['contents', 'author'],
+ 'scope': ['tablist'],
+ 'superclasses': ['sectionhead', 'widget'],
+ 'supportedAttributes': ['aria-selected'],
+ 'childrenPresentational': true,
+ 'implicitValues': {'aria-selected': 'false'}
+ },
+ 'table': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['section'],
+ // TODO(aboxhall): Figure out how to express "rowgroup --> row"
+ 'mustContain': ['row'],
+ 'supportedAttributes': ['aria-colcount', 'aria-rowcount'],
+ 'nameRequired': true
+ },
+ 'tablist': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['composite'],
+ 'mustContain': ['tab'],
+ 'supportedAttributes': ['aria-level', 'aria-multiselectable', 'aria-orientation'],
+ 'implicitValues': {'aria-orientation': 'horizontal'}
+ },
+ 'tabpanel': {'nameFrom': ['author'], 'superclasses': ['section'], 'nameRequired': true},
+ 'term': {'nameFrom': ['author'], 'superclasses': ['section']},
+ 'textbox': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['input'],
+ 'supportedAttributes': [
+ 'aria-activedescendant', 'aria-autocomplete', 'aria-multiline', 'aria-placeholder', 'aria-readonly',
+ 'aria-required'
+ ],
+ 'nameRequired': true
+ },
+ 'timer': {'nameFrom': ['author'], 'superclasses': ['status']},
+ 'toolbar': {
+ 'nameFrom': ['author'],
+ 'superclasses': ['group'],
+ 'supportedAttributes': ['aria-orientation'],
+ 'implicitValues': {'aria-orientation': 'horizontal'}
+ },
+ 'tooltip': {'nameFrom': ['contents', 'author'], 'superclasses': ['section'], 'nameRequired': true},
+ 'tree': {
+ 'nameFrom': ['author'],
+ 'mustContain': ['group', 'treeitem'],
+ 'superclasses': ['select'],
+ 'supportedAttributes': ['aria-multiselectable', 'aria-required'],
+ 'nameRequired': true,
+ 'implicitValues': {'aria-orientation': 'vertical'}
+ },
+ 'treegrid': {
+ // TODO(aboxhall): Figure out how to express "rowgroup --> row"
+ 'mustContain': ['row'],
+ 'nameFrom': ['author'],
+ 'superclasses': ['grid', 'tree'],
+ 'nameRequired': true
+ },
+ 'treeitem': {
+ 'nameFrom': ['contents', 'author'],
+ 'scope': ['group', 'tree'],
+ 'superclasses': ['listitem', 'option'],
+ 'nameRequired': true
+ },
+ 'widget': {'abstract': true, 'superclasses': ['roletype']},
+ 'window': {
+ 'abstract': true,
+ 'nameFrom': ['author'],
+ 'superclasses': ['roletype'],
+ 'supportedAttributes': ['aria-expanded', 'aria-modal']
+ }
+ }
+};
diff --git a/front_end/accessibility/ARIAMetadata.js b/front_end/accessibility/ARIAMetadata.js
new file mode 100644
index 0000000..5fbf0cd
--- /dev/null
+++ b/front_end/accessibility/ARIAMetadata.js
@@ -0,0 +1,82 @@
+// Copyright (c) 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+/**
+ * @unrestricted
+ */
+Accessibility.ARIAMetadata = class {
+ /**
+ * @param {?Object} config
+ */
+ constructor(config) {
+ /** @type {!Map<string, !Accessibility.ARIAMetadata.Attribute>} */
+ this._attributes = new Map();
+
+ if (config)
+ this._initialize(config);
+ }
+
+ /**
+ * @param {!Object} config
+ */
+ _initialize(config) {
+ const attributes = config['attributes'];
+
+ const booleanEnum = ['true', 'false'];
+ for (const name in attributes) {
+ const attributeConfig = attributes[name];
+ if (attributeConfig.type === 'boolean')
+ attributeConfig.enum = booleanEnum;
+ this._attributes.set(name, new Accessibility.ARIAMetadata.Attribute(attributeConfig));
+ }
+
+ /** @type {!Array<string>} */
+ this._roleNames = Object.keys(config['roles']);
+ }
+
+ /**
+ * @param {string} property
+ * @return {!Array<string>}
+ */
+ valuesForProperty(property) {
+ if (this._attributes.has(property))
+ return this._attributes.get(property).getEnum();
+
+ if (property === 'role')
+ return this._roleNames;
+
+ return [];
+ }
+};
+
+/**
+ * @return {!Accessibility.ARIAMetadata}
+ */
+Accessibility.ariaMetadata = function() {
+ if (!Accessibility.ARIAMetadata._instance)
+ Accessibility.ARIAMetadata._instance = new Accessibility.ARIAMetadata(Accessibility.ARIAMetadata._config || null);
+ return Accessibility.ARIAMetadata._instance;
+};
+
+/**
+ * @unrestricted
+ */
+Accessibility.ARIAMetadata.Attribute = class {
+ /**
+ * @param {!Object} config
+ */
+ constructor(config) {
+ /** @type {!Array<string>} */
+ this._enum = [];
+
+ if ('enum' in config)
+ this._enum = config.enum;
+ }
+
+ /**
+ * @return {!Array<string>}
+ */
+ getEnum() {
+ return this._enum;
+ }
+};
diff --git a/front_end/accessibility/AXBreadcrumbsPane.js b/front_end/accessibility/AXBreadcrumbsPane.js
new file mode 100644
index 0000000..34708e0
--- /dev/null
+++ b/front_end/accessibility/AXBreadcrumbsPane.js
@@ -0,0 +1,498 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+Accessibility.AXBreadcrumbsPane = class extends Accessibility.AccessibilitySubPane {
+ /**
+ * @param {!Accessibility.AccessibilitySidebarView} axSidebarView
+ */
+ constructor(axSidebarView) {
+ super(ls`Accessibility Tree`);
+
+ this.element.classList.add('ax-subpane');
+ UI.ARIAUtils.markAsTree(this.element);
+ this.element.tabIndex = -1;
+
+ this._axSidebarView = axSidebarView;
+
+ /** @type {?Accessibility.AXBreadcrumb} */
+ this._preselectedBreadcrumb = null;
+ /** @type {?Accessibility.AXBreadcrumb} */
+ this._inspectedNodeBreadcrumb = null;
+
+ this._hoveredBreadcrumb = null;
+ this._rootElement = this.element.createChild('div', 'ax-breadcrumbs');
+
+ this._rootElement.addEventListener('keydown', this._onKeyDown.bind(this), true);
+ this._rootElement.addEventListener('mousemove', this._onMouseMove.bind(this), false);
+ this._rootElement.addEventListener('mouseleave', this._onMouseLeave.bind(this), false);
+ this._rootElement.addEventListener('click', this._onClick.bind(this), false);
+ this._rootElement.addEventListener('contextmenu', this._contextMenuEventFired.bind(this), false);
+ this._rootElement.addEventListener('focusout', this._onFocusOut.bind(this), false);
+ this.registerRequiredCSS('accessibility/axBreadcrumbs.css');
+ }
+
+ /**
+ * @override
+ */
+ focus() {
+ if (this._inspectedNodeBreadcrumb)
+ this._inspectedNodeBreadcrumb.nodeElement().focus();
+ else
+ this.element.focus();
+ }
+
+ /**
+ * @param {?Accessibility.AccessibilityNode} axNode
+ * @override
+ */
+ setAXNode(axNode) {
+ const hadFocus = this.element.hasFocus();
+ super.setAXNode(axNode);
+
+ this._rootElement.removeChildren();
+
+ if (!axNode)
+ return;
+
+ const ancestorChain = [];
+ let ancestor = axNode;
+ while (ancestor) {
+ ancestorChain.push(ancestor);
+ ancestor = ancestor.parentNode();
+ }
+ ancestorChain.reverse();
+
+ let depth = 0;
+ let breadcrumb = null;
+ let parent = null;
+ for (ancestor of ancestorChain) {
+ breadcrumb = new Accessibility.AXBreadcrumb(ancestor, depth, (ancestor === axNode));
+ if (parent)
+ parent.appendChild(breadcrumb);
+ else
+ this._rootElement.appendChild(breadcrumb.element());
+ parent = breadcrumb;
+ depth++;
+ }
+
+ this._inspectedNodeBreadcrumb = breadcrumb;
+ this._inspectedNodeBreadcrumb.setPreselected(true, hadFocus);
+
+ this._setPreselectedBreadcrumb(this._inspectedNodeBreadcrumb);
+
+ /**
+ * @param {!Accessibility.AXBreadcrumb} parentBreadcrumb
+ * @param {!Accessibility.AccessibilityNode} axNode
+ * @param {number} localDepth
+ */
+ function append(parentBreadcrumb, axNode, localDepth) {
+ const childBreadcrumb = new Accessibility.AXBreadcrumb(axNode, localDepth, false);
+ parentBreadcrumb.appendChild(childBreadcrumb);
+
+ // In most cases there will be no children here, but there are some special cases.
+ for (const child of axNode.children())
+ append(childBreadcrumb, child, localDepth + 1);
+ }
+
+ for (const child of axNode.children())
+ append(this._inspectedNodeBreadcrumb, child, depth);
+ }
+
+ /**
+ * @override
+ */
+ willHide() {
+ this._setPreselectedBreadcrumb(null);
+ }
+
+ /**
+ * @param {!Event} event
+ */
+ _onKeyDown(event) {
+ if (!this._preselectedBreadcrumb)
+ return;
+ if (!event.path.some(element => element === this._preselectedBreadcrumb.element()))
+ return;
+ if (event.shiftKey || event.metaKey || event.ctrlKey)
+ return;
+
+ let handled = false;
+ if ((event.key === 'ArrowUp' || event.key === 'ArrowLeft') && !event.altKey)
+ handled = this._preselectPrevious();
+ else if ((event.key === 'ArrowDown' || event.key === 'ArrowRight') && !event.altKey)
+ handled = this._preselectNext();
+ else if (isEnterKey(event))
+ handled = this._inspectDOMNode(this._preselectedBreadcrumb.axNode());
+
+ if (handled)
+ event.consume(true);
+ }
+
+ /**
+ * @return {boolean}
+ */
+ _preselectPrevious() {
+ const previousBreadcrumb = this._preselectedBreadcrumb.previousBreadcrumb();
+ if (!previousBreadcrumb)
+ return false;
+ this._setPreselectedBreadcrumb(previousBreadcrumb);
+ return true;
+ }
+
+ /**
+ * @return {boolean}
+ */
+ _preselectNext() {
+ const nextBreadcrumb = this._preselectedBreadcrumb.nextBreadcrumb();
+ if (!nextBreadcrumb)
+ return false;
+ this._setPreselectedBreadcrumb(nextBreadcrumb);
+ return true;
+ }
+
+ /**
+ * @param {?Accessibility.AXBreadcrumb} breadcrumb
+ */
+ _setPreselectedBreadcrumb(breadcrumb) {
+ if (breadcrumb === this._preselectedBreadcrumb)
+ return;
+ const hadFocus = this.element.hasFocus();
+ if (this._preselectedBreadcrumb)
+ this._preselectedBreadcrumb.setPreselected(false, hadFocus);
+
+ if (breadcrumb)
+ this._preselectedBreadcrumb = breadcrumb;
+ else
+ this._preselectedBreadcrumb = this._inspectedNodeBreadcrumb;
+ this._preselectedBreadcrumb.setPreselected(true, hadFocus);
+ if (!breadcrumb && hadFocus)
+ SDK.OverlayModel.hideDOMNodeHighlight();
+ }
+
+ /**
+ * @param {!Event} event
+ */
+ _onMouseLeave(event) {
+ this._setHoveredBreadcrumb(null);
+ }
+
+ /**
+ * @param {!Event} event
+ */
+ _onMouseMove(event) {
+ const breadcrumbElement = event.target.enclosingNodeOrSelfWithClass('ax-breadcrumb');
+ if (!breadcrumbElement) {
+ this._setHoveredBreadcrumb(null);
+ return;
+ }
+ const breadcrumb = breadcrumbElement.breadcrumb;
+ if (!breadcrumb.isDOMNode())
+ return;
+ this._setHoveredBreadcrumb(breadcrumb);
+ }
+
+ /**
+ * @param {!Event} event
+ */
+ _onFocusOut(event) {
+ if (!this._preselectedBreadcrumb || event.target !== this._preselectedBreadcrumb.nodeElement())
+ return;
+ this._setPreselectedBreadcrumb(null);
+ }
+
+ /**
+ * @param {!Event} event
+ */
+ _onClick(event) {
+ const breadcrumbElement = event.target.enclosingNodeOrSelfWithClass('ax-breadcrumb');
+ if (!breadcrumbElement) {
+ this._setHoveredBreadcrumb(null);
+ return;
+ }
+ const breadcrumb = breadcrumbElement.breadcrumb;
+ if (breadcrumb.inspected()) {
+ // If the user is clicking the inspected breadcrumb, they probably want to
+ // focus it.
+ breadcrumb.nodeElement().focus();
+ return;
+ }
+ if (!breadcrumb.isDOMNode())
+ return;
+ this._inspectDOMNode(breadcrumb.axNode());
+ }
+
+ /**
+ * @param {?Accessibility.AXBreadcrumb} breadcrumb
+ */
+ _setHoveredBreadcrumb(breadcrumb) {
+ if (breadcrumb === this._hoveredBreadcrumb)
+ return;
+
+ if (this._hoveredBreadcrumb)
+ this._hoveredBreadcrumb.setHovered(false);
+
+ if (breadcrumb) {
+ breadcrumb.setHovered(true);
+ } else if (this.node()) {
+ // Highlight and scroll into view the currently inspected node.
+ this.node().domModel().overlayModel().nodeHighlightRequested(this.node().id);
+ }
+
+ this._hoveredBreadcrumb = breadcrumb;
+ }
+
+ /**
+ * @param {!Accessibility.AccessibilityNode} axNode
+ * @return {boolean}
+ */
+ _inspectDOMNode(axNode) {
+ if (!axNode.isDOMNode())
+ return false;
+
+ axNode.deferredDOMNode().resolve(domNode => {
+ this._axSidebarView.setNode(domNode, true /* fromAXTree */);
+ Common.Revealer.reveal(domNode, true /* omitFocus */);
+ });
+
+ return true;
+ }
+
+ /**
+ * @param {!Event} event
+ */
+ _contextMenuEventFired(event) {
+ const breadcrumbElement = event.target.enclosingNodeOrSelfWithClass('ax-breadcrumb');
+ if (!breadcrumbElement)
+ return;
+
+ const axNode = breadcrumbElement.breadcrumb.axNode();
+ if (!axNode.isDOMNode() || !axNode.deferredDOMNode())
+ return;
+
+ const contextMenu = new UI.ContextMenu(event);
+ contextMenu.viewSection().appendItem(ls`Scroll into view`, () => {
+ axNode.deferredDOMNode().resolvePromise().then(domNode => {
+ if (!domNode)
+ return;
+ domNode.scrollIntoView();
+ });
+ });
+
+ contextMenu.appendApplicableItems(axNode.deferredDOMNode());
+ contextMenu.show();
+ }
+};
+
+Accessibility.AXBreadcrumb = class {
+ /**
+ * @param {!Accessibility.AccessibilityNode} axNode
+ * @param {number} depth
+ * @param {boolean} inspected
+ */
+ constructor(axNode, depth, inspected) {
+ /** @type {!Accessibility.AccessibilityNode} */
+ this._axNode = axNode;
+
+ this._element = createElementWithClass('div', 'ax-breadcrumb');
+ this._element.breadcrumb = this;
+
+ this._nodeElement = createElementWithClass('div', 'ax-node');
+ UI.ARIAUtils.markAsTreeitem(this._nodeElement);
+ this._nodeElement.tabIndex = -1;
+ this._element.appendChild(this._nodeElement);
+ this._nodeWrapper = createElementWithClass('div', 'wrapper');
+ this._nodeElement.appendChild(this._nodeWrapper);
+
+ this._selectionElement = createElementWithClass('div', 'selection fill');
+ this._nodeElement.appendChild(this._selectionElement);
+
+ this._childrenGroupElement = createElementWithClass('div', 'children');
+ UI.ARIAUtils.markAsGroup(this._childrenGroupElement);
+ this._element.appendChild(this._childrenGroupElement);
+
+ /** @type !Array<!Accessibility.AXBreadcrumb> */
+ this._children = [];
+ this._hovered = false;
+ this._preselected = false;
+ this._parent = null;
+
+ this._inspected = inspected;
+ this._nodeElement.classList.toggle('inspected', inspected);
+
+ this._nodeElement.style.paddingLeft = (16 * depth + 4) + 'px';
+
+ if (this._axNode.ignored()) {
+ this._appendIgnoredNodeElement();
+ } else {
+ this._appendRoleElement(this._axNode.role());
+ if (this._axNode.name() && this._axNode.name().value) {
+ this._nodeWrapper.createChild('span', 'separator').textContent = '\u00A0';
+ this._appendNameElement(/** @type {string} */ (this._axNode.name().value));
+ }
+ }
+
+ if (this._axNode.hasOnlyUnloadedChildren())
+ this._nodeElement.classList.add('children-unloaded');
+
+ if (!this._axNode.isDOMNode())
+ this._nodeElement.classList.add('no-dom-node');
+ }
+
+ /**
+ * @return {!Element}
+ */
+ element() {
+ return this._element;
+ }
+
+ /**
+ * @return {!Element}
+ */
+ nodeElement() {
+ return this._nodeElement;
+ }
+
+ /**
+ * @param {!Accessibility.AXBreadcrumb} breadcrumb
+ */
+ appendChild(breadcrumb) {
+ this._children.push(breadcrumb);
+ breadcrumb.setParent(this);
+ this._nodeElement.classList.add('parent');
+ UI.ARIAUtils.setExpanded(this._nodeElement, true);
+ this._childrenGroupElement.appendChild(breadcrumb.element());
+ }
+
+ /**
+ * @param {!Accessibility.AXBreadcrumb} breadcrumb
+ */
+ setParent(breadcrumb) {
+ this._parent = breadcrumb;
+ }
+
+ /**
+ * @return {boolean}
+ */
+ preselected() {
+ return this._preselected;
+ }
+
+ /**
+ * @param {boolean} preselected
+ * @param {boolean} selectedByUser
+ */
+ setPreselected(preselected, selectedByUser) {
+ if (this._preselected === preselected)
+ return;
+ this._preselected = preselected;
+ this._nodeElement.classList.toggle('preselected', preselected);
+ if (preselected)
+ this._nodeElement.setAttribute('tabIndex', 0);
+ else
+ this._nodeElement.setAttribute('tabIndex', -1);
+ if (this._preselected) {
+ if (selectedByUser)
+ this._nodeElement.focus();
+ if (!this._inspected)
+ this._axNode.highlightDOMNode();
+ else
+ SDK.OverlayModel.hideDOMNodeHighlight();
+ }
+ }
+
+ /**
+ * @param {boolean} hovered
+ */
+ setHovered(hovered) {
+ if (this._hovered === hovered)
+ return;
+ this._hovered = hovered;
+ this._nodeElement.classList.toggle('hovered', hovered);
+ if (this._hovered) {
+ this._nodeElement.classList.toggle('hovered', true);
+ this._axNode.highlightDOMNode();
+ }
+ }
+
+ /**
+ * @return {!Accessibility.AccessibilityNode}
+ */
+ axNode() {
+ return this._axNode;
+ }
+
+ /**
+ * @return {boolean}
+ */
+ inspected() {
+ return this._inspected;
+ }
+
+ /**
+ * @return {boolean}
+ */
+ isDOMNode() {
+ return this._axNode.isDOMNode();
+ }
+
+ /**
+ * @return {?Accessibility.AXBreadcrumb}
+ */
+ nextBreadcrumb() {
+ if (this._children.length)
+ return this._children[0];
+ const nextSibling = this.element().nextSibling;
+ if (nextSibling)
+ return nextSibling.breadcrumb;
+ return null;
+ }
+
+ /**
+ * @return {?Accessibility.AXBreadcrumb}
+ */
+ previousBreadcrumb() {
+ const previousSibling = this.element().previousSibling;
+ if (previousSibling)
+ return previousSibling.breadcrumb;
+
+ return this._parent;
+ }
+
+ /**
+ * @param {string} name
+ */
+ _appendNameElement(name) {
+ const nameElement = createElement('span');
+ nameElement.textContent = '"' + name + '"';
+ nameElement.classList.add('ax-readable-string');
+ this._nodeWrapper.appendChild(nameElement);
+ }
+
+ /**
+ * @param {?Protocol.Accessibility.AXValue} role
+ */
+ _appendRoleElement(role) {
+ if (!role)
+ return;
+
+ const roleElement = createElementWithClass('span', 'monospace');
+ roleElement.classList.add(Accessibility.AXBreadcrumb.RoleStyles[role.type]);
+ roleElement.setTextContentTruncatedIfNeeded(role.value || '');
+
+ this._nodeWrapper.appendChild(roleElement);
+ }
+
+ _appendIgnoredNodeElement() {
+ const ignoredNodeElement = createElementWithClass('span', 'monospace');
+ ignoredNodeElement.textContent = ls`Ignored`;
+ ignoredNodeElement.classList.add('ax-breadcrumbs-ignored-node');
+ this._nodeWrapper.appendChild(ignoredNodeElement);
+ }
+};
+
+/** @type {!Object<string, string>} */
+Accessibility.AXBreadcrumb.RoleStyles = {
+ internalRole: 'ax-internal-role',
+ role: 'ax-role',
+};
diff --git a/front_end/accessibility/AccessibilityModel.js b/front_end/accessibility/AccessibilityModel.js
new file mode 100644
index 0000000..6dd0923
--- /dev/null
+++ b/front_end/accessibility/AccessibilityModel.js
@@ -0,0 +1,307 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+/**
+ * @unrestricted
+ */
+Accessibility.AccessibilityNode = class {
+ /**
+ * @param {!Accessibility.AccessibilityModel} accessibilityModel
+ * @param {!Protocol.Accessibility.AXNode} payload
+ */
+ constructor(accessibilityModel, payload) {
+ this._accessibilityModel = accessibilityModel;
+ this._agent = accessibilityModel._agent;
+
+ this._id = payload.nodeId;
+ accessibilityModel._setAXNodeForAXId(this._id, this);
+ if (payload.backendDOMNodeId) {
+ accessibilityModel._setAXNodeForBackendDOMNodeId(payload.backendDOMNodeId, this);
+ this._backendDOMNodeId = payload.backendDOMNodeId;
+ this._deferredDOMNode = new SDK.DeferredDOMNode(accessibilityModel.target(), payload.backendDOMNodeId);
+ } else {
+ this._backendDOMNodeId = null;
+ this._deferredDOMNode = null;
+ }
+ this._ignored = payload.ignored;
+ if (this._ignored && 'ignoredReasons' in payload)
+ this._ignoredReasons = payload.ignoredReasons;
+
+ this._role = payload.role || null;
+ this._name = payload.name || null;
+ this._description = payload.description || null;
+ this._value = payload.value || null;
+ this._properties = payload.properties || null;
+ this._childIds = payload.childIds || null;
+ this._parentNode = null;
+ }
+
+ /**
+ * @return {!Accessibility.AccessibilityModel}
+ */
+ accessibilityModel() {
+ return this._accessibilityModel;
+ }
+
+ /**
+ * @return {boolean}
+ */
+ ignored() {
+ return this._ignored;
+ }
+
+ /**
+ * @return {?Array<!Protocol.Accessibility.AXProperty>}
+ */
+ ignoredReasons() {
+ return this._ignoredReasons || null;
+ }
+
+ /**
+ * @return {?Protocol.Accessibility.AXValue}
+ */
+ role() {
+ return this._role || null;
+ }
+
+ /**
+ * @return {!Array<!Protocol.Accessibility.AXProperty>}
+ */
+ coreProperties() {
+ const properties = [];
+
+ if (this._name)
+ properties.push(/** @type {!Protocol.Accessibility.AXProperty} */ ({name: 'name', value: this._name}));
+ if (this._description) {
+ properties.push(
+ /** @type {!Protocol.Accessibility.AXProperty} */ ({name: 'description', value: this._description}));
+ }
+ if (this._value)
+ properties.push(/** @type {!Protocol.Accessibility.AXProperty} */ ({name: 'value', value: this._value}));
+
+ return properties;
+ }
+
+ /**
+ * @return {?Protocol.Accessibility.AXValue}
+ */
+ name() {
+ return this._name || null;
+ }
+
+ /**
+ * @return {?Protocol.Accessibility.AXValue}
+ */
+ description() {
+ return this._description || null;
+ }
+
+ /**
+ * @return {?Protocol.Accessibility.AXValue}
+ */
+ value() {
+ return this._value || null;
+ }
+
+ /**
+ * @return {?Array<!Protocol.Accessibility.AXProperty>}
+ */
+ properties() {
+ return this._properties || null;
+ }
+
+ /**
+ * @return {?Accessibility.AccessibilityNode}
+ */
+ parentNode() {
+ return this._parentNode;
+ }
+
+ /**
+ * @param {?Accessibility.AccessibilityNode} parentNode
+ */
+ _setParentNode(parentNode) {
+ this._parentNode = parentNode;
+ }
+
+ /**
+ * @return {boolean}
+ */
+ isDOMNode() {
+ return !!this._backendDOMNodeId;
+ }
+
+ /**
+ * @return {?number}
+ */
+ backendDOMNodeId() {
+ return this._backendDOMNodeId;
+ }
+
+ /**
+ * @return {?SDK.DeferredDOMNode}
+ */
+ deferredDOMNode() {
+ return this._deferredDOMNode;
+ }
+
+ highlightDOMNode() {
+ if (!this.deferredDOMNode())
+ return;
+
+ // Highlight node in page.
+ this.deferredDOMNode().highlight();
+
+ // Highlight node in Elements tree.
+ this.deferredDOMNode().resolvePromise().then(node => {
+ if (!node)
+ return;
+ node.domModel().overlayModel().nodeHighlightRequested(node.id);
+ });
+ }
+
+ /**
+ * @return {!Array<!Accessibility.AccessibilityNode>}
+ */
+ children() {
+ const children = [];
+ if (!this._childIds)
+ return children;
+
+ for (const childId of this._childIds) {
+ const child = this._accessibilityModel.axNodeForId(childId);
+ if (child)
+ children.push(child);
+ }
+
+ return children;
+ }
+
+ /**
+ * @return {number}
+ */
+ numChildren() {
+ if (!this._childIds)
+ return 0;
+ return this._childIds.length;
+ }
+
+ /**
+ * @return {boolean}
+ */
+ hasOnlyUnloadedChildren() {
+ if (!this._childIds || !this._childIds.length)
+ return false;
+
+ return !this._childIds.some(id => this._accessibilityModel.axNodeForId(id) !== undefined);
+ }
+
+ /**
+ * TODO(aboxhall): Remove once protocol is stable.
+ * @param {!Accessibility.AccessibilityNode} inspectedNode
+ * @param {string=} leadingSpace
+ * @return {string}
+ */
+ printSelfAndChildren(inspectedNode, leadingSpace) {
+ let string = leadingSpace || '';
+ if (this._role)
+ string += this._role.value;
+ else
+ string += '<no role>';
+ string += (this._name ? ' ' + this._name.value : '');
+ string += ' ' + this._id;
+ if (this._domNode)
+ string += ' (' + this._domNode.nodeName() + ')';
+ if (this === inspectedNode)
+ string += ' *';
+ for (const child of this.children())
+ string += '\n' + child.printSelfAndChildren(inspectedNode, (leadingSpace || '') + ' ');
+ return string;
+ }
+};
+
+/**
+ * @unrestricted
+ */
+Accessibility.AccessibilityModel = class extends SDK.SDKModel {
+ /**
+ * @param {!SDK.Target} target
+ */
+ constructor(target) {
+ super(target);
+ this._agent = target.accessibilityAgent();
+
+ /** @type {!Map<string, !Accessibility.AccessibilityNode>} */
+ this._axIdToAXNode = new Map();
+ this._backendDOMNodeIdToAXNode = new Map();
+ }
+
+ clear() {
+ this._axIdToAXNode.clear();
+ }
+
+ /**
+ * @param {!SDK.DOMNode} node
+ * @return {!Promise}
+ */
+ async requestPartialAXTree(node) {
+ const payloads = await this._agent.getPartialAXTree(node.id, undefined, undefined, true);
+ if (!payloads)
+ return;
+
+ for (const payload of payloads)
+ new Accessibility.AccessibilityNode(this, payload);
+
+ for (const axNode of this._axIdToAXNode.values()) {
+ for (const axChild of axNode.children())
+ axChild._setParentNode(axNode);
+ }
+ }
+
+ /**
+ * @param {string} axId
+ * @return {?Accessibility.AccessibilityNode}
+ */
+ axNodeForId(axId) {
+ return this._axIdToAXNode.get(axId);
+ }
+
+ /**
+ * @param {string} axId
+ * @param {!Accessibility.AccessibilityNode} axNode
+ */
+ _setAXNodeForAXId(axId, axNode) {
+ this._axIdToAXNode.set(axId, axNode);
+ }
+
+ /**
+ * @param {?SDK.DOMNode} domNode
+ * @return {?Accessibility.AccessibilityNode}
+ */
+ axNodeForDOMNode(domNode) {
+ if (!domNode)
+ return null;
+ return this._backendDOMNodeIdToAXNode.get(domNode.backendNodeId());
+ }
+
+ /**
+ * @param {number} backendDOMNodeId
+ * @param {!Accessibility.AccessibilityNode} axNode
+ */
+ _setAXNodeForBackendDOMNodeId(backendDOMNodeId, axNode) {
+ this._backendDOMNodeIdToAXNode.set(backendDOMNodeId, axNode);
+ }
+
+ // TODO(aboxhall): Remove once protocol is stable.
+ /**
+ * @param {!SDK.DOMNode} inspectedNode
+ */
+ logTree(inspectedNode) {
+ let rootNode = inspectedNode;
+ while (rootNode.parentNode())
+ rootNode = rootNode.parentNode();
+ console.log(rootNode.printSelfAndChildren(inspectedNode)); // eslint-disable-line no-console
+ }
+};
+
+SDK.SDKModel.register(Accessibility.AccessibilityModel, SDK.Target.Capability.DOM, false);
diff --git a/front_end/accessibility/AccessibilityNodeView.js b/front_end/accessibility/AccessibilityNodeView.js
new file mode 100644
index 0000000..f0c0a2e
--- /dev/null
+++ b/front_end/accessibility/AccessibilityNodeView.js
@@ -0,0 +1,624 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+/**
+ * @unrestricted
+ */
+Accessibility.AXNodeSubPane = class 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');
+ }
+
+ /**
+ * @param {?Accessibility.AccessibilityNode} axNode
+ * @override
+ */
+ setAXNode(axNode) {
+ if (this._axNode === axNode)
+ return;
+ 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;
+ }
+
+ 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');
+ /**
+ * @param {!Protocol.Accessibility.AXProperty} property
+ */
+ function addIgnoredReason(property) {
+ ignoredReasons.appendChild(new Accessibility.AXNodeIgnoredReasonTreeElement(
+ property, /** @type {!Accessibility.AccessibilityNode} */ (axNode)));
+ }
+ const ignoredReasonsArray = /** @type {!Array<!Protocol.Accessibility.AXProperty>} */ (axNode.ignoredReasons());
+ for (const reason of ignoredReasonsArray)
+ addIgnoredReason(reason);
+ if (!ignoredReasons.firstChild())
+ ignoredReasons.element.classList.add('hidden');
+ return;
+ }
+ 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');
+
+ /**
+ * @param {!Protocol.Accessibility.AXProperty} property
+ */
+ function addProperty(property) {
+ treeOutline.appendChild(new Accessibility.AXNodePropertyTreePropertyElement(
+ property, /** @type {!Accessibility.AccessibilityNode} */ (axNode)));
+ }
+
+ for (const property of axNode.coreProperties())
+ addProperty(property);
+
+ const roleProperty = /** @type {!Protocol.Accessibility.AXProperty} */ ({name: 'role', value: axNode.role()});
+ addProperty(roleProperty);
+ for (const property of /** @type {!Array.<!Protocol.Accessibility.AXProperty>} */ (axNode.properties()))
+ addProperty(property);
+ }
+
+ /**
+ * @override
+ * @param {?SDK.DOMNode} node
+ */
+ setNode(node) {
+ super.setNode(node);
+ this._axNode = null;
+ }
+};
+
+/**
+ * @unrestricted
+ */
+Accessibility.AXNodePropertyTreeElement = class extends UI.TreeElement {
+ /**
+ * @param {!Accessibility.AccessibilityNode} axNode
+ */
+ constructor(axNode) {
+ // Pass an empty title, the title gets made later in onattach.
+ super('');
+ this._axNode = axNode;
+ }
+
+ /**
+ * @param {?Protocol.Accessibility.AXValueType} type
+ * @param {string} value
+ * @return {!Element}
+ */
+ 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');
+ let valueText;
+ const isStringProperty = type && Accessibility.AXNodePropertyTreeElement.StringProperties.has(type);
+ if (isStringProperty) {
+ // Render \n as a nice unicode cr symbol.
+ valueText = '"' + value.replace(/\n/g, '\u21B5') + '"';
+ valueElement._originalTextContent = value;
+ } else {
+ valueText = String(value);
+ }
+
+ if (type && type in Accessibility.AXNodePropertyTreeElement.TypeStyles)
+ valueElement.classList.add(Accessibility.AXNodePropertyTreeElement.TypeStyles[type]);
+
+ valueElement.setTextContentTruncatedIfNeeded(valueText || '');
+
+ valueElement.title = String(value) || '';
+
+ return valueElement;
+ }
+
+ /**
+ * @param {string} tooltip
+ * @return {!Element}
+ */
+ static createExclamationMark(tooltip) {
+ const exclamationElement = createElement('label', 'dt-icon-label');
+ exclamationElement.type = 'smallicon-warning';
+ exclamationElement.title = tooltip;
+ return exclamationElement;
+ }
+
+ /**
+ * @param {string} name
+ */
+ appendNameElement(name) {
+ const nameElement = createElement('span');
+ const AXAttributes = Accessibility.AccessibilityStrings.AXAttributes;
+ if (name in AXAttributes) {
+ nameElement.textContent = ls(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');
+ }
+ this.listItemElement.appendChild(nameElement);
+ }
+
+ /**
+ * @param {!Protocol.Accessibility.AXValue} value
+ */
+ 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);
+ }
+ this.expand();
+ }
+ const element = Accessibility.AXNodePropertyTreeElement.createSimpleValueElement(value.type, String(value.value));
+ this.listItemElement.appendChild(element);
+ }
+
+ /**
+ * @param {!Protocol.Accessibility.AXRelatedNode} relatedNode
+ * @param {number} index
+ */
+ 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);
+ }
+
+ /**
+ * @param {!Protocol.Accessibility.AXRelatedNode} relatedNode
+ */
+ 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());
+ }
+
+ /**
+ * @param {!Protocol.Accessibility.AXValue} value
+ */
+ appendRelatedNodeListValueElement(value) {
+ if (value.relatedNodes.length === 1 && !value.value) {
+ this.appendRelatedNodeInline(value.relatedNodes[0]);
+ return;
+ }
+
+ value.relatedNodes.forEach(this.appendRelatedNode, this);
+ if (value.relatedNodes.length <= 3)
+ this.expand();
+ else
+ this.collapse();
+ }
+};
+
+
+/** @type {!Object<string, string>} */
+Accessibility.AXNodePropertyTreeElement.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'
+};
+
+/** @type {!Set.<!Protocol.Accessibility.AXValueType>} */
+Accessibility.AXNodePropertyTreeElement.StringProperties = new Set([
+ Protocol.Accessibility.AXValueType.String, Protocol.Accessibility.AXValueType.ComputedString,
+ Protocol.Accessibility.AXValueType.IdrefList, Protocol.Accessibility.AXValueType.Idref
+]);
+
+/**
+ * @unrestricted
+ */
+Accessibility.AXNodePropertyTreePropertyElement = class extends Accessibility.AXNodePropertyTreeElement {
+ /**
+ * @param {!Protocol.Accessibility.AXProperty} property
+ * @param {!Accessibility.AccessibilityNode} axNode
+ */
+ constructor(property, axNode) {
+ super(axNode);
+
+ this._property = property;
+ this.toggleOnClick = true;
+ this.selectable = false;
+
+ this.listItemElement.classList.add('property');
+ }
+
+ /**
+ * @override
+ */
+ onattach() {
+ this._update();
+ }
+
+ _update() {
+ this.listItemElement.removeChildren();
+
+ this.appendNameElement(this._property.name);
+
+ this.listItemElement.createChild('span', 'separator').textContent = ':\u00A0';
+
+ this.appendValueElement(this._property.value);
+ }
+};
+
+/**
+ * @unrestricted
+ */
+Accessibility.AXValueSourceTreeElement = class extends Accessibility.AXNodePropertyTreeElement {
+ /**
+ * @param {!Protocol.Accessibility.AXValueSource} source
+ * @param {!Accessibility.AccessibilityNode} axNode
+ */
+ constructor(source, axNode) {
+ super(axNode);
+ this._source = source;
+ this.selectable = false;
+ }
+
+ /**
+ * @override
+ */
+ onattach() {
+ this._update();
+ }
+
+ /**
+ * @param {!Protocol.Accessibility.AXRelatedNode} relatedNode
+ * @param {number} index
+ * @param {string} idref
+ */
+ 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);
+ }
+
+ /**
+ * @param {!Protocol.Accessibility.AXValue} value
+ */
+ 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 {
+ // TODO(aboxhall): exclamation mark if not idreflist type
+ 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}));
+ }
+ }
+ }
+
+ /**
+ * @param {!Protocol.Accessibility.AXValue} value
+ * @override
+ */
+ 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);
+
+
+ if (numNodes <= 3)
+ this.expand();
+ else
+ this.collapse();
+ }
+
+ /**
+ * @param {!Protocol.Accessibility.AXValueSource} source
+ */
+ 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 = ls(AXNativeSourceTypes[nativeSource].name);
+ nameElement.title = ls(AXNativeSourceTypes[nativeSource].description);
+ nameElement.classList.add('ax-readable-name');
+ break;
+ }
+ 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 = ls(AXSourceTypes[type].name);
+ nameElement.title = ls(AXSourceTypes[type].description);
+ nameElement.classList.add('ax-readable-name');
+ } else {
+ console.warn(type, 'not in AXSourceTypes');
+ nameElement.textContent = ls(type);
+ }
+ }
+ this.listItemElement.appendChild(nameElement);
+ }
+
+ _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');
+ }
+
+ this.appendSourceNameElement(this._source);
+
+ this.listItemElement.createChild('span', 'separator').textContent = ':\u00a0';
+
+ if (this._source.attributeValue) {
+ this.appendValueElement(this._source.attributeValue);
+ this.listItemElement.createTextChild('\u00a0');
+ } else if (this._source.nativeSourceValue) {
+ this.appendValueElement(this._source.nativeSourceValue);
+ this.listItemElement.createTextChild('\u00a0');
+ 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');
+ }
+
+ if (this._source.value && this._source.superseded)
+ this.listItemElement.classList.add('ax-value-source-superseded');
+ }
+};
+
+/**
+ * @unrestricted
+ */
+Accessibility.AXRelatedNodeSourceTreeElement = class extends UI.TreeElement {
+ /**
+ * @param {{deferredNode: (!SDK.DeferredDOMNode|undefined), idref: (string|undefined)}} node
+ * @param {!Protocol.Accessibility.AXRelatedNode=} value
+ */
+ constructor(node, value) {
+ super('');
+
+ this._value = value;
+ this._axRelatedNodeElement = new Accessibility.AXRelatedNodeElement(node, value);
+ this.selectable = false;
+ }
+
+ /**
+ * @override
+ */
+ onattach() {
+ this.listItemElement.appendChild(this._axRelatedNodeElement.render());
+ if (!this._value)
+ return;
+
+ if (this._value.text) {
+ this.listItemElement.appendChild(Accessibility.AXNodePropertyTreeElement.createSimpleValueElement(
+ Protocol.Accessibility.AXValueType.ComputedString, this._value.text));
+ }
+ }
+};
+
+/**
+ * @unrestricted
+ */
+Accessibility.AXRelatedNodeElement = class {
+ /**
+ * @param {{deferredNode: (!SDK.DeferredDOMNode|undefined), idref: (string|undefined)}} node
+ * @param {!Protocol.Accessibility.AXRelatedNode=} value
+ */
+ constructor(node, value) {
+ this._deferredNode = node.deferredNode;
+ this._idref = node.idref;
+ this._value = value;
+ }
+
+ /**
+ * @return {!Element}
+ */
+ render() {
+ const element = createElement('span');
+ let valueElement;
+
+ if (this._deferredNode) {
+ valueElement = createElement('span');
+ element.appendChild(valueElement);
+ Common.Linkifier.linkify(this._deferredNode).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);
+ }
+
+ return element;
+ }
+};
+
+/**
+ * @unrestricted
+ */
+Accessibility.AXNodeIgnoredReasonTreeElement = class extends Accessibility.AXNodePropertyTreeElement {
+ /**
+ * @param {!Protocol.Accessibility.AXProperty} property
+ * @param {!Accessibility.AccessibilityNode} axNode
+ */
+ constructor(property, axNode) {
+ super(axNode);
+ this._property = property;
+ this._axNode = axNode;
+ this.toggleOnClick = true;
+ this.selectable = false;
+ }
+
+ /**
+ * @param {?string} reason
+ * @param {?Accessibility.AccessibilityNode} axNode
+ * @return {?Element}
+ */
+ static createReasonElement(reason, axNode) {
+ let reasonElement = null;
+ switch (reason) {
+ case 'activeModalDialog':
+ reasonElement = UI.formatLocalized('Element is hidden by active modal dialog:\u00a0', []);
+ break;
+ case 'ancestorIsLeafNode':
+ reasonElement = UI.formatLocalized('Ancestor\'s children are all presentational:\u00a0', []);
+ break;
+ case 'ariaHiddenElement': {
+ const ariaHiddenSpan = createElement('span', 'source-code').textContent = 'aria-hidden';
+ reasonElement = UI.formatLocalized('Element is %s.', [ariaHiddenSpan]);
+ break;
+ }
+ 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:\u00a0', [ariaHiddenSpan, trueSpan]);
+ break;
+ }
+ 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\u00a0', []);
+ break;
+ case 'inheritsPresentation':
+ reasonElement = UI.formatLocalized('Element inherits presentational role from\u00a0', []);
+ break;
+ case 'labelContainer':
+ reasonElement = UI.formatLocalized('Part of label element:\u00a0', []);
+ break;
+ case 'labelFor':
+ reasonElement = UI.formatLocalized('Label for\u00a0', []);
+ 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;
+ }
+ case 'probablyPresentational':
+ reasonElement = UI.formatLocalized('Element is presentational.', []);
+ break;
+ case 'staticTextUsedAsNameFor':
+ reasonElement = UI.formatLocalized('Static text node is used as name for\u00a0', []);
+ break;
+ case 'uninteresting':
+ reasonElement = UI.formatLocalized('Element not interesting for accessibility.', []);
+ break;
+ }
+ if (reasonElement)
+ reasonElement.classList.add('ax-reason');
+ return reasonElement;
+ }
+
+ /**
+ * @override
+ */
+ 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);
+ }
+};
diff --git a/front_end/accessibility/AccessibilitySidebarView.js b/front_end/accessibility/AccessibilitySidebarView.js
new file mode 100644
index 0000000..21da831
--- /dev/null
+++ b/front_end/accessibility/AccessibilitySidebarView.js
@@ -0,0 +1,214 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+/**
+ * @unrestricted
+ */
+Accessibility.AccessibilitySidebarView = class extends UI.ThrottledWidget {
+ constructor() {
+ super();
+ this._node = null;
+ this._axNode = null;
+ this._skipNextPullNode = false;
+ this._sidebarPaneStack = UI.viewManager.createStackLocation();
+ this._breadcrumbsSubPane = new Accessibility.AXBreadcrumbsPane(this);
+ this._sidebarPaneStack.showView(this._breadcrumbsSubPane);
+ this._ariaSubPane = new Accessibility.ARIAAttributesPane();
+ this._sidebarPaneStack.showView(this._ariaSubPane);
+ this._axNodeSubPane = new Accessibility.AXNodeSubPane();
+ this._sidebarPaneStack.showView(this._axNodeSubPane);
+ this._sidebarPaneStack.widget().show(this.element);
+ UI.context.addFlavorChangeListener(SDK.DOMNode, this._pullNode, this);
+ this._pullNode();
+ }
+
+ /**
+ * @return {?SDK.DOMNode}
+ */
+ node() {
+ return this._node;
+ }
+
+ /**
+ * @return {?Accessibility.AccessibilityNode}
+ */
+ axNode() {
+ return this._axNode;
+ }
+
+ /**
+ * @param {?SDK.DOMNode} node
+ * @param {boolean=} fromAXTree
+ */
+ setNode(node, fromAXTree) {
+ this._skipNextPullNode = !!fromAXTree;
+ this._node = node;
+ this.update();
+ }
+
+ /**
+ * @param {?Accessibility.AccessibilityNode} axNode
+ */
+ accessibilityNodeCallback(axNode) {
+ if (!axNode)
+ return;
+
+ this._axNode = axNode;
+
+ if (axNode.isDOMNode())
+ this._sidebarPaneStack.showView(this._ariaSubPane, this._axNodeSubPane);
+ else
+ this._sidebarPaneStack.removeView(this._ariaSubPane);
+
+ if (this._axNodeSubPane)
+ this._axNodeSubPane.setAXNode(axNode);
+ if (this._breadcrumbsSubPane)
+ this._breadcrumbsSubPane.setAXNode(axNode);
+ }
+
+ /**
+ * @override
+ * @protected
+ * @return {!Promise.<?>}
+ */
+ doUpdate() {
+ const node = this.node();
+ this._axNodeSubPane.setNode(node);
+ this._ariaSubPane.setNode(node);
+ this._breadcrumbsSubPane.setNode(node);
+ if (!node)
+ return Promise.resolve();
+ const accessibilityModel = node.domModel().target().model(Accessibility.AccessibilityModel);
+ accessibilityModel.clear();
+ return accessibilityModel.requestPartialAXTree(node).then(() => {
+ this.accessibilityNodeCallback(accessibilityModel.axNodeForDOMNode(node));
+ });
+ }
+
+ /**
+ * @override
+ */
+ wasShown() {
+ super.wasShown();
+
+ this._breadcrumbsSubPane.setNode(this.node());
+ this._breadcrumbsSubPane.setAXNode(this.axNode());
+ this._axNodeSubPane.setNode(this.node());
+ this._axNodeSubPane.setAXNode(this.axNode());
+ this._ariaSubPane.setNode(this.node());
+
+ SDK.targetManager.addModelListener(SDK.DOMModel, SDK.DOMModel.Events.AttrModified, this._onAttrChange, this);
+ SDK.targetManager.addModelListener(SDK.DOMModel, SDK.DOMModel.Events.AttrRemoved, this._onAttrChange, this);
+ SDK.targetManager.addModelListener(
+ SDK.DOMModel, SDK.DOMModel.Events.CharacterDataModified, this._onNodeChange, this);
+ SDK.targetManager.addModelListener(
+ SDK.DOMModel, SDK.DOMModel.Events.ChildNodeCountUpdated, this._onNodeChange, this);
+ }
+
+ /**
+ * @override
+ */
+ willHide() {
+ SDK.targetManager.removeModelListener(SDK.DOMModel, SDK.DOMModel.Events.AttrModified, this._onAttrChange, this);
+ SDK.targetManager.removeModelListener(SDK.DOMModel, SDK.DOMModel.Events.AttrRemoved, this._onAttrChange, this);
+ SDK.targetManager.removeModelListener(
+ SDK.DOMModel, SDK.DOMModel.Events.CharacterDataModified, this._onNodeChange, this);
+ SDK.targetManager.removeModelListener(
+ SDK.DOMModel, SDK.DOMModel.Events.ChildNodeCountUpdated, this._onNodeChange, this);
+ }
+
+ _pullNode() {
+ if (this._skipNextPullNode) {
+ this._skipNextPullNode = false;
+ return;
+ }
+ this.setNode(UI.context.flavor(SDK.DOMNode));
+ }
+
+ /**
+ * @param {!Common.Event} event
+ */
+ _onAttrChange(event) {
+ if (!this.node())
+ return;
+ const node = event.data.node;
+ if (this.node() !== node)
+ return;
+ this.update();
+ }
+
+ /**
+ * @param {!Common.Event} event
+ */
+ _onNodeChange(event) {
+ if (!this.node())
+ return;
+ const node = event.data;
+ if (this.node() !== node)
+ return;
+ this.update();
+ }
+};
+
+/**
+ * @unrestricted
+ */
+Accessibility.AccessibilitySubPane = class extends UI.SimpleView {
+ /**
+ * @param {string} name
+ */
+ constructor(name) {
+ super(name);
+
+ this._axNode = null;
+ this.registerRequiredCSS('accessibility/accessibilityProperties.css');
+ }
+
+ /**
+ * @param {?Accessibility.AccessibilityNode} axNode
+ * @protected
+ */
+ setAXNode(axNode) {
+ }
+
+ /**
+ * @return {?SDK.DOMNode}
+ */
+ node() {
+ return this._node;
+ }
+
+ /**
+ * @param {?SDK.DOMNode} node
+ */
+ setNode(node) {
+ this._node = node;
+ }
+
+ /**
+ * @param {string} textContent
+ * @param {string=} className
+ * @return {!Element}
+ */
+ createInfo(textContent, className) {
+ const classNameOrDefault = className || 'gray-info-message';
+ const info = this.element.createChild('div', classNameOrDefault);
+ info.textContent = textContent;
+ return info;
+ }
+
+ /**
+ * @return {!UI.TreeOutline}
+ */
+ createTreeOutline() {
+ const treeOutline = new UI.TreeOutlineInShadow();
+ treeOutline.registerRequiredCSS('accessibility/accessibilityNode.css');
+ treeOutline.registerRequiredCSS('accessibility/accessibilityProperties.css');
+ treeOutline.registerRequiredCSS('object_ui/objectValue.css');
+
+ treeOutline.element.classList.add('hidden');
+ treeOutline.hideOverflow();
+ this.element.appendChild(treeOutline.element);
+ return treeOutline;
+ }
+};
diff --git a/front_end/accessibility/AccessibilityStrings.js b/front_end/accessibility/AccessibilityStrings.js
new file mode 100644
index 0000000..50d31b2
--- /dev/null
+++ b/front_end/accessibility/AccessibilityStrings.js
@@ -0,0 +1,186 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+Accessibility.AccessibilityStrings = {};
+
+Accessibility.AccessibilityStrings.AXAttributes = {
+ 'disabled': {
+ name: 'Disabled',
+ description: 'If true, this element currently cannot be interacted with.',
+ group: 'AXGlobalStates'
+ },
+ 'invalid': {
+ name: 'Invalid user entry',
+ description: 'If true, this element\'s user-entered value does not conform to validation requirement.',
+ group: 'AXGlobalStates'
+ },
+ 'live': {
+ name: 'Live region',
+ description: 'Whether and what priority of live updates may be expected for this element.',
+ group: 'AXLiveRegionAttributes'
+ },
+ 'atomic': {
+ name: 'Atomic (live regions)',
+ description:
+ 'If this element may receive live updates, whether the entire live region should be presented to the user on changes, or only changed nodes.',
+ group: 'AXLiveRegionAttributes'
+ },
+ 'relevant': {
+ name: 'Relevant (live regions)',
+ description: 'If this element may receive live updates, what type of updates should trigger a notification.',
+ group: 'AXLiveRegionAttributes'
+ },
+ 'busy': {
+ name: 'Busy (live regions)',
+ description:
+ 'Whether this element or its subtree are currently being updated (and thus may be in an inconsistent state).',
+ group: 'AXLiveRegionAttributes'
+ },
+ 'root': {
+ name: 'Live region root',
+ description: 'If this element may receive live updates, the root element of the containing live region.',
+ group: 'AXLiveRegionAttributes'
+ },
+ 'autocomplete': {
+ name: 'Has autocomplete',
+ description: 'Whether and what type of autocomplete suggestions are currently provided by this element.',
+ group: 'AXWidgetAttributes'
+ },
+ 'haspopup': {
+ name: 'Has popup',
+ description: 'Whether this element has caused some kind of pop-up (such as a menu) to appear.',
+ group: 'AXWidgetAttributes'
+ },
+ 'level': {name: 'Level', description: 'The hierarchical level of this element.', group: 'AXWidgetAttributes'},
+ 'multiselectable': {
+ name: 'Multi-selectable',
+ description: 'Whether a user may select more than one option from this widget.',
+ group: 'AXWidgetAttributes'
+ },
+ 'orientation': {
+ name: 'Orientation',
+ description: 'Whether this linear element\'s orientation is horizontal or vertical.',
+ group: 'AXWidgetAttributes'
+ },
+ 'multiline': {
+ name: 'Multi-line',
+ description: 'Whether this textbox may have more than one line.',
+ group: 'AXWidgetAttributes'
+ },
+ 'readonly': {
+ name: 'Read-only',
+ description: 'If true, this element may be interacted with, but its value cannot be changed.',
+ group: 'AXWidgetAttributes'
+ },
+ 'required': {
+ name: 'Required',
+ description: 'Whether this element is a required field in a form.',
+ group: 'AXWidgetAttributes'
+ },
+ 'valuemin': {
+ name: 'Minimum value',
+ description: 'For a range widget, the minimum allowed value.',
+ group: 'AXWidgetAttributes'
+ },
+ 'valuemax': {
+ name: 'Maximum value',
+ description: 'For a range widget, the maximum allowed value.',
+ group: 'AXWidgetAttributes'
+ },
+ 'valuetext': {
+ name: 'Value description',
+ description: 'A human-readable version of the value of a range widget (where necessary).',
+ group: 'AXWidgetAttributes'
+ },
+ 'checked': {
+ name: 'Checked',
+ description:
+ 'Whether this checkbox, radio button or tree item is checked, unchecked, or mixed (e.g. has both checked and un-checked children).',
+ group: 'AXWidgetStates'
+ },
+ 'expanded': {
+ name: 'Expanded',
+ description: 'Whether this element, or another grouping element it controls, is expanded.',
+ group: 'AXWidgetStates'
+ },
+ 'pressed': {
+ name: 'Pressed',
+ description: 'Whether this toggle button is currently in a pressed state.',
+ group: 'AXWidgetStates'
+ },
+ 'selected': {
+ name: 'Selected',
+ description: 'Whether the option represented by this element is currently selected.',
+ group: 'AXWidgetStates'
+ },
+ 'activedescendant': {
+ name: 'Active descendant',
+ description: 'The descendant of this element which is active; i.e. the element to which focus should be delegated.',
+ group: 'AXRelationshipAttributes'
+ },
+ 'flowto': {
+ name: 'Flows to',
+ description:
+ 'Element to which the user may choose to navigate after this one, instead of the next element in the DOM order.',
+ group: 'AXRelationshipAttributes'
+ },
+ 'controls': {
+ name: 'Controls',
+ description: 'Element or elements whose content or presence is/are controlled by this widget.',
+ group: 'AXRelationshipAttributes'
+ },
+ 'describedby': {
+ name: 'Described by',
+ description: 'Element or elements which form the description of this element.',
+ group: 'AXRelationshipAttributes'
+ },
+ 'labelledby': {
+ name: 'Labeled by',
+ description: 'Element or elements which may form the name of this element.',
+ group: 'AXRelationshipAttributes'
+ },
+ 'owns': {
+ name: 'Owns',
+ description:
+ 'Element or elements which should be considered descendants of this element, despite not being descendants in the DOM.',
+ group: 'AXRelationshipAttributes'
+ },
+ 'name': {name: 'Name', description: 'The computed name of this element.', group: 'Default'},
+ 'role': {
+ name: 'Role',
+ description:
+ 'Indicates the purpose of this element, such as a user interface idiom for a widget, or structural role within a document.',
+ group: 'Default'
+ },
+ 'value': {
+ name: 'Value',
+ description:
+ 'The value of this element; this may be user-provided or developer-provided, depending on the element.',
+ group: 'Default'
+ },
+ 'help': {name: 'Help', description: 'The computed help text for this element.', group: 'Default'},
+ 'description': {name: 'Description', description: 'The accessible description for this element.', group: 'Default'}
+};
+
+Accessibility.AccessibilityStrings.AXSourceTypes = {
+ 'attribute': {name: 'From attribute', description: 'Value from attribute.'},
+ 'implicit': {
+ name: 'Implicit',
+ description: 'Implicit value.',
+ },
+ 'style': {name: 'From style', description: 'Value from style.'},
+ 'contents': {name: 'Contents', description: 'Value from element contents.'},
+ 'placeholder': {name: 'From placeholder attribute', description: 'Value from placeholder attribute.'},
+ 'relatedElement': {name: 'Related element', description: 'Value from related element.'}
+};
+
+Accessibility.AccessibilityStrings.AXNativeSourceTypes = {
+ 'figcaption': {name: 'From caption', description: 'Value from figcaption element.'},
+ 'label': {name: 'From label', description: 'Value from label element.'},
+ 'labelfor': {name: 'From label (for)', description: 'Value from label element with for= attribute.'},
+ 'labelwrapped': {name: 'From label (wrapped)', description: 'Value from label element wrapped.'},
+ 'tablecaption': {name: 'From caption', description: 'Value from table caption.'},
+ 'title': {'name': 'From title', 'description': 'Value from title attribute.'},
+ 'other': {name: 'From native HTML', description: 'Value from native HTML (unknown source).'},
+
+};
diff --git a/front_end/accessibility/accessibilityNode.css b/front_end/accessibility/accessibilityNode.css
new file mode 100644
index 0000000..a6e59ca
--- /dev/null
+++ b/front_end/accessibility/accessibilityNode.css
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+.sidebar-pane.accessibility-computed {
+ background-color: rgba(0, 0, 0, 0.03);
+}
+
+.widget.ax-subpane {
+ overflow-x: hidden;
+ -webkit-user-select: text;
+}
+
+div.ax-text-alternatives {
+ margin-bottom: 3px;
+ border-bottom: 1px solid #BFBFBF;
+}
+
+.ax-ignored-info {
+ padding: 6px;
+}
+
+.ax-ignored-node-pane {
+ flex: none;
+}
+
+.invalid {
+ text-decoration: line-through;
+}
+
+span.ax-value-undefined {
+ font-style: italic;
+}
+
+.ax-value-source-unused {
+ opacity: 0.7;
+}
+
+.ax-value-source-superseded,
+.ax-value-source-invalid {
+ text-decoration: line-through;
+}
+
+.sidebar-pane-stack .sidebar-pane {
+ padding-left: 4px;
+}
+
+.tree-outline label[is=dt-icon-label] {
+ position: relative;
+ left: -11px;
+}
+
+.tree-outline li {
+ display: block;
+ overflow-x: hidden;
+ padding-left: 1px;
+ align-items: baseline;
+}
+
+.tree-outline li::before {
+ content: "";
+ width: 14px;
+ display: inline-block;
+}
+
+.tree-outline li.property {
+ color: rgb(33, 33, 33);
+}
+
+.tree-outline li.invalid {
+ position: relative;
+ left: -2px;
+}
+
+.tree-outline label[is=dt-icon-label] + .ax-name {
+ margin-left: -11px;
+}
+
+.tree-outline li span {
+ flex-shrink: 0;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
diff --git a/front_end/accessibility/accessibilityProperties.css b/front_end/accessibility/accessibilityProperties.css
new file mode 100644
index 0000000..c193898
--- /dev/null
+++ b/front_end/accessibility/accessibilityProperties.css
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2015 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+.ax-name {
+ color: rgb(153, 69, 0);
+ flex-shrink: 0;
+}
+
+.ax-readable-name {
+ flex-shrink: 0;
+ padding-left: 2px;
+}
+
+.ax-readable-string {
+ font-style: italic;
+}
+
+.ax-value-string {
+ color: rgb(200, 0, 0);
+}
+
+span.ax-internal-role {
+ font-style: italic;
+}
diff --git a/front_end/accessibility/axBreadcrumbs.css b/front_end/accessibility/axBreadcrumbs.css
new file mode 100644
index 0000000..70c8f49
--- /dev/null
+++ b/front_end/accessibility/axBreadcrumbs.css
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2017 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+.ax-breadcrumbs-ignored-node {
+ font-style: italic;
+ opacity: 0.7;
+}
+
+.ax-breadcrumbs {
+ padding-top: 1px;
+ margin: 0;
+ position: relative;
+}
+
+.ax-breadcrumbs .ax-node {
+ align-items: center;
+ margin-top: 1px;
+ min-height: 16px;
+ overflow-x: hidden;
+ padding-left: 4px;
+ padding-right: 4px;
+ padding-top: 1px;
+ position: relative;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.ax-breadcrumbs .ax-node span {
+ flex-shrink: 0;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.ax-breadcrumbs .ax-node .wrapper {
+ padding-left: 12px;
+ overflow-x: hidden;
+}
+
+.ax-breadcrumbs .ax-node::before {
+ -webkit-mask-image: url(Images/chevrons.png);
+ -webkit-mask-position: 0 0;
+ -webkit-mask-size: 30px 10px;
+ -webkit-mask-repeat: no-repeat;
+ background-color: rgb(48, 57, 66);
+ content: "";
+ text-shadow: none;
+ margin-right: -2px;
+ height: 12px;
+ width: 14px;
+ position: absolute;
+ display: inline-block;
+}
+
+@media (-webkit-min-device-pixel-ratio: 1.1) {
+ .ax-breadcrumbs .ax-node::before {
+ -webkit-mask-image: url(Images/chevrons_2x.png);
+ }
+} /* media */
+
+.ax-breadcrumbs .ax-node:not(.parent):not(.children-unloaded)::before {
+ background-color: transparent;
+}
+
+.ax-breadcrumbs .ax-node.parent::before {
+ -webkit-mask-position: -20px 1px;
+}
+
+.ax-breadcrumbs .ax-node.children-unloaded::before {
+ -webkit-mask-position: 0px 1px;
+ width: 13px;
+}
+
+.ax-breadcrumbs .ax-node.no-dom-node {
+ opacity: 0.7;
+}
+
+.ax-breadcrumbs .ax-node.children-unloaded::before {
+ opacity: 0.4;
+}
+
+.ax-breadcrumbs .ax-node.preselected:not(.inspected) .selection,
+.ax-breadcrumbs .ax-node.hovered:not(.inspected) .selection {
+ display: block;
+ left: 2px;
+ right: 2px;
+ background-color: rgba(56, 121, 217, 0.1);
+ border-radius: 5px;
+}
+
+.ax-breadcrumbs .ax-node.preselected:not(.inspected):focus .selection {
+ border: 1px solid rgba(56, 121, 217, 0.4);
+}
+
+.ax-breadcrumbs .ax-node .selection {
+ display: none;
+ z-index: -1;
+}
+
+.ax-breadcrumbs .ax-node.inspected .selection {
+ display: block;
+ background-color: #ddd;
+}
+
+.ax-breadcrumbs .ax-node.inspected:focus .selection {
+ background-color: var(--selection-bg-color);
+}
+
+.ax-breadcrumbs .ax-node.parent.inspected:focus::before {
+ background-color: white;
+}
+
+.ax-breadcrumbs .ax-node.inspected:focus {
+ color: white;
+}
+
+.ax-breadcrumbs .ax-node.inspected:focus * {
+ color: inherit;
+}
diff --git a/front_end/accessibility/module.json b/front_end/accessibility/module.json
new file mode 100644
index 0000000..f0e42b3
--- /dev/null
+++ b/front_end/accessibility/module.json
@@ -0,0 +1,32 @@
+{
+ "extensions": [
+ {
+ "type": "view",
+ "location": "elements-sidebar",
+ "id": "accessibility.view",
+ "title": "Accessibility",
+ "order": 10,
+ "persistence": "permanent",
+ "className": "Accessibility.AccessibilitySidebarView"
+ }
+ ],
+ "dependencies": ["elements"],
+ "scripts": [
+ "AccessibilityModel.js",
+ "AccessibilitySidebarView.js",
+ "AccessibilityNodeView.js",
+ "AccessibilityStrings.js",
+ "ARIAAttributesView.js",
+ "ARIAMetadata.js",
+ "ARIAConfig.js",
+ "AXBreadcrumbsPane.js"
+ ],
+ "skip_compilation": [
+ "ARIAConfig.js"
+ ],
+ "resources": [
+ "accessibilityNode.css",
+ "accessibilityProperties.css",
+ "axBreadcrumbs.css"
+ ]
+}