Skip to content

Commit

Permalink
feat(color-contrast): greatly improve performance for very large sites (
Browse files Browse the repository at this point in the history
#1943)

* fix(color-contrast): greatly improve perf of color-contrast for extremely large pages

* comments

* fix visuallyContains for scrolling regions

* comments

* fix ie11

* fix test

* debug test

* more debug

* moar

* always add html as last element

* fix shadow boundary sorting

* fix test

* revert

* reduce number of nodes for ie11

* dont use parentNode first

* remove comments

* more comments

* rename, refactor, make work in shadow dom

* check stack

* fix html as scroll region
  • Loading branch information
straker authored Jan 8, 2020
1 parent 4af4a3c commit 9ea0065
Show file tree
Hide file tree
Showing 9 changed files with 855 additions and 753 deletions.
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

0 comments on commit 9ea0065

Please sign in to comment.