Skip to content

Commit

Permalink
Merge branch 'develop' into aaa-tag
Browse files Browse the repository at this point in the history
  • Loading branch information
WilcoFiers committed Oct 11, 2021
2 parents d5a0491 + cad3994 commit 5e208c9
Show file tree
Hide file tree
Showing 28 changed files with 613 additions and 109 deletions.
21 changes: 21 additions & 0 deletions doc/issue_impact.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Issue Impacts

Axe-core assigns an impact according to our assessment of the likely impact of an issue on a user with a disability that would be affected by this issue. In any given context the actual impact for the user could be lower; in some instances, it could be higher. For this reason, we encourage users of tools to evaluate each individual issue and assess the impact in the context of their application or content.

## Definitions

### Minor

Considered to be a nuisance or an annoyance bug. Prioritize fixing if the fix only takes a few minutes and the developer is working on the same screen/feature at the same time, otherwise the issue should not be prioritized. Will still get in the way of compliance if not fixed.

### Moderate

Results in some difficulty for people with disabilities, but will generally not prevent them from accessing fundamental features or content. Users may be frustrated and abandon non-critical workflows. Prioritize fixing in this release, if there are no higher-priority issues. Will get in the way of compliance if not fixed.

### Serious

Results in serious barriers for people with disabilities, and will partially or fully prevent them from accessing fundamental features or content. People relying on assistive technologies will experience significant frustration and may abandon essential workflows. Issues falling under this category are major problems, and remediation should be a priority.

### Critical

Results in blocked content for people with disabilities, and will definitely prevent them from accessing fundamental features or content. This type of issue puts your organization at risk. Prioritize fixing as soon as possible, within the week if possible. Remediation should be a top priority.
62 changes: 31 additions & 31 deletions doc/rule-descriptions.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions lib/checks/color/color-contrast-evaluate.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
getContrast,
getOwnBackgroundColor,
getTextShadowColors,
flattenColors
flattenShadowColors
} from '../../commons/color';
import { memoize } from '../../core/utils';

