Skip to content

Commit c33755d

Browse files
committed
fix(captcha): preserve active frame visibility
1 parent 59155f3 commit c33755d

7 files changed

Lines changed: 220 additions & 22 deletions

File tree

src/chrome/src/agent/agent.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3027,6 +3027,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
30273027
if (!challenge && hasDialogSurface) {
30283028
await inspectCaptchaFrames();
30293029
const selectedActiveFrame = detection?.selected?.activeChallengeFrame === true
3030+
&& detection?.selected?.activeChallengeFrameVisible === true
30303031
&& detection?.selected?.visible === true
30313032
&& detection?.selected?.frameVisible !== false;
30323033
const diagnosticActiveFrame = (detection?.diagnostics?.frames || []).find(frame =>
@@ -3220,10 +3221,15 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
32203221
&& frame?.visible === true
32213222
))
32223223
.map(frame => frame.vendor))];
3223-
const selectedCorrelated = (
3224-
detection?.selected?.dialogAssociated === true
3225-
|| detection?.selected?.activeChallengeFrame === true
3226-
) && detection?.selected?.frameVisible !== false;
3224+
const selectedCorrelated = detection?.selected?.frameVisible !== false
3225+
&& (
3226+
detection?.selected?.dialogAssociated === true
3227+
|| (
3228+
detection?.selected?.activeChallengeFrame === true
3229+
&& detection?.selected?.activeChallengeFrameVisible === true
3230+
&& detection?.selected?.visible === true
3231+
)
3232+
);
32273233
const supported = this.captchaSolverEnabled
32283234
&& !detectionFailed
32293235
&& !detection?.error

src/chrome/src/agent/captcha-frame-runtime.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -263,18 +263,28 @@ export function applyCaptchaFrameVisibility(candidates, frameContexts, navigatio
263263
const candidate = reconcileAncestorLoader(rawCandidate);
264264
const frameVisible = frameIsVisible(candidate?.frameId)
265265
&& candidate?.frameVisibleWithinAnchor !== false;
266+
const visible = candidate?.visible === true && frameVisible;
266267
return {
267268
...candidate,
268269
frameVisible,
269270
websiteURL: nearestHttpUrl(candidate),
270271
dialogAssociated: candidate?.dialogAssociated === true
271272
|| frameIsDialogAssociated(candidate?.frameId),
272-
visible: candidate?.visible === true && frameVisible,
273+
visible,
273274
normalCheckbox: candidate?.normalCheckbox === true && candidate?.visible === true && frameVisible,
275+
activeChallengeFrameVisible: candidate?.activeChallengeFrame === true && visible,
274276
};
275277
});
276278
}
277279

