govuk_publishing_components 21.16.3 → 21.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/component_guide/accessibility-test.js +0 -6
  3. data/app/views/govuk_publishing_components/components/docs/machine_readable_metadata.yml +7 -0
  4. data/lib/govuk_publishing_components/presenters/machine_readable/dataset_schema.rb +34 -0
  5. data/lib/govuk_publishing_components/presenters/schema_org.rb +3 -0
  6. data/lib/govuk_publishing_components/version.rb +1 -1
  7. data/node_modules/axe-core/CHANGELOG.md +166 -2
  8. data/node_modules/axe-core/CONTRIBUTING.md +5 -5
  9. data/node_modules/axe-core/README.md +4 -4
  10. data/node_modules/axe-core/axe.d.ts +27 -11
  11. data/node_modules/axe-core/axe.js +9597 -2431
  12. data/node_modules/axe-core/axe.min.js +2 -2
  13. data/node_modules/axe-core/bower.json +1 -1
  14. data/node_modules/axe-core/doc/API.md +211 -128
  15. data/node_modules/axe-core/doc/accessibility-supported.md +1 -1
  16. data/node_modules/axe-core/doc/aria-supported.md +4 -13
  17. data/node_modules/axe-core/doc/backwards-compatibility-doc.md +93 -0
  18. data/node_modules/axe-core/doc/code-submission-guidelines.md +4 -4
  19. data/node_modules/axe-core/doc/developer-guide.md +27 -13
  20. data/node_modules/axe-core/doc/examples/chrome-debugging-protocol/package.json +5 -2
  21. data/node_modules/axe-core/doc/examples/jasmine/README.md +3 -5
  22. data/node_modules/axe-core/doc/examples/jasmine/karma.conf.js +29 -0
  23. data/node_modules/axe-core/doc/examples/jasmine/package.json +6 -5
  24. data/node_modules/axe-core/doc/examples/jest_react/README.md +1 -1
  25. data/node_modules/axe-core/doc/examples/jest_react/link.test.js +3 -3
  26. data/node_modules/axe-core/doc/examples/jest_react/package.json +9 -9
  27. data/node_modules/axe-core/doc/examples/jsdom/package.json +15 -0
  28. data/node_modules/axe-core/doc/examples/mocha/README.md +5 -7
  29. data/node_modules/axe-core/doc/examples/mocha/karma.conf.js +29 -0
  30. data/node_modules/axe-core/doc/examples/mocha/package.json +7 -6
  31. data/node_modules/axe-core/doc/examples/phantomjs/README.md +3 -3
  32. data/node_modules/axe-core/doc/examples/phantomjs/axe-phantom.js +4 -2
  33. data/node_modules/axe-core/doc/examples/phantomjs/package.json +3 -3
  34. data/node_modules/axe-core/doc/examples/puppeteer/package.json +5 -2
  35. data/node_modules/axe-core/doc/examples/qunit/README.md +2 -2
  36. data/node_modules/axe-core/doc/examples/qunit/package.json +2 -2
  37. data/node_modules/axe-core/doc/examples/test-examples.js +32 -0
  38. data/node_modules/axe-core/doc/plugins.md +10 -10
  39. data/node_modules/axe-core/doc/projects.md +12 -8
  40. data/node_modules/axe-core/doc/rule-descriptions.md +87 -79
  41. data/node_modules/axe-core/doc/rule-development.md +30 -2
  42. data/node_modules/axe-core/lib/checks/aria/allowed-attr.js +1 -1
  43. data/node_modules/axe-core/lib/checks/aria/aria-roledescription.js +14 -0
  44. data/node_modules/axe-core/lib/checks/aria/aria-roledescription.json +23 -0
  45. data/node_modules/axe-core/lib/checks/aria/no-implicit-explicit-label.js +21 -0
  46. data/node_modules/axe-core/lib/checks/aria/no-implicit-explicit-label.json +11 -0
  47. data/node_modules/axe-core/lib/checks/aria/required-attr.js +32 -7
  48. data/node_modules/axe-core/lib/checks/aria/required-children.js +40 -14
  49. data/node_modules/axe-core/lib/checks/aria/required-children.json +12 -1
  50. data/node_modules/axe-core/lib/checks/aria/required-parent.js +1 -1
  51. data/node_modules/axe-core/lib/checks/aria/unsupportedattr.js +1 -1
  52. data/node_modules/axe-core/lib/checks/aria/valid-attr-value.js +50 -17
  53. data/node_modules/axe-core/lib/checks/aria/valid-attr-value.json +2 -1
  54. data/node_modules/axe-core/lib/checks/aria/valid-attr.js +1 -1
  55. data/node_modules/axe-core/lib/checks/color/color-contrast.js +2 -2
  56. data/node_modules/axe-core/lib/checks/forms/autocomplete-appropriate.js +4 -4
  57. data/node_modules/axe-core/lib/checks/forms/autocomplete-valid.js +1 -1
  58. data/node_modules/axe-core/lib/checks/forms/fieldset.json +1 -0
  59. data/node_modules/axe-core/lib/checks/forms/group-labelledby.json +1 -0
  60. data/node_modules/axe-core/lib/checks/keyboard/focusable-content.js +16 -0
  61. data/node_modules/axe-core/lib/checks/keyboard/focusable-content.json +11 -0
  62. data/node_modules/axe-core/lib/checks/keyboard/focusable-element.js +12 -0
  63. data/node_modules/axe-core/lib/checks/keyboard/focusable-element.json +11 -0
  64. data/node_modules/axe-core/lib/checks/keyboard/tabindex.js +6 -1
  65. data/node_modules/axe-core/lib/checks/label/alt-space-value.js +3 -2
  66. data/node_modules/axe-core/lib/checks/label/duplicate-img-label.js +18 -15
  67. data/node_modules/axe-core/lib/checks/label/label-content-name-mismatch.js +13 -3
  68. data/node_modules/axe-core/lib/checks/label/label-content-name-mismatch.json +4 -0
  69. data/node_modules/axe-core/lib/checks/label/multiple-label.js +22 -12
  70. data/node_modules/axe-core/lib/checks/label/multiple-label.json +1 -1
  71. data/node_modules/axe-core/lib/checks/landmarks/landmark-is-unique-after.js +23 -0
  72. data/node_modules/axe-core/lib/checks/landmarks/landmark-is-unique.js +7 -0
  73. data/node_modules/axe-core/lib/checks/landmarks/landmark-is-unique.json +12 -0
  74. data/node_modules/axe-core/lib/checks/lists/listitem.js +1 -0
  75. data/node_modules/axe-core/lib/checks/lists/listitem.json +1 -1
  76. data/node_modules/axe-core/lib/checks/lists/only-listitems.js +0 -4
  77. data/node_modules/axe-core/lib/checks/mobile/css-orientation-lock.js +8 -6
  78. data/node_modules/axe-core/lib/checks/navigation/region.js +2 -19
  79. data/node_modules/axe-core/lib/checks/shared/avoid-inline-spacing.js +18 -0
  80. data/node_modules/axe-core/lib/checks/shared/avoid-inline-spacing.json +11 -0
  81. data/node_modules/axe-core/lib/checks/shared/exists.js +1 -1
  82. data/node_modules/axe-core/lib/checks/shared/exists.json +1 -1
  83. data/node_modules/axe-core/lib/checks/shared/has-alt.js +6 -4
  84. data/node_modules/axe-core/lib/checks/shared/non-empty-alt.js +1 -1
  85. data/node_modules/axe-core/lib/checks/tables/caption-faked.json +1 -1
  86. data/node_modules/axe-core/lib/checks/tables/td-has-header.js +5 -4
  87. data/node_modules/axe-core/lib/checks/tables/th-has-data-cells.js +19 -29
  88. data/node_modules/axe-core/lib/commons/aria/get-owned-virtual.js +1 -1
  89. data/node_modules/axe-core/lib/commons/aria/index.js +50 -46
  90. data/node_modules/axe-core/lib/commons/aria/is-accessible-ref.js +41 -37
  91. data/node_modules/axe-core/lib/commons/aria/label-virtual.js +2 -2
  92. data/node_modules/axe-core/lib/commons/aria/roles.js +1 -1
  93. data/node_modules/axe-core/lib/commons/aria/validate-attr-value.js +0 -1
  94. data/node_modules/axe-core/lib/commons/color/center-point-of-rect.js +30 -0
  95. data/node_modules/axe-core/lib/commons/color/contrast.js +7 -1
  96. data/node_modules/axe-core/lib/commons/color/element-has-image.js +36 -0
  97. data/node_modules/axe-core/lib/commons/color/get-background-color.js +332 -306
  98. data/node_modules/axe-core/lib/commons/color/get-foreground-color.js +35 -6
  99. data/node_modules/axe-core/lib/commons/color/get-own-background-color.js +22 -0
  100. data/node_modules/axe-core/lib/commons/dom/find-up.js +5 -5
  101. data/node_modules/axe-core/lib/commons/dom/get-scroll-offset.js +0 -1
  102. data/node_modules/axe-core/lib/commons/dom/get-tabbable-elements.js +1 -1
  103. data/node_modules/axe-core/lib/commons/dom/has-content-virtual.js +7 -5
  104. data/node_modules/axe-core/lib/commons/dom/is-focusable.js +8 -5
  105. data/node_modules/axe-core/lib/commons/dom/is-hidden-with-css.js +15 -2
  106. data/node_modules/axe-core/lib/commons/dom/is-in-text-block.js +1 -2
  107. data/node_modules/axe-core/lib/commons/dom/is-skip-link.js +45 -0
  108. data/node_modules/axe-core/lib/commons/dom/is-visible.js +43 -17
  109. data/node_modules/axe-core/lib/commons/dom/is-visual-content.js +0 -1
  110. data/node_modules/axe-core/lib/commons/dom/visually-contains.js +0 -1
  111. data/node_modules/axe-core/lib/commons/dom/visually-overlaps.js +0 -1
  112. data/node_modules/axe-core/lib/commons/forms/index.js +8 -0
  113. data/node_modules/axe-core/lib/commons/forms/is-aria-combobox.js +13 -0
  114. data/node_modules/axe-core/lib/commons/forms/is-aria-listbox.js +13 -0
  115. data/node_modules/axe-core/lib/commons/forms/is-aria-range.js +14 -0
  116. data/node_modules/axe-core/lib/commons/forms/is-aria-textbox.js +13 -0
  117. data/node_modules/axe-core/lib/commons/forms/is-native-select.js +13 -0
  118. data/node_modules/axe-core/lib/commons/forms/is-native-textbox.js +28 -0
  119. data/node_modules/axe-core/lib/commons/table/get-cell-position.js +2 -2
  120. data/node_modules/axe-core/lib/commons/table/get-headers.js +55 -11
  121. data/node_modules/axe-core/lib/commons/table/get-scope.js +1 -1
  122. data/node_modules/axe-core/lib/commons/table/is-data-table.js +0 -1
  123. data/node_modules/axe-core/lib/commons/table/to-grid.js +2 -2
  124. data/node_modules/axe-core/lib/commons/text/accessible-text-virtual.js +5 -5
  125. data/node_modules/axe-core/lib/commons/text/form-control-value.js +18 -30
  126. data/node_modules/axe-core/lib/commons/text/is-icon-ligature.js +210 -0
  127. data/node_modules/axe-core/lib/commons/text/is-valid-autocomplete.js +0 -1
  128. data/node_modules/axe-core/lib/commons/text/label-text.js +1 -1
  129. data/node_modules/axe-core/lib/commons/text/label-virtual.js +1 -1
  130. data/node_modules/axe-core/lib/commons/text/native-text-methods.js +1 -1
  131. data/node_modules/axe-core/lib/commons/text/subtree-text.js +3 -3
  132. data/node_modules/axe-core/lib/commons/text/unicode.js +15 -0
  133. data/node_modules/axe-core/lib/commons/text/visible-text-nodes.js +25 -0
  134. data/node_modules/axe-core/lib/commons/text/visible-virtual.js +1 -1
  135. data/node_modules/axe-core/lib/core/base/audit.js +90 -15
  136. data/node_modules/axe-core/lib/core/base/cache.js +33 -0
  137. data/node_modules/axe-core/lib/core/base/check.js +48 -1
  138. data/node_modules/axe-core/lib/core/base/context.js +15 -14
  139. data/node_modules/axe-core/lib/core/base/rule.js +223 -46
  140. data/node_modules/axe-core/lib/core/base/virtual-node/abstract-virtual-node.js +40 -0
  141. data/node_modules/axe-core/lib/core/base/virtual-node/serial-virtual-node.js +86 -0
  142. data/node_modules/axe-core/lib/core/base/virtual-node/virtual-node.js +85 -0
  143. data/node_modules/axe-core/lib/core/constants.js +10 -2
  144. data/node_modules/axe-core/lib/core/imports/index.js +28 -3
  145. data/node_modules/axe-core/lib/core/index.js +2 -4
  146. data/node_modules/axe-core/lib/core/public/configure.js +28 -1
  147. data/node_modules/axe-core/lib/core/public/run-rules.js +2 -0
  148. data/node_modules/axe-core/lib/core/public/run-virtual-rule.js +50 -0
  149. data/node_modules/axe-core/lib/core/public/run.js +13 -2
  150. data/node_modules/axe-core/lib/core/reporters/helpers/process-aggregate.js +1 -1
  151. data/node_modules/axe-core/lib/core/reporters/na.js +4 -0
  152. data/node_modules/axe-core/lib/core/reporters/raw-env.js +12 -0
  153. data/node_modules/axe-core/lib/core/reporters/raw.js +25 -1
  154. data/node_modules/axe-core/lib/core/utils/are-styles-set.js +4 -7
  155. data/node_modules/axe-core/lib/core/utils/assert.js +12 -0
  156. data/node_modules/axe-core/lib/core/utils/collect-results-from-frames.js +2 -2
  157. data/node_modules/axe-core/lib/core/utils/contains.js +27 -13
  158. data/node_modules/axe-core/lib/core/utils/css-parser.js +2 -0
  159. data/node_modules/axe-core/lib/core/utils/element-matches.js +5 -1
  160. data/node_modules/axe-core/lib/core/utils/escape-selector.js +1 -2
  161. data/node_modules/axe-core/lib/core/utils/flattened-tree.js +47 -61
  162. data/node_modules/axe-core/lib/core/utils/get-check-option.js +0 -1
  163. data/node_modules/axe-core/lib/core/utils/get-friendly-uri-end.js +1 -1
  164. data/node_modules/axe-core/lib/core/utils/get-node-attributes.js +21 -0
  165. data/node_modules/axe-core/lib/core/utils/get-scroll.js +39 -0
  166. data/node_modules/axe-core/lib/core/utils/get-selector.js +9 -6
  167. data/node_modules/axe-core/lib/core/utils/get-stylesheet-factory.js +51 -0
  168. data/node_modules/axe-core/lib/core/utils/get-xpath.js +0 -1
  169. data/node_modules/axe-core/lib/core/utils/is-hidden.js +16 -4
  170. data/node_modules/axe-core/lib/core/utils/is-html-element.js +5 -5
  171. data/node_modules/axe-core/lib/core/utils/is-shadow-root.js +3 -3
  172. data/node_modules/axe-core/lib/core/utils/memoize.js +17 -0
  173. data/node_modules/axe-core/lib/core/utils/parse-crossorigin-stylesheet.js +53 -0
  174. data/node_modules/axe-core/lib/core/utils/parse-sameorigin-stylesheet.js +96 -0
  175. data/node_modules/axe-core/lib/core/utils/parse-stylesheet.js +70 -0
  176. data/node_modules/axe-core/lib/core/utils/performance-timer.js +7 -2
  177. data/node_modules/axe-core/lib/core/utils/preload-cssom.js +77 -281
  178. data/node_modules/axe-core/lib/core/utils/preload.js +49 -23
  179. data/node_modules/axe-core/lib/core/utils/qsa.js +39 -50
  180. data/node_modules/axe-core/lib/core/utils/respondable.js +20 -3
  181. data/node_modules/axe-core/lib/core/utils/rule-should-run.js +0 -1
  182. data/node_modules/axe-core/lib/core/utils/scroll-state.js +12 -25
  183. data/node_modules/axe-core/lib/core/utils/select.js +12 -23
  184. data/node_modules/axe-core/lib/core/utils/uuid.js +1 -2
  185. data/node_modules/axe-core/lib/intro.stub +1 -1
  186. data/node_modules/axe-core/lib/misc/incomplete-fallback.json +1 -1
  187. data/node_modules/axe-core/lib/rules/aria-allowed-attr-matches.js +1 -1
  188. data/node_modules/axe-core/lib/rules/aria-form-field-name-matches.js +50 -0
  189. data/node_modules/axe-core/lib/rules/aria-has-attr-matches.js +1 -1
  190. data/node_modules/axe-core/lib/rules/aria-hidden-focus.json +1 -1
  191. data/node_modules/axe-core/lib/rules/aria-input-field-name.json +13 -0
  192. data/node_modules/axe-core/lib/rules/aria-roledescription.json +12 -0
  193. data/node_modules/axe-core/lib/rules/aria-toggle-field-name.json +18 -0
  194. data/node_modules/axe-core/lib/rules/autocomplete-matches.js +14 -10
  195. data/node_modules/axe-core/lib/rules/avoid-inline-spacing.json +12 -0
  196. data/node_modules/axe-core/lib/rules/button-name.json +3 -5
  197. data/node_modules/axe-core/lib/rules/checkboxgroup.json +2 -1
  198. data/node_modules/axe-core/lib/rules/color-contrast-matches.js +37 -22
  199. data/node_modules/axe-core/lib/rules/duplicate-id-active-matches.js +1 -1
  200. data/node_modules/axe-core/lib/rules/duplicate-id-misc-matches.js +2 -2
  201. data/node_modules/axe-core/lib/rules/form-field-multiple-labels.json +1 -1
  202. data/node_modules/axe-core/lib/rules/html-has-lang.json +1 -0
  203. data/node_modules/axe-core/lib/rules/image-alt.json +1 -1
  204. data/node_modules/axe-core/lib/rules/img-redundant-alt.json +3 -3
  205. data/node_modules/axe-core/lib/rules/input-button-name.json +26 -0
  206. data/node_modules/axe-core/lib/rules/landmark-unique-matches.js +41 -0
  207. data/node_modules/axe-core/lib/rules/landmark-unique.json +13 -0
  208. data/node_modules/axe-core/lib/rules/link-name.json +5 -4
  209. data/node_modules/axe-core/lib/rules/meta-refresh.json +8 -1
  210. data/node_modules/axe-core/lib/rules/radiogroup.json +2 -1
  211. data/node_modules/axe-core/lib/rules/role-img-alt.json +18 -0
  212. data/node_modules/axe-core/lib/rules/scrollable-region-focusable-matches.js +30 -0
  213. data/node_modules/axe-core/lib/rules/scrollable-region-focusable.json +12 -0
  214. data/node_modules/axe-core/lib/rules/skip-link-matches.js +1 -1
  215. data/node_modules/axe-core/lib/rules/skip-link.json +1 -1
  216. data/node_modules/axe-core/lib/rules/video-description.json +3 -1
  217. data/node_modules/axe-core/locales/de.json +1 -5
  218. data/node_modules/axe-core/locales/es.json +773 -0
  219. data/node_modules/axe-core/locales/fr.json +15 -19
  220. data/node_modules/axe-core/locales/ja.json +65 -11
  221. data/node_modules/axe-core/locales/ko.json +777 -0
  222. data/node_modules/axe-core/locales/nl.json +35 -35
  223. data/node_modules/axe-core/locales/pt_BR.json +773 -0
  224. data/node_modules/axe-core/package.json +56 -52
  225. data/node_modules/axe-core/sri-history.json +20 -0
  226. data/node_modules/axe-core/typings/axe-core/axe-core-tests.ts +5 -15
  227. data/node_modules/govuk-frontend/package.json +10 -10
  228. metadata +62 -4
  229. data/node_modules/axe-core/doc/axelogo2018.png +0 -0
  230. data/node_modules/axe-core/lib/rules/role-not-button-matches.js +0 -1
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Memoize a function.
3
+ * @method memoize
4
+ * @memberof axe.utils
5
+ * @param {Function} fn Function to memoize
6
+ * @return {Function}
7
+ */
8
+ axe._memoizedFns = [];
9
+ axe.utils.memoize = function(fn) {
10
+ // keep track of each function that is memoized so it can be cleared at
11
+ // the end of a run. each memoized function has its own cache, so there is
12
+ // no method to clear all memoized caches. instead, we have to clear each
13
+ // individual memoized function ourselves.
14
+ const memoized = axe.imports.memoize(fn);
15
+ axe._memoizedFns.push(memoized);
16
+ return memoized;
17
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Parse cross-origin stylesheets
3
+ *
4
+ * @method parseCrossOriginStylesheet
5
+ * @memberof axe.utils
6
+ * @param {String} url url from which to fetch stylesheet
7
+ * @param {Object} options options object from `axe.utils.parseStylesheet`
8
+ * @param {Array<Number>} priority sheet priority
9
+ * @param {Array<String>} importedUrls urls of already imported stylesheets
10
+ * @param {Boolean} isCrossOrigin boolean denoting if a stylesheet is `cross-origin`
11
+ * @returns {Promise}
12
+ */
13
+ axe.utils.parseCrossOriginStylesheet = function parseCrossOriginStylesheet(
14
+ url,
15
+ options,
16
+ priority,
17
+ importedUrls,
18
+ isCrossOrigin
19
+ ) {
20
+ const axiosOptions = {
21
+ method: 'get',
22
+ url
23
+ };
24
+
25
+ /**
26
+ * Add `url` to `importedUrls`
27
+ */
28
+ importedUrls.push(url);
29
+
30
+ /**
31
+ * Fetch `cross-origin stylesheet` via axios
32
+ */
33
+ return axe.imports.axios(axiosOptions).then(({ data }) => {
34
+ const result = options.convertDataToStylesheet({
35
+ data,
36
+ isCrossOrigin,
37
+ priority,
38
+ root: options.rootNode,
39
+ shadowId: options.shadowId
40
+ });
41
+
42
+ /**
43
+ * Parse resolved stylesheet further for any `@import` styles
44
+ */
45
+ return axe.utils.parseStylesheet(
46
+ result.sheet,
47
+ options,
48
+ priority,
49
+ importedUrls,
50
+ result.isCrossOrigin
51
+ );
52
+ });
53
+ };
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Parse non cross-origin stylesheets
3
+ *
4
+ * @method parseSameOriginStylesheet
5
+ * @memberof axe.utils
6
+ * @param {Object} sheet CSSStylesheet object
7
+ * @param {Object} options options object from `axe.utils.parseStylesheet`
8
+ * @param {Array<Number>} priority sheet priority
9
+ * @param {Array<String>} importedUrls urls of already imported stylesheets
10
+ * @param {Boolean} isCrossOrigin boolean denoting if a stylesheet is `cross-origin`
11
+ * @returns {Promise}
12
+ */
13
+ axe.utils.parseSameOriginStylesheet = function parseSameOriginStylesheet(
14
+ sheet,
15
+ options,
16
+ priority,
17
+ importedUrls,
18
+ isCrossOrigin = false
19
+ ) {
20
+ const rules = Array.from(sheet.cssRules);
21
+
22
+ if (!rules) {
23
+ return Promise.resolve();
24
+ }
25
+
26
+ /**
27
+ * reference -> https://developer.mozilla.org/en-US/docs/Web/API/CSSRule#Type_constants
28
+ */
29
+ const cssImportRules = rules.filter(r => r.type === 3); // type === 3 -> CSSRule.IMPORT_RULE
30
+
31
+ /**
32
+ * when no `@import` rules in given sheet -> resolve the current `sheet` & exit
33
+ */
34
+ if (!cssImportRules.length) {
35
+ // exit
36
+ return Promise.resolve({
37
+ isCrossOrigin,
38
+ priority,
39
+ root: options.rootNode,
40
+ shadowId: options.shadowId,
41
+ sheet
42
+ });
43
+ }
44
+
45
+ /**
46
+ * filter rules that are not already fetched
47
+ */
48
+ const cssImportUrlsNotAlreadyImported = cssImportRules
49
+ // ensure rule has a href
50
+ .filter(rule => rule.href)
51
+ // extract href from object
52
+ .map(rule => rule.href)
53
+ // only href that are not already imported
54
+ .filter(url => !importedUrls.includes(url));
55
+
56
+ /**
57
+ * iterate `@import` rules and fetch styles
58
+ */
59
+ const promises = cssImportUrlsNotAlreadyImported.map(
60
+ (importUrl, cssRuleIndex) => {
61
+ const newPriority = [...priority, cssRuleIndex];
62
+ const isCrossOriginRequest = /^https?:\/\/|^\/\//i.test(importUrl);
63
+
64
+ return axe.utils.parseCrossOriginStylesheet(
65
+ importUrl,
66
+ options,
67
+ newPriority,
68
+ importedUrls,
69
+ isCrossOriginRequest
70
+ );
71
+ }
72
+ );
73
+
74
+ const nonImportCSSRules = rules.filter(r => r.type !== 3);
75
+
76
+ // no further rules to process in this sheet
77
+ if (!nonImportCSSRules.length) {
78
+ return Promise.all(promises);
79
+ }
80
+
81
+ // convert all `nonImportCSSRules` style rules into `text` and chain
82
+
83
+ promises.push(
84
+ Promise.resolve(
85
+ options.convertDataToStylesheet({
86
+ data: nonImportCSSRules.map(rule => rule.cssText).join(),
87
+ isCrossOrigin,
88
+ priority,
89
+ root: options.rootNode,
90
+ shadowId: options.shadowId
91
+ })
92
+ )
93
+ );
94
+
95
+ return Promise.all(promises);
96
+ };
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Parse a given stylesheet
3
+ *
4
+ * @method parseStylesheet
5
+ * @memberof axe.utils
6
+ * @param {Object} sheet stylesheet to parse
7
+ * @param {Object} options configuration options object from `axe.utils.parseStylesheets`
8
+ * @param {Array<Number>} priority priority of stylesheet
9
+ * @param {Array<String>} importedUrls list of resolved `@import` urls
10
+ * @param {Boolean} isCrossOrigin boolean denoting if a stylesheet is `cross-origin`, passed for re-parsing `cross-origin` sheets
11
+ * @returns {Promise}
12
+ */
13
+ axe.utils.parseStylesheet = function parseStylesheet(
14
+ sheet,
15
+ options,
16
+ priority,
17
+ importedUrls,
18
+ isCrossOrigin = false
19
+ ) {
20
+ const isSameOrigin = isSameOriginStylesheet(sheet);
21
+ if (isSameOrigin) {
22
+ /**
23
+ * resolve `same-origin` stylesheet
24
+ */
25
+ return axe.utils.parseSameOriginStylesheet(
26
+ sheet,
27
+ options,
28
+ priority,
29
+ importedUrls,
30
+ isCrossOrigin
31
+ );
32
+ }
33
+
34
+ /**
35
+ * resolve `cross-origin` stylesheet
36
+ */
37
+ return axe.utils.parseCrossOriginStylesheet(
38
+ sheet.href,
39
+ options,
40
+ priority,
41
+ importedUrls,
42
+ true // -> isCrossOrigin
43
+ );
44
+ };
45
+
46
+ /**
47
+ * Check if a given stylesheet is from the `same-origin`
48
+ * Note:
49
+ * `sheet.cssRules` throws an error on `cross-origin` stylesheets
50
+ *
51
+ * @param {Object} sheet CSS stylesheet
52
+ * @returns {Boolean}
53
+ */
54
+ function isSameOriginStylesheet(sheet) {
55
+ try {
56
+ /*eslint no-unused-vars: 0*/
57
+ const rules = sheet.cssRules;
58
+
59
+ /**
60
+ * Safari, does not throw an error when accessing cssRules property,
61
+ */
62
+ if (!rules && sheet.href) {
63
+ return false;
64
+ }
65
+
66
+ return true;
67
+ } catch (e) {
68
+ return false;
69
+ }
70
+ }
@@ -27,7 +27,7 @@ utils.performanceTimer = (function() {
27
27
  */
28
28
  return {
29
29
  /**
30
- * @member {Function} start Kicks off performance timing for overall aXe audit
30
+ * @member {Function} start Kicks off performance timing for overall axe audit
31
31
  */
32
32
  start: function() {
33
33
  this.mark('mark_axe_start');
@@ -90,7 +90,12 @@ utils.performanceTimer = (function() {
90
90
  window.performance &&
91
91
  window.performance.getEntriesByType !== undefined
92
92
  ) {
93
- var measures = window.performance.getEntriesByType('measure');
93
+ // only output measures that were started after axe started, otherwise
94
+ // we get measures made by the page before axe ran (which is confusing)
95
+ var axeStart = window.performance.getEntriesByName('mark_axe_start')[0];
96
+ var measures = window.performance
97
+ .getEntriesByType('measure')
98
+ .filter(measure => measure.startTime >= axeStart.startTime);
94
99
  for (var i = 0; i < measures.length; ++i) {
95
100
  var req = measures[i];
96
101
  if (req.name === measureName) {
@@ -1,51 +1,41 @@
1
1
  /**
2
- * NOTE:
2
+ * NOTE:
3
3
  * this `eslint` rule is disabled because of calling `getStyleSheetFactory` before it is defined (further below).
4
4
  */
5
5
  /* eslint no-use-before-define: 0 */
6
6
 
7
-
8
7
  /**
9
8
  * Given a rootNode - construct CSSOM
10
9
  * -> get all source nodes (document & document fragments) within given root node
11
- * -> recursively call `loadCssom` to resolve styles
10
+ * -> recursively call `axe.utils.parseStylesheets` to resolve styles for each node
12
11
  *
13
12
  * @method preloadCssom
14
13
  * @memberof `axe.utils`
15
- *
16
- * @param {Object} object argument which is a composite object, with attributes timeout, treeRoot(optional), resolve & reject
17
- * @property {Number} timeout timeout for any network calls made
18
- * @property {Object} treeRoot the DOM tree to be inspected
19
- * @returns {Object} `axe.utils.queue` with CSSOM assets
14
+ * @param {Object} options composite options object
15
+ * @property {Array<String>} options.assets array of preloaded assets requested, eg: [`cssom`]
16
+ * @property {Number} options.timeout timeout
17
+ * @property {Object} options.treeRoot (optional) the DOM tree to be inspected
18
+ * @returns {Promise}
20
19
  */
21
- axe.utils.preloadCssom = function preloadCssom({
22
- timeout,
23
- treeRoot = axe._tree[0]
24
- }) {
20
+ axe.utils.preloadCssom = function preloadCssom({ treeRoot = axe._tree[0] }) {
25
21
  /**
26
22
  * get all `document` and `documentFragment` with in given `tree`
27
23
  */
28
24
  const rootNodes = getAllRootNodesInTree(treeRoot);
29
25
 
30
- const q = axe.utils.queue();
31
-
32
26
  if (!rootNodes.length) {
33
- return q;
27
+ return Promise.resolve();
34
28
  }
35
29
 
36
- const dynamicDoc = document.implementation.createHTMLDocument();
37
- const convertDataToStylesheet = getStyleSheetFactory(dynamicDoc);
30
+ const dynamicDoc = document.implementation.createHTMLDocument(
31
+ 'Dynamic document for loading cssom'
32
+ );
38
33
 
39
- q.defer((resolve, reject) => {
40
- getCssomForAllRootNodes(rootNodes, convertDataToStylesheet, timeout)
41
- .then(assets => {
42
- const cssom = processCssomAssets(assets);
43
- resolve(cssom);
44
- })
45
- .catch(reject);
46
- });
34
+ const convertDataToStylesheet = axe.utils.getStyleSheetFactory(dynamicDoc);
47
35
 
48
- return q;
36
+ return getCssomForAllRootNodes(rootNodes, convertDataToStylesheet).then(
37
+ assets => flattenAssets(assets)
38
+ );
49
39
  };
50
40
 
51
41
  /**
@@ -76,283 +66,89 @@ function getAllRootNodesInTree(tree) {
76
66
  }
77
67
 
78
68
  /**
79
- * Convert text to CSSStyleSheet
80
- * Is a factory (closure) function, initialized with `document.implementation.createHTMLDocument()` which surfaces DOM API for creating `style` elements.
81
- *
82
- * @param {Object} param `document.implementation.createHTMLDocument()
83
- * @param {Object} arg an object with properties to construct stylesheet
84
- * @property {String} arg.data text content of the stylesheet
85
- * @property {Boolean} arg.isExternal flag to notify if the resource was fetched from the network
86
- * @property {String} arg.shadowId (Optional) shadowId if shadowDOM
87
- * @property {Object} arg.root implementation document to create style elements
88
- * @property {String} arg.priority a number indicating the loaded priority of CSS, to denote specificity of styles contained in the sheet.
89
- */
90
- const getStyleSheetFactory = dynamicDoc => ({
91
- data,
92
- isExternal,
93
- shadowId,
94
- root,
95
- priority,
96
- isLink = false
97
- }) => {
98
- const style = dynamicDoc.createElement('style');
99
- if (isLink) {
100
- // as creating a stylesheet as link will need to be awaited
101
- // till `onload`, it is wise to convert link href to @import statement
102
- const text = dynamicDoc.createTextNode(`@import "${data.href}"`);
103
- style.appendChild(text);
104
- } else {
105
- style.appendChild(dynamicDoc.createTextNode(data));
106
- }
107
- dynamicDoc.head.appendChild(style);
108
- return {
109
- sheet: style.sheet,
110
- isExternal,
111
- shadowId,
112
- root,
113
- priority
114
- };
115
- };
116
-
117
- /**
118
- * Deferred function for CSSOM queue processing on all root nodes
69
+ * Process CSSOM on all root nodes
119
70
  *
120
71
  * @param {Array<Object>} rootNodes array of root nodes, where node is an enhanced `document` or `documentFragment` object returned from `getAllRootNodesInTree`
121
72
  * @param {Function} convertDataToStylesheet fn to convert given data to Stylesheet object
122
- * @returns {Object} `axe.utils.queue`
73
+ * @returns {Promise}
123
74
  */
124
- function getCssomForAllRootNodes(rootNodes, convertDataToStylesheet, timeout) {
125
- const q = axe.utils.queue();
126
-
127
- rootNodes.forEach(({ rootNode, shadowId }, index) =>
128
- q.defer((resolve, reject) =>
129
- loadCssom({
130
- rootNode,
131
- shadowId,
132
- timeout,
133
- convertDataToStylesheet,
134
- rootIndex: index + 1
135
- })
136
- .then(resolve)
137
- .catch(reject)
138
- )
139
- );
140
-
141
- return q;
142
- }
143
-
144
- /**
145
- * Process results from `loadCssom` queues of all root nodes
146
- * NOTE:
147
- * using `axe.utils.queue` from various `loadCssom` paths, returns a nested array of arrays at various depths,
148
- * hence the need to flatten arrays
149
- *
150
- * @param {Array<Object>} assets CSSOM assets for each root
151
- * @returns {Object} CSSOM object
152
- */
153
- function processCssomAssets(nestedAssets) {
154
- const result = [];
155
-
156
- nestedAssets.forEach(item => {
157
- if (Array.isArray(item)) {
158
- result.push(...processCssomAssets(item));
159
- } else {
160
- result.push(item);
161
- }
162
- });
163
-
164
- return result;
165
- }
166
-
167
- /**
168
- * Returns `axe.utils.queue` of CSSStyleSheet(s) for a given root node
169
- *
170
- * @param {Object} options configuration options
171
- * @property {Object} options.rootNode document or document fragment
172
- * @property {Number} options.rootIndex a number representing the index of the document or document fragment, used for priority computation
173
- * @property {String} options.shadowId an id if undefined denotes that given root is a document fragment/ shadowDOM
174
- * @property {Number} options.timeout abort duration for network request
175
- * @property {Function} options.convertDataToStylesheet a utility function to generate a style sheet from given data (text)
176
- * @return {Object} queue
177
- */
178
- function loadCssom(options) {
179
- const { rootIndex } = options;
180
-
181
- const q = axe.utils.queue();
182
-
183
- const sheets = getStylesheetsOfRootNode(options);
184
- if (!sheets) {
185
- return q;
186
- }
187
-
188
- sheets.forEach((sheet, sheetIndex) => {
189
- const priority = [rootIndex, sheetIndex];
190
- try {
191
- const deferredQ = parseNonCrossOriginStylesheet(sheet, options, priority);
192
- q.defer(deferredQ);
193
- } catch (e) {
194
- // cross-origin stylesheet -> make an XHR and q the response
195
- const deferredQ = parseCrossOriginStylesheet(
196
- sheet.href,
197
- options,
198
- priority
199
- );
200
- q.defer(deferredQ);
75
+ function getCssomForAllRootNodes(rootNodes, convertDataToStylesheet) {
76
+ const promises = [];
77
+
78
+ rootNodes.forEach(({ rootNode, shadowId }, index) => {
79
+ const sheets = getStylesheetsOfRootNode(
80
+ rootNode,
81
+ shadowId,
82
+ convertDataToStylesheet
83
+ );
84
+ if (!sheets) {
85
+ return Promise.all(promises);
201
86
  }
202
- });
203
-
204
- return q;
205
- }
206
-
207
- /**
208
- * Parse non cross-origin stylesheets
209
- *
210
- * @param {Object} sheet CSSStylesheet object
211
- * @param {Object} options `loadCssom` options
212
- * @param {Array<Number>} priority sheet priority
213
- */
214
- function parseNonCrossOriginStylesheet(sheet, options, priority) {
215
- const q = axe.utils.queue();
216
-
217
- /**
218
- * `sheet.cssRules` throws an error on `cross-origin` stylesheets
219
- */
220
- const cssRules = sheet.cssRules;
221
87
 
222
- const rules = Array.from(cssRules);
223
- if (!rules) {
224
- return q;
225
- }
226
-
227
- /**
228
- * reference -> https://developer.mozilla.org/en-US/docs/Web/API/CSSRule#Type_constants
229
- */
230
- const cssImportRules = rules.filter(r => r.type === 3); // type === 3 -> CSSRule.IMPORT_RULE
231
-
232
- /**
233
- * when no `@import` rules in given sheet
234
- * -> resolve the current `sheet` & exit
235
- */
236
- if (!cssImportRules.length) {
237
- q.defer(resolve =>
238
- resolve({
239
- isExternal: false,
240
- priority,
241
- root: options.rootNode,
242
- shadowId: options.shadowId,
243
- sheet
88
+ const rootIndex = index + 1;
89
+ const parseOptions = {
90
+ rootNode,
91
+ shadowId,
92
+ convertDataToStylesheet,
93
+ rootIndex
94
+ };
95
+ /**
96
+ * Note:
97
+ * `importedUrls` - keeps urls of already imported stylesheets, to prevent re-fetching
98
+ * eg: nested, cyclic or cross referenced `@import` urls
99
+ */
100
+ const importedUrls = [];
101
+
102
+ const p = Promise.all(
103
+ sheets.map((sheet, sheetIndex) => {
104
+ const priority = [rootIndex, sheetIndex];
105
+
106
+ return axe.utils.parseStylesheet(
107
+ sheet,
108
+ parseOptions,
109
+ priority,
110
+ importedUrls
111
+ );
244
112
  })
245
113
  );
246
114
 
247
- // exit
248
- return q;
249
- }
250
-
251
- /**
252
- * iterate `@import` rules and fetch styles
253
- */
254
- cssImportRules.forEach((importRule, cssRuleIndex) =>
255
- q.defer((resolve, reject) => {
256
- const importUrl = importRule.href;
257
- const newPriority = [...priority, cssRuleIndex];
258
- const axiosOptions = {
259
- method: 'get',
260
- url: importUrl,
261
- timeout: options.timeout
262
- };
263
- axe.imports
264
- .axios(axiosOptions)
265
- .then(({ data }) =>
266
- resolve(
267
- options.convertDataToStylesheet({
268
- data,
269
- isExternal: true,
270
- priority: newPriority,
271
- root: options.rootNode,
272
- shadowId: options.shadowId
273
- })
274
- )
275
- )
276
- .catch(reject);
277
- })
278
- );
279
-
280
- const nonImportCSSRules = rules.filter(r => r.type !== 3);
281
-
282
- // no further rules to process in this sheet
283
- if (!nonImportCSSRules.length) {
284
- return q;
285
- }
286
-
287
- // convert all `nonImportCSSRules` style rules into `text` and defer into queue
288
- q.defer(resolve =>
289
- resolve(
290
- options.convertDataToStylesheet({
291
- data: nonImportCSSRules.map(rule => rule.cssText).join(),
292
- isExternal: false,
293
- priority,
294
- root: options.rootNode,
295
- shadowId: options.shadowId
296
- })
297
- )
298
- );
115
+ promises.push(p);
116
+ });
299
117
 
300
- return q;
118
+ return Promise.all(promises);
301
119
  }
302
120
 
303
121
  /**
304
- * Parse cross-origin stylesheets
122
+ * Flatten CSSOM assets
305
123
  *
306
- * @param {String} url url from which to fetch stylesheet
307
- * @param {Object} options `loadCssom` options
308
- * @param {Array<Number>} priority sheet priority
124
+ * @param {Array.<Object[]>} assets nested assets (varying depth)
125
+ * @returns {Array<Object>} Array of CSSOM object
309
126
  */
310
- function parseCrossOriginStylesheet(url, options, priority) {
311
- const q = axe.utils.queue();
312
-
313
- if (!url) {
314
- return q;
315
- }
316
-
317
- const axiosOptions = {
318
- method: 'get',
319
- url,
320
- timeout: options.timeout
321
- };
322
-
323
- q.defer((resolve, reject) => {
324
- axe.imports
325
- .axios(axiosOptions)
326
- .then(({ data }) =>
327
- resolve(
328
- options.convertDataToStylesheet({
329
- data,
330
- isExternal: true,
331
- priority,
332
- root: options.rootNode,
333
- shadowId: options.shadowId
334
- })
335
- )
336
- )
337
- .catch(reject);
338
- });
339
-
340
- return q;
127
+ function flattenAssets(assets) {
128
+ return assets.reduce(
129
+ (acc, val) =>
130
+ Array.isArray(val) ? acc.concat(flattenAssets(val)) : acc.concat(val),
131
+ []
132
+ );
341
133
  }
342
134
 
343
135
  /**
344
136
  * Get stylesheet(s) for root
345
137
  *
346
- * @param {Object} options configuration options of `loadCssom`
347
- * @returns an array of stylesheets
138
+ * @param {Object} options.rootNode `document` or `documentFragment`
139
+ * @param {String} options.shadowId an id if undefined denotes that given root is a document fragment/ shadowDOM
140
+ * @param {Function} options.convertDataToStylesheet a utility function to generate a style sheet from given data (text)
141
+ * @returns {Array<Object>} an array of stylesheets
348
142
  */
349
- function getStylesheetsOfRootNode(options) {
350
- const { rootNode, shadowId } = options;
143
+ function getStylesheetsOfRootNode(rootNode, shadowId, convertDataToStylesheet) {
351
144
  let sheets;
352
145
 
353
146
  // nodeType === 11 -> DOCUMENT_FRAGMENT
354
147
  if (rootNode.nodeType === 11 && shadowId) {
355
- sheets = getStylesheetsFromDocumentFragment(options);
148
+ sheets = getStylesheetsFromDocumentFragment(
149
+ rootNode,
150
+ convertDataToStylesheet
151
+ );
356
152
  } else {
357
153
  sheets = getStylesheetsFromDocument(rootNode);
358
154
  }
@@ -363,11 +159,11 @@ function getStylesheetsOfRootNode(options) {
363
159
  /**
364
160
  * Get stylesheets from `documentFragment`
365
161
  *
366
- * @param {Object} options configuration options of `loadCssom`
162
+ * @property {Object} options.rootNode `documentFragment`
163
+ * @property {Function} options.convertDataToStylesheet a utility function to generate a stylesheet from given data
367
164
  * @returns {Array<Object>}
368
165
  */
369
- function getStylesheetsFromDocumentFragment(options) {
370
- const { rootNode, convertDataToStylesheet } = options;
166
+ function getStylesheetsFromDocumentFragment(rootNode, convertDataToStylesheet) {
371
167
  return (
372
168
  Array.from(rootNode.children)
373
169
  .filter(filerStyleAndLinkAttributesInDocumentFragment)