Skip to content

Commit

Permalink
Merge pull request #317 from dequelabs/shadowDOM
Browse files Browse the repository at this point in the history
Shadow DOM implementation
  • Loading branch information
marcysutton authored Aug 8, 2017
2 parents ebb2a5d + 8dcfb07 commit 1fb2278
Show file tree
Hide file tree
Showing 146 changed files with 5,494 additions and 1,646 deletions.
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ That having been said, we prefer:
5. Use of 'use strict'
6. Variables declared at the top of functions

### Shadow DOM

For any proposed changes to rules, checks, commons, or other APIs to be accepted
in axe-core,your code must support Shadow DOM. See [API.md](./doc/API.md) and the
[developer guide](./doc/developer-guide.md) for documentation on the available methods
and test utilities.

### Testing

We expect all code to be 100% covered by tests. We don't have or want code coverage metrics but we will review tests and suggest changes when we think the test(s) do(es) not adequately exercise the code/code changes.
Expand Down
12 changes: 6 additions & 6 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ module.exports = function (grunt) {
]
}
},
retire: {
retire: {
options: {
/** list of files to ignore **/
ignorefile: '.retireignore.json' //or '.retireignore.json'
},
js: ['lib/*.js'], /** Which js-files to scan. **/
node: ['./'] /** Which node directories to scan (containing package.json). **/
},
},
clean: ['dist', 'tmp', 'axe.js', 'axe.*.js'],
babel: {
options: {
Expand Down Expand Up @@ -94,15 +94,15 @@ module.exports = function (grunt) {
},
concat: {
engine: {
options: {
process: true
},
coreFiles: [
'tmp/core/index.js',
'tmp/core/*/index.js',
'tmp/core/**/index.js',
'tmp/core/**/*.js'
],
options: {
process: true
},
files: langs.map(function (lang, i) {
return {
src: [
Expand All @@ -112,7 +112,7 @@ module.exports = function (grunt) {
'<%= configure.rules.files[' + i + '].dest.auto %>',
'lib/outro.stub'
],
dest: 'axe' + lang + '.js',
dest: 'axe' + lang + '.js'
};
})
},
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,9 @@ Read the [documentation on contributing](CONTRIBUTING.md)
## Projects using axe-core

[List of projects using axe-core](doc/projects.md)

## Acknowledgements

Thanks to Dulin Marat for his [css-selector-parser](https://www.npmjs.com/package/css-selector-parser) implementation which is included for shadow DOM support.

Thanks to the [Slick Parser](https://github.com/mootools/slick/blob/master/Source/Slick.Parser.js) implementers for their contribution, we have used some of their algorithms in our shadow DOM support code.
4 changes: 2 additions & 2 deletions build/templates.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module.exports = {
evaluate: 'function (node, options) {\n<%=source%>\n}',
evaluate: 'function (node, options, virtualNode) {\n<%=source%>\n}',
after: 'function (results, options) {\n<%=source%>\n}',
gather: 'function (context) {\n<%=source%>\n}',
matches: 'function (node) {\n<%=source%>\n}',
matches: 'function (node, virtualNode) {\n<%=source%>\n}',
source: '(function () {\n<%=source%>\n}())',
commons: '<%=source%>'
};
138 changes: 129 additions & 9 deletions doc/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
1. [API Name: axe.registerPlugin](#api-name-axeregisterplugin)
1. [API Name: axe.cleanup](#api-name-axecleanup)
1. [API Name: axe.a11yCheck](#api-name-axea11ycheck)
1. [Section 3: Example Reference](#section-3-example-reference)
1. [Virtual DOM Utilities](#virtual-dom-utilities)
1. [API Name: axe.utils.querySelectorAll](#api-name-axeutilsqueryselectorall)
1. [Common Functions](#common-functions)
1. [Section 3: Example Reference](#section-5-example-reference)

## Section 1: Introduction

Expand Down Expand Up @@ -450,7 +453,15 @@ This will either be null or an object which is an instance of Error. If you are
#### Results Object
The callback function passed in as the third parameter of `axe.a11yCheck` runs on the results object. This object has two components – a passes array and a violations array. The passes array keeps track of all the passed tests, along with detailed information on each one. This leads to more efficient testing, especially when used in conjunction with manual testing, as the user can easily find out what tests have already been passed. Similarly, the violations array keeps track of all the failed tests, along with detailed information on each one.
The callback function passed in as the third parameter of `axe.run` runs on the results object. This object has four components – a `passes` array, a `violations` array, an `incomplete` array and an `inapplicable` array.
The `passes` array keeps track of all the passed tests, along with detailed information on each one. This leads to more efficient testing, especially when used in conjunction with manual testing, as the user can easily find out what tests have already been passed.
Similarly, the `violations` array keeps track of all the failed tests, along with detailed information on each one.
The `incomplete` array (also referred to as the "review items") indicates which nodes could neither be determined to definitively pass or definitively fail. They are separated out in order that a user interface can display these to the user for manual review (hence the term "review items").
The `inapplicable` array lists all the rules for which no matching elements were found on the page.
###### `url`
Expand All @@ -475,11 +486,11 @@ Each object returned in these arrays have the following properties:
* `helpUrl` - URL that provides more information about the specifics of the violation. Links to a page on the Deque University site.
* `id` - Unique identifier for the rule; [see the list of rules](rule-descriptions.md)
* `impact` - How serious the violation is. Can be one of "minor", "moderate", "serious", or "critical" if the Rule failed or `null` if the check passed
* `tags` - Array of tags that this rule is assigned. These tags can be used in the option structure to select which rules are run ([see `axe.a11yCheck` parameters below for more information](#a11ycheck-parameters)).
* `tags` - Array of tags that this rule is assigned. These tags can be used in the option structure to select which rules are run ([see `axe.run` parameters for more information](#parameters-axerun)).
* `nodes` - Array of all elements the Rule tested
* `html` - Snippet of HTML of the Element
* `impact` - How serious the violation is. Can be one of "minor", "moderate", "serious", or "critical" if the test failed or `null` if the check passed
* `target` - Array of selectors that has each element correspond to one level of iframe or frame. If there is one iframe or frame, there should be two entries in `target`. If there are three iframe levels, there should be four entries in `target`.
* `target` - Array of either strings or Arrays of strings. If the item in the array is a string, then it is a CSS selector. If there are multiple items in the array each item corresponds to one level of iframe or frame. If there is one iframe or frame, there should be two entries in `target`. If there are three iframe levels, there should be four entries in `target`. If the item in the Array is an Array of strings, then it points to an element in a shadow DOM and each item (except the n-1th) in this array is a selector to a DOM element with a shadow DOM. The last element in the array points to the final shadow DOM node.
* `any` - Array of checks that were made where at least one must have passed. Each entry in the array contains:
* `id` - Unique identifier for this check. Check ids may be the same as Rule ids
* `impact` - How serious this particular check is. Can be one of "minor", "moderate", "serious", or "critical". Each check that is part of a rule can have different impacts. The highest impact of all the checks that fail is reported for the rule
Expand Down Expand Up @@ -509,8 +520,8 @@ axe.run(document, function(err, results) {
* `help` - `"Elements must have sufficient color contrast"`
* `helpUrl` - `"https://dequeuniversity.com/courses/html-css/visual-layout/color-contrast"`
* `id` - `"color-contrast"`
* `nodes`
* `target[0]` - `"#js_off-canvas-wrap > .inner-wrap >.kinja-title.proxima.js_kinja-title-desktop"`
* `nodes`
* `target[0]` - `"#js_off-canvas-wrap > .inner-wrap >.kinja-title.proxima.js_kinja-title-desktop"`

* `passes[1]`
...
Expand All @@ -521,9 +532,9 @@ axe.run(document, function(err, results) {
* `help` - `"<button> elements must have alternate text"`
* `helpUrl` - `"https://dequeuniversity.com/courses/html-css/forms/form-labels#id84_example_button"`
* `id` - `"button-name"`
* `nodes`
* `target[0]` - `"post_5919997 > .row.content-wrapper > .column > span > iframe"`
* `target[1]` - `"#u_0_1 > .pluginConnectButton > .pluginButtonImage > button"`
* `nodes`
* `target[0]` - `"post_5919997 > .row.content-wrapper > .column > span > iframe"`
* `target[1]` - `"#u_0_1 > .pluginConnectButton > .pluginButtonImage > button"`

* `violations[1]` ...

Expand Down Expand Up @@ -562,6 +573,28 @@ axe.run(document, {
console.log(results);
});
```

#### Example 4

This example shows a result object that points to a shadow DOM element.

##### `violations[0]`
```
{
help : "Elements must have sufficient color contrast",
helpUrl: "https://dequeuniversity.com/rules/axe/2.1/color-contrast?application=axeAPI",
id: "color-contrast",
nodes: [{
target: [[
"header > aria-menu",
"li.expanded"
]]
}]
}
```

As you can see the `target` array contains one item that is an array. This array contains two items, the first is a CSS selector string that finds the custom element `<aria-menu>` in the `<header>`. The second item in this array is the selector within that custom element's shadow DOM to find the `<li>` element with a class of `expanded`.

### API Name: axe.registerPlugin

Register a plugin with the aXe plugin system. See [implementing a plugin](plugins.md) for more information on the plugin system
Expand All @@ -578,6 +611,93 @@ In axe-core v1 the main method for axe was `axe.a11yCheck()`. This method was re
- .a11yCheck requires a context object, and so will not fall back to the document root.
- .a11yCheck does not return a Promise.

### Virtual DOM Utilities

Note: If you’re writing rules or checks, you’ll have both the `node` and `virtualNode` passed in.
But if you need to query the flattened tree, the documented function below should help. See the
[developer guide](./developer-guide.md) for more information.

#### API Name: axe.utils.querySelectorAll

##### Description

A querySelectorAll implementation that works on the virtual DOM and Shadow DOM by manually walking the flattened tree instead of relying on DOM API methods which don’t step into Shadow DOM.

Note: while there is no `axe.utils.querySelector` method, you can reproduce that behavior by accessing the first item returned in the array.

##### Synopsis

```javascript
axe.utils.querySelectorAll(virtualNode, 'a[href]');
```

##### Parameters

* `virtualNode` – object, the flattened DOM tree to query against. `axe._tree` is available for this purpose during an audit; see below.
* `selector` – string, the CSS selector to use as a filter. For the most part, this should work seamlessly with `document.querySelectorAll`.

##### Returns

An Array of filtered HTML nodes.


### Common Functions

#### axe.commons.dom.getComposedParent

Get an element's parent in the flattened tree

##### Synopsis

```javascript
axe.commons.dom.getComposedParent(node)
```

##### Parameters
* `element` – HTMLElement. The element for which you want to find a parent

##### Returns

A DOMNode for the parent


#### axe.commons.dom.getRootNode

Return the document or document fragment (shadow DOM)

##### Synopsis

```javascript
axe.commons.dom.getRootNode(node)
```

##### Parameters
* `element` – HTMLElement. The element for which you want to find the root node

##### Returns

The top-level document or shadow DOM document fragment


#### axe.commons.dom.findUp

Recusively walk up the DOM, checking for a node which matches a selector. Warning: this should be used sparingly for performance reasons.

##### Synopsis

```javascript
axe.commons.dom.findUp(node, '.selector')
```

##### Parameters
* `element` – HTMLElement. The starting element
* `selector` – String. The target selector for the HTMLElement

##### Returns

Either the matching HTMLElement or `null` if there was no match.


## Section 3: Example Reference

This package contains examples for [jasmine](examples/jasmine), [mocha](examples/mocha), [phantomjs](examples/phantomjs), [qunit](examples/qunit), [selenium using javascript](examples/selenium), and [generating HTML from the violations array](examples/html-handlebars.md). Each of these examples is in the [doc/examples](examples) folder. In each folder, there is a README.md file which contains specific information about each example.
Loading

0 comments on commit 1fb2278

Please sign in to comment.