280+
function isVisibleActiveChallengeFrame(candidate) {
281+
if (candidate?.activeChallengeFrameVisible === true) return true;
282+
if (candidate?.activeChallengeFrameVisible === false) return false;
283+
return candidate?.activeChallengeFrame === true
284+
&& candidate?.visible === true
285+
&& candidate?.frameVisible !== false;
286+
}
287+
278288
function candidateSummary(candidate) {
279289
return {
280290
frameId: Number.isInteger(candidate?.frameId) ? candidate.frameId : null,
@@ -292,6 +302,7 @@ function candidateSummary(candidate) {
292302
normalCheckbox: candidate?.normalCheckbox === true,
293303
challengeFrame: candidate?.challengeFrame === true,
294304
activeChallengeFrame: candidate?.activeChallengeFrame === true,
305+
activeChallengeFrameVisible: isVisibleActiveChallengeFrame(candidate),
295306
dialogAssociated: candidate?.dialogAssociated === true,
296307
frameVisible: candidate?.frameVisible !== false,
297308
isInvisible: candidate?.isInvisible === true,
@@ -327,9 +338,7 @@ function candidateScore(candidate) {
327338
// Priority is tiered so no combination of secondary signals can make a
328339
// generic visible/background integration outrank an active challenge
329340
// frame or visible checkbox.
330-
const activeChallengeFrame = candidate?.activeChallengeFrame
331-
&& candidate?.visible === true
332-
&& candidate?.frameVisible !== false;
341+
const activeChallengeFrame = isVisibleActiveChallengeFrame(candidate);
333342
const primary = (candidate?.normalCheckbox && candidate?.visible) || activeChallengeFrame;
334343
const tier = primary ? 3 : (candidate?.visible ? 2 : 1);
335344
let score = tier * 1000;
@@ -409,6 +418,8 @@ export function selectCaptchaCandidate(candidates, constraints = {}) {
409418
challengeFrame: previous.challengeFrame === true || candidate.challengeFrame === true,
410419
activeChallengeFrame: previous.activeChallengeFrame === true
411420
|| candidate.activeChallengeFrame === true,
421+
activeChallengeFrameVisible: isVisibleActiveChallengeFrame(previous)
422+
|| isVisibleActiveChallengeFrame(candidate),
412423
dialogAssociated: previous.dialogAssociated === true || candidate.dialogAssociated === true,
413424
responseField: previous.responseField === true || candidate.responseField === true,
414425
responseTokenPresent: previous.responseTokenPresent === true
@@ -583,7 +594,7 @@ function selectedReason(candidate, constraints) {
583594
if (constraints.frameUrl) return 'exact frameUrl match';
584595
if (constraints.websiteKey) return 'exact websiteKey match';
585596
if (candidate.normalCheckbox && candidate.visible) return 'visible checkbox challenge';
586-
if (candidate.visible && candidate.activeChallengeFrame && candidate.frameVisible !== false) return 'visible challenge frame';
597+
if (isVisibleActiveChallengeFrame(candidate)) return 'visible challenge frame';
587598
if (candidate.visible) return 'visible CAPTCHA widget';
588599
if (candidate.challengeFrame && candidate.frameVisible !== false) return 'challenge frame candidate';
589600
return 'only detected CAPTCHA candidate';

src/chrome/src/agent/captcha-gate.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { applyCaptchaFrameVisibility } from './captcha-frame-runtime.js';
2+
13
const CHALLENGE_DIALOG_RE = /\b(?:(?:re|h|fun)?captcha|security verification|human verification|verify (?:that )?you(?:'|\u2019)re (?:a )?human|verify (?:that )?you are (?:a )?human|are you (?:a )?human|robot check|challenge verification)\b/i;
24
const CHALLENGE_FAILURE_RE = /\b(?:verification (?:failed|error|unsuccessful|expired|timed out)|could not verify|unable to verify)\b/i;
35
const CHALLENGE_CONTEXT_RE = /\b(?:(?:re|h|fun)?captcha|human|robot|challenge)\b/i;
@@ -374,21 +376,36 @@ export function buildCaptchaDiagnostics({
374376
source: 'navigation',
375377
});
376378
}
379+
const embeddedFrames = [];
377380
for (const context of frameContexts || []) {
378381
addFrame({
379382
frameId: context?.frameId,
380383
frameUrl: context?.frameUrl,
381384
source: 'document',
382385
});
383386
for (const child of context?.childFrames || []) {
384-
addFrame({
387+
embeddedFrames.push({
388+
frameId: context?.frameId,
385389
frameUrl: child?.loadedUrl || child?.url,
386-
source: 'embedded',
387390
visible: child?.visible,
388391
activeChallengeFrame: child?.activeChallengeFrame === true,
389392
});
390393
}
391394
}
395+
const visibleEmbeddedFrames = applyCaptchaFrameVisibility(
396+
embeddedFrames,
397+
frameContexts,
398+
navigationFrames,
399+
);
400+
for (const frame of visibleEmbeddedFrames) {
401+
addFrame({
402+
frameId: frame?.frameId,
403+
frameUrl: frame?.frameUrl,
404+
source: 'embedded',
405+
visible: frame?.visible,
406+
activeChallengeFrame: frame?.activeChallengeFrame === true,
407+
});
408+
}
392409
for (const candidate of candidates || []) {
393410
addFrame({
394411
frameId: candidate?.frameId,

src/firefox/src/agent/agent.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2652,6 +2652,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
26522652
if (!challenge && hasDialogSurface) {
26532653
await inspectCaptchaFrames();
26542654
const selectedActiveFrame = detection?.selected?.activeChallengeFrame === true
2655+
&& detection?.selected?.activeChallengeFrameVisible === true
26552656
&& detection?.selected?.visible === true
26562657
&& detection?.selected?.frameVisible !== false;
26572658
const diagnosticActiveFrame = (detection?.diagnostics?.frames || []).find(frame =>
@@ -2845,10 +2846,15 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
28452846
&& frame?.visible === true
28462847
))
28472848
.map(frame => frame.vendor))];
2848-
const selectedCorrelated = (
2849-
detection?.selected?.dialogAssociated === true
2850-
|| detection?.selected?.activeChallengeFrame === true
2851-
) && detection?.selected?.frameVisible !== false;
2849+
const selectedCorrelated = detection?.selected?.frameVisible !== false
2850+
&& (
2851+
detection?.selected?.dialogAssociated === true
2852+
|| (
2853+
detection?.selected?.activeChallengeFrame === true
2854+
&& detection?.selected?.activeChallengeFrameVisible === true
2855+
&& detection?.selected?.visible === true
2856+
)
2857+
);
28522858
const supported = this.captchaSolverEnabled
28532859
&& !detectionFailed
28542860
&& !detection?.error

src/firefox/src/agent/captcha-frame-runtime.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -263,18 +263,28 @@ export function applyCaptchaFrameVisibility(candidates, frameContexts, navigatio
263263
const candidate = reconcileAncestorLoader(rawCandidate);
264264
const frameVisible = frameIsVisible(candidate?.frameId)
265265
&& candidate?.frameVisibleWithinAnchor !== false;
266+
const visible = candidate?.visible === true && frameVisible;
266267
return {
267268
...candidate,
268269
frameVisible,
269270
websiteURL: nearestHttpUrl(candidate),
270271
dialogAssociated: candidate?.dialogAssociated === true
271272
|| frameIsDialogAssociated(candidate?.frameId),
272-
visible: candidate?.visible === true && frameVisible,
273+
visible,
273274
normalCheckbox: candidate?.normalCheckbox === true && candidate?.visible === true && frameVisible,
275+
activeChallengeFrameVisible: candidate?.activeChallengeFrame === true && visible,
274276
};
275277
});
276278
}
277279

280+
function isVisibleActiveChallengeFrame(candidate) {
281+
if (candidate?.activeChallengeFrameVisible === true) return true;
282+
if (candidate?.activeChallengeFrameVisible === false) return false;
283+
return candidate?.activeChallengeFrame === true
284+
&& candidate?.visible === true
285+
&& candidate?.frameVisible !== false;
286+
}
287+
278288
function candidateSummary(candidate) {
279289
return {
280290
frameId: Number.isInteger(candidate?.frameId) ? candidate.frameId : null,
@@ -292,6 +302,7 @@ function candidateSummary(candidate) {
292302
normalCheckbox: candidate?.normalCheckbox === true,
293303
challengeFrame: candidate?.challengeFrame === true,
294304
activeChallengeFrame: candidate?.activeChallengeFrame === true,
305+
activeChallengeFrameVisible: isVisibleActiveChallengeFrame(candidate),
295306
dialogAssociated: candidate?.dialogAssociated === true,
296307
frameVisible: candidate?.frameVisible !== false,
297308
isInvisible: candidate?.isInvisible === true,
@@ -327,9 +338,7 @@ function candidateScore(candidate) {
327338
// Priority is tiered so no combination of secondary signals can make a
328339
// generic visible/background integration outrank an active challenge
329340
// frame or visible checkbox.
330-
const activeChallengeFrame = candidate?.activeChallengeFrame
331-
&& candidate?.visible === true
332-
&& candidate?.frameVisible !== false;
341+
const activeChallengeFrame = isVisibleActiveChallengeFrame(candidate);
333342
const primary = (candidate?.normalCheckbox && candidate?.visible) || activeChallengeFrame;
334343
const tier = primary ? 3 : (candidate?.visible ? 2 : 1);
335344
let score = tier * 1000;
@@ -409,6 +418,8 @@ export function selectCaptchaCandidate(candidates, constraints = {}) {
409418
challengeFrame: previous.challengeFrame === true || candidate.challengeFrame === true,
410419
activeChallengeFrame: previous.activeChallengeFrame === true
411420
|| candidate.activeChallengeFrame === true,
421+
activeChallengeFrameVisible: isVisibleActiveChallengeFrame(previous)
422+
|| isVisibleActiveChallengeFrame(candidate),
412423
dialogAssociated: previous.dialogAssociated === true || candidate.dialogAssociated === true,
413424
responseField: previous.responseField === true || candidate.responseField === true,
414425
responseTokenPresent: previous.responseTokenPresent === true
@@ -583,7 +594,7 @@ function selectedReason(candidate, constraints) {
583594
if (constraints.frameUrl) return 'exact frameUrl match';
584595
if (constraints.websiteKey) return 'exact websiteKey match';
585596
if (candidate.normalCheckbox && candidate.visible) return 'visible checkbox challenge';
586-
if (candidate.visible && candidate.activeChallengeFrame && candidate.frameVisible !== false) return 'visible challenge frame';
597+
if (isVisibleActiveChallengeFrame(candidate)) return 'visible challenge frame';
587598
if (candidate.visible) return 'visible CAPTCHA widget';
588599
if (candidate.challengeFrame && candidate.frameVisible !== false) return 'challenge frame candidate';
589600
return 'only detected CAPTCHA candidate';

src/firefox/src/agent/captcha-gate.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { applyCaptchaFrameVisibility } from './captcha-frame-runtime.js';
2+
13
const CHALLENGE_DIALOG_RE = /\b(?:(?:re|h|fun)?captcha|security verification|human verification|verify (?:that )?you(?:'|\u2019)re (?:a )?human|verify (?:that )?you are (?:a )?human|are you (?:a )?human|robot check|challenge verification)\b/i;
24
const CHALLENGE_FAILURE_RE = /\b(?:verification (?:failed|error|unsuccessful|expired|timed out)|could not verify|unable to verify)\b/i;
35
const CHALLENGE_CONTEXT_RE = /\b(?:(?:re|h|fun)?captcha|human|robot|challenge)\b/i;
@@ -374,21 +376,36 @@ export function buildCaptchaDiagnostics({
374376
source: 'navigation',
375377
});
376378
}
379+
const embeddedFrames = [];
377380
for (const context of frameContexts || []) {
378381
addFrame({
379382
frameId: context?.frameId,
380383
frameUrl: context?.frameUrl,
381384
source: 'document',
382385
});
383386
for (const child of context?.childFrames || []) {
384-
addFrame({
387+
embeddedFrames.push({
388+
frameId: context?.frameId,
385389
frameUrl: child?.loadedUrl || child?.url,
386-
source: 'embedded',
387390
visible: child?.visible,
388391
activeChallengeFrame: child?.activeChallengeFrame === true,
389392
});
390393
}
391394
}
395+
const visibleEmbeddedFrames = applyCaptchaFrameVisibility(
396+
embeddedFrames,
397+
frameContexts,
398+
navigationFrames,
399+
);
400+
for (const frame of visibleEmbeddedFrames) {
401+
addFrame({
402+
frameId: frame?.frameId,
403+
frameUrl: frame?.frameUrl,
404+
source: 'embedded',
405+
visible: frame?.visible,
406+
activeChallengeFrame: frame?.activeChallengeFrame === true,
407+
});
408+
}
392409
for (const candidate of candidates || []) {
393410
addFrame({
394411
frameId: candidate?.frameId,

0 commit comments

Comments
 (0)