@surea11y/core 1.2.0 → 1.3.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.
- package/CHANGELOG.md +37 -7
- package/LICENSE +373 -21
- package/README.md +67 -1
- package/bin/core.js +144 -19
- package/docs/API_STABILITY.md +1 -1
- package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
- package/docs/CI_INTEGRATIONS.md +103 -0
- package/docs/CLI.md +54 -1
- package/docs/ENGINE_OPTIONS.md +2 -0
- package/docs/INTEGRATION.md +18 -0
- package/docs/OUTPUT_SCHEMA.md +1 -1
- package/docs/RULE_CATALOG.md +1 -1
- package/docs/SARIF.md +59 -0
- package/package.json +15 -4
- package/src/baseline.js +0 -0
- package/src/catalogs/composites.wcag.js +414 -450
- package/src/checks/automatic/area-alt-present.js +59 -25
- package/src/checks/automatic/aria-allowed-attr.js +193 -33
- package/src/checks/automatic/aria-allowed-role.js +21 -7
- package/src/checks/automatic/aria-braille-equivalent.js +32 -10
- package/src/checks/automatic/aria-conditional-attr.js +24 -7
- package/src/checks/automatic/aria-deprecated-role.js +22 -8
- package/src/checks/automatic/aria-hidden-body.js +42 -19
- package/src/checks/automatic/aria-hidden-focus.js +408 -53
- package/src/checks/automatic/aria-prohibited-attr.js +296 -22
- package/src/checks/automatic/aria-prohibited-children.js +55 -16
- package/src/checks/automatic/aria-required-attr.js +23 -8
- package/src/checks/automatic/aria-required-children.js +37 -14
- package/src/checks/automatic/aria-required-parent.js +48 -14
- package/src/checks/automatic/aria-role-name-present.js +47 -21
- package/src/checks/automatic/aria-roles-valid.js +22 -12
- package/src/checks/automatic/aria-valid-attr-value.js +28 -7
- package/src/checks/automatic/aria-valid-attr.js +17 -5
- package/src/checks/automatic/autocomplete-valid.js +74 -16
- package/src/checks/automatic/avoid-inline-spacing.js +20 -6
- package/src/checks/automatic/binary-control-name-present.js +60 -50
- package/src/checks/automatic/button-name-present.js +48 -18
- package/src/checks/automatic/bypass-blocks-present.js +42 -25
- package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
- package/src/checks/automatic/combobox-name-present.js +38 -45
- package/src/checks/automatic/contrast-computable.js +361 -341
- package/src/checks/automatic/contrast-enhanced.js +487 -466
- package/src/checks/automatic/contrast-minimum.js +486 -465
- package/src/checks/automatic/css-orientation-lock.js +30 -8
- package/src/checks/automatic/definition-list-children-valid.js +40 -19
- package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
- package/src/checks/automatic/dialog-name-present.js +37 -75
- package/src/checks/automatic/dlitem-parent-valid.js +23 -8
- package/src/checks/automatic/duplicate-id-aria.js +24 -6
- package/src/checks/automatic/embed-text-alternative-present.js +86 -35
- package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
- package/src/checks/automatic/form-control-single-label.js +47 -10
- package/src/checks/automatic/html-xml-lang-mismatch.js +34 -18
- package/src/checks/automatic/iframe-focusable-content.js +26 -11
- package/src/checks/automatic/iframe-name-present.js +31 -9
- package/src/checks/automatic/iframe-title-unique.js +29 -8
- package/src/checks/automatic/img-alt-present.js +47 -43
- package/src/checks/automatic/input-image-alt-present.js +143 -112
- package/src/checks/automatic/label-in-name.js +50 -22
- package/src/checks/automatic/language-page-present.js +109 -109
- package/src/checks/automatic/link-in-text-block.js +59 -19
- package/src/checks/automatic/link-name-present.js +45 -14
- package/src/checks/automatic/list-children-valid.js +26 -9
- package/src/checks/automatic/listbox-name-present.js +39 -19
- package/src/checks/automatic/listitem-parent-valid.js +18 -6
- package/src/checks/automatic/menuitem-name-present.js +39 -61
- package/src/checks/automatic/meta-refresh-no-exceptions.js +28 -7
- package/src/checks/automatic/meta-refresh-timing-absent.js +20 -6
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +24 -7
- package/src/checks/automatic/meter-name-present.js +36 -33
- package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
- package/src/checks/automatic/object-text-alternative-present.js +91 -39
- package/src/checks/automatic/option-name-present.js +38 -21
- package/src/checks/automatic/page-title-present.js +17 -6
- package/src/checks/automatic/progressbar-name-present.js +41 -34
- package/src/checks/automatic/role-img-alt-present.js +209 -157
- package/src/checks/automatic/searchbox-name-present.js +39 -19
- package/src/checks/automatic/server-side-image-map-absent.js +23 -8
- package/src/checks/automatic/slider-name-present.js +40 -47
- package/src/checks/automatic/spinbutton-name-present.js +39 -19
- package/src/checks/automatic/summary-name-present.js +37 -17
- package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
- package/src/checks/automatic/svg-text-alternative-present.js +246 -226
- package/src/checks/automatic/tab-name-present.js +37 -60
- package/src/checks/automatic/table-headers-attr-valid.js +24 -8
- package/src/checks/automatic/table-th-has-data-cells.js +22 -8
- package/src/checks/automatic/target-size-minimum.js +118 -48
- package/src/checks/automatic/td-has-header.js +29 -11
- package/src/checks/automatic/textbox-name-present.js +39 -19
- package/src/checks/automatic/tooltip-name-present.js +37 -18
- package/src/checks/automatic/treeitem-name-present.js +38 -21
- package/src/checks/automatic/valid-lang.js +20 -6
- package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
- package/src/checks/manual/accesskeys-manual.js +14 -5
- package/src/checks/manual/area-alt-decorative-manual.js +192 -193
- package/src/checks/manual/area-alt-quality-manual.js +182 -141
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
- package/src/checks/manual/aria-text-manual.js +14 -6
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
- package/src/checks/manual/css-hidden-focus.js +196 -165
- package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
- package/src/checks/manual/empty-heading-manual.js +24 -7
- package/src/checks/manual/empty-table-header-manual.js +17 -6
- package/src/checks/manual/focus-order-semantics-manual.js +45 -10
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
- package/src/checks/manual/heading-order-manual.js +22 -7
- package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
- package/src/checks/manual/image-redundant-alt-manual.js +17 -7
- package/src/checks/manual/img-alt-decorative-manual.js +131 -96
- package/src/checks/manual/img-alt-quality-manual.js +176 -127
- package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
- package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
- package/src/checks/manual/label-title-only-manual.js +14 -5
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +66 -16
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +53 -16
- package/src/checks/manual/landmark-main-is-top-level-manual.js +35 -10
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +29 -10
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +29 -10
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
- package/src/checks/manual/landmark-one-main-manual.js +26 -20
- package/src/checks/manual/landmark-unique-manual.js +41 -15
- package/src/checks/manual/link-name-quality-manual.js +43 -12
- package/src/checks/manual/media-transcript-present-manual.js +35 -22
- package/src/checks/manual/meta-viewport-large-manual.js +16 -5
- package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
- package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
- package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
- package/src/checks/manual/p-as-heading-manual.js +22 -7
- package/src/checks/manual/page-has-heading-one-manual.js +31 -22
- package/src/checks/manual/page-title-patterns-manual.js +78 -50
- package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
- package/src/checks/manual/region-manual.js +241 -48
- package/src/checks/manual/scope-attr-valid-manual.js +10 -3
- package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
- package/src/checks/manual/skip-link-manual.js +35 -12
- package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
- package/src/checks/manual/tabindex-manual.js +10 -3
- package/src/checks/manual/table-duplicate-name-manual.js +17 -7
- package/src/checks/manual/table-fake-caption-manual.js +24 -7
- package/src/checks/manual/video-caption-manual.js +15 -4
- package/src/checks/manual-review.js +56 -12
- package/src/core/aria-helpers.js +1127 -886
- package/src/core/contrast-helpers.js +1217 -1062
- package/src/core/dom-helpers.js +4175 -3917
- package/src/core/dom-runner.js +720 -604
- package/src/core/frame-messaging.js +189 -138
- package/src/core/frame-scan.js +94 -82
- package/src/core/rollup-composites.js +94 -102
- package/src/core/rule-meta.js +58 -41
- package/src/core.js +36552 -29111
- package/src/i18n/en.js +1194 -889
- package/src/i18n/fr.js +1136 -795
- package/src/policy/contracts.js +13 -13
- package/src/policy/resolvePolicy.js +48 -44
- package/src/report.js +63 -43
- package/src/sarif.js +175 -0
- package/surea11y.browser.js +36042 -0
|
@@ -18,7 +18,8 @@ const id = 'canvas-text-alternative-present';
|
|
|
18
18
|
|
|
19
19
|
const meta = {
|
|
20
20
|
title: '<canvas> must provide a text alternative',
|
|
21
|
-
description:
|
|
21
|
+
description:
|
|
22
|
+
'Checks that <canvas> elements provide a text alternative via fallback content or an accessible name.',
|
|
22
23
|
i18n: {
|
|
23
24
|
titleKey: 'canvas_textAltPresent_title',
|
|
24
25
|
descriptionKey: 'canvas_textAltPresent_description'
|
|
@@ -27,7 +28,13 @@ const meta = {
|
|
|
27
28
|
tags: ['wcag2a', 'wcag111', 'nontext', 'canvas', 'atomic', 'automatic'],
|
|
28
29
|
wcagSc: ['1.1.1'],
|
|
29
30
|
normativeMappings: [
|
|
30
|
-
{
|
|
31
|
+
{
|
|
32
|
+
standard: 'WCAG',
|
|
33
|
+
version: '2.2',
|
|
34
|
+
requirement: '1.1.1',
|
|
35
|
+
title: 'Non-text Content',
|
|
36
|
+
conformanceLevel: 'A'
|
|
37
|
+
}
|
|
31
38
|
],
|
|
32
39
|
defaultSeverity: 'serious',
|
|
33
40
|
category: 'perceivable',
|
|
@@ -44,29 +51,38 @@ function runInPage(ctx) {
|
|
|
44
51
|
const { document, root, helpers, rule } = ctx;
|
|
45
52
|
const safeRoot = root || document;
|
|
46
53
|
|
|
47
|
-
const queryAllSmart =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
54
|
+
const queryAllSmart =
|
|
55
|
+
helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
|
|
56
|
+
const queryAll =
|
|
57
|
+
helpers && typeof helpers.queryAll === 'function'
|
|
58
|
+
? helpers.queryAll
|
|
59
|
+
: (sel) => {
|
|
60
|
+
try {
|
|
61
|
+
return safeRoot && safeRoot.querySelectorAll
|
|
62
|
+
? Array.from(safeRoot.querySelectorAll(sel))
|
|
63
|
+
: [];
|
|
64
|
+
} catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const getEligibilityInfo =
|
|
70
|
+
helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
|
|
71
|
+
|
|
72
|
+
const isAccTreeEligible =
|
|
73
|
+
helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
|
|
74
|
+
|
|
75
|
+
const getTextAlternativeInfo =
|
|
76
|
+
helpers && typeof helpers.getTextAlternativeInfo === 'function'
|
|
77
|
+
? helpers.getTextAlternativeInfo
|
|
78
|
+
: null;
|
|
66
79
|
|
|
67
80
|
const canvases = (() => {
|
|
68
|
-
try {
|
|
69
|
-
|
|
81
|
+
try {
|
|
82
|
+
return Array.from((queryAllSmart ? queryAllSmart('canvas') : queryAll('canvas')) || []);
|
|
83
|
+
} catch {
|
|
84
|
+
return queryAll('canvas');
|
|
85
|
+
}
|
|
70
86
|
})();
|
|
71
87
|
|
|
72
88
|
if (!canvases.length) {
|
|
@@ -82,7 +98,11 @@ function runInPage(ctx) {
|
|
|
82
98
|
// Applicability: eligible in the accessibility tree (with focusable/IDREF exceptions handled by helper).
|
|
83
99
|
if (isAccTreeEligible) {
|
|
84
100
|
const elig = (() => {
|
|
85
|
-
try {
|
|
101
|
+
try {
|
|
102
|
+
return isAccTreeEligible(el, ctx);
|
|
103
|
+
} catch {
|
|
104
|
+
return { eligible: true, reasons: [] };
|
|
105
|
+
}
|
|
86
106
|
})();
|
|
87
107
|
if (elig && elig.eligible === false) continue;
|
|
88
108
|
}
|
|
@@ -91,7 +111,13 @@ function runInPage(ctx) {
|
|
|
91
111
|
|
|
92
112
|
// Expectation: must provide a text alternative.
|
|
93
113
|
const ti = getTextAlternativeInfo
|
|
94
|
-
? (() => {
|
|
114
|
+
? (() => {
|
|
115
|
+
try {
|
|
116
|
+
return getTextAlternativeInfo(el, ctx);
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
})()
|
|
95
121
|
: null;
|
|
96
122
|
|
|
97
123
|
const hasTextAlt = !!(ti && ti.present);
|
|
@@ -134,7 +160,12 @@ function runInPage(ctx) {
|
|
|
134
160
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
135
161
|
}
|
|
136
162
|
|
|
137
|
-
return {
|
|
163
|
+
return {
|
|
164
|
+
ruleId: rule.ruleId,
|
|
165
|
+
outcome: 'fail',
|
|
166
|
+
severity: rule.defaultSeverity || 'minor',
|
|
167
|
+
occurrences
|
|
168
|
+
};
|
|
138
169
|
}
|
|
139
170
|
|
|
140
171
|
module.exports = { id, meta, runInPage };
|
|
@@ -10,10 +10,16 @@ const meta = {
|
|
|
10
10
|
descriptionKey: 'comboboxNamePresent_description'
|
|
11
11
|
},
|
|
12
12
|
helpUrl: null,
|
|
13
|
-
tags: ['wcag2a','wcag412','forms','atomic','automatic','name','combobox'],
|
|
13
|
+
tags: ['wcag2a', 'wcag412', 'forms', 'atomic', 'automatic', 'name', 'combobox'],
|
|
14
14
|
wcagSc: ['4.1.2'],
|
|
15
15
|
normativeMappings: [
|
|
16
|
-
{
|
|
16
|
+
{
|
|
17
|
+
standard: 'WCAG',
|
|
18
|
+
version: '2.2',
|
|
19
|
+
requirement: '4.1.2',
|
|
20
|
+
title: 'Name, Role, Value',
|
|
21
|
+
conformanceLevel: 'A'
|
|
22
|
+
}
|
|
17
23
|
],
|
|
18
24
|
defaultSeverity: 'serious',
|
|
19
25
|
category: 'robust',
|
|
@@ -24,38 +30,22 @@ const meta = {
|
|
|
24
30
|
|
|
25
31
|
function runInPage(ctx) {
|
|
26
32
|
const { document, helpers, rule } = ctx;
|
|
27
|
-
const getEligibilityInfo =
|
|
28
|
-
|
|
29
|
-
: null;
|
|
30
|
-
|
|
33
|
+
const getEligibilityInfo =
|
|
34
|
+
helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
|
|
31
35
|
|
|
32
36
|
function normalizeWs(s) {
|
|
33
|
-
return String(s || '')
|
|
37
|
+
return String(s || '')
|
|
38
|
+
.replace(/\s+/g, ' ')
|
|
39
|
+
.trim();
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
function getAttr(el, name) {
|
|
37
43
|
try {
|
|
38
44
|
if (!el || !el.getAttribute) return '';
|
|
39
45
|
return normalizeWs(el.getAttribute(name));
|
|
40
|
-
} catch {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
function hasAttr(el, name) {
|
|
44
|
-
try { return !!(el && el.hasAttribute && el.hasAttribute(name)); } catch { return false; }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function isExplicitProgrammatic(el) {
|
|
48
|
-
// Programmatic name mechanisms we treat as authoritative (presence-only):
|
|
49
|
-
// - aria-label (non-empty)
|
|
50
|
-
// - aria-labelledby (non-empty)
|
|
51
|
-
// - title (non-empty) [weak but allowed for presence]
|
|
52
|
-
const al = getAttr(el, 'aria-label');
|
|
53
|
-
if (al) return true;
|
|
54
|
-
const alb = getAttr(el, 'aria-labelledby');
|
|
55
|
-
if (alb) return true;
|
|
56
|
-
const t = getAttr(el, 'title');
|
|
57
|
-
if (t) return true;
|
|
58
|
-
return false;
|
|
46
|
+
} catch {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
59
49
|
}
|
|
60
50
|
|
|
61
51
|
function buildLabelForMap(doc) {
|
|
@@ -85,7 +75,7 @@ function runInPage(ctx) {
|
|
|
85
75
|
const info = helpers.getContentNameInfo(container, ctx);
|
|
86
76
|
return info && info.present ? info.value : '';
|
|
87
77
|
}
|
|
88
|
-
const t =
|
|
78
|
+
const t = container && container.textContent ? String(container.textContent) : '';
|
|
89
79
|
return t.replace(/\s+/g, ' ').trim();
|
|
90
80
|
}
|
|
91
81
|
|
|
@@ -132,17 +122,9 @@ function runInPage(ctx) {
|
|
|
132
122
|
return '';
|
|
133
123
|
}
|
|
134
124
|
|
|
135
|
-
function getInputValueName(el) {
|
|
136
|
-
try {
|
|
137
|
-
const type = getAttr(el, 'type').toLowerCase();
|
|
138
|
-
if (type !== 'button' && type !== 'submit' && type !== 'reset') return '';
|
|
139
|
-
const v = getAttr(el, 'value');
|
|
140
|
-
return v;
|
|
141
|
-
} catch { return ''; }
|
|
142
|
-
}
|
|
143
|
-
|
|
144
125
|
function isEligibleAcc(helpers, el, ctx) {
|
|
145
|
-
const fn =
|
|
126
|
+
const fn =
|
|
127
|
+
helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
|
|
146
128
|
if (!fn) return true;
|
|
147
129
|
try {
|
|
148
130
|
const r = fn(el, ctx);
|
|
@@ -153,12 +135,13 @@ function runInPage(ctx) {
|
|
|
153
135
|
}
|
|
154
136
|
}
|
|
155
137
|
|
|
156
|
-
|
|
157
138
|
const occurrences = [];
|
|
158
139
|
let applicableCount = 0;
|
|
159
140
|
|
|
160
141
|
const selector = '[role="combobox"]';
|
|
161
|
-
const nodes = helpers.queryAllSmart
|
|
142
|
+
const nodes = helpers.queryAllSmart
|
|
143
|
+
? helpers.queryAllSmart(selector)
|
|
144
|
+
: helpers.queryAll(selector);
|
|
162
145
|
|
|
163
146
|
// Precompute label[for] map for combobox elements that are labelable
|
|
164
147
|
// native form controls (e.g. <input role="combobox">).
|
|
@@ -231,16 +214,22 @@ function runInPage(ctx) {
|
|
|
231
214
|
if (res.ok) continue;
|
|
232
215
|
|
|
233
216
|
const eligInfo = getEligibilityInfo
|
|
234
|
-
|
|
235
|
-
|
|
217
|
+
? (() => {
|
|
218
|
+
try {
|
|
219
|
+
return getEligibilityInfo(el, ctx, { targetSet: 'acc' });
|
|
220
|
+
} catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
})()
|
|
224
|
+
: null;
|
|
236
225
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
237
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
226
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
238
227
|
|
|
239
228
|
occurrences.push({
|
|
240
229
|
selector: stableSelector,
|
|
241
230
|
html,
|
|
242
231
|
summary: 'This combobox has no accessible name.',
|
|
243
|
-
hint:
|
|
232
|
+
hint: "Provide aria-label, aria-labelledby, or a title attribute — visible text content is not exposed as this combobox's accessible name.",
|
|
244
233
|
i18n: {
|
|
245
234
|
summaryKey: 'comboboxNamePresent_summary_fail',
|
|
246
235
|
hintKey: 'comboboxNamePresent_hint_fail',
|
|
@@ -253,12 +242,16 @@ function runInPage(ctx) {
|
|
|
253
242
|
});
|
|
254
243
|
}
|
|
255
244
|
|
|
256
|
-
|
|
257
245
|
if (applicableCount === 0) {
|
|
258
246
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
259
247
|
}
|
|
260
248
|
if (occurrences.length) {
|
|
261
|
-
return {
|
|
249
|
+
return {
|
|
250
|
+
ruleId: rule.ruleId,
|
|
251
|
+
outcome: 'fail',
|
|
252
|
+
severity: rule.defaultSeverity || 'minor',
|
|
253
|
+
occurrences
|
|
254
|
+
};
|
|
262
255
|
}
|
|
263
256
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
264
257
|
}
|