Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(color-contrast): greatly improve performance for very large sites #1943

Merged
merged 21 commits into from
Jan 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 3 additions & 155 deletions lib/commons/color/get-background-color.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,9 @@
* @memberof axe.commons.color
* @param {Element} elm Element to determine background color
* @param {Array} [bgElms=[]] elements to inspect
* @param {Boolean} [noScroll=false] should scroll
* @returns {Color}
*/
color.getBackgroundColor = function getBackgroundColor(
elm,
bgElms = [],
noScroll = false
) {
if (noScroll !== true) {
/**
* Avoid scrolling overflow:hidden containers, by only aligning to top,
* when not doing so would move the center point above the viewport top.
*/
const clientHeight = elm.getBoundingClientRect().height;
const alignToTop = clientHeight - 2 >= window.innerHeight * 2;
elm.scrollIntoView(alignToTop);

// ensure element is scrolled into view horizontally
let center, scrollParent;
do {
const rect = elm.getBoundingClientRect();

// 'x' does not exist in IE11
const x = 'x' in rect ? rect.x : rect.left;
center = x + rect.width / 2;

if (center < 0) {
scrollParent = getScrollParent(elm);
scrollParent.scrollLeft = 0;
}
} while (center < 0 && scrollParent !== document.documentElement);
}

color.getBackgroundColor = function getBackgroundColor(elm, bgElms = []) {
let bgColors = [];
let elmStack = color.getBackgroundStack(elm);

Expand Down Expand Up @@ -101,7 +71,6 @@ color.getBackgroundStack = function getBackgroundStack(elm) {
if (elmStack === null) {
return null;
}
elmStack = includeMissingElements(elmStack, elm);
elmStack = dom.reduceToElementsBelowFloating(elmStack, elm);
elmStack = sortPageBackground(elmStack);

Expand Down Expand Up @@ -133,15 +102,6 @@ color.filteredRectStack = function filteredRectStack(elm) {
const boundingStack = rectStack.shift();
let isSame;

// Safari v12.1 does not include labels as part of elementsFromPoint()
// if they wrap an input element (UNLESS the label has a background
// color). this results in two different rectStacks (since
// elm.getClientRects() returns two rects for the element) which
// returns null as isSame is false. we can fix this by adding in the
// missing label to the boundingStack before checking for isSame
// @see https://bugs.webkit.org/show_bug.cgi?id=197743
includeMissingElements(boundingStack, elm);

// iterating over arrays of DOMRects
rectStack.forEach((rectList, index) => {
if (index === 0) {
Expand Down Expand Up @@ -179,37 +139,15 @@ color.filteredRectStack = function filteredRectStack(elm) {
* @return {Array}
*/
color.getRectStack = function(elm) {
const boundingCoords = axe.commons.color.centerPointOfRect(
elm.getBoundingClientRect()
);

if (!boundingCoords) {
return null;
}

let boundingStack = dom.shadowElementsFromPoint(
boundingCoords.x,
boundingCoords.y
);

const boundingStack = axe.commons.dom.getElementStack(elm);
let rects = Array.from(elm.getClientRects());
// If the element does not have multiple rects, like for display:block, return a single stack
if (!rects || rects.length <= 1) {
return [boundingStack];
}

// Handle inline elements spanning multiple lines to be evaluated
let filteredArr = rects
.filter(rect => {
// exclude manual line breaks in Chrome/Safari
return rect.width && rect.width > 0;
})
.map(rect => {
const coords = axe.commons.color.centerPointOfRect(rect);
if (coords) {
return dom.shadowElementsFromPoint(coords.x, coords.y);
}
});
let filteredArr = axe.commons.dom.getClientElementStack(elm);

if (filteredArr.some(stack => stack === undefined)) {
// Can be happen when one or more of the rects sits outside the viewport
Expand Down Expand Up @@ -263,66 +201,6 @@ function sortPageBackground(elmStack) {
return bgNodes;
}

/**
* Include nodes missing from initial gathering because
* document.elementsFromPoint misses some elements we need
* i.e. TR is missing from table elementStack and leaves out bgColor
* https://github.com/dequelabs/axe-core/issues/273
* @private
* @param {Array} elmStack
* @param {Element} elm
* @returns {Array}
*/
function includeMissingElements(elmStack, elm) {
/*eslint max-depth:["error",7]*/
const nodeName = elm.nodeName.toUpperCase();
const elementMap = {
TD: ['TR', 'THEAD', 'TBODY', 'TFOOT'],
TH: ['TR', 'THEAD', 'TBODY', 'TFOOT'],
INPUT: ['LABEL']
};
const tagArray = elmStack.map(elm => {
return elm.nodeName.toUpperCase();
});
let bgNodes = elmStack;
for (let candidate in elementMap) {
// check that TR or LABEL has paired nodeName from elementMap, but don't expect elm to be that candidate
if (tagArray.includes(candidate)) {
for (
let candidateIndex = 0;
candidateIndex < elementMap[candidate].length;
candidateIndex++
) {
// look up the tree for a matching candidate
let ancestorMatch = axe.commons.dom.findUp(
elm,
elementMap[candidate][candidateIndex]
);
if (ancestorMatch && elmStack.indexOf(ancestorMatch) === -1) {
// found an ancestor not in elmStack, and it overlaps
let overlaps = axe.commons.dom.visuallyOverlaps(
elm.getBoundingClientRect(),
ancestorMatch
);
if (overlaps) {
// if target is in the elementMap, use its position.
bgNodes.splice(tagArray.indexOf(candidate) + 1, 0, ancestorMatch);
}
}
// nodeName matches value
// (such as LABEL, when matching itself. It should be in the list, but Phantom skips it)
if (
nodeName === elementMap[candidate][candidateIndex] &&
tagArray.indexOf(nodeName) === -1
) {
bgNodes.splice(tagArray.indexOf(candidate) + 1, 0, elm);
}
}
}
}
return bgNodes;
}

/**
* Determine if element is partially overlapped, triggering a Can't Tell result
* @private
Expand Down Expand Up @@ -393,36 +271,6 @@ function contentOverlapping(targetElement, bgNode) {
return false;
}

/**
* Return the scrolling parent element
* @see https://stackoverflow.com/questions/35939886/find-first-scrollable-parent#42543908
* @param {Element} element
* @param {Boolean} includeHidden
* @return {Element}
*/
function getScrollParent(element, includeHidden) {
var style = getComputedStyle(element);
var excludeStaticParent = style.position === 'absolute';
var overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/;

if (style.position === 'fixed') {
return document.documentElement;
}
for (var parent = element; (parent = parent.parentElement); ) {
style = getComputedStyle(parent);
if (excludeStaticParent && style.position === 'static') {
continue;
}
if (
overflowRegex.test(style.overflow + style.overflowY + style.overflowX)
) {
return parent;
}
}

return document.documentElement;
}

/**
* Determines whether an element has a fully opaque background, whether solid color or an image
* @param {Element} node
Expand Down
Loading