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"
+    ]
+}