From 0bf87ffabbb363ee05b3a884b24b2dfc9140a651 Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Mon, 16 Nov 2020 11:04:29 -0700 Subject: [PATCH 1/6] chore: fix watch (#2636) --- Gruntfile.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 50889eef94..e11bcfa238 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -68,7 +68,10 @@ module.exports = function(grunt) { }); return driverTests; })(), - clean: ['dist', 'tmp', 'axe.js', 'axe.*.js'], + clean: { + core: ['dist', 'tmp/core', 'tmp/rules.js', 'axe.js', 'axe.*.js'], + tests: ['tmp/integration-tests.js'] + }, babel: { options: { compact: false @@ -240,7 +243,7 @@ module.exports = function(grunt) { }, tests: { files: ['test/**/*.js', 'test/integration/**/!(index).{html,json}'], - tasks: ['testconfig', 'fixture'] + tasks: ['clean:tests', 'testconfig', 'fixture'] } }, testconfig: { @@ -353,7 +356,7 @@ module.exports = function(grunt) { grunt.registerTask('translate', ['validate', 'esbuild', 'add-locale']); grunt.registerTask('build', [ - 'clean', + 'clean:core', 'validate', 'esbuild', 'configure', From 1e47fb102983c7f1f16b6c9b1cf4d0af23fc3c16 Mon Sep 17 00:00:00 2001 From: James Addison Date: Mon, 16 Nov 2020 21:01:54 +0000 Subject: [PATCH 2/6] refactor: remove core-js and core-js-pure dependencies (#2637) * core(cleanup): remove core-js and core-js-pure dependencies The polyfills previously provided by these dependencies can be fulfilled by other existing dependencies of axe-core * Restore conditional check around Promise polyfilling * Remove redundant `WeakMap` declaration presence check Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com> Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com> --- lib/core/imports/index.js | 24 ++++-------------------- package-lock.json | 6 ------ package.json | 3 +-- 3 files changed, 5 insertions(+), 28 deletions(-) diff --git a/lib/core/imports/index.js b/lib/core/imports/index.js index 0591d45e4b..74ec271723 100644 --- a/lib/core/imports/index.js +++ b/lib/core/imports/index.js @@ -5,30 +5,14 @@ import doT from '@deque/dot'; import emojiRegexText from 'emoji-regex'; import memoize from 'memoizee'; -import Promise from 'core-js-pure/features/promise'; -import Uint32Array from 'core-js-pure/features/typed-array/uint32-array'; -import WeakMap from 'core-js-pure/es/weak-map'; +import es6promise from 'es6-promise'; +import { Uint32Array } from 'typedarray'; +import 'weakmap-polyfill'; -/** - * Polyfill `WeakMap` - * Reference https://github.com/zloirock/core-js/ - */ -if (!('WeakMap' in window)) { - window.WeakMap = WeakMap; -} - -/** - * Polyfill `Promise` - * Reference https://github.com/zloirock/core-js/ - */ if (!('Promise' in window)) { - window.Promise = Promise; + es6promise.polyfill(); } -/** - * Polyfill required TypedArray and functions - * Reference https://github.com/zloirock/core-js/ - */ if (!('Uint32Array' in window)) { window.Uint32Array = Uint32Array; } diff --git a/package-lock.json b/package-lock.json index d37bb2de35..21575e6a39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2845,12 +2845,6 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, - "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", - "dev": true - }, "core-js-compat": { "version": "3.6.5", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", diff --git a/package.json b/package.json index 739aa7dd56..75fbb50fe1 100644 --- a/package.json +++ b/package.json @@ -92,8 +92,6 @@ "chalk": "^4.1.0", "clone": "~2.1.1", "conventional-commits-parser": "^3.1.0", - "core-js": "^3.2.1", - "core-js-pure": "^3.6.5", "css-selector-parser": "^1.3.0", "derequire": "^2.1.1", "emoji-regex": "8.0.0", @@ -136,6 +134,7 @@ "sinon": "^7.5.0", "sri-toolbox": "^0.2.0", "standard-version": "^9.0.0", + "typedarray": "^0.0.6", "typescript": "^3.5.3", "uglify-js": "^3.4.4", "weakmap-polyfill": "^2.0.0" From ea0917e88c30336863f99c7a62ff35589ca382fc Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Tue, 17 Nov 2020 08:41:00 -0700 Subject: [PATCH 3/6] refactor: set default return for text.sanitize and utils.tokenList (#2645) * refactor: set default return for text.sanitize and utils.tokenList * fix --- lib/checks/aria/aria-errormessage-evaluate.js | 3 +-- lib/checks/generic/attr-non-space-content-evaluate.js | 4 ++-- lib/checks/navigation/unique-frame-title-evaluate.js | 4 +--- lib/checks/shared/doc-has-title-evaluate.js | 2 +- lib/commons/aria/implicit-nodes.js | 2 -- lib/commons/aria/label-virtual.js | 2 +- lib/commons/aria/validate-attr-value.js | 1 - lib/commons/dom/get-element-coordinates.js | 2 -- lib/commons/dom/get-scroll-offset.js | 2 -- lib/commons/dom/get-viewport-size.js | 2 -- lib/commons/dom/is-node.js | 1 - lib/commons/dom/is-visible.js | 2 -- lib/commons/text/sanitize.js | 5 ++++- lib/core/base/check.js | 1 - lib/core/index.js | 1 - lib/core/log.js | 1 - lib/core/public/cleanup.js | 2 -- lib/core/public/configure.js | 1 - lib/core/public/get-rules.js | 2 -- lib/core/public/load.js | 3 --- lib/core/public/plugins.js | 6 ------ lib/core/public/reporter.js | 3 --- lib/core/public/reset.js | 1 - lib/core/public/run-rules.js | 1 - lib/core/public/run.js | 3 --- lib/core/utils/dq-element.js | 1 - lib/core/utils/performance-timer.js | 2 -- lib/core/utils/token-list.js | 2 +- lib/rules/frame-title-has-text-matches.js | 2 +- test/commons/text/sanitize.js | 4 ++++ test/core/utils/token-list.js | 4 ++++ 31 files changed, 20 insertions(+), 52 deletions(-) diff --git a/lib/checks/aria/aria-errormessage-evaluate.js b/lib/checks/aria/aria-errormessage-evaluate.js index 0ec8d6df52..681184c49b 100644 --- a/lib/checks/aria/aria-errormessage-evaluate.js +++ b/lib/checks/aria/aria-errormessage-evaluate.js @@ -42,8 +42,7 @@ function ariaErrormessageEvaluate(node, options) { idref.getAttribute('role') === 'alert' || idref.getAttribute('aria-live') === 'assertive' || idref.getAttribute('aria-live') === 'polite' || - tokenList(node.getAttribute('aria-describedby') || '').indexOf(attr) > - -1 + tokenList(node.getAttribute('aria-describedby')).indexOf(attr) > -1 ); } return; diff --git a/lib/checks/generic/attr-non-space-content-evaluate.js b/lib/checks/generic/attr-non-space-content-evaluate.js index 28a4eda74c..729cb36389 100644 --- a/lib/checks/generic/attr-non-space-content-evaluate.js +++ b/lib/checks/generic/attr-non-space-content-evaluate.js @@ -14,8 +14,8 @@ function attrNonSpaceContentEvaluate(node, options = {}, vNode) { return false; } - const attribute = vNode.attr(options.attribute) || ''; - const attributeIsEmpty = !sanitize(attribute.trim()); + const attribute = vNode.attr(options.attribute); + const attributeIsEmpty = !sanitize(attribute); if (attributeIsEmpty) { this.data({ messageKey: 'emptyAttr' diff --git a/lib/checks/navigation/unique-frame-title-evaluate.js b/lib/checks/navigation/unique-frame-title-evaluate.js index e9b95dd61a..909e6aadbe 100644 --- a/lib/checks/navigation/unique-frame-title-evaluate.js +++ b/lib/checks/navigation/unique-frame-title-evaluate.js @@ -1,9 +1,7 @@ import { sanitize } from '../../commons/text'; function uniqueFrameTitleEvaluate(node, options, vNode) { - var title = sanitize(vNode.attr('title') || '') - .trim() - .toLowerCase(); + var title = sanitize(vNode.attr('title')).toLowerCase(); this.data(title); return true; } diff --git a/lib/checks/shared/doc-has-title-evaluate.js b/lib/checks/shared/doc-has-title-evaluate.js index cf1218b897..9831cdc111 100644 --- a/lib/checks/shared/doc-has-title-evaluate.js +++ b/lib/checks/shared/doc-has-title-evaluate.js @@ -2,7 +2,7 @@ import { sanitize } from '../../commons/text'; function docHasTitleEvaluate() { var title = document.title; - return !!(title ? sanitize(title).trim() : ''); + return !!sanitize(title); } export default docHasTitleEvaluate; diff --git a/lib/commons/aria/implicit-nodes.js b/lib/commons/aria/implicit-nodes.js index ae2ce3faa2..af9aa7f5e3 100644 --- a/lib/commons/aria/implicit-nodes.js +++ b/lib/commons/aria/implicit-nodes.js @@ -11,8 +11,6 @@ import lookupTable from './lookup-table'; * @return {Mixed} Either an Array of CSS selectors or `null` if there are none */ function implicitNodes(role) { - 'use strict'; - let implicit = null; const roles = lookupTable.role[role]; diff --git a/lib/commons/aria/label-virtual.js b/lib/commons/aria/label-virtual.js index 9e00f97c4d..acefff6228 100644 --- a/lib/commons/aria/label-virtual.js +++ b/lib/commons/aria/label-virtual.js @@ -34,7 +34,7 @@ function labelVirtual(virtualNode) { // aria-label candidate = virtualNode.attr('aria-label'); if (candidate) { - candidate = sanitize(candidate).trim(); + candidate = sanitize(candidate); if (candidate) { return candidate; } diff --git a/lib/commons/aria/validate-attr-value.js b/lib/commons/aria/validate-attr-value.js index 53f59d98a1..5b68551bc4 100644 --- a/lib/commons/aria/validate-attr-value.js +++ b/lib/commons/aria/validate-attr-value.js @@ -12,7 +12,6 @@ import { tokenList } from '../../core/utils'; * @return {Boolean} */ function validateAttrValue(node, attr) { - 'use strict'; let matches; let list; const value = node.getAttribute(attr); diff --git a/lib/commons/dom/get-element-coordinates.js b/lib/commons/dom/get-element-coordinates.js index 7c8385b179..480cb525f2 100644 --- a/lib/commons/dom/get-element-coordinates.js +++ b/lib/commons/dom/get-element-coordinates.js @@ -20,8 +20,6 @@ import getScrollOffset from './get-scroll-offset'; * @property {Number} height The height of the element */ function getElementCoordinates(element) { - 'use strict'; - var scrollOffset = getScrollOffset(document), xOffset = scrollOffset.left, yOffset = scrollOffset.top, diff --git a/lib/commons/dom/get-scroll-offset.js b/lib/commons/dom/get-scroll-offset.js index d7eb5fe71d..2995390143 100644 --- a/lib/commons/dom/get-scroll-offset.js +++ b/lib/commons/dom/get-scroll-offset.js @@ -7,8 +7,6 @@ * @return {Object} Contains the attributes `x` and `y` which contain the scroll offsets */ function getScrollOffset(element) { - 'use strict'; - if (!element.nodeType && element.document) { element = element.document; } diff --git a/lib/commons/dom/get-viewport-size.js b/lib/commons/dom/get-viewport-size.js index 0b2fe01d23..9a1359a741 100644 --- a/lib/commons/dom/get-viewport-size.js +++ b/lib/commons/dom/get-viewport-size.js @@ -7,8 +7,6 @@ * @return {Object} Object with the `width` and `height` of the viewport */ function getViewportSize(win) { - 'use strict'; - const doc = win.document; const docElement = doc.documentElement; diff --git a/lib/commons/dom/is-node.js b/lib/commons/dom/is-node.js index d4f7c96062..390410d358 100644 --- a/lib/commons/dom/is-node.js +++ b/lib/commons/dom/is-node.js @@ -8,7 +8,6 @@ * @return {Boolean} */ function isNode(element) { - 'use strict'; return element instanceof window.Node; } diff --git a/lib/commons/dom/is-visible.js b/lib/commons/dom/is-visible.js index b355beab08..9e4574f536 100644 --- a/lib/commons/dom/is-visible.js +++ b/lib/commons/dom/is-visible.js @@ -20,8 +20,6 @@ const clipPathRegex = /(\w+)\((\d+)/; * @return {Boolean} */ function isClipped(style) { - 'use strict'; - const matchesClip = style.getPropertyValue('clip').match(clipRegex); const matchesClipPath = style .getPropertyValue('clip-path') diff --git a/lib/commons/text/sanitize.js b/lib/commons/text/sanitize.js index d2501c84d0..33f5ca86b4 100644 --- a/lib/commons/text/sanitize.js +++ b/lib/commons/text/sanitize.js @@ -7,7 +7,10 @@ * @return {String} Sanitized string */ function sanitize(str) { - 'use strict'; + if (!str) { + return ''; + } + return str .replace(/\r\n/g, '\n') .replace(/\u00A0/g, ' ') diff --git a/lib/core/base/check.js b/lib/core/base/check.js index 8edd930b0a..392b406d39 100644 --- a/lib/core/base/check.js +++ b/lib/core/base/check.js @@ -83,7 +83,6 @@ Check.prototype.enabled = true; * @param {Function} callback Function to fire when check is complete */ Check.prototype.run = function(node, options, context, resolve, reject) { - 'use strict'; options = options || {}; const enabled = options.hasOwnProperty('enabled') ? options.enabled diff --git a/lib/core/index.js b/lib/core/index.js index 820b51b0e6..b161ac57e2 100644 --- a/lib/core/index.js +++ b/lib/core/index.js @@ -8,7 +8,6 @@ axe.version = '<%= pkg.version %>'; if (typeof define === 'function' && define.amd) { // Explicitly naming the module to avoid mismatched anonymous define() modules when injected in a page define('axe-core', [], function() { - 'use strict'; return axe; }); } diff --git a/lib/core/log.js b/lib/core/log.js index 57ad9fc9aa..b937289b70 100644 --- a/lib/core/log.js +++ b/lib/core/log.js @@ -4,7 +4,6 @@ * Logs a message to the developer console (if it exists and is active). */ function log() { - 'use strict'; if (typeof console === 'object' && console.log) { // IE does not support console.log.apply Function.prototype.apply.call(console.log, console, arguments); diff --git a/lib/core/public/cleanup.js b/lib/core/public/cleanup.js index 657ace9af2..13dd4eda62 100644 --- a/lib/core/public/cleanup.js +++ b/lib/core/public/cleanup.js @@ -1,6 +1,4 @@ function cleanup(resolve, reject) { - 'use strict'; - resolve = resolve || function() {}; reject = reject || axe.log; diff --git a/lib/core/public/configure.js b/lib/core/public/configure.js index 31f878fbbf..66cacf5e15 100644 --- a/lib/core/public/configure.js +++ b/lib/core/public/configure.js @@ -2,7 +2,6 @@ import { hasReporter } from './reporter'; import { configureStandards } from '../../standards'; function configure(spec) { - 'use strict'; var audit; audit = axe._audit; diff --git a/lib/core/public/get-rules.js b/lib/core/public/get-rules.js index bfd41efa56..a0d3041f8c 100644 --- a/lib/core/public/get-rules.js +++ b/lib/core/public/get-rules.js @@ -4,8 +4,6 @@ * @return {Array} Array of rules */ function getRules(tags) { - 'use strict'; - tags = tags || []; var matchingRules = !tags.length ? axe._audit.rules diff --git a/lib/core/public/load.js b/lib/core/public/load.js index 495e3bb47c..05c4a71adc 100644 --- a/lib/core/public/load.js +++ b/lib/core/public/load.js @@ -3,7 +3,6 @@ import cleanup from './cleanup'; import runRules from './run-rules'; function runCommand(data, keepalive, callback) { - 'use strict'; var resolve = callback; var reject = function(err) { if (err instanceof Error === false) { @@ -50,8 +49,6 @@ function runCommand(data, keepalive, callback) { * @private */ function load(audit) { - 'use strict'; - axe.utils.respondable.subscribe('axe.ping', function( data, keepalive, diff --git a/lib/core/public/plugins.js b/lib/core/public/plugins.js index 6ed7b2fb36..dd35ddb3d4 100644 --- a/lib/core/public/plugins.js +++ b/lib/core/public/plugins.js @@ -1,7 +1,6 @@ /*eslint no-use-before-define:0 */ function Plugin(spec) { - 'use strict'; this._run = spec.run; this._collect = spec.collect; this._registry = {}; @@ -11,17 +10,14 @@ function Plugin(spec) { } Plugin.prototype.run = function() { - 'use strict'; return this._run.apply(this, arguments); }; Plugin.prototype.collect = function() { - 'use strict'; return this._collect.apply(this, arguments); }; Plugin.prototype.cleanup = function(done) { - 'use strict'; var q = axe.utils.queue(); var that = this; Object.keys(this._registry).forEach(function(key) { @@ -35,12 +31,10 @@ Plugin.prototype.cleanup = function(done) { }; Plugin.prototype.add = function(impl) { - 'use strict'; this._registry[impl.id] = impl; }; function registerPlugin(plugin) { - 'use strict'; axe.plugins[plugin.id] = new Plugin(plugin); } diff --git a/lib/core/public/reporter.js b/lib/core/public/reporter.js index ef1a314f2d..0658f97992 100644 --- a/lib/core/public/reporter.js +++ b/lib/core/public/reporter.js @@ -6,7 +6,6 @@ export function hasReporter(reporterName) { } export function getReporter(reporter) { - 'use strict'; if (typeof reporter === 'string' && reporters[reporter]) { return reporters[reporter]; } @@ -19,8 +18,6 @@ export function getReporter(reporter) { } export function addReporter(name, cb, isDefault) { - 'use strict'; - reporters[name] = cb; if (isDefault) { defaultReporter = cb; diff --git a/lib/core/public/reset.js b/lib/core/public/reset.js index 57aef46b4c..ca6efda529 100644 --- a/lib/core/public/reset.js +++ b/lib/core/public/reset.js @@ -1,7 +1,6 @@ import { resetStandards } from '../../standards'; function reset() { - 'use strict'; var audit = axe._audit; if (!audit) { diff --git a/lib/core/public/run-rules.js b/lib/core/public/run-rules.js index b20d3a7b0c..c28163bb3f 100644 --- a/lib/core/public/run-rules.js +++ b/lib/core/public/run-rules.js @@ -35,7 +35,6 @@ function cleanup() { * @param {Function} reject Called when execution failed, receives (err : Error) */ function runRules(context, options, resolve, reject) { - 'use strict'; try { context = new Context(context); axe._tree = context.flatTree; diff --git a/lib/core/public/run.js b/lib/core/public/run.js index b9ec8abda4..faabc27471 100644 --- a/lib/core/public/run.js +++ b/lib/core/public/run.js @@ -2,7 +2,6 @@ import { getReporter } from './reporter'; import cache from '../base/cache'; function isContext(potential) { - 'use strict'; switch (true) { case typeof potential === 'string': case Array.isArray(potential): @@ -33,7 +32,6 @@ var noop = function() {}; * @return {object} With 3 keys: context, options, callback */ function normalizeRunParams(context, options, callback) { - 'use strict'; let typeErr = new TypeError('axe.run arguments are invalid'); // Determine the context @@ -83,7 +81,6 @@ function normalizeRunParams(context, options, callback) { * @return {Promise} Resolves with the axe results. Only available when natively supported */ function run(context, options, callback) { - 'use strict'; if (!axe._audit) { throw new Error('No audit configured'); } diff --git a/lib/core/utils/dq-element.js b/lib/core/utils/dq-element.js index 3648f75379..118730deec 100644 --- a/lib/core/utils/dq-element.js +++ b/lib/core/utils/dq-element.js @@ -87,7 +87,6 @@ DqElement.prototype = { }, toJSON: function() { - 'use strict'; return { selector: this.selector, source: this.source, diff --git a/lib/core/utils/performance-timer.js b/lib/core/utils/performance-timer.js index f031adfd72..b814ea3872 100644 --- a/lib/core/utils/performance-timer.js +++ b/lib/core/utils/performance-timer.js @@ -8,8 +8,6 @@ import log from '../log'; * */ const performanceTimer = (function() { - 'use strict'; - /** * Get a time/date object using performance.now() if supported * @return {DOMTimeStamp} diff --git a/lib/core/utils/token-list.js b/lib/core/utils/token-list.js index 524723c476..6529eb9265 100644 --- a/lib/core/utils/token-list.js +++ b/lib/core/utils/token-list.js @@ -6,7 +6,7 @@ * @return {Array} */ function tokenList(str) { - return str + return (str || '') .trim() .replace(/\s{2,}/g, ' ') .split(' '); diff --git a/lib/rules/frame-title-has-text-matches.js b/lib/rules/frame-title-has-text-matches.js index daf6925ed0..9789f1d3ff 100644 --- a/lib/rules/frame-title-has-text-matches.js +++ b/lib/rules/frame-title-has-text-matches.js @@ -2,7 +2,7 @@ import { sanitize } from '../commons/text'; function frameTitleHasTextMatches(node) { var title = node.getAttribute('title'); - return !!(title ? sanitize(title).trim() : ''); + return !!sanitize(title); } export default frameTitleHasTextMatches; diff --git a/test/commons/text/sanitize.js b/test/commons/text/sanitize.js index 20487fbb65..49114ec048 100644 --- a/test/commons/text/sanitize.js +++ b/test/commons/text/sanitize.js @@ -8,4 +8,8 @@ describe('text.sanitize', function() { assert.equal(axe.commons.text.sanitize(' hi\r\nok'), 'hi\nok'); assert.equal(axe.commons.text.sanitize('hello\u00A0there'), 'hello there'); }); + + it('should accept null', function() { + assert.equal(axe.commons.text.sanitize(null), ''); + }); }); diff --git a/test/core/utils/token-list.js b/test/core/utils/token-list.js index b546fd5807..49c8b3906f 100644 --- a/test/core/utils/token-list.js +++ b/test/core/utils/token-list.js @@ -24,4 +24,8 @@ describe('axe.utils.tokenList', function() { '42' ]); }); + + it('should return empty string array for null value', function() { + assert.deepEqual(axe.utils.tokenList(null), ['']); + }); }); From ba174bd5496d7146c1baf982cb762444cda26cff Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Tue, 17 Nov 2020 10:17:02 -0700 Subject: [PATCH 4/6] fix(color-contrast): greatly improve color-contrast-matches speed. add aria/get-accessible-ref (#2635) * feat(color-contrast): greatly improve color-contrast-matches speed. add aria/get-accessible-ref * filter by aria-labelledby * fixes * delete --- lib/commons/aria/get-accessible-refs.js | 73 +++++++++++++ lib/commons/aria/index.js | 1 + lib/commons/aria/is-accessible-ref.js | 57 +--------- lib/rules/color-contrast-matches.js | 20 ++-- test/commons/aria/get-accessible-refs.js | 133 +++++++++++++++++++++++ 5 files changed, 218 insertions(+), 66 deletions(-) create mode 100644 lib/commons/aria/get-accessible-refs.js create mode 100644 test/commons/aria/get-accessible-refs.js diff --git a/lib/commons/aria/get-accessible-refs.js b/lib/commons/aria/get-accessible-refs.js new file mode 100644 index 0000000000..b1dae083b3 --- /dev/null +++ b/lib/commons/aria/get-accessible-refs.js @@ -0,0 +1,73 @@ +import getRootNode from '../dom/get-root-node'; +import cache from '../../core/base/cache'; +import { tokenList } from '../../core/utils'; +import standards from '../../standards'; +import { sanitize } from '../text/'; + +const idRefsRegex = /^idrefs?$/; + +/** + * Cache all ID references of a node and its children + */ +function cacheIdRefs(node, idRefs, refAttrs) { + if (node.hasAttribute) { + if (node.nodeName.toUpperCase() === 'LABEL' && node.hasAttribute('for')) { + const id = node.getAttribute('for'); + idRefs[id] = idRefs[id] || []; + idRefs[id].push(node); + } + + for (let i = 0; i < refAttrs.length; ++i) { + const attr = refAttrs[i]; + const attrValue = sanitize(node.getAttribute(attr) || ''); + + if (!attrValue) { + continue; + } + + const tokens = tokenList(attrValue); + for (let k = 0; k < tokens.length; ++k) { + idRefs[tokens[k]] = idRefs[tokens[k]] || []; + idRefs[tokens[k]].push(node); + } + } + } + + for (let i = 0; i < node.children.length; i++) { + cacheIdRefs(node.children[i], idRefs, refAttrs); + } +} + +/** + * Return all DOM nodes that use the nodes ID in the accessibility tree. + * @param {Element} node + * @returns {Element[]} + */ +function getAccessibleRefs(node) { + node = node.actualNode || node; + let root = getRootNode(node); + root = root.documentElement || root; // account for shadow roots + + let idRefsByRoot = cache.get('idRefsByRoot'); + if (!idRefsByRoot) { + idRefsByRoot = new WeakMap(); + cache.set('idRefsByRoot', idRefsByRoot); + } + + let idRefs = idRefsByRoot.get(root); + if (!idRefs) { + idRefs = {}; + idRefsByRoot.set(root, idRefs); + + const refAttrs = Object.keys(standards.ariaAttrs).filter(attr => { + const { type } = standards.ariaAttrs[attr]; + return idRefsRegex.test(type); + }); + + cacheIdRefs(root, idRefs, refAttrs); + } + + return idRefs[node.id] || []; +} + +export default getAccessibleRefs; diff --git a/lib/commons/aria/index.js b/lib/commons/aria/index.js index 0ae5994acf..14de4ebc0c 100644 --- a/lib/commons/aria/index.js +++ b/lib/commons/aria/index.js @@ -6,6 +6,7 @@ export { default as allowedAttr } from './allowed-attr'; export { default as arialabelText } from './arialabel-text'; export { default as arialabelledbyText } from './arialabelledby-text'; +export { default as getAccessibleRefs } from './get-accessible-refs'; export { default as getElementUnallowedRoles } from './get-element-unallowed-roles'; export { default as getExplicitRole } from './get-explicit-role'; export { default as getOwnedVirtual } from './get-owned-virtual'; diff --git a/lib/commons/aria/is-accessible-ref.js b/lib/commons/aria/is-accessible-ref.js index 56fcceeeab..1fd6094a59 100644 --- a/lib/commons/aria/is-accessible-ref.js +++ b/lib/commons/aria/is-accessible-ref.js @@ -1,39 +1,4 @@ -import standards from '../../standards'; -import getRootNode from '../dom/get-root-node'; -import cache from '../../core/base/cache'; -import { tokenList } from '../../core/utils'; - -const idRefsRegex = /^idrefs?$/; - -function cacheIdRefs(node, refAttrs) { - if (node.hasAttribute) { - const idRefs = cache.get('idRefs'); - - if (node.nodeName.toUpperCase() === 'LABEL' && node.hasAttribute('for')) { - idRefs[node.getAttribute('for')] = true; - } - - for (let i = 0; i < refAttrs.length; ++i) { - const attr = refAttrs[i]; - - if (!node.hasAttribute(attr)) { - continue; - } - - const attrValue = node.getAttribute(attr); - - const tokens = tokenList(attrValue); - - for (let k = 0; k < tokens.length; ++k) { - idRefs[tokens[k]] = true; - } - } - } - - for (let i = 0; i < node.children.length; i++) { - cacheIdRefs(node.children[i], refAttrs); - } -} +import getAccessibleRefs from './get-accessible-refs'; /** * Check that a DOM node is a reference in the accessibility tree @@ -41,25 +6,7 @@ function cacheIdRefs(node, refAttrs) { * @returns {Boolean} */ function isAccessibleRef(node) { - node = node.actualNode || node; - let root = getRootNode(node); - root = root.documentElement || root; // account for shadow roots - const id = node.id; - - // because axe.commons is not available in axe.utils, we can't do - // this caching when we build up the virtual tree - if (!cache.get('idRefs')) { - cache.set('idRefs', {}); - // Get all idref(s) attributes on the lookup table - const refAttrs = Object.keys(standards.ariaAttrs).filter(attr => { - const { type } = standards.ariaAttrs[attr]; - return idRefsRegex.test(type); - }); - - cacheIdRefs(root, refAttrs); - } - - return cache.get('idRefs')[id] === true; + return !!getAccessibleRefs(node).length; } export default isAccessibleRef; diff --git a/lib/rules/color-contrast-matches.js b/lib/rules/color-contrast-matches.js index 54cebc9797..e5448008d3 100644 --- a/lib/rules/color-contrast-matches.js +++ b/lib/rules/color-contrast-matches.js @@ -1,12 +1,9 @@ /* global document */ +import { getAccessibleRefs } from '../commons/aria'; import { findUpVirtual, visuallyOverlaps, getRootNode } from '../commons/dom'; import { visibleVirtual, removeUnicode, sanitize } from '../commons/text'; import { isDisabled } from '../commons/forms'; -import { - getNodeFromTree, - querySelectorAll, - escapeSelector -} from '../core/utils'; +import { getNodeFromTree, querySelectorAll, tokenList } from '../core/utils'; function colorContrastMatches(node, virtualNode) { const { nodeName, type: inputType } = virtualNode.props; @@ -96,12 +93,13 @@ function colorContrastMatches(node, virtualNode) { while (ancestorNode) { // Find any ancestor (including itself) that is used with aria-labelledby if (ancestorNode.props.id) { - const doc = getRootNode(node); - const escapedId = escapeSelector(ancestorNode.props.id); - const controls = Array.from( - doc.querySelectorAll(`[aria-labelledby~="${escapedId}"]`) - ); - const virtualControls = controls.map(control => getNodeFromTree(control)); + const virtualControls = getAccessibleRefs(ancestorNode) + .filter(control => { + return tokenList( + control.getAttribute('aria-labelledby') || '' + ).includes(ancestorNode.props.id); + }) + .map(control => getNodeFromTree(control)); ariaLabelledbyControls.push(...virtualControls); } diff --git a/test/commons/aria/get-accessible-refs.js b/test/commons/aria/get-accessible-refs.js new file mode 100644 index 0000000000..241839415a --- /dev/null +++ b/test/commons/aria/get-accessible-refs.js @@ -0,0 +1,133 @@ +describe('aria.getAccessibleRefs', function() { + 'use strict'; + + var fixture = document.getElementById('fixture'); + var getAccessibleRefs = axe.commons.aria.getAccessibleRefs; + var shadowSupport = axe.testUtils.shadowSupport.v1; + + function setLookup(attrs) { + axe.configure({ + standards: { + ariaAttrs: attrs + } + }); + } + + before(function() { + axe._load({}); + }); + + afterEach(function() { + fixture.innerHTML = ''; + axe.reset(); + }); + + it('returns empty array by default', function() { + fixture.innerHTML = '
'; + var node = document.getElementById('foo'); + assert.lengthOf(getAccessibleRefs(node), 0); + }); + + it('returns array of nodes for IDs used in aria IDREF attributes', function() { + setLookup({ 'aria-foo': { type: 'idref' } }); + fixture.innerHTML = '
'; + var node = document.getElementById('foo'); + var ref = document.getElementById('ref'); + assert.deepEqual(getAccessibleRefs(node), [ref]); + }); + + it('returns array of nodes for IDs used in aria IDREFS attributes', function() { + setLookup({ 'aria-bar': { type: 'idrefs' } }); + fixture.innerHTML = + '
'; + + var node1 = document.getElementById('foo'); + var node2 = document.getElementById('bar'); + var ref = document.getElementById('ref'); + assert.deepEqual(getAccessibleRefs(node1), [ref]); + assert.deepEqual(getAccessibleRefs(node2), [ref]); + }); + + it('returns array of nodes for IDs used in label[for] attributes', function() { + setLookup({ 'aria-foo': { type: 'idref' } }); + fixture.innerHTML = ''; + var node = document.getElementById('baz'); + var ref = document.getElementById('ref'); + assert.deepEqual(getAccessibleRefs(node), [ref]); + }); + + it('returns all nodes used in aria IDREF attributes', function() { + setLookup({ 'aria-bar': { type: 'idrefs' } }); + fixture.innerHTML = + '
'; + + var node = document.getElementById('foo'); + var ref1 = document.getElementById('ref1'); + var ref2 = document.getElementById('ref2'); + + assert.deepEqual(getAccessibleRefs(node), [ref1, ref2]); + }); + + (shadowSupport ? it : xit)('works inside shadow DOM', function() { + setLookup({ 'aria-bar': { type: 'idref' } }); + fixture.innerHTML = '
'; + + var shadow = document.getElementById('foo').attachShadow({ mode: 'open' }); + shadow.innerHTML = '
'; + + var node = shadow.getElementById('bar'); + var ref = shadow.getElementById('ref'); + assert.deepEqual(getAccessibleRefs(node), [ref]); + }); + + (shadowSupport ? it : xit)( + 'returns empty array for IDREFs inside shadow DOM', + function() { + setLookup({ 'aria-foo': { type: 'idrefs' } }); + fixture.innerHTML = '
'; + var node1 = document.getElementById('foo'); + var node2 = document.getElementById('bar'); + + var shadow = node1.attachShadow({ mode: 'open' }); + shadow.innerHTML = '
'; + + assert.lengthOf(getAccessibleRefs(node1), 0); + assert.lengthOf(getAccessibleRefs(node2), 0); + } + ); + + (shadowSupport ? it : xit)( + 'returns empty array for IDREFs outside shadow DOM', + function() { + setLookup({ 'aria-bar': { type: 'idref' } }); + fixture.innerHTML = + '
'; + + var shadow = document + .getElementById('foo') + .attachShadow({ mode: 'open' }); + shadow.innerHTML = '
'; + + var node = shadow.getElementById('bar'); + assert.lengthOf(getAccessibleRefs(node), 0); + } + ); + + (shadowSupport ? it : xit)('separates IDREFs by roots', function() { + setLookup({ 'aria-bar': { type: 'idref' } }); + fixture.innerHTML = + '
'; + + var shadow = document + .getElementById('shadow') + .attachShadow({ mode: 'open' }); + shadow.innerHTML = '
'; + + var outsideNode = document.getElementById('foo'); + var outsideRef = document.getElementById('outside'); + var insideNode = shadow.getElementById('foo'); + var insideRef = shadow.getElementById('inside'); + assert.deepEqual(getAccessibleRefs(outsideNode), [outsideRef]); + assert.deepEqual(getAccessibleRefs(insideNode), [insideRef]); + }); +}); From 2d20cdee819265f3c4efcb3ccb61da0a9a88981c Mon Sep 17 00:00:00 2001 From: Michael <45568605+michael-siek@users.noreply.github.com> Date: Thu, 19 Nov 2020 13:57:10 -0500 Subject: [PATCH 5/6] fix: remove axios import (#2653) * fix: remove axios import * remove axios dep --- lib/core/imports/index.js | 4 +-- package-lock.json | 35 ------------------- package.json | 1 - .../full/umd/umd-module-exports.js | 4 --- test/integration/full/umd/umd-window.js | 12 ------- 5 files changed, 1 insertion(+), 55 deletions(-) diff --git a/lib/core/imports/index.js b/lib/core/imports/index.js index 74ec271723..5792c14612 100644 --- a/lib/core/imports/index.js +++ b/lib/core/imports/index.js @@ -1,5 +1,3 @@ -// @deprecated (axios) -import axios from 'axios'; import { CssSelectorParser } from 'css-selector-parser'; import doT from '@deque/dot'; import emojiRegexText from 'emoji-regex'; @@ -38,4 +36,4 @@ if (window.Uint32Array) { * @namespace imports * @memberof axe */ -export { axios, CssSelectorParser, doT, emojiRegexText, memoize }; +export { CssSelectorParser, doT, emojiRegexText, memoize }; diff --git a/package-lock.json b/package-lock.json index 21575e6a39..6fcb233438 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1539,15 +1539,6 @@ "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", "dev": true }, - "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", - "dev": true, - "requires": { - "follow-redirects": "1.5.10" - } - }, "babel-plugin-dynamic-import-node": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", @@ -4495,32 +4486,6 @@ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, - "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "dev": true, - "requires": { - "debug": "=3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", diff --git a/package.json b/package.json index 75fbb50fe1..dee6600b6c 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,6 @@ "@babel/preset-env": "^7.5.4", "@deque/dot": "^1.1.5", "aria-query": "^3.0.0", - "axios": "^0.19.0", "babelify": "^10.0.0", "blanket": "~1.2.3", "browserify": "^16.2.3", diff --git a/test/integration/full/umd/umd-module-exports.js b/test/integration/full/umd/umd-module-exports.js index 440c9fcb20..b192b75d38 100644 --- a/test/integration/full/umd/umd-module-exports.js +++ b/test/integration/full/umd/umd-module-exports.js @@ -16,10 +16,6 @@ describe('UMD module.export', function() { ); }); - it('should ensure axe source includes axios', function() { - assert.isTrue(axe.source.includes(axe.imports.axios.toString())); - }); - it('should include doT', function() { var doT = axe.imports.doT; assert(doT, 'doT is registered on axe.imports'); diff --git a/test/integration/full/umd/umd-window.js b/test/integration/full/umd/umd-window.js index 70887abc38..50d9abe5f7 100644 --- a/test/integration/full/umd/umd-window.js +++ b/test/integration/full/umd/umd-window.js @@ -49,16 +49,4 @@ describe('UMD window', function() { it('should ensure axe has prototype chained keys', function() { assert.hasAnyKeys(axe, ['utils', 'commons', 'core']); }); - - it('should expose not expose axios as a property of window', function() { - assert.notProperty(window, 'axios'); - }); - - it('should ensure axios is a mounted to axe.imports', function() { - assert.hasAnyKeys(axe.imports, ['axios']); - }); - - it('should ensure axios has prototype chained keys', function() { - assert.hasAnyKeys(axe.imports.axios, ['get', 'request', 'options', 'post']); - }); }); From d72e2c3983ec0ceece67ba3d26c77b3e71eeded9 Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Thu, 19 Nov 2020 12:05:36 -0700 Subject: [PATCH 6/6] chore(release): 4.1.1 --- CHANGELOG.md | 7 +++++++ bower.json | 2 +- package-lock.json | 2 +- package.json | 2 +- sri-history.json | 4 ++++ 5 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34ed899bc0..f4793e2403 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [4.1.1](https://github.com/dequelabs/axe-core/compare/v4.1.0...v4.1.1) (2020-11-19) + +### Bug Fixes + +- remove axios import ([#2653](https://github.com/dequelabs/axe-core/issues/2653)) ([2d20cde](https://github.com/dequelabs/axe-core/commit/2d20cdee819265f3c4efcb3ccb61da0a9a88981c)) +- **color-contrast:** greatly improve color-contrast-matches speed. add aria/get-accessible-ref ([#2635](https://github.com/dequelabs/axe-core/issues/2635)) ([ba174bd](https://github.com/dequelabs/axe-core/commit/ba174bd5496d7146c1baf982cb762444cda26cff)) + ## [4.1.0](https://github.com/dequelabs/axe-core/compare/v4.0.2...v4.1.0) (2020-11-13) ### Features diff --git a/bower.json b/bower.json index 3981ff094d..a870e4ede7 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "4.1.0", + "version": "4.1.1", "contributors": [ { "name": "David Sturley", diff --git a/package-lock.json b/package-lock.json index 6fcb233438..7b7a697c81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "4.1.0", + "version": "4.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index dee6600b6c..d16519fa0f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "axe-core", "description": "Accessibility engine for automated Web UI testing", - "version": "4.1.0", + "version": "4.1.1", "license": "MPL-2.0", "engines": { "node": ">=4" diff --git a/sri-history.json b/sri-history.json index 2db71d50e7..416a32eab8 100644 --- a/sri-history.json +++ b/sri-history.json @@ -206,5 +206,9 @@ "4.1.0": { "axe.js": "sha256-CpsJouSBpr1ARLhTtIKjZlS1eMWKAdb6k2USgtNGHQI=", "axe.min.js": "sha256-Slv1vTH024BG87L3ztHvVdlKpeQXG+wN5BcNMcHp1qk=" + }, + "4.1.1": { + "axe.js": "sha256-Tz6yShAcKxP8ce//YhbRkOLinUj+htEK2MjIi5yjY58=", + "axe.min.js": "sha256-43geecotYZGPnRt6o9Uu7Pjl3GthxnDacXrzCGv11go=" } }