Expand Down Expand Up @@ -73,7 +73,7 @@ export default function colorContrastEvaluate(node, options, virtualNode) {
} else if (fgColor && bgColor) {
// Thin shadows can pass either by contrasting with the text color
// or when contrasting with the background.
shadowColor = [...shadowColors, bgColor].reduce(flattenColors);
shadowColor = [...shadowColors, bgColor].reduce(flattenShadowColors);
const bgContrast = getContrast(bgColor, shadowColor);
const fgContrast = getContrast(shadowColor, fgColor);
contrast = Math.max(bgContrast, fgContrast);
Expand Down
2 changes: 1 addition & 1 deletion lib/commons/aria/arialabelledby-text.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { getNodeFromTree } from '../../core/utils';
* @property {Bool} inControlContext Whether or not the lookup is part of a native label reference
* @property {Element} startNode First node in accessible name computation
* @property {Bool} debug Enable logging for formControlValue
* @return {string} Cancatinated text value for referenced elements
* @return {string} Concatenated text value for referenced elements
*/
function arialabelledbyText(vNode, context = {}) {
if (!(vNode instanceof AbstractVirtualNode)) {
Expand Down
2 changes: 1 addition & 1 deletion lib/commons/aria/label-virtual.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function labelVirtual(virtualNode) {
candidate = ref
.map(thing => {
const vNode = getNodeFromTree(thing);
return vNode ? visibleVirtual(vNode, true) : '';
return vNode ? visibleVirtual(vNode) : '';
})
.join(' ')
.trim();
Expand Down
64 changes: 58 additions & 6 deletions lib/commons/color/flatten-colors.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
import Color from './color';

// how to combine background and foreground colors together when using
// the CSS property `mix-blend-mode`. Defaults to `normal`
// @see https://www.w3.org/TR/compositing-1/#blendingseparable
const blendFunctions = {
normal(Cb, Cs) {
return Cs;
}
};

// Simple Alpha Compositing written as non-premultiplied.
// formula: Rrgb × Ra = Srgb × Sa + Drgb × Da × (1 − Sa)
// Cs: the source color
// αs: the source alpha
// Cb: the backdrop color
// αb: the backdrop alpha
// @see https://www.w3.org/TR/compositing-1/#simplealphacompositing
// @see https://www.w3.org/TR/compositing-1/#blending
// @see https://ciechanow.ski/alpha-compositing/
function simpleAlphaCompositing(Cs, αs, Cb, αb, blendMode) {
// RGB color space doesn't have decimal values so we will follow what browsers do and round
// e.g. rgb(255.2, 127.5, 127.8) === rgb(255, 128, 128)
return Math.round(
αs * (1 - αb) * Cs +
αs * αb * blendFunctions[blendMode](Cb, Cs) +
(1 - αs) * αb * Cb
);
}

/**
* Combine the two given color according to alpha blending.
* @method flattenColors
Expand All @@ -9,12 +37,36 @@ import Color from './color';
* @param {Color} bgColor Background color
* @return {Color} Blended color
*/
function flattenColors(fgColor, bgColor) {
var alpha = fgColor.alpha;
var r = (1 - alpha) * bgColor.red + alpha * fgColor.red;
var g = (1 - alpha) * bgColor.green + alpha * fgColor.green;
var b = (1 - alpha) * bgColor.blue + alpha * fgColor.blue;
var a = fgColor.alpha + bgColor.alpha * (1 - fgColor.alpha);
function flattenColors(fgColor, bgColor, blendMode = 'normal') {
// foreground is the "source" color and background is the "backdrop" color
const r = simpleAlphaCompositing(
fgColor.red,
fgColor.alpha,
bgColor.red,
bgColor.alpha,
blendMode
);
const g = simpleAlphaCompositing(
fgColor.green,
fgColor.alpha,
bgColor.green,
bgColor.alpha,
blendMode
);
const b = simpleAlphaCompositing(
fgColor.blue,
fgColor.alpha,
bgColor.blue,
bgColor.alpha,
blendMode
);

// formula: αo = αs + αb x (1 - αs)
// clamp alpha between 0 and 1
const a = Math.max(
0,
Math.min(fgColor.alpha + bgColor.alpha * (1 - fgColor.alpha), 1)
);

return new Color(r, g, b, a);
}
Expand Down
22 changes: 22 additions & 0 deletions lib/commons/color/flatten-shadow-colors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Color from './color';

/**
* Combine the two given shadow colors according to alpha blending.
* @method flattenColors
* @memberof axe.commons.color.Color
* @instance
* @param {Color} fgColor Foreground color
* @param {Color} bgColor Background color
* @return {Color} Blended color
*/
function flattenColors(fgColor, bgColor) {
var alpha = fgColor.alpha;
var r = (1 - alpha) * bgColor.red + alpha * fgColor.red;
var g = (1 - alpha) * bgColor.green + alpha * fgColor.green;
var b = (1 - alpha) * bgColor.blue + alpha * fgColor.blue;
var a = fgColor.alpha + bgColor.alpha * (1 - fgColor.alpha);

return new Color(r, g, b, a);
}

export default flattenColors;
17 changes: 13 additions & 4 deletions lib/commons/color/get-background-color.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import getOwnBackgroundColor from './get-own-background-color';
import elementHasImage from './element-has-image';
import Color from './color';
import flattenColors from './flatten-colors';
import flattenShadowColors from './flatten-shadow-colors';
import getTextShadowColors from './get-text-shadow-colors';
import visuallyContains from '../dom/visually-contains';

Expand Down Expand Up @@ -38,6 +39,9 @@ function elmPartiallyObscured(elm, bgElm, bgColor) {
*/
function getBackgroundColor(elm, bgElms = [], shadowOutlineEmMax = 0.1) {
let bgColors = getTextShadowColors(elm, { minRatio: shadowOutlineEmMax });
if (bgColors.length) {
bgColors = [bgColors.reduce(flattenShadowColors)];
}
const elmStack = getBackgroundStack(elm);

// Search the stack until we have an alpha === 1 background
Expand All @@ -62,7 +66,7 @@ function getBackgroundColor(elm, bgElms = [], shadowOutlineEmMax = 0.1) {
if (bgColor.alpha !== 0) {
// store elements contributing to the br color.
bgElms.push(bgElm);
bgColors.push(bgColor);
bgColors.unshift(bgColor);

// Exit if the background is opaque
return bgColor.alpha === 1;
Expand All @@ -75,9 +79,14 @@ function getBackgroundColor(elm, bgElms = [], shadowOutlineEmMax = 0.1) {
return null;
}

// Mix the colors together, on top of a default white
bgColors.push(new Color(255, 255, 255, 1));
var colors = bgColors.reduce(flattenColors);
// Mix the colors together, on top of a default white. Colors must be mixed
// in bottom up order (background to foreground order) to produce the correct
// result.
// @see https://github.com/dequelabs/axe-core/issues/2924
bgColors.unshift(new Color(255, 255, 255, 1));
var colors = bgColors.reduce((bgColor, fgColor) => {
return flattenColors(fgColor, bgColor);
});
return colors;
}

Expand Down
3 changes: 2 additions & 1 deletion lib/commons/color/get-foreground-color.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Color from './color';
import getBackgroundColor from './get-background-color';
import incompleteData from './incomplete-data';
import flattenColors from './flatten-colors';
import flattenShadowColors from './flatten-shadow-colors';
import getTextShadowColors from './get-text-shadow-colors';
import { getNodeFromTree } from '../../core/utils';

Expand Down Expand Up @@ -66,7 +67,7 @@ function getForegroundColor(node, _, bgColor) {

if (fgColor.alpha < 1) {
const textShadowColors = getTextShadowColors(node, { minRatio: 0 });
return [fgColor, ...textShadowColors, bgColor].reduce(flattenColors);
return [fgColor, ...textShadowColors, bgColor].reduce(flattenShadowColors);
}

return flattenColors(fgColor, bgColor);
Expand Down
1 change: 1 addition & 0 deletions lib/commons/color/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { default as elementHasImage } from './element-has-image';
export { default as elementIsDistinct } from './element-is-distinct';
export { default as filteredRectStack } from './filtered-rect-stack';
export { default as flattenColors } from './flatten-colors';
export { default as flattenShadowColors } from './flatten-shadow-colors';
export { default as getBackgroundColor } from './get-background-color';
export { default as getBackgroundStack } from './get-background-stack';
export { default as getContrast } from './get-contrast';
Expand Down
45 changes: 30 additions & 15 deletions lib/commons/dom/get-rect-stack.js
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ function addNodeToGrid(grid, vNode) {
const endRow = ((y + rect.height) / gridSize) | 0;
const endCol = ((x + rect.width) / gridSize) | 0;

grid.numCols = Math.max(grid.numCols ?? 0, endCol);

for (let row = startRow; row <= endRow; row++) {
grid.cells[row] = grid.cells[row] || [];

Expand Down Expand Up @@ -475,21 +477,34 @@ export function getRectStack(grid, rect, recursed = false) {
// went with pixel perfect collision rather than rounding
const row = (y / gridSize) | 0;
const col = (x / gridSize) | 0;
let stack = grid.cells[row][col].filter(gridCellNode => {
return gridCellNode.clientRects.find(clientRect => {
const rectX = clientRect.left;
const rectY = clientRect.top;

// perform an AABB (axis-aligned bounding box) collision check for the
// point inside the rect
return (
x <= rectX + clientRect.width &&
x >= rectX &&
y <= rectY + clientRect.height &&
y >= rectY
);
});
});

// we're making an assumption that there cannot be an element in the
// grid which escapes the grid bounds. For example, if the grid is 4x4 there
// can't be an element whose midpoint is at column 5. If this happens this
// means there's an error in our grid logic that needs to be fixed
if (row > grid.cells.length || col > grid.numCols) {
throw new Error('Element midpoint exceeds the grid bounds');
}

// it is acceptable if a row has empty cells due to client rects not filling
// the entire bounding rect of an element
// @see https://github.com/dequelabs/axe-core/issues/3166
let stack =
grid.cells[row][col]?.filter(gridCellNode => {
return gridCellNode.clientRects.find(clientRect => {
const rectX = clientRect.left;
const rectY = clientRect.top;

// perform an AABB (axis-aligned bounding box) collision check for the
// point inside the rect
return (
x <= rectX + clientRect.width &&
x >= rectX &&
y <= rectY + clientRect.height &&
y >= rectY
);
});
}) ?? [];

const gridContainer = grid.container;
if (gridContainer) {
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/label-title-only.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"matches": "label-matches",
"tags": ["cat.forms", "best-practice"],
"metadata": {
"description": "Ensures that every form element is not solely labeled using the title or aria-describedby attributes",
"description": "Ensures that every form element has a visible label and is not solely labeled using hidden labels, or the title or aria-describedby attributes",
"help": "Form elements should have a visible label"
},
"all": [],
Expand Down
4 changes: 2 additions & 2 deletions lib/standards/aria-roles.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ const ariaRoles = {
},
listbox: {
type: 'composite',
requiredOwned: ['option'],
requiredOwned: ['group', 'option'],
allowedAttrs: [
'aria-multiselectable',
'aria-readonly',
Expand Down Expand Up @@ -418,7 +418,7 @@ const ariaRoles = {
},
option: {
type: 'widget',
requiredContext: ['listbox'],
requiredContext: ['group', 'listbox'],
// Note: since the option role has an implicit
// aria-selected value it is not required to be added by
// the user
Expand Down
2 changes: 1 addition & 1 deletion lib/standards/html-elms.js
Original file line number Diff line number Diff line change
Expand Up @@ -936,7 +936,7 @@ const htmlElms = {
},
wbr: {
contentTypes: ['phrasing', 'flow'],
allowedRoles: true
allowedRoles: ['presentation', 'none']
}
};

Expand Down
2 changes: 1 addition & 1 deletion test/checks/aria/required-children.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ describe('aria-required-children', function() {

it('should fail when role does not allow group', function() {
var params = checkSetup(
'<div role="listbox" id="target"><ul role="group"><li role="option">Option</li></ul></div>'
'<div role="table" id="target"><ul role="group"><li role="row">Option</li></ul></div>'
);
assert.isFalse(
axe.testUtils
Expand Down
Loading

0 comments on commit 5e208c9

Please sign in to comment.