diff --git a/CHANGELOG.md b/CHANGELOG.md index f0fe5c41da..6fa5753efc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ 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.8.4](https://github.com/dequelabs/axe-core/compare/v4.8.3...v4.8.4) (2024-02-07) + +### Bug Fixes + +- Add LICENSE-3RD-PARTY.txt file ([#4304](https://github.com/dequelabs/axe-core/issues/4304)) ([139c553](https://github.com/dequelabs/axe-core/commit/139c5535c72e926f03bb37a9ba0b7fd6b97cba8c)) +- avoid reading element-specific node properties of non-element node types ([#4317](https://github.com/dequelabs/axe-core/issues/4317)) ([a2a6935](https://github.com/dequelabs/axe-core/commit/a2a69355ea5aafce14367cf967153f7958a8878c)), closes [#4316](https://github.com/dequelabs/axe-core/issues/4316) [#4316](https://github.com/dequelabs/axe-core/issues/4316) +- **d.ts:** RawNodesResult issues ([#4229](https://github.com/dequelabs/axe-core/issues/4229)) ([f105266](https://github.com/dequelabs/axe-core/commit/f1052662b3b8b57d520fcbd23a3e9d4a5660a7e1)) +- **d.ts:** RunOptions.reporter can be any string ([#4218](https://github.com/dequelabs/axe-core/issues/4218)) ([80de793](https://github.com/dequelabs/axe-core/commit/80de793362bbbffde85654e874942a26df0108a8)) +- **utils/get-selector:** ignore 'xmlns' attribute when generating a selector ([#4303](https://github.com/dequelabs/axe-core/issues/4303)) ([8c68546](https://github.com/dequelabs/axe-core/commit/8c6854661f4613d0b7a6ba98bbfdc0c9ca61b4d1)) + ### [4.8.3](https://github.com/dequelabs/axe-core/compare/v4.8.2...v4.8.3) (2023-12-18) ### Bug Fixes diff --git a/LICENSE-3RD-PARTY.txt b/LICENSE-3RD-PARTY.txt new file mode 100644 index 0000000000..368c293bef --- /dev/null +++ b/LICENSE-3RD-PARTY.txt @@ -0,0 +1,66 @@ +----------------------------------------------------------------------------- + MIT License + Applies to: + - colorjs.io; Copyright (c) 2021 Lea Verou, Chris Lilley + - core-js-pure; Copyright (c) 2014-2023 Denis Pushkarev + - css-selector-parser; Copyright (c) 2013 Dulin Marat + - doT.js; Copyright (c) 2011 Laura Doktorova + Software includes portions from jQote2 Copyright (c) 2010 aefxx, + http://aefxx.com/ licensed under the MIT license. + - emoji-regex; Copyright (c) Mathias Bynens + - es6-iterator; Copyright (c) 2013-2017 Mariusz Nowak (www.medikoo.com) + - es6-promise; + Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors + - event-emitter; Copyright (C) 2012-2015 Mariusz Nowak (www.medikoo.com) + - is-promise; Copyright (c) 2014 Forbes Lindesay + - lru-queue; Copyright (C) 2014 Mariusz Nowak (www.medikoo.com) + - typedarray; + Copyright (c) 2010, Linden Research, Inc. + Copyright (c) 2012, Joshua Bell + - weakmap-polyfill; Copyright (c) 2015-2021 polygonplanet +----------------------------------------------------------------------------- + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +----------------------------------------------------------------------------- + ISC License + Applies to: + - d; Copyright (c) 2013-2019, Mariusz Nowak, @medikoo, medikoo.com + - es5-ext; Copyright (c) 2011-2022, Mariusz Nowak, @medikoo, medikoo.com + - es6-symbol; Copyright (c) 2013-2019, Mariusz Nowak, @medikoo, medikoo.com + - es6-weak-map; Copyright (c) 2013-2018, Mariusz Nowak, @medikoo, medikoo.com + - ext; Copyright (c) 2011-2022, Mariusz Nowak, @medikoo, medikoo.com + - memoizee; Copyright (c) 2012-2018, Mariusz Nowak, @medikoo, medikoo.com + - next-tick; Copyright (c) 2012-2020, Mariusz Nowak, @medikoo, medikoo.com + - timers-ext; Copyright (c) 2013-2018, Mariusz Nowak, @medikoo, medikoo.com + - type; Copyright (c) 2019, Mariusz Nowak, @medikoo, medikoo.com +----------------------------------------------------------------------------- + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md index 1812ee0b1b..5697270b10 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,8 @@ Read the [documentation on contributing](CONTRIBUTING.md) ## Acknowledgements -Thanks to Marat Dulin for his [css-selector-parser](https://www.npmjs.com/package/css-selector-parser) implementation which is included for shadow DOM support. +Thanks to Marat Dulin for his [css-selector-parser](https://www.npmjs.com/package/css-selector-parser) implementation which is included for shadow DOM support. Another thank you to the [Slick Parser](https://github.com/mootools/slick/blob/master/Source/Slick.Parser.js) implementers for their contribution, we have used some of their algorithms in our shadow DOM support code. Thanks to Lea Verou and Chris Lilley for their [colorjs.io](https://colorjs.io/) library which we have used for converting between color formats. -Thanks to the [Slick Parser](https://github.com/mootools/slick/blob/master/Source/Slick.Parser.js) implementers for their contribution, we have used some of their algorithms in our shadow DOM support code. +## Licenses + +Axe-core is distributed under the [Mozilla Public License, version 2.0](LICENSE). It comes bundled with several dependencies which are distributed under their own terms. (See [LICENSE-3RD-PARTY.txt](LICENSE-3RD-PARTY.txt)) diff --git a/axe.d.ts b/axe.d.ts index 403e504873..8ecd9de75a 100644 --- a/axe.d.ts +++ b/axe.d.ts @@ -1,13 +1,12 @@ // Type definitions for axe-core // Project: https://github.com/dequelabs/axe-core -// Definitions by: Marcy Sutton declare namespace axe { type ImpactValue = 'minor' | 'moderate' | 'serious' | 'critical' | null; type TagValue = string; - type ReporterVersion = 'v1' | 'v2' | 'raw' | 'raw-env' | 'no-passes'; + type ReporterVersion = 'v1' | 'v2' | 'raw' | 'rawEnv' | 'no-passes'; type RunOnlyType = 'rule' | 'rules' | 'tag' | 'tags'; @@ -132,7 +131,7 @@ declare namespace axe { interface RunOptions { runOnly?: RunOnly | TagValue[] | string[] | string; rules?: RuleObject; - reporter?: ReporterVersion; + reporter?: ReporterVersion | string; resultTypes?: resultGroups[]; selectors?: boolean; ancestry?: boolean; @@ -333,6 +332,14 @@ declare namespace axe { xpath: string[]; ancestry: UnlabelledFrameSelector; } + interface DqElement extends SerialDqElement { + element: Element; + toJSON(): SerialDqElement; + mergeSpecs( + childSpec: SerialDqElement, + parentSpec: SerialDqElement + ): SerialDqElement; + } interface PartialRuleResult { id: string; result: 'inapplicable'; @@ -351,16 +358,21 @@ declare namespace axe { frameContext: FrameContextObject; } + interface RawCheckResult extends Omit { + relatedNodes?: Array; + } + interface RawNodeResult { - any: CheckResult[]; - all: CheckResult[]; - none: CheckResult[]; + node: SerialDqElement | DqElement; + any: RawCheckResult[]; + all: RawCheckResult[]; + none: RawCheckResult[]; impact: ImpactValue | null; result: T; } interface RawResult extends Omit { - inapplicable: []; + inapplicable: Array; passes: RawNodeResult<'passed'>[]; incomplete: RawNodeResult<'incomplete'>[]; violations: RawNodeResult<'failed'>[]; @@ -383,6 +395,7 @@ declare namespace axe { attr(attr: string): string | null; hasAttr(attr: string): boolean; props: { [key: string]: unknown }; + boundingClientRect: DOMRect; } interface Utils { @@ -396,7 +409,7 @@ declare namespace axe { DqElement: new ( elm: Element, options?: { absolutePaths?: boolean } - ) => SerialDqElement; + ) => DqElement; uuid: ( options?: { random?: Uint8Array | Array }, buf?: Uint8Array | Array, diff --git a/bower.json b/bower.json index fac1bab71a..27a80bd8c1 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "4.8.3", + "version": "4.8.4", "deprecated": true, "contributors": [ { diff --git a/doc/examples/mocha/package.json b/doc/examples/mocha/package.json index 002eb36f09..870909591d 100644 --- a/doc/examples/mocha/package.json +++ b/doc/examples/mocha/package.json @@ -13,6 +13,7 @@ }, "devDependencies": { "axe-core": "^4.6.2", + "chai": "^4.3.7", "karma": "^6.4.1", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^3.1.1", diff --git a/lib/core/base/virtual-node/virtual-node.js b/lib/core/base/virtual-node/virtual-node.js index 589b61237f..4a19c8b021 100644 --- a/lib/core/base/virtual-node/virtual-node.js +++ b/lib/core/base/virtual-node/virtual-node.js @@ -51,30 +51,25 @@ class VirtualNode extends AbstractVirtualNode { // add to the prototype so memory is shared across all virtual nodes get props() { if (!this._cache.hasOwnProperty('props')) { - const { - nodeType, - nodeName, - id, - multiple, - nodeValue, - value, - selected, - checked, - indeterminate - } = this.actualNode; + const { nodeType, nodeName, id, nodeValue } = this.actualNode; this._cache.props = { nodeType, nodeName: this._isXHTML ? nodeName : nodeName.toLowerCase(), id, type: this._type, - multiple, - nodeValue, - value, - selected, - checked, - indeterminate + nodeValue }; + + // We avoid reading these on node types where they won't be relevant + // to work around issues like #4316. + if (nodeType === 1) { + this._cache.props.multiple = this.actualNode.multiple; + this._cache.props.value = this.actualNode.value; + this._cache.props.selected = this.actualNode.selected; + this._cache.props.checked = this.actualNode.checked; + this._cache.props.indeterminate = this.actualNode.indeterminate; + } } return this._cache.props; diff --git a/lib/core/utils/get-selector.js b/lib/core/utils/get-selector.js index 28af5bf6af..a90d3c1528 100644 --- a/lib/core/utils/get-selector.js +++ b/lib/core/utils/get-selector.js @@ -22,7 +22,8 @@ const ignoredAttributes = [ 'aria-expanded', 'aria-grabbed', 'aria-pressed', - 'aria-valuenow' + 'aria-valuenow', + 'xmlns' ]; const MAXATTRIBUTELENGTH = 31; const attrCharsRegex = /([\\"])/g; diff --git a/package-lock.json b/package-lock.json index 820325f185..1f52d91f50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "axe-core", - "version": "4.8.3", + "version": "4.8.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "axe-core", - "version": "4.8.3", + "version": "4.8.4", "license": "MPL-2.0", "devDependencies": { "@axe-core/webdriverjs": "^4.5.2", diff --git a/package.json b/package.json index a6c34d59c3..74c6186edd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "axe-core", "description": "Accessibility engine for automated Web UI testing", - "version": "4.8.3", + "version": "4.8.4", "license": "MPL-2.0", "engines": { "node": ">=4" @@ -55,7 +55,8 @@ "axe.min.js", "axe.d.ts", "sri-history.json", - "locales/" + "locales/", + "LICENSE-3RD-PARTY.txt" ], "standard-version": { "scripts": { diff --git a/sri-history.json b/sri-history.json index a842d92fa6..48678b1c35 100644 --- a/sri-history.json +++ b/sri-history.json @@ -366,5 +366,9 @@ "4.8.3": { "axe.js": "sha256-YWpAAdIo7fwKmLq8nJx1f6pwt7HAXwWm15RSGJKbxhw=", "axe.min.js": "sha256-/mct+I/4SJnZ30Ce+j9T7ll9zPwzbJVrjdKpbKIP+NA=" + }, + "4.8.4": { + "axe.js": "sha256-RRn+EjX3fX893zHeLzMQebvK4/HR3yZpVFNxsV3Pbm0=", + "axe.min.js": "sha256-HXl1GEx0+LwVB27fLmwgdXCmeTM2beVwwFosWvFzLmo=" } } diff --git a/test/core/base/virtual-node/virtual-node.js b/test/core/base/virtual-node/virtual-node.js index 771d3693b1..338442d75e 100644 --- a/test/core/base/virtual-node/virtual-node.js +++ b/test/core/base/virtual-node/virtual-node.js @@ -37,15 +37,45 @@ describe('VirtualNode', () => { assert.equal(vNode.props.type, 'text'); }); - it('should reflect selected property', () => { - node = document.createElement('option'); - let vNode = new VirtualNode(node); - assert.equal(vNode.props.selected, false); - - node.selected = true; - vNode = new VirtualNode(node); - assert.equal(vNode.props.selected, true); - }); + for (const [prop, tagName, examplePropValue] of [ + ['value', 'input', 'test value'], + ['selected', 'option', true], + ['checked', 'input', true], + ['indeterminate', 'input', true], + ['multiple', 'select', true] + ]) { + describe(`props.${prop}`, () => { + it(`should reflect a ${tagName} element's ${prop} property`, () => { + node = document.createElement(tagName); + let vNode = new VirtualNode(node); + assert.equal(vNode.props[prop], ''); + + node[prop] = examplePropValue; + vNode = new VirtualNode(node); + assert.equal(vNode.props[prop], examplePropValue); + }); + + it('should be undefined for a text node', () => { + node = document.createTextNode('text content'); + let vNode = new VirtualNode(node); + assert.equal(vNode.props[prop], undefined); + }); + + // Regression test for #4316 + it(`should be resilient to text node with un-gettable ${prop} property`, () => { + node = document.createTextNode('text content'); + Object.defineProperty(node, prop, { + get() { + throw new Error('Unqueryable value'); + } + }); + let vNode = new VirtualNode(node); + assert.throws(() => node[prop]); + assert.doesNotThrow(() => vNode.props[prop]); + assert.equal(vNode.props[prop], undefined); + }); + }); + } it('should lowercase type', () => { node = document.createElement('input'); diff --git a/test/core/utils/get-selector.js b/test/core/utils/get-selector.js index 520bfbea56..0a2cab5b78 100644 --- a/test/core/utils/get-selector.js +++ b/test/core/utils/get-selector.js @@ -471,7 +471,8 @@ describe('axe.utils.getSelector', function () { 'aria-expanded', 'aria-grabbed', 'aria-pressed', - 'aria-valuenow' + 'aria-valuenow', + 'xmlns' ]; ignoredAttributes.forEach(function (att) { node.setAttribute(att, 'true'); diff --git a/typings/axe-core/axe-core-tests.ts b/typings/axe-core/axe-core-tests.ts index bff4ec46da..5d421307a8 100644 --- a/typings/axe-core/axe-core-tests.ts +++ b/typings/axe-core/axe-core-tests.ts @@ -2,7 +2,13 @@ import * as axe from '../../axe'; var context: any = document; var $fixture = [document]; -var options = { iframes: false, selectors: false, elementRef: false }; +var options: axe.RunOptions = { + iframes: false, + selectors: false, + elementRef: false +}; +options.reporter = 'rawEnv'; +options.reporter = 'custom'; axe.run(context, {}, (error: Error, results: axe.AxeResults) => { if (error) { @@ -344,6 +350,44 @@ axe.configure({ } }); +const results: axe.RawResult[] = [ + { + id: 'the-best-rule', + result: 'passed', + pageLevel: false, + impact: null, + tags: ['best-practice'], + description: 'Be cool', + help: 'No, cooler', + helpUrl: + 'https://dequeuniversity.com/rules/axe/4.8/the-best-rule?application=axeAPI', + inapplicable: [], + passes: [ + { + any: [ + { + id: 'the-best-check', + data: null, + impact: 'serious', + message: 'Element has sufficient color contrast of 21', + relatedNodes: [ + new axe.utils.DqElement(document.body), + new axe.utils.DqElement(document.body).toJSON() + ] + } + ], + all: [], + none: [], + impact: null, + result: 'passed', + node: new axe.utils.DqElement(document.body) + } + ], + incomplete: [], + violations: [] + } +]; + // Reporters let fooReporter = ( results: axe.RawResult[],