diff --git a/lib/commons/dom/get-rect-stack.js b/lib/commons/dom/get-rect-stack.js index 06172262f4..2f9c5183e3 100644 --- a/lib/commons/dom/get-rect-stack.js +++ b/lib/commons/dom/get-rect-stack.js @@ -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] || []; @@ -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) { diff --git a/test/checks/color/color-contrast.js b/test/checks/color/color-contrast.js index f2f322eb4a..339e5d1a3b 100644 --- a/test/checks/color/color-contrast.js +++ b/test/checks/color/color-contrast.js @@ -358,13 +358,55 @@ describe('color-contrast', function() { ); }); - describe('with pseudo elements', function () { + it('should not error if client rects do not fill entire bounding rect', function() { + var params = checkSetup( + '
' +
+ '\nx x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x ' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\nx' +
+ '\n
'
+ );
+ assert.doesNotThrow(function() {
+ contrastEvaluate.apply(checkContext, params);
+ });
+ });
+
+ describe('with pseudo elements', function() {
it('should return undefined if :before pseudo element has a background color', function() {
var params = checkSetup(
'' +
'Content
Content
Content
Content
Content
Content
Content
Content
Content
Content
' - ); - assert.isUndefined(contrastEvaluate.apply(checkContext, params)); - }); + (isIE11 ? it : xit)( + 'should return undefined if the unit is not in px', + function() { + var params = checkSetup( + '' + + 'Content
' + ); + assert.isUndefined(contrastEvaluate.apply(checkContext, params)); + } + ); }); - describe('with special texts', function () { + describe('with special texts', function() { it('should return undefined for a single character text with insufficient contrast', function() { var params = checkSetup( 'Content
', { - pseudoSizeThreshold: 0.20 + pseudoSizeThreshold: 0.2 } ); assert.isUndefined(contrastEvaluate.apply(checkContext, params)); }); }); - describe('with shadowDOM', function () { + describe('with shadowDOM', function() { (shadowSupported ? it : xit)( 'returns colors across Shadow DOM boundaries', function() { diff --git a/test/commons/dom/get-element-stack.js b/test/commons/dom/get-element-stack.js index ed31136946..290213430e 100644 --- a/test/commons/dom/get-element-stack.js +++ b/test/commons/dom/get-element-stack.js @@ -348,6 +348,46 @@ describe('dom.getElementStack', function() { assert.deepEqual(stack, []); }); + it('should throw error if element midpoint-x exceeds the grid', function() { + fixture.innerHTML = '