From 2dd0c861bff6bfa53b1b3096754f7fb534203d25 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Tue, 7 Jun 2022 14:44:17 +0200 Subject: [PATCH] Outline fields which are required (bug 1724918) - it aims to fix https://bugzilla.mozilla.org/show_bug.cgi?id=1724918; - it applies for both Acroform and XFA. --- src/core/annotation.js | 1 + src/core/xfa/template.js | 35 +++++++++++++++++++++++++++++++ src/display/annotation_layer.js | 20 +++++++++++++----- test/pdfs/.gitignore | 2 +- test/pdfs/bug1724918.pdf | Bin 0 -> 13107 bytes test/test_manifest.json | 7 +++++++ web/annotation_layer_builder.css | 20 +++++++++++++++++- web/xfa_layer_builder.css | 10 +++++++++ 8 files changed, 88 insertions(+), 7 deletions(-) create mode 100755 test/pdfs/bug1724918.pdf diff --git a/src/core/annotation.js b/src/core/annotation.js index bcad74973..d39e5dc58 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -1363,6 +1363,7 @@ class WidgetAnnotation extends Annotation { } data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY); + data.required = this.hasFieldFlag(AnnotationFieldFlag.REQUIRED); data.hidden = this._hasFlag(data.annotationFlags, AnnotationFlag.HIDDEN); } diff --git a/src/core/xfa/template.js b/src/core/xfa/template.js index c3f0f65cc..8bc4659de 100644 --- a/src/core/xfa/template.js +++ b/src/core/xfa/template.js @@ -204,6 +204,10 @@ function* getContainedChildren(node) { } } +function isRequired(node) { + return node.validate && node.validate.nullTest === "error"; +} + function setTabIndex(node) { while (node) { if (!node.traversal) { @@ -1368,6 +1372,7 @@ class CheckButton extends XFAObject { xfaOn: exportedValue.on, xfaOff: exportedValue.off, "aria-label": ariaLabel(field), + "aria-required": false, }, }; @@ -1375,6 +1380,11 @@ class CheckButton extends XFAObject { input.attributes.name = groupId; } + if (isRequired(field)) { + input.attributes["aria-required"] = true; + input.attributes.required = true; + } + return HTMLResult.success({ name: "label", attributes: { @@ -1465,8 +1475,14 @@ class ChoiceList extends XFAObject { dataId: (field[$data] && field[$data][$uid]) || field[$uid], style, "aria-label": ariaLabel(field), + "aria-required": false, }; + if (isRequired(field)) { + selectAttributes["aria-required"] = true; + selectAttributes.required = true; + } + if (this.open === "multiSelect") { selectAttributes.multiple = true; } @@ -1704,9 +1720,15 @@ class DateTimeEdit extends XFAObject { class: ["xfaTextfield"], style, "aria-label": ariaLabel(field), + "aria-required": false, }, }; + if (isRequired(field)) { + html.attributes["aria-required"] = true; + html.attributes.required = true; + } + return HTMLResult.success({ name: "label", attributes: { @@ -3859,9 +3881,15 @@ class NumericEdit extends XFAObject { class: ["xfaTextfield"], style, "aria-label": ariaLabel(field), + "aria-required": false, }, }; + if (isRequired(field)) { + html.attributes["aria-required"] = true; + html.attributes.required = true; + } + return HTMLResult.success({ name: "label", attributes: { @@ -5833,6 +5861,7 @@ class TextEdit extends XFAObject { class: ["xfaTextfield"], style, "aria-label": ariaLabel(field), + "aria-required": false, }, }; } else { @@ -5845,10 +5874,16 @@ class TextEdit extends XFAObject { class: ["xfaTextfield"], style, "aria-label": ariaLabel(field), + "aria-required": false, }, }; } + if (isRequired(field)) { + html.attributes["aria-required"] = true; + html.attributes.required = true; + } + return HTMLResult.success({ name: "label", attributes: { diff --git a/src/display/annotation_layer.js b/src/display/annotation_layer.js index 32ec62641..00aca1a4c 100644 --- a/src/display/annotation_layer.js +++ b/src/display/annotation_layer.js @@ -343,11 +343,7 @@ class AnnotationElement { } }, required: event => { - if (event.detail.required) { - event.target.setAttribute("required", ""); - } else { - event.target.removeAttribute("required"); - } + this._setRequired(event.target, event.detail.required); }, bgColor: event => { setColor("bgColor", "backgroundColor", event); @@ -944,6 +940,15 @@ class WidgetAnnotationElement extends AnnotationElement { style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment]; } } + + _setRequired(element, isRequired) { + if (isRequired) { + element.setAttribute("required", true); + } else { + element.removeAttribute("required"); + } + element.setAttribute("aria-required", isRequired); + } } class TextWidgetAnnotationElement extends WidgetAnnotationElement { @@ -1010,6 +1015,8 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement { elementData.userValue = textContent; element.setAttribute("id", id); + this._setRequired(element, this.data.required); + element.addEventListener("input", event => { storage.setValue(id, { value: event.target.value }); this.setPropertyOnSiblings( @@ -1255,6 +1262,7 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement { const element = document.createElement("input"); GetElementsByNameSet.add(element); element.disabled = data.readOnly; + this._setRequired(element, this.data.required); element.type = "checkbox"; element.name = data.fieldName; if (value) { @@ -1338,6 +1346,7 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement { const element = document.createElement("input"); GetElementsByNameSet.add(element); element.disabled = data.readOnly; + this._setRequired(element, this.data.required); element.type = "radio"; element.name = data.fieldName; if (value) { @@ -1447,6 +1456,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement { const selectElement = document.createElement("select"); GetElementsByNameSet.add(selectElement); selectElement.disabled = this.data.readOnly; + this._setRequired(selectElement, this.data.required); selectElement.name = this.data.fieldName; selectElement.setAttribute("id", id); selectElement.tabIndex = DEFAULT_TAB_INDEX; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 14b686d3c..9fa9ec66c 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -525,4 +525,4 @@ !issue14862.pdf !issue14705.pdf !bug1771477.pdf - +!bug1724918.pdf diff --git a/test/pdfs/bug1724918.pdf b/test/pdfs/bug1724918.pdf new file mode 100755 index 0000000000000000000000000000000000000000..3652bb5852c3956e40c49e89d69231f9b2d4a208 GIT binary patch literal 13107 zcmeHO3p`X?+jj{yj8I8YCYOrL?7incW6&76#2|!7XKs5KVKg&@>ZC*^qMLA3heYW{ zR63=QuDTy8sZ&ztbdgRe4t4VGLC18OzVv%f@9%rRFF(efz1Moydj8LUJ!?H{J=@aT z$r%gb$Y{&^t(SjBqaX+aV#K^~G=l+nh@xPwM3evvFc9E@fd~*t0lbj^zzhtPLZJbk z$bCNygr|Z;z!ejWA=2;|JcJLiw@1TK0=X2-$3JKkfjroT6U-M2U~jG@Qi`<55=X>D zMoBS5z*_1@9jOEYL?%(}8IDZH8B`(_DZs=-5RuFz;7K5rMxNmeIXgKqN%kRtTa*Bf3q}+t zg7PmyF$MyW4*CEda8#%)3_~T$dlTEICkgNoi)F}VUjbhP8w?4-AU+xf0)1d15D*Mu z@E8a|Kq`~K+(=zzSpvU3xIJZaq6}9*!nts)&IvX96X+}m=I_5`JH-D4VaYj1hD&sfsjvdXDRnR?c zX3;AOLT_!if{Lv>h#{A4w*7JT`c>7EUFftYp());I2;DJY zKfmPtRp=XWuy2lv!Xc8p1Ngp{l4|FcKIgMa@Df zv!5Km0gV5v^-%C>AL>Ds1B`rx^r7~lIf(eLZw?3(ikL*+2l+2z3E`l1C>V(HKN&|p zj+7clL8*^D^T)JYd%x7g@2&(d%`{(b~ zE$)}S7rd?;8^G$y6>F2+*4jm~085w2BW8%E5zkYLT#s&d!$zJFyn8*CqmmCA`6}bM z+-ZCc#*c<7;&s3NP{mP5cpBl~V2Ip*eonQaF@*3%>V5@=s6p=;Qr=YO9(47RbD&<^ zXuF#G_g?kZpgW4<)yvJ!`AI3q*JbaF{Cx>`ZN9{>n7Pvw7nq;crj@@hFgm&|s;f0A zs4%GL^4V)8Wv<~fmpZZ&0A~lj@1a*CJtuy*R8>=j1dU5oLK3{f&5$eUkRy9LP`{$R}5Xf1j*_72_yn%Mv{PtFKF3SEuYd#kB6Kh)To?gd`mpw-$tDtKm-ft`_o(3Via%6eeh{&0Op#d>i?l-G@&_o`N; zEC}Fa3AD;nM5=~vW}f4At8BAfb<*-od;7}?ypadbaBR5qVPC!Eu8BekXAfpFUN)m? zw#H+w+tcfY;?%LM^j(c@mp2kM(_9y5aIh|mOImxzn<9It|hut|MokvMaH-^tBSl0%cst152b?sXL}a?RM1wk{8edT%zW+d z?MxB>zUk$PT4nzIlMCH1HV=eEq+fYyfSAY2P4^^5?Rgf!OOXoRmyb6_%nW|#Fn)cAgN54DaPQtOxA4Q4dQqUt zqFmum%Ag<0+Pu%l3}IwY{sYhtjtsDHcu@SZ(AH5NuO2Y}T*}Lr9X3WrhNdtj_GYrd z4dDwox(Ew`&3YR<_C z6;5PIHi_+-V-8)=)(X|QoHACF-TL@)QRzND_f#1cwKX8b?Rn=Gt9Nk*aOb0J-)WY{ zJAGaW*Cv%0+`Ma)S{6Je=;Vf9A}xFP-N^36g2I)6G8+v!)cCB8AR6=^u#vaBuHz(~ z+foz`EM&y*&lpuno>=1)fNOX&CCe)xh|(FcgTM++R4ewVKbKT{z(+_f?mAgD_m#!8 z9-F6~vN^d2iZgR!u^MA;abFo3jP{vYGrCOYeANh#*+*j+ZoaFxC~$t7kMB*%tqNjM z8|gwdr!dS+-_QA~`5N-c<^Frk?MV|gCTFGXy?3+o!fk>SZLr~OJdn=4$N-J3Dvjrz z0(&~P6?xxCxY9z^)#xhryi&5aBOp;#rT&a)iZc9%5~B=tjC`9t{_)vk$Z^)^hsU?s za_Y6UpDZ{PKfvoz`IP3r?> z!&;4{)mkEOPLGBkY$v|483fs?NJ9I#X zoN>hV{PZJR?&zA{u5+`iTtESiwe5Fk3TGwOmQh%(XRnz*T_@0R5VC%qlICLH;c}rP z#irD_GTN|5Rhwbhd@A(C@Xd+o>-DuAPup!R#qb!zc_~X7sZ{MN>&l(IwzsTW(yjLM zIsLbjUfWG9Kl@@wKZCQy~nOUn0wJIlM^ioEv|1kG{tD0}=n!~NA5U8%bTcc;80s~Gkuu0 zesH(pzUJ8-6`Ou%!^shx1r?bYXor;KJ9!&-h)%c8TijB7|IOU67m0jRCpY5oltbUS zu3fo$)7{kbd-IN)o0}J>UOZ~RMvvv0 zhZM&iw@DjyGxx;BWxvHR?0RSG9&0IIyEHS&>*ubr{Ownx^>{A9J^qKrICY!%)VzTi zA;z0#USe2pUPEuc`sh_nx&31H!w2KB`&&$oLs9!X*F4cQpQx#2Rr~mXtLX{L@j;IH zyUQ&fwd|GPuaA4E?sUSx=uS%SjBEUNrRFUi6brjwEiYTRoM2yj6S95;SscK&5IyYL z{n*v{c{=Lu8;+kxFD=M@FPlR2yg?3Y@8FtLcN=VH5LpR(sdvfsTQZt&c&yLv$vo3e z@0qmeVwUzpb&sM=O~POE7c(_Gq2tH4W~D~>S>J06NNF#by)-VM*!nVTS?_n<9Y~yQ z`P{Ow0B>1XGP9Yz@1eP%NMB_A;#f?uVL>=0r~;i`yYZ#2HDk>99fZ~21^v;K>wLX( zho$ihZ@>HVcn3xW9t<)+;W0XSPptc)Yl(Xr4*8SLvL`X+iiQuhDtC4$L*<&gRYIkF2mNeRr$#tcq#Qh5Yr&CF{~I zaLj$iX4+U=ad$W8N*Wp2Kb=}(<;yI04w?(d#*!oC~8c<^<+x6?*j4g z1lE-wCcY{M$UZiydJSVc$CEzDbW*{^C>PZycUxr)Zo>cwxh*y$DsYPkqGI*tr zVEb9;*Aaf9<`CL)Lg?AsnMWSy{IG?&@_a+p0ppD;vJ$bX%@a$X>3J1K%r)5B*?gF| zzO&=$sfJF6t+z%NU2DqU^=!%a;Ttjz``R+!OwHe)X*#Fk&rSn7iAeQ`of zM@2@zl&j2ZDr{H{oF0RNUQkH|g*DhGy)2~QWyy$>{iKh@`-0wJ09fW%>Rn3oDJ{FY z@Dgxw+AOePVZDttPIczEsly(>v8c_Nv`J%E&$}I#w4C@|r($ELqt{N~i^)l>EAac# zld$T*h4p!U=;FM(V-d-Tb+7!`r9PX(So~6%lVJ4Oc|WGd@1W7jdTM&wyC-$W#;PE@ zcSXfh$VzlmSl2Y>uTqL6g zrX$z#+khFSuZV2Eof%X9029de#<0W^7(>B9SUw2K^AB)DJW0T(Qf!bl7#;$UQ&>C& z3z6w~kd7?b27b)!jxSjv}(qGcj+6hvU)KbD0FHnox3zD%Q#SbFT2}C*`ng-(OAjkmzR&o&PJ{c?|8wp9B`nwE>P9V`q z6h#>dCI2SlJ-I)UqO2zaxi@`f|0bue1+m0WERL}6=k5R&803f_+N(@o!L(uOWRv=C?+^2;6UQeS_xg!-YRRbyCeSc*OB)TkT2>|Hi|YOE=F}m$FIb>l!b*-v`1y>Sf#S+HDQ)aoV9|F3u-A2z9bqM?5{(crgt mDsyyn(!k}9O*1(H6(lENM~&tLC9o*GQ2{~7uE4-M75@TC7>92F literal 0 HcmV?d00001 diff --git a/test/test_manifest.json b/test/test_manifest.json index 8215e0966..f1b394a8e 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -6553,5 +6553,12 @@ "link": true, "type": "eq", "annotations": true + }, + { "id": "bug1724918", + "file": "pdfs/bug1724918.pdf", + "md5": "ab30269570c8ff2c9f8eb73fb376a081", + "rounds": 1, + "type": "eq", + "annotations": true } ] diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index 68172ac6f..17f796188 100644 --- a/web/annotation_layer_builder.css +++ b/web/annotation_layer_builder.css @@ -17,6 +17,16 @@ --annotation-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,"); } +@media (forced-colors: active) { + .annotationLayer .textWidgetAnnotation input:required, + .annotationLayer .textWidgetAnnotation textarea:required, + .annotationLayer .choiceWidgetAnnotation select:required, + .annotationLayer .buttonWidgetAnnotation.checkBox input:required, + .annotationLayer .buttonWidgetAnnotation.radioButton input:required { + outline: 1.5px solid selectedItem; + } +} + .annotationLayer section { position: absolute; text-align: initial; @@ -66,6 +76,14 @@ width: 100%; } +.annotationLayer .textWidgetAnnotation input:required, +.annotationLayer .textWidgetAnnotation textarea:required, +.annotationLayer .choiceWidgetAnnotation select:required, +.annotationLayer .buttonWidgetAnnotation.checkBox input:required, +.annotationLayer .buttonWidgetAnnotation.radioButton input:required { + outline: 1.5px solid red; +} + .annotationLayer .choiceWidgetAnnotation select option { padding: 0; } @@ -116,7 +134,7 @@ .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before, .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after, .annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before { - background-color: rgba(0, 0, 0, 1); + background-color: CanvasText; content: ""; display: block; position: absolute; diff --git a/web/xfa_layer_builder.css b/web/xfa_layer_builder.css index ab1a911cf..8c97c84c2 100644 --- a/web/xfa_layer_builder.css +++ b/web/xfa_layer_builder.css @@ -17,6 +17,12 @@ --xfa-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,"); } +@media (forced-colors: active) { + .xfaLayer *:required { + outline: 1.5px solid selectedItem; + } +} + .xfaLayer .highlight { margin: -1px; padding: 1px; @@ -87,6 +93,10 @@ line-height: inherit; } +.xfaLayer *:required { + outline: 1.5px solid red; +} + .xfaLayer div { pointer-events: none; }