@@ -137,6 +137,24 @@ export function applyCaptchaFrameVisibility(candidates, frameContexts, navigatio
137137 visibilityByFrameId . set ( frameId , visible ) ;
138138 return visible ;
139139 } ;
140+ const dialogAssociationByFrameId = new Map ( ) ;
141+ const frameIsDialogAssociated = ( frameId , visiting = new Set ( ) ) => {
142+ if ( dialogAssociationByFrameId . has ( frameId ) ) {
143+ return dialogAssociationByFrameId . get ( frameId ) ;
144+ }
145+ if ( ! Number . isInteger ( frameId ) || frameId === 0 || visiting . has ( frameId ) ) {
146+ return false ;
147+ }
148+ const parentFrameId = navigationByFrameId . get ( frameId ) ?. parentFrameId ;
149+ if ( ! Number . isInteger ( parentFrameId ) || parentFrameId === - 1 ) return false ;
150+ const embeddingFrame = findEmbeddingFrame ( frameId , parentFrameId ) ;
151+ visiting . add ( frameId ) ;
152+ const associated = embeddingFrame ?. dialogAssociated === true
153+ || frameIsDialogAssociated ( parentFrameId , visiting ) ;
154+ visiting . delete ( frameId ) ;
155+ dialogAssociationByFrameId . set ( frameId , associated ) ;
156+ return associated ;
157+ } ;
140158
141159 const sourceCandidates = Array . isArray ( candidates ) ? candidates : [ ] ;
142160 const pathIsStrictAncestor = ( sourcePath , targetPath ) => {
@@ -249,6 +267,8 @@ export function applyCaptchaFrameVisibility(candidates, frameContexts, navigatio
249267 ...candidate ,
250268 frameVisible,
251269 websiteURL : nearestHttpUrl ( candidate ) ,
270+ dialogAssociated : candidate ?. dialogAssociated === true
271+ || frameIsDialogAssociated ( candidate ?. frameId ) ,
252272 visible : candidate ?. visible === true && frameVisible ,
253273 normalCheckbox : candidate ?. normalCheckbox === true && candidate ?. visible === true && frameVisible ,
254274 } ;
@@ -271,6 +291,7 @@ function candidateSummary(candidate) {
271291 visible : candidate ?. visible === true ,
272292 normalCheckbox : candidate ?. normalCheckbox === true ,
273293 challengeFrame : candidate ?. challengeFrame === true ,
294+ dialogAssociated : candidate ?. dialogAssociated === true ,
274295 frameVisible : candidate ?. frameVisible !== false ,
275296 isInvisible : candidate ?. isInvisible === true ,
276297 isEnterprise : candidate ?. isEnterprise === true ,
@@ -384,6 +405,7 @@ export function selectCaptchaCandidate(candidates, constraints = {}) {
384405 visible : previous . visible === true || candidate . visible === true ,
385406 normalCheckbox : previous . normalCheckbox === true || candidate . normalCheckbox === true ,
386407 challengeFrame : previous . challengeFrame === true || candidate . challengeFrame === true ,
408+ dialogAssociated : previous . dialogAssociated === true || candidate . dialogAssociated === true ,
387409 responseField : previous . responseField === true || candidate . responseField === true ,
388410 } ;
389411 for ( const field of taskParameterFields ) {
@@ -622,20 +644,67 @@ export function detectCaptchaCandidatesInPage(scope = null) {
622644 return false ;
623645 }
624646 } ;
625- const add = ( candidate ) => {
647+ const challengeDialogRe = / \b (?: (?: r e | h | f u n ) ? c a p t c h a | s e c u r i t y v e r i f i c a t i o n | h u m a n v e r i f i c a t i o n | v e r i f y (?: t h a t ) ? y o u (?: ' | \u2019 ) r e (?: a ) ? h u m a n | v e r i f y (?: t h a t ) ? y o u a r e (?: a ) ? h u m a n | a r e y o u (?: a ) ? h u m a n | r o b o t c h e c k | c h a l l e n g e v e r i f i c a t i o n ) \b / i;
648+ const challengeDialogs = Array . from (
649+ pageDocument . querySelectorAll ( 'dialog, [role="dialog"], [role="alertdialog"]' )
650+ ) . filter ( ( element ) => {
651+ if ( ! visibleElement ( element ) ) return false ;
652+ let labelledBy = '' ;
653+ try {
654+ labelledBy = String ( element . getAttribute ?. ( 'aria-labelledby' ) || '' )
655+ . split ( / \s + / )
656+ . filter ( Boolean )
657+ . map ( id => pageDocument . getElementById ?. ( id ) ?. textContent || '' )
658+ . join ( ' ' ) ;
659+ } catch ( _ ) { }
660+ return [
661+ element . getAttribute ?. ( 'aria-label' ) ,
662+ labelledBy ,
663+ element . querySelector ?. ( 'h1, h2, h3, [role="heading"]' ) ?. textContent ,
664+ element . getAttribute ?. ( 'title' ) ,
665+ element . innerText ,
666+ element . textContent ,
667+ ] . some ( value => challengeDialogRe . test ( String ( value || '' ) ) ) ;
668+ } ) ;
669+ const elementInChallengeDialog = ( element ) => {
670+ if ( ! element ) return false ;
671+ return challengeDialogs . some ( ( dialog ) => {
672+ if ( dialog === element ) return true ;
673+ try {
674+ if ( typeof dialog . contains === 'function' && dialog . contains ( element ) ) return true ;
675+ } catch ( _ ) { }
676+ let ancestor = element . parentElement || null ;
677+ for ( let depth = 0 ; ancestor && depth < 20 ; depth += 1 ) {
678+ if ( ancestor === dialog ) return true ;
679+ ancestor = ancestor . parentElement || null ;
680+ }
681+ return false ;
682+ } ) ;
683+ } ;
684+ const add = ( candidate , associationElement = null ) => {
626685 if ( ! candidate ?. type ) return ;
686+ const {
687+ responseFieldDialogAssociated,
688+ alsoResponseFieldDialogAssociated,
689+ ...serializableCandidate
690+ } = candidate ;
627691 candidates . push ( {
628- ...candidate ,
692+ ...serializableCandidate ,
629693 frameUrl,
630694 challengeFrame,
631695 responseField,
632696 documentTimeOrigin,
697+ dialogAssociated : candidate . dialogAssociated === true
698+ || responseFieldDialogAssociated === true
699+ || alsoResponseFieldDialogAssociated === true
700+ || elementInChallengeDialog ( associationElement ) ,
633701 } ) ;
634702 } ;
635703 const scriptElements = Array . from ( pageDocument . querySelectorAll ( 'script[src]' ) ) ;
636- const scriptUrls = scriptElements . map ( element => {
637- try { return element . src || '' ; } catch ( _ ) { return '' ; }
638- } ) . filter ( Boolean ) ;
704+ const scriptRecords = scriptElements . map ( ( element ) => {
705+ try { return { element, url : element . src || '' } ; } catch ( _ ) { return { element, url : '' } ; }
706+ } ) . filter ( record => record . url ) ;
707+ const scriptUrls = scriptRecords . map ( record => record . url ) ;
639708 const responseFieldIdentity = ( widget , name , fallbackIndex , widgetCount ) => {
640709 const selector = `textarea[name="${ name } "], input[name="${ name } "]` ;
641710 const fields = Array . from ( pageDocument . querySelectorAll ( selector ) ) ;
@@ -662,6 +731,7 @@ export function detectCaptchaCandidatesInPage(scope = null) {
662731 return {
663732 ...( responseFieldId ? { responseFieldId } : { } ) ,
664733 ...( responseFieldIndex >= 0 ? { responseFieldIndex } : { } ) ,
734+ ...( elementInChallengeDialog ( field ) ? { responseFieldDialogAssociated : true } : { } ) ,
665735 } ;
666736 } ;
667737 const alsoResponseFieldIdentity = ( widget , name , fallbackIndex , widgetCount ) => {
@@ -671,8 +741,14 @@ export function detectCaptchaCandidatesInPage(scope = null) {
671741 ...( Number . isInteger ( identity . responseFieldIndex )
672742 ? { alsoResponseFieldIndex : identity . responseFieldIndex }
673743 : { } ) ,
744+ ...( identity . responseFieldDialogAssociated
745+ ? { alsoResponseFieldDialogAssociated : true }
746+ : { } ) ,
674747 } ;
675748 } ;
749+ const recaptchaResponseInChallengeDialog = Array . from ( pageDocument . querySelectorAll (
750+ 'textarea[name="g-recaptcha-response"], input[name="g-recaptcha-response"]'
751+ ) ) . some ( elementInChallengeDialog ) ;
676752
677753 const hcaptchaHosts = Array . from ( pageDocument . querySelectorAll (
678754 '.h-captcha[data-sitekey], div[data-hcaptcha-widget-id]'
@@ -691,7 +767,7 @@ export function detectCaptchaCandidatesInPage(scope = null) {
691767 ...responseFieldIdentity ( host , 'h-captcha-response' , widgetIndex , hcaptchaHosts . length ) ,
692768 ...alsoResponseFieldIdentity ( host , 'g-recaptcha-response' , widgetIndex , hcaptchaHosts . length ) ,
693769 detectedVia : 'host' ,
694- } ) ;
770+ } , host ) ;
695771 }
696772
697773 const turnstileHosts = Array . from ( pageDocument . querySelectorAll (
@@ -708,7 +784,7 @@ export function detectCaptchaCandidatesInPage(scope = null) {
708784 callbackName : host . getAttribute ( 'data-callback' ) || null ,
709785 ...responseFieldIdentity ( host , 'cf-turnstile-response' , widgetIndex , turnstileHosts . length ) ,
710786 detectedVia : 'host' ,
711- } ) ;
787+ } , host ) ;
712788 }
713789
714790 const recaptchaHosts = Array . from ( pageDocument . querySelectorAll (
@@ -751,7 +827,7 @@ export function detectCaptchaCandidatesInPage(scope = null) {
751827 : { } ) ,
752828 ...responseFieldIdentity ( host , 'g-recaptcha-response' , widgetIndex , recaptchaHosts . length ) ,
753829 detectedVia : 'host' ,
754- } ) ;
830+ } , host ) ;
755831 }
756832
757833 const allIframeElements = Array . from ( pageDocument . querySelectorAll ( 'iframe' ) ) ;
@@ -791,7 +867,7 @@ export function detectCaptchaCandidatesInPage(scope = null) {
791867 hcaptchaFrames . length ,
792868 ) ,
793869 detectedVia : 'url' ,
794- } ) ;
870+ } , element ) ;
795871 }
796872 continue ;
797873 }
@@ -810,7 +886,7 @@ export function detectCaptchaCandidatesInPage(scope = null) {
810886 turnstileFrames . length ,
811887 ) ,
812888 detectedVia : 'url' ,
813- } ) ;
889+ } , element ) ;
814890 }
815891 continue ;
816892 }
@@ -846,10 +922,10 @@ export function detectCaptchaCandidatesInPage(scope = null) {
846922 recaptchaFrames . length ,
847923 ) ,
848924 detectedVia : 'url' ,
849- } ) ;
925+ } , element ) ;
850926 }
851927
852- for ( const url of scriptUrls ) {
928+ for ( const { element , url } of scriptRecords ) {
853929 if ( ! / r e c a p t c h a \/ ( a p i \. j s | e n t e r p r i s e \. j s ) / i. test ( url ) ) continue ;
854930 const websiteKey = urlParam ( url , 'render' ) ;
855931 if ( ! websiteKey || websiteKey === 'explicit' ) continue ;
@@ -862,9 +938,10 @@ export function detectCaptchaCandidatesInPage(scope = null) {
862938 isEnterprise,
863939 visible : false ,
864940 normalCheckbox : false ,
941+ dialogAssociated : recaptchaResponseInChallengeDialog ,
865942 ...( pageAction ? { pageAction } : { note : V3_NO_ACTION_NOTE } ) ,
866943 detectedVia : 'script' ,
867- } ) ;
944+ } , element ) ;
868945 }
869946
870947 const hasDetectedTurnstile = candidates . some ( candidate => candidate . type === 'turnstile' ) ;
@@ -894,6 +971,7 @@ export function detectCaptchaCandidatesInPage(scope = null) {
894971 loadedUrl,
895972 name,
896973 visible : visibleElement ( element ) ,
974+ dialogAssociated : elementInChallengeDialog ( element ) ,
897975 } ;
898976 } ) ;
899977 let frameName = '' ;
0 commit